blob: 944ff1543ea39ecf241b6d9762cd85e9f5f6b7d9 [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
43import utils
44
45try:
46 import simplejson as json
47except ImportError:
48 import json
49
50is_cygwin = sys.platform == 'cygwin'
51
52
53def popen(arguments):
54 return subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
55
56
57def to_platform_path(filepath):
58 if not is_cygwin:
59 return filepath
60 return re.sub(r'^/cygdrive/(\w)', '\\1:', filepath)
61
62
63def to_platform_path_exact(filepath):
64 if not is_cygwin:
65 return filepath
66 output, _ = popen(['cygpath', '-w', filepath]).communicate()
67 # pylint: disable=E1103
68 return output.strip().replace('\\', '\\\\')
69
70
Yang Guo5ebd8a72019-06-26 08:04:01 +000071SCRIPTS_PATH = path.dirname(path.abspath(__file__))
72DEVTOOLS_PATH = path.dirname(SCRIPTS_PATH)
73INSPECTOR_PATH = path.join(path.dirname(DEVTOOLS_PATH), 'core', 'inspector')
Blink Reformat4c46d092018-04-07 15:32:37 +000074# TODO(dgozman): move these checks to v8.
Yang Guo5ebd8a72019-06-26 08:04:01 +000075V8_INCLUDE_PATH = path.normpath(path.join(path.dirname(DEVTOOLS_PATH), os.pardir, os.pardir, os.pardir, 'v8', 'include'))
76DEVTOOLS_FRONTEND_PATH = path.join(DEVTOOLS_PATH, 'front_end')
77GLOBAL_EXTERNS_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'externs.js'))
78DEFAULT_PROTOCOL_EXTERNS_FILE = path.join(DEVTOOLS_FRONTEND_PATH, 'protocol_externs.js')
79RUNTIME_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'Runtime.js'))
Tim van der Lippe790b9292019-09-19 15:14:16 +000080ROOT_MODULE_FILE = to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'root.js'))
Blink Reformat4c46d092018-04-07 15:32:37 +000081
Yang Guo5ebd8a72019-06-26 08:04:01 +000082CLOSURE_COMPILER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'compiler.jar'))
83CLOSURE_RUNNER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'closure_runner', 'closure_runner.jar'))
84JSDOC_VALIDATOR_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'jsdoc_validator', 'jsdoc_validator.jar'))
Blink Reformat4c46d092018-04-07 15:32:37 +000085
Yang Guo5ebd8a72019-06-26 08:04:01 +000086TYPE_CHECKED_JSDOC_TAGS_LIST = ['param', 'return', 'type', 'enum']
87TYPE_CHECKED_JSDOC_TAGS_OR = '|'.join(TYPE_CHECKED_JSDOC_TAGS_LIST)
Blink Reformat4c46d092018-04-07 15:32:37 +000088
89# 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 +000090INVALID_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR +
Blink Reformat4c46d092018-04-07 15:32:37 +000091 r')\s*\{.*(?<![!?:._A-Za-z0-9])([A-Z][_A-Za-z0-9.]+[A-Za-z0-9])[^/]*\}')
Yang Guo5ebd8a72019-06-26 08:04:01 +000092INVALID_TYPE_DESIGNATOR_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*.*(?<![{: ])([?!])=?\}')
93INVALID_NON_OBJECT_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*\{.*(![a-z]+)[^/]*\}')
94ERROR_WARNING_REGEX = re.compile(r'WARNING|ERROR')
95LOADED_CSS_REGEX = re.compile(r'(?:registerRequiredCSS|WebInspector\.View\.createStyleElement)\s*\(\s*"(.+)"\s*\)')
Blink Reformat4c46d092018-04-07 15:32:37 +000096
Yang Guo5ebd8a72019-06-26 08:04:01 +000097JAVA_BUILD_REGEX = re.compile(r'\w+ version "(\d+)\.(\d+)')
Blink Reformat4c46d092018-04-07 15:32:37 +000098
99
100def log_error(message):
101 print 'ERROR: ' + message
102
103
104def error_excepthook(exctype, value, traceback):
105 print 'ERROR:'
106 sys.__excepthook__(exctype, value, traceback)
107
108
109sys.excepthook = error_excepthook
110
Yang Guo5ebd8a72019-06-26 08:04:01 +0000111APPLICATION_DESCRIPTORS = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000112 'inspector',
113 'toolbox',
114 'integration_test_runner',
115 'formatter_worker',
116 'heap_snapshot_worker',
117]
118
Yang Guo5ebd8a72019-06-26 08:04:01 +0000119SKIPPED_NAMESPACES = {
Blink Reformat4c46d092018-04-07 15:32:37 +0000120 'Console', # Closure uses Console as a namespace item so we cannot override it right now.
121 'Gonzales', # third party module defined in front_end/externs.js
122 'Terminal', # third party module defined in front_end/externs.js
123}
124
125
126def has_errors(output):
Yang Guo5ebd8a72019-06-26 08:04:01 +0000127 return re.search(ERROR_WARNING_REGEX, output) is not None
Blink Reformat4c46d092018-04-07 15:32:37 +0000128
129
130class JSDocChecker:
131
132 def __init__(self, descriptors, java_exec):
133 self._error_found = False
134 self._all_files = descriptors.all_compiled_files()
135 self._java_exec = java_exec
136
137 def check(self):
138 print 'Verifying JSDoc comments...'
139 self._verify_jsdoc()
140 self._run_jsdoc_validator()
141 return self._error_found
142
143 def _run_jsdoc_validator(self):
144 files = [to_platform_path(f) for f in self._all_files]
145 file_list = tempfile.NamedTemporaryFile(mode='wt', delete=False)
146 try:
147 file_list.write('\n'.join(files))
148 finally:
149 file_list.close()
Yang Guo5ebd8a72019-06-26 08:04:01 +0000150 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 +0000151 (out, _) = proc.communicate()
152 if out:
153 print('JSDoc validator output:%s%s' % (os.linesep, out))
154 self._error_found = True
155 os.remove(file_list.name)
156
157 def _verify_jsdoc(self):
158 for full_file_name in self._all_files:
159 line_index = 0
160 with open(full_file_name, 'r') as sourceFile:
161 for line in sourceFile:
162 line_index += 1
163 if line.rstrip():
164 self._verify_jsdoc_line(full_file_name, line_index, line)
165
166 def _verify_jsdoc_line(self, file_name, line_index, line):
167
168 def print_error(message, error_position):
169 print '%s:%s: ERROR - %s%s%s%s%s%s' % (file_name, line_index, message, os.linesep, line, os.linesep,
170 ' ' * error_position + '^', os.linesep)
171
172 known_css = {}
Yang Guo5ebd8a72019-06-26 08:04:01 +0000173 match = re.search(INVALID_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000174 if match:
175 print_error('Type "%s" nullability not marked explicitly with "?" (nullable) or "!" (non-nullable)' % match.group(1),
176 match.start(1))
177 self._error_found = True
178
Yang Guo5ebd8a72019-06-26 08:04:01 +0000179 match = re.search(INVALID_NON_OBJECT_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000180 if match:
181 print_error('Non-object type explicitly marked with "!" (non-nullable), which is the default and should be omitted',
182 match.start(1))
183 self._error_found = True
184
Yang Guo5ebd8a72019-06-26 08:04:01 +0000185 match = re.search(INVALID_TYPE_DESIGNATOR_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000186 if match:
187 print_error('Type nullability indicator misplaced, should precede type', match.start(1))
188 self._error_found = True
189
Yang Guo5ebd8a72019-06-26 08:04:01 +0000190 match = re.search(LOADED_CSS_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000191 if match:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000192 css_file = path.join(DEVTOOLS_FRONTEND_PATH, match.group(1))
193 exists = known_css.get(css_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000194 if exists is None:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000195 exists = path.isfile(css_file)
196 known_css[css_file] = exists
Blink Reformat4c46d092018-04-07 15:32:37 +0000197 if not exists:
198 print_error('Dynamically loaded CSS stylesheet is missing in the source tree', match.start(1))
199 self._error_found = True
200
201
202def find_java():
203 required_major = 1
204 required_minor = 7
205 exec_command = None
206 has_server_jvm = True
207 java_path = utils.which('java')
208
209 if not java_path:
210 print 'NOTE: No Java executable found in $PATH.'
211 sys.exit(1)
212
213 is_ok = False
214 java_version_out, _ = popen([java_path, '-version']).communicate()
215 # pylint: disable=E1103
Yang Guo5ebd8a72019-06-26 08:04:01 +0000216 match = re.search(JAVA_BUILD_REGEX, java_version_out)
Blink Reformat4c46d092018-04-07 15:32:37 +0000217 if match:
218 major = int(match.group(1))
219 minor = int(match.group(2))
Alexei Filippov87e097d2018-11-06 19:18:29 +0000220 is_ok = major > required_major or major == required_major and minor >= required_minor
Blink Reformat4c46d092018-04-07 15:32:37 +0000221 if is_ok:
222 exec_command = [java_path, '-Xms1024m', '-server', '-XX:+TieredCompilation']
223 check_server_proc = popen(exec_command + ['-version'])
224 check_server_proc.communicate()
225 if check_server_proc.returncode != 0:
226 # Not all Java installs have server JVMs.
227 exec_command = exec_command.remove('-server')
228 has_server_jvm = False
229
230 if not is_ok:
231 print 'NOTE: Java executable version %d.%d or above not found in $PATH.' % (required_major, required_minor)
232 sys.exit(1)
233 print 'Java executable: %s%s' % (java_path, '' if has_server_jvm else ' (no server JVM)')
234 return exec_command
235
236
237common_closure_args = [
238 '--summary_detail_level',
239 '3',
240 '--jscomp_error',
241 'visibility',
242 '--jscomp_warning',
243 'missingOverride',
244 '--compilation_level',
245 'SIMPLE_OPTIMIZATIONS',
246 '--warning_level',
247 'VERBOSE',
Tim van der Lippe620071a2019-09-25 13:33:32 +0000248 '--language_in=ECMASCRIPT_NEXT',
Blink Reformat4c46d092018-04-07 15:32:37 +0000249 '--language_out=ES5_STRICT',
250 '--extra_annotation_name',
251 'suppressReceiverCheck',
252 '--extra_annotation_name',
253 'suppressGlobalPropertiesCheck',
254 '--checks-only',
Blink Reformat4c46d092018-04-07 15:32:37 +0000255]
256
Tim van der Lippe9b7d21d2019-10-07 18:48:07 +0000257GENERATED_SKIP_COMPILATION_FILES = [
258 'SupportedCSSProperties.js',
259 'InspectorBackendCommands.js',
260]
261
Blink Reformat4c46d092018-04-07 15:32:37 +0000262
263def check_conditional_dependencies(modules_by_name):
264 errors_found = False
265 for name in modules_by_name:
266 if 'test_runner' in name:
267 continue
268 for dep_name in modules_by_name[name].get('dependencies', []):
269 dependency = modules_by_name[dep_name]
270 if dependency.get('experiment') or dependency.get('condition'):
271 log_error('Module "%s" may not depend on the conditional module "%s"' % (name, dep_name))
272 errors_found = True
273 return errors_found
274
275
Yang Guo5ebd8a72019-06-26 08:04:01 +0000276def prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path, protocol_externs_file):
Blink Reformat4c46d092018-04-07 15:32:37 +0000277 temp_frontend_path = path.join(temp_devtools_path, 'front_end')
Yang Guo5ebd8a72019-06-26 08:04:01 +0000278 checker = dependency_preprocessor.DependencyPreprocessor(descriptors, temp_frontend_path, DEVTOOLS_FRONTEND_PATH)
Blink Reformat4c46d092018-04-07 15:32:37 +0000279 checker.enforce_dependencies()
280
281 command = common_closure_args + [
282 '--externs',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000283 to_platform_path(GLOBAL_EXTERNS_FILE),
Blink Reformat4c46d092018-04-07 15:32:37 +0000284 '--externs',
285 namespace_externs_path,
286 '--js',
Paul Lewis0cd45e32019-09-25 22:19:27 +0000287 RUNTIME_FILE,
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000288 '--js',
289 ROOT_MODULE_FILE,
Blink Reformat4c46d092018-04-07 15:32:37 +0000290 ]
291
292 all_files = descriptors.all_compiled_files()
293 args = []
294 for file in all_files:
295 args.extend(['--js', file])
296 if "InspectorBackend.js" in file:
297 args.extend(['--js', protocol_externs_file])
Tim van der Lippe798ea142019-10-04 12:01:23 +0000298
Tim van der Lippe9b7d21d2019-10-07 18:48:07 +0000299 for file in GENERATED_SKIP_COMPILATION_FILES:
300 # Write a dummy file for skipped compilation files that are autogenerated.
301 # We don't type-check this file, but we import them via ES modules
302 generated_file = path.join(temp_frontend_path, file)
303 modular_build.write_file(generated_file, '')
304 args.extend(['--js', generated_file])
305
Blink Reformat4c46d092018-04-07 15:32:37 +0000306 command += args
Yang Guo5ebd8a72019-06-26 08:04:01 +0000307 command = [arg.replace(DEVTOOLS_FRONTEND_PATH, temp_frontend_path) for arg in command]
Blink Reformat4c46d092018-04-07 15:32:37 +0000308 compiler_args_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
309 try:
310 compiler_args_file.write('devtools_frontend %s' % (' '.join(command)))
311 finally:
312 compiler_args_file.close()
313 return compiler_args_file.name
314
315
316def generate_namespace_externs(modules_by_name):
317 special_case_namespaces_path = path.join(path.dirname(path.abspath(__file__)), 'special_case_namespaces.json')
318 with open(special_case_namespaces_path) as json_file:
319 special_case_namespaces = json.load(json_file)
320
321 def map_module_to_namespace(module):
322 return special_case_namespaces.get(module, to_camel_case(module))
323
324 def to_camel_case(snake_string):
325 components = snake_string.split('_')
326 return ''.join(x.title() for x in components)
327
328 all_namespaces = [map_module_to_namespace(module) for module in modules_by_name]
Yang Guo5ebd8a72019-06-26 08:04:01 +0000329 namespaces = [namespace for namespace in all_namespaces if namespace not in SKIPPED_NAMESPACES]
Blink Reformat4c46d092018-04-07 15:32:37 +0000330 namespaces.sort()
331 namespace_externs_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
332 try:
Tim van der Lippe99e59b82019-09-30 20:00:59 +0000333 namespace_externs_file.write('var Root = {};\n')
Blink Reformat4c46d092018-04-07 15:32:37 +0000334 for namespace in namespaces:
Blink Reformat4c46d092018-04-07 15:32:37 +0000335 namespace_externs_file.write('var %s = {};\n' % namespace)
336 finally:
337 namespace_externs_file.close()
338 namespace_externs_path = to_platform_path(namespace_externs_file.name)
339 return namespace_externs_path
340
341
342def main():
Yang Guo5ebd8a72019-06-26 08:04:01 +0000343 protocol_externs_file = DEFAULT_PROTOCOL_EXTERNS_FILE
Blink Reformat4c46d092018-04-07 15:32:37 +0000344 errors_found = False
345 parser = argparse.ArgumentParser()
346 parser.add_argument('--protocol-externs-file')
347 args, _ = parser.parse_known_args()
348 if args.protocol_externs_file:
349 protocol_externs_file = args.protocol_externs_file
350 else:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000351 generate_protocol_externs.generate_protocol_externs(protocol_externs_file, path.join(
352 INSPECTOR_PATH, 'browser_protocol.pdl'), path.join(V8_INCLUDE_PATH, 'js_protocol.pdl'))
353 loader = modular_build.DescriptorLoader(DEVTOOLS_FRONTEND_PATH)
354 descriptors = loader.load_applications(APPLICATION_DESCRIPTORS)
Blink Reformat4c46d092018-04-07 15:32:37 +0000355 modules_by_name = descriptors.modules
356
357 java_exec = find_java()
358 errors_found |= check_conditional_dependencies(modules_by_name)
359
360 print 'Compiling frontend...'
361 temp_devtools_path = tempfile.mkdtemp()
362 namespace_externs_path = generate_namespace_externs(modules_by_name)
Yang Guo5ebd8a72019-06-26 08:04:01 +0000363 compiler_args_file_path = prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path,
364 protocol_externs_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000365 frontend_compile_proc = popen(
Yang Guo5ebd8a72019-06-26 08:04:01 +0000366 java_exec + ['-jar', CLOSURE_RUNNER_JAR, '--compiler-args-file',
367 to_platform_path_exact(compiler_args_file_path)])
Blink Reformat4c46d092018-04-07 15:32:37 +0000368
369 print 'Compiling devtools_compatibility.js...'
370
Yang Guo5ebd8a72019-06-26 08:04:01 +0000371 closure_compiler_command = java_exec + ['-jar', CLOSURE_COMPILER_JAR] + common_closure_args
Blink Reformat4c46d092018-04-07 15:32:37 +0000372
373 devtools_js_compile_command = closure_compiler_command + [
Yang Guo5ebd8a72019-06-26 08:04:01 +0000374 '--externs',
Tim van der Lippe7b190162019-09-27 15:10:44 +0000375 to_platform_path(GLOBAL_EXTERNS_FILE), '--jscomp_off=externsValidation', '--js',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000376 to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'devtools_compatibility.js'))
Blink Reformat4c46d092018-04-07 15:32:37 +0000377 ]
378 devtools_js_compile_proc = popen(devtools_js_compile_command)
379
380 errors_found |= JSDocChecker(descriptors, java_exec).check()
381
382 (devtools_js_compile_out, _) = devtools_js_compile_proc.communicate()
383 print 'devtools_compatibility.js compilation output:%s' % os.linesep, devtools_js_compile_out
384 errors_found |= has_errors(devtools_js_compile_out)
385
386 (frontend_compile_out, _) = frontend_compile_proc.communicate()
387 print 'devtools frontend compilation output:'
388 for line in frontend_compile_out.splitlines():
389 if "@@ START_MODULE" in line or "@@ END_MODULE" in line:
390 continue
391 print line
392 errors_found |= has_errors(frontend_compile_out)
393
394 os.remove(protocol_externs_file)
395 os.remove(namespace_externs_path)
396 os.remove(compiler_args_file_path)
397 shutil.rmtree(temp_devtools_path, True)
398
399 if errors_found:
400 print 'ERRORS DETECTED'
401 sys.exit(1)
402 print 'DONE - compiled without errors'
403
404
405if __name__ == "__main__":
406 main()