blob: 58f1bd6daf6cd265f84243a1e7cf47a0fbe9c8f4 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:37 +00001#!/usr/bin/env python
2# Copyright (c) 2012 Google Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14# * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30import argparse
31import os
32import os.path as path
33import re
34import shutil
35import subprocess
36import sys
37import tempfile
38
39from build import modular_build
40from build import generate_protocol_externs
41
42import dependency_preprocessor
Yang Guo4fd355c2019-09-19 10:59:03 +020043import devtools_paths
Blink Reformat4c46d092018-04-07 15:32:37 +000044import utils
45
46try:
47 import simplejson as json
48except ImportError:
49 import json
50
51is_cygwin = sys.platform == 'cygwin'
52
53
54def popen(arguments):
55 return subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
56
57
58def to_platform_path(filepath):
59 if not is_cygwin:
60 return filepath
61 return re.sub(r'^/cygdrive/(\w)', '\\1:', filepath)
62
63
64def to_platform_path_exact(filepath):
65 if not is_cygwin:
66 return filepath
67 output, _ = popen(['cygpath', '-w', filepath]).communicate()
68 # pylint: disable=E1103
69 return output.strip().replace('\\', '\\\\')
70
71
Yang Guo5ebd8a72019-06-26 08:04:01 +000072SCRIPTS_PATH = path.dirname(path.abspath(__file__))
73DEVTOOLS_PATH = path.dirname(SCRIPTS_PATH)
Yang Guo4fd355c2019-09-19 10:59:03 +020074ROOT_PATH = devtools_paths.root_path()
75BROWSER_PROTOCOL_PATH = path.join(ROOT_PATH, 'third_party', 'blink', 'renderer', 'core', 'inspector', 'browser_protocol.pdl')
Blink Reformat4c46d092018-04-07 15:32:37 +000076# TODO(dgozman): move these checks to v8.
Yang Guo4fd355c2019-09-19 10:59:03 +020077JS_PROTOCOL_PATH = path.join(ROOT_PATH, 'v8', 'include', 'js_protocol.pdl')
Yang Guo5ebd8a72019-06-26 08:04:01 +000078DEVTOOLS_FRONTEND_PATH = path.join(DEVTOOLS_PATH, 'front_end')
79GLOBAL_EXTERNS_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'externs.js'))
80DEFAULT_PROTOCOL_EXTERNS_FILE = path.join(DEVTOOLS_FRONTEND_PATH, 'protocol_externs.js')
81RUNTIME_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'Runtime.js'))
Tim van der Lippe790b9292019-09-19 15:14:16 +000082ROOT_MODULE_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'root.js'))
Blink Reformat4c46d092018-04-07 15:32:37 +000083
Yang Guo5ebd8a72019-06-26 08:04:01 +000084CLOSURE_COMPILER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'compiler.jar'))
85CLOSURE_RUNNER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'closure_runner', 'closure_runner.jar'))
86JSDOC_VALIDATOR_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'jsdoc_validator', 'jsdoc_validator.jar'))
Blink Reformat4c46d092018-04-07 15:32:37 +000087
Yang Guo5ebd8a72019-06-26 08:04:01 +000088TYPE_CHECKED_JSDOC_TAGS_LIST = ['param', 'return', 'type', 'enum']
89TYPE_CHECKED_JSDOC_TAGS_OR = '|'.join(TYPE_CHECKED_JSDOC_TAGS_LIST)
Blink Reformat4c46d092018-04-07 15:32:37 +000090
91# Basic regex for invalid JsDoc types: an object type name ([A-Z][_A-Za-z0-9.]+[A-Za-z0-9]) not preceded by '!', '?', ':' (this, new), or '.' (object property).
Yang Guo5ebd8a72019-06-26 08:04:01 +000092INVALID_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR +
Blink Reformat4c46d092018-04-07 15:32:37 +000093 r')\s*\{.*(?<![!?:._A-Za-z0-9])([A-Z][_A-Za-z0-9.]+[A-Za-z0-9])[^/]*\}')
Yang Guo5ebd8a72019-06-26 08:04:01 +000094INVALID_TYPE_DESIGNATOR_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*.*(?<![{: ])([?!])=?\}')
95INVALID_NON_OBJECT_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*\{.*(![a-z]+)[^/]*\}')
96ERROR_WARNING_REGEX = re.compile(r'WARNING|ERROR')
97LOADED_CSS_REGEX = re.compile(r'(?:registerRequiredCSS|WebInspector\.View\.createStyleElement)\s*\(\s*"(.+)"\s*\)')
Blink Reformat4c46d092018-04-07 15:32:37 +000098
Yang Guo5ebd8a72019-06-26 08:04:01 +000099JAVA_BUILD_REGEX = re.compile(r'\w+ version "(\d+)\.(\d+)')
Blink Reformat4c46d092018-04-07 15:32:37 +0000100
101
102def log_error(message):
103 print 'ERROR: ' + message
104
105
106def error_excepthook(exctype, value, traceback):
107 print 'ERROR:'
108 sys.__excepthook__(exctype, value, traceback)
109
110
111sys.excepthook = error_excepthook
112
Yang Guo5ebd8a72019-06-26 08:04:01 +0000113APPLICATION_DESCRIPTORS = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000114 'inspector',
115 'toolbox',
116 'integration_test_runner',
117 'formatter_worker',
118 'heap_snapshot_worker',
119]
120
Yang Guo5ebd8a72019-06-26 08:04:01 +0000121SKIPPED_NAMESPACES = {
Blink Reformat4c46d092018-04-07 15:32:37 +0000122 'Console', # Closure uses Console as a namespace item so we cannot override it right now.
123 'Gonzales', # third party module defined in front_end/externs.js
124 'Terminal', # third party module defined in front_end/externs.js
125}
126
127
128def has_errors(output):
Yang Guo5ebd8a72019-06-26 08:04:01 +0000129 return re.search(ERROR_WARNING_REGEX, output) is not None
Blink Reformat4c46d092018-04-07 15:32:37 +0000130
131
132class JSDocChecker:
133
134 def __init__(self, descriptors, java_exec):
135 self._error_found = False
136 self._all_files = descriptors.all_compiled_files()
137 self._java_exec = java_exec
138
139 def check(self):
140 print 'Verifying JSDoc comments...'
141 self._verify_jsdoc()
142 self._run_jsdoc_validator()
143 return self._error_found
144
145 def _run_jsdoc_validator(self):
146 files = [to_platform_path(f) for f in self._all_files]
147 file_list = tempfile.NamedTemporaryFile(mode='wt', delete=False)
148 try:
149 file_list.write('\n'.join(files))
150 finally:
151 file_list.close()
Yang Guo5ebd8a72019-06-26 08:04:01 +0000152 proc = popen(self._java_exec + ['-jar', JSDOC_VALIDATOR_JAR, '--files-list-name', to_platform_path_exact(file_list.name)])
Blink Reformat4c46d092018-04-07 15:32:37 +0000153 (out, _) = proc.communicate()
154 if out:
155 print('JSDoc validator output:%s%s' % (os.linesep, out))
156 self._error_found = True
157 os.remove(file_list.name)
158
159 def _verify_jsdoc(self):
160 for full_file_name in self._all_files:
161 line_index = 0
162 with open(full_file_name, 'r') as sourceFile:
163 for line in sourceFile:
164 line_index += 1
165 if line.rstrip():
166 self._verify_jsdoc_line(full_file_name, line_index, line)
167
168 def _verify_jsdoc_line(self, file_name, line_index, line):
169
170 def print_error(message, error_position):
171 print '%s:%s: ERROR - %s%s%s%s%s%s' % (file_name, line_index, message, os.linesep, line, os.linesep,
172 ' ' * error_position + '^', os.linesep)
173
174 known_css = {}
Yang Guo5ebd8a72019-06-26 08:04:01 +0000175 match = re.search(INVALID_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000176 if match:
177 print_error('Type "%s" nullability not marked explicitly with "?" (nullable) or "!" (non-nullable)' % match.group(1),
178 match.start(1))
179 self._error_found = True
180
Yang Guo5ebd8a72019-06-26 08:04:01 +0000181 match = re.search(INVALID_NON_OBJECT_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000182 if match:
183 print_error('Non-object type explicitly marked with "!" (non-nullable), which is the default and should be omitted',
184 match.start(1))
185 self._error_found = True
186
Yang Guo5ebd8a72019-06-26 08:04:01 +0000187 match = re.search(INVALID_TYPE_DESIGNATOR_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000188 if match:
189 print_error('Type nullability indicator misplaced, should precede type', match.start(1))
190 self._error_found = True
191
Yang Guo5ebd8a72019-06-26 08:04:01 +0000192 match = re.search(LOADED_CSS_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000193 if match:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000194 css_file = path.join(DEVTOOLS_FRONTEND_PATH, match.group(1))
195 exists = known_css.get(css_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000196 if exists is None:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000197 exists = path.isfile(css_file)
198 known_css[css_file] = exists
Blink Reformat4c46d092018-04-07 15:32:37 +0000199 if not exists:
200 print_error('Dynamically loaded CSS stylesheet is missing in the source tree', match.start(1))
201 self._error_found = True
202
203
204def find_java():
205 required_major = 1
206 required_minor = 7
207 exec_command = None
208 has_server_jvm = True
209 java_path = utils.which('java')
210
211 if not java_path:
212 print 'NOTE: No Java executable found in $PATH.'
213 sys.exit(1)
214
215 is_ok = False
216 java_version_out, _ = popen([java_path, '-version']).communicate()
217 # pylint: disable=E1103
Yang Guo5ebd8a72019-06-26 08:04:01 +0000218 match = re.search(JAVA_BUILD_REGEX, java_version_out)
Blink Reformat4c46d092018-04-07 15:32:37 +0000219 if match:
220 major = int(match.group(1))
221 minor = int(match.group(2))
Alexei Filippov87e097d2018-11-06 19:18:29 +0000222 is_ok = major > required_major or major == required_major and minor >= required_minor
Blink Reformat4c46d092018-04-07 15:32:37 +0000223 if is_ok:
224 exec_command = [java_path, '-Xms1024m', '-server', '-XX:+TieredCompilation']
225 check_server_proc = popen(exec_command + ['-version'])
226 check_server_proc.communicate()
227 if check_server_proc.returncode != 0:
228 # Not all Java installs have server JVMs.
229 exec_command = exec_command.remove('-server')
230 has_server_jvm = False
231
232 if not is_ok:
233 print 'NOTE: Java executable version %d.%d or above not found in $PATH.' % (required_major, required_minor)
234 sys.exit(1)
235 print 'Java executable: %s%s' % (java_path, '' if has_server_jvm else ' (no server JVM)')
236 return exec_command
237
238
239common_closure_args = [
240 '--summary_detail_level',
241 '3',
242 '--jscomp_error',
243 'visibility',
244 '--jscomp_warning',
245 'missingOverride',
246 '--compilation_level',
247 'SIMPLE_OPTIMIZATIONS',
248 '--warning_level',
249 'VERBOSE',
Tim van der Lippe620071a2019-09-25 13:33:32 +0000250 '--language_in=ECMASCRIPT_NEXT',
Blink Reformat4c46d092018-04-07 15:32:37 +0000251 '--language_out=ES5_STRICT',
252 '--extra_annotation_name',
253 'suppressReceiverCheck',
254 '--extra_annotation_name',
255 'suppressGlobalPropertiesCheck',
256 '--checks-only',
Blink Reformat4c46d092018-04-07 15:32:37 +0000257]
258
Tim van der Lippe9b7d21d2019-10-07 18:48:07 +0000259GENERATED_SKIP_COMPILATION_FILES = [
260 'SupportedCSSProperties.js',
261 'InspectorBackendCommands.js',
262]
263
Blink Reformat4c46d092018-04-07 15:32:37 +0000264
265def check_conditional_dependencies(modules_by_name):
266 errors_found = False
267 for name in modules_by_name:
268 if 'test_runner' in name:
269 continue
270 for dep_name in modules_by_name[name].get('dependencies', []):
271 dependency = modules_by_name[dep_name]
272 if dependency.get('experiment') or dependency.get('condition'):
273 log_error('Module "%s" may not depend on the conditional module "%s"' % (name, dep_name))
274 errors_found = True
275 return errors_found
276
277
Yang Guo5ebd8a72019-06-26 08:04:01 +0000278def prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path, protocol_externs_file):
Blink Reformat4c46d092018-04-07 15:32:37 +0000279 temp_frontend_path = path.join(temp_devtools_path, 'front_end')
Yang Guo5ebd8a72019-06-26 08:04:01 +0000280 checker = dependency_preprocessor.DependencyPreprocessor(descriptors, temp_frontend_path, DEVTOOLS_FRONTEND_PATH)
Blink Reformat4c46d092018-04-07 15:32:37 +0000281 checker.enforce_dependencies()
282
283 command = common_closure_args + [
284 '--externs',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000285 to_platform_path(GLOBAL_EXTERNS_FILE),
Blink Reformat4c46d092018-04-07 15:32:37 +0000286 '--externs',
287 namespace_externs_path,
288 '--js',
Paul Lewis0cd45e32019-09-25 22:19:27 +0000289 RUNTIME_FILE,
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000290 '--js',
291 ROOT_MODULE_FILE,
Blink Reformat4c46d092018-04-07 15:32:37 +0000292 ]
293
294 all_files = descriptors.all_compiled_files()
295 args = []
296 for file in all_files:
297 args.extend(['--js', file])
298 if "InspectorBackend.js" in file:
299 args.extend(['--js', protocol_externs_file])
Tim van der Lippe798ea142019-10-04 12:01:23 +0000300
Tim van der Lippe9b7d21d2019-10-07 18:48:07 +0000301 for file in GENERATED_SKIP_COMPILATION_FILES:
302 # Write a dummy file for skipped compilation files that are autogenerated.
303 # We don't type-check this file, but we import them via ES modules
304 generated_file = path.join(temp_frontend_path, file)
305 modular_build.write_file(generated_file, '')
306 args.extend(['--js', generated_file])
307
Blink Reformat4c46d092018-04-07 15:32:37 +0000308 command += args
Yang Guo5ebd8a72019-06-26 08:04:01 +0000309 command = [arg.replace(DEVTOOLS_FRONTEND_PATH, temp_frontend_path) for arg in command]
Blink Reformat4c46d092018-04-07 15:32:37 +0000310 compiler_args_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
311 try:
312 compiler_args_file.write('devtools_frontend %s' % (' '.join(command)))
313 finally:
314 compiler_args_file.close()
315 return compiler_args_file.name
316
317
318def generate_namespace_externs(modules_by_name):
319 special_case_namespaces_path = path.join(path.dirname(path.abspath(__file__)), 'special_case_namespaces.json')
320 with open(special_case_namespaces_path) as json_file:
321 special_case_namespaces = json.load(json_file)
322
323 def map_module_to_namespace(module):
324 return special_case_namespaces.get(module, to_camel_case(module))
325
326 def to_camel_case(snake_string):
327 components = snake_string.split('_')
328 return ''.join(x.title() for x in components)
329
330 all_namespaces = [map_module_to_namespace(module) for module in modules_by_name]
Yang Guo5ebd8a72019-06-26 08:04:01 +0000331 namespaces = [namespace for namespace in all_namespaces if namespace not in SKIPPED_NAMESPACES]
Blink Reformat4c46d092018-04-07 15:32:37 +0000332 namespaces.sort()
333 namespace_externs_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
334 try:
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000335 namespace_externs_file.write('var Root = {};\n')
Blink Reformat4c46d092018-04-07 15:32:37 +0000336 for namespace in namespaces:
Blink Reformat4c46d092018-04-07 15:32:37 +0000337 namespace_externs_file.write('var %s = {};\n' % namespace)
338 finally:
339 namespace_externs_file.close()
340 namespace_externs_path = to_platform_path(namespace_externs_file.name)
341 return namespace_externs_path
342
343
344def main():
Yang Guo5ebd8a72019-06-26 08:04:01 +0000345 protocol_externs_file = DEFAULT_PROTOCOL_EXTERNS_FILE
Blink Reformat4c46d092018-04-07 15:32:37 +0000346 errors_found = False
347 parser = argparse.ArgumentParser()
348 parser.add_argument('--protocol-externs-file')
349 args, _ = parser.parse_known_args()
350 if args.protocol_externs_file:
351 protocol_externs_file = args.protocol_externs_file
352 else:
Yang Guo4fd355c2019-09-19 10:59:03 +0200353 generate_protocol_externs.generate_protocol_externs(protocol_externs_file, BROWSER_PROTOCOL_PATH, JS_PROTOCOL_PATH)
Yang Guo5ebd8a72019-06-26 08:04:01 +0000354 loader = modular_build.DescriptorLoader(DEVTOOLS_FRONTEND_PATH)
355 descriptors = loader.load_applications(APPLICATION_DESCRIPTORS)
Blink Reformat4c46d092018-04-07 15:32:37 +0000356 modules_by_name = descriptors.modules
357
358 java_exec = find_java()
359 errors_found |= check_conditional_dependencies(modules_by_name)
360
361 print 'Compiling frontend...'
362 temp_devtools_path = tempfile.mkdtemp()
363 namespace_externs_path = generate_namespace_externs(modules_by_name)
Yang Guo5ebd8a72019-06-26 08:04:01 +0000364 compiler_args_file_path = prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path,
365 protocol_externs_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000366 frontend_compile_proc = popen(
Yang Guo5ebd8a72019-06-26 08:04:01 +0000367 java_exec + ['-jar', CLOSURE_RUNNER_JAR, '--compiler-args-file',
368 to_platform_path_exact(compiler_args_file_path)])
Blink Reformat4c46d092018-04-07 15:32:37 +0000369
370 print 'Compiling devtools_compatibility.js...'
371
Yang Guo5ebd8a72019-06-26 08:04:01 +0000372 closure_compiler_command = java_exec + ['-jar', CLOSURE_COMPILER_JAR] + common_closure_args
Blink Reformat4c46d092018-04-07 15:32:37 +0000373
374 devtools_js_compile_command = closure_compiler_command + [
Yang Guo5ebd8a72019-06-26 08:04:01 +0000375 '--externs',
Tim van der Lippe7b190162019-09-27 15:10:44 +0000376 to_platform_path(GLOBAL_EXTERNS_FILE), '--jscomp_off=externsValidation', '--js',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000377 to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'devtools_compatibility.js'))
Blink Reformat4c46d092018-04-07 15:32:37 +0000378 ]
379 devtools_js_compile_proc = popen(devtools_js_compile_command)
380
381 errors_found |= JSDocChecker(descriptors, java_exec).check()
382
383 (devtools_js_compile_out, _) = devtools_js_compile_proc.communicate()
384 print 'devtools_compatibility.js compilation output:%s' % os.linesep, devtools_js_compile_out
385 errors_found |= has_errors(devtools_js_compile_out)
386
387 (frontend_compile_out, _) = frontend_compile_proc.communicate()
388 print 'devtools frontend compilation output:'
389 for line in frontend_compile_out.splitlines():
390 if "@@ START_MODULE" in line or "@@ END_MODULE" in line:
391 continue
392 print line
393 errors_found |= has_errors(frontend_compile_out)
394
395 os.remove(protocol_externs_file)
396 os.remove(namespace_externs_path)
397 os.remove(compiler_args_file_path)
398 shutil.rmtree(temp_devtools_path, True)
399
400 if errors_found:
401 print 'ERRORS DETECTED'
402 sys.exit(1)
403 print 'DONE - compiled without errors'
404
405
406if __name__ == "__main__":
407 main()