blob: 53ad724f2fe96cd0284d19bee61b5c298907cce6 [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'))
Blink Reformat4c46d092018-04-07 15:32:37 +000080
Yang Guo5ebd8a72019-06-26 08:04:01 +000081CLOSURE_COMPILER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'compiler.jar'))
82CLOSURE_RUNNER_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'closure', 'closure_runner', 'closure_runner.jar'))
83JSDOC_VALIDATOR_JAR = to_platform_path(path.join(SCRIPTS_PATH, 'jsdoc_validator', 'jsdoc_validator.jar'))
Blink Reformat4c46d092018-04-07 15:32:37 +000084
Yang Guo5ebd8a72019-06-26 08:04:01 +000085TYPE_CHECKED_JSDOC_TAGS_LIST = ['param', 'return', 'type', 'enum']
86TYPE_CHECKED_JSDOC_TAGS_OR = '|'.join(TYPE_CHECKED_JSDOC_TAGS_LIST)
Blink Reformat4c46d092018-04-07 15:32:37 +000087
88# 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 +000089INVALID_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR +
Blink Reformat4c46d092018-04-07 15:32:37 +000090 r')\s*\{.*(?<![!?:._A-Za-z0-9])([A-Z][_A-Za-z0-9.]+[A-Za-z0-9])[^/]*\}')
Yang Guo5ebd8a72019-06-26 08:04:01 +000091INVALID_TYPE_DESIGNATOR_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*.*(?<![{: ])([?!])=?\}')
92INVALID_NON_OBJECT_TYPE_REGEX = re.compile(r'@(?:' + TYPE_CHECKED_JSDOC_TAGS_OR + r')\s*\{.*(![a-z]+)[^/]*\}')
93ERROR_WARNING_REGEX = re.compile(r'WARNING|ERROR')
94LOADED_CSS_REGEX = re.compile(r'(?:registerRequiredCSS|WebInspector\.View\.createStyleElement)\s*\(\s*"(.+)"\s*\)')
Blink Reformat4c46d092018-04-07 15:32:37 +000095
Yang Guo5ebd8a72019-06-26 08:04:01 +000096JAVA_BUILD_REGEX = re.compile(r'\w+ version "(\d+)\.(\d+)')
Blink Reformat4c46d092018-04-07 15:32:37 +000097
98
99def log_error(message):
100 print 'ERROR: ' + message
101
102
103def error_excepthook(exctype, value, traceback):
104 print 'ERROR:'
105 sys.__excepthook__(exctype, value, traceback)
106
107
108sys.excepthook = error_excepthook
109
Yang Guo5ebd8a72019-06-26 08:04:01 +0000110APPLICATION_DESCRIPTORS = [
Blink Reformat4c46d092018-04-07 15:32:37 +0000111 'inspector',
112 'toolbox',
113 'integration_test_runner',
114 'formatter_worker',
115 'heap_snapshot_worker',
116]
117
Yang Guo5ebd8a72019-06-26 08:04:01 +0000118SKIPPED_NAMESPACES = {
Blink Reformat4c46d092018-04-07 15:32:37 +0000119 'Console', # Closure uses Console as a namespace item so we cannot override it right now.
120 'Gonzales', # third party module defined in front_end/externs.js
121 'Terminal', # third party module defined in front_end/externs.js
122}
123
124
125def has_errors(output):
Yang Guo5ebd8a72019-06-26 08:04:01 +0000126 return re.search(ERROR_WARNING_REGEX, output) is not None
Blink Reformat4c46d092018-04-07 15:32:37 +0000127
128
129class JSDocChecker:
130
131 def __init__(self, descriptors, java_exec):
132 self._error_found = False
133 self._all_files = descriptors.all_compiled_files()
134 self._java_exec = java_exec
135
136 def check(self):
137 print 'Verifying JSDoc comments...'
138 self._verify_jsdoc()
139 self._run_jsdoc_validator()
140 return self._error_found
141
142 def _run_jsdoc_validator(self):
143 files = [to_platform_path(f) for f in self._all_files]
144 file_list = tempfile.NamedTemporaryFile(mode='wt', delete=False)
145 try:
146 file_list.write('\n'.join(files))
147 finally:
148 file_list.close()
Yang Guo5ebd8a72019-06-26 08:04:01 +0000149 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 +0000150 (out, _) = proc.communicate()
151 if out:
152 print('JSDoc validator output:%s%s' % (os.linesep, out))
153 self._error_found = True
154 os.remove(file_list.name)
155
156 def _verify_jsdoc(self):
157 for full_file_name in self._all_files:
158 line_index = 0
159 with open(full_file_name, 'r') as sourceFile:
160 for line in sourceFile:
161 line_index += 1
162 if line.rstrip():
163 self._verify_jsdoc_line(full_file_name, line_index, line)
164
165 def _verify_jsdoc_line(self, file_name, line_index, line):
166
167 def print_error(message, error_position):
168 print '%s:%s: ERROR - %s%s%s%s%s%s' % (file_name, line_index, message, os.linesep, line, os.linesep,
169 ' ' * error_position + '^', os.linesep)
170
171 known_css = {}
Yang Guo5ebd8a72019-06-26 08:04:01 +0000172 match = re.search(INVALID_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000173 if match:
174 print_error('Type "%s" nullability not marked explicitly with "?" (nullable) or "!" (non-nullable)' % match.group(1),
175 match.start(1))
176 self._error_found = True
177
Yang Guo5ebd8a72019-06-26 08:04:01 +0000178 match = re.search(INVALID_NON_OBJECT_TYPE_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000179 if match:
180 print_error('Non-object type explicitly marked with "!" (non-nullable), which is the default and should be omitted',
181 match.start(1))
182 self._error_found = True
183
Yang Guo5ebd8a72019-06-26 08:04:01 +0000184 match = re.search(INVALID_TYPE_DESIGNATOR_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000185 if match:
186 print_error('Type nullability indicator misplaced, should precede type', match.start(1))
187 self._error_found = True
188
Yang Guo5ebd8a72019-06-26 08:04:01 +0000189 match = re.search(LOADED_CSS_REGEX, line)
Blink Reformat4c46d092018-04-07 15:32:37 +0000190 if match:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000191 css_file = path.join(DEVTOOLS_FRONTEND_PATH, match.group(1))
192 exists = known_css.get(css_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000193 if exists is None:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000194 exists = path.isfile(css_file)
195 known_css[css_file] = exists
Blink Reformat4c46d092018-04-07 15:32:37 +0000196 if not exists:
197 print_error('Dynamically loaded CSS stylesheet is missing in the source tree', match.start(1))
198 self._error_found = True
199
200
201def find_java():
202 required_major = 1
203 required_minor = 7
204 exec_command = None
205 has_server_jvm = True
206 java_path = utils.which('java')
207
208 if not java_path:
209 print 'NOTE: No Java executable found in $PATH.'
210 sys.exit(1)
211
212 is_ok = False
213 java_version_out, _ = popen([java_path, '-version']).communicate()
214 # pylint: disable=E1103
Yang Guo5ebd8a72019-06-26 08:04:01 +0000215 match = re.search(JAVA_BUILD_REGEX, java_version_out)
Blink Reformat4c46d092018-04-07 15:32:37 +0000216 if match:
217 major = int(match.group(1))
218 minor = int(match.group(2))
Alexei Filippov87e097d2018-11-06 19:18:29 +0000219 is_ok = major > required_major or major == required_major and minor >= required_minor
Blink Reformat4c46d092018-04-07 15:32:37 +0000220 if is_ok:
221 exec_command = [java_path, '-Xms1024m', '-server', '-XX:+TieredCompilation']
222 check_server_proc = popen(exec_command + ['-version'])
223 check_server_proc.communicate()
224 if check_server_proc.returncode != 0:
225 # Not all Java installs have server JVMs.
226 exec_command = exec_command.remove('-server')
227 has_server_jvm = False
228
229 if not is_ok:
230 print 'NOTE: Java executable version %d.%d or above not found in $PATH.' % (required_major, required_minor)
231 sys.exit(1)
232 print 'Java executable: %s%s' % (java_path, '' if has_server_jvm else ' (no server JVM)')
233 return exec_command
234
235
236common_closure_args = [
237 '--summary_detail_level',
238 '3',
239 '--jscomp_error',
240 'visibility',
241 '--jscomp_warning',
242 'missingOverride',
243 '--compilation_level',
244 'SIMPLE_OPTIMIZATIONS',
245 '--warning_level',
246 'VERBOSE',
247 '--language_in=ECMASCRIPT_2017',
248 '--language_out=ES5_STRICT',
249 '--extra_annotation_name',
250 'suppressReceiverCheck',
251 '--extra_annotation_name',
252 'suppressGlobalPropertiesCheck',
253 '--checks-only',
Blink Reformat4c46d092018-04-07 15:32:37 +0000254]
255
256
257def check_conditional_dependencies(modules_by_name):
258 errors_found = False
259 for name in modules_by_name:
260 if 'test_runner' in name:
261 continue
262 for dep_name in modules_by_name[name].get('dependencies', []):
263 dependency = modules_by_name[dep_name]
264 if dependency.get('experiment') or dependency.get('condition'):
265 log_error('Module "%s" may not depend on the conditional module "%s"' % (name, dep_name))
266 errors_found = True
267 return errors_found
268
269
Yang Guo5ebd8a72019-06-26 08:04:01 +0000270def prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path, protocol_externs_file):
Blink Reformat4c46d092018-04-07 15:32:37 +0000271 temp_frontend_path = path.join(temp_devtools_path, 'front_end')
Yang Guo5ebd8a72019-06-26 08:04:01 +0000272 checker = dependency_preprocessor.DependencyPreprocessor(descriptors, temp_frontend_path, DEVTOOLS_FRONTEND_PATH)
Blink Reformat4c46d092018-04-07 15:32:37 +0000273 checker.enforce_dependencies()
274
275 command = common_closure_args + [
276 '--externs',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000277 to_platform_path(GLOBAL_EXTERNS_FILE),
Blink Reformat4c46d092018-04-07 15:32:37 +0000278 '--externs',
279 namespace_externs_path,
280 '--js',
Yang Guo5ebd8a72019-06-26 08:04:01 +0000281 RUNTIME_FILE,
Blink Reformat4c46d092018-04-07 15:32:37 +0000282 ]
283
284 all_files = descriptors.all_compiled_files()
285 args = []
286 for file in all_files:
287 args.extend(['--js', file])
288 if "InspectorBackend.js" in file:
289 args.extend(['--js', protocol_externs_file])
290 command += args
Yang Guo5ebd8a72019-06-26 08:04:01 +0000291 command = [arg.replace(DEVTOOLS_FRONTEND_PATH, temp_frontend_path) for arg in command]
Blink Reformat4c46d092018-04-07 15:32:37 +0000292 compiler_args_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
293 try:
294 compiler_args_file.write('devtools_frontend %s' % (' '.join(command)))
295 finally:
296 compiler_args_file.close()
297 return compiler_args_file.name
298
299
300def generate_namespace_externs(modules_by_name):
301 special_case_namespaces_path = path.join(path.dirname(path.abspath(__file__)), 'special_case_namespaces.json')
302 with open(special_case_namespaces_path) as json_file:
303 special_case_namespaces = json.load(json_file)
304
305 def map_module_to_namespace(module):
306 return special_case_namespaces.get(module, to_camel_case(module))
307
308 def to_camel_case(snake_string):
309 components = snake_string.split('_')
310 return ''.join(x.title() for x in components)
311
312 all_namespaces = [map_module_to_namespace(module) for module in modules_by_name]
Yang Guo5ebd8a72019-06-26 08:04:01 +0000313 namespaces = [namespace for namespace in all_namespaces if namespace not in SKIPPED_NAMESPACES]
Blink Reformat4c46d092018-04-07 15:32:37 +0000314 namespaces.sort()
315 namespace_externs_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
316 try:
317 for namespace in namespaces:
318 namespace_externs_file.write('/** @const */\n')
319 namespace_externs_file.write('var %s = {};\n' % namespace)
320 finally:
321 namespace_externs_file.close()
322 namespace_externs_path = to_platform_path(namespace_externs_file.name)
323 return namespace_externs_path
324
325
326def main():
Yang Guo5ebd8a72019-06-26 08:04:01 +0000327 protocol_externs_file = DEFAULT_PROTOCOL_EXTERNS_FILE
Blink Reformat4c46d092018-04-07 15:32:37 +0000328 errors_found = False
329 parser = argparse.ArgumentParser()
330 parser.add_argument('--protocol-externs-file')
331 args, _ = parser.parse_known_args()
332 if args.protocol_externs_file:
333 protocol_externs_file = args.protocol_externs_file
334 else:
Yang Guo5ebd8a72019-06-26 08:04:01 +0000335 generate_protocol_externs.generate_protocol_externs(protocol_externs_file, path.join(
336 INSPECTOR_PATH, 'browser_protocol.pdl'), path.join(V8_INCLUDE_PATH, 'js_protocol.pdl'))
337 loader = modular_build.DescriptorLoader(DEVTOOLS_FRONTEND_PATH)
338 descriptors = loader.load_applications(APPLICATION_DESCRIPTORS)
Blink Reformat4c46d092018-04-07 15:32:37 +0000339 modules_by_name = descriptors.modules
340
341 java_exec = find_java()
342 errors_found |= check_conditional_dependencies(modules_by_name)
343
344 print 'Compiling frontend...'
345 temp_devtools_path = tempfile.mkdtemp()
346 namespace_externs_path = generate_namespace_externs(modules_by_name)
Yang Guo5ebd8a72019-06-26 08:04:01 +0000347 compiler_args_file_path = prepare_closure_frontend_compile(temp_devtools_path, descriptors, namespace_externs_path,
348 protocol_externs_file)
Blink Reformat4c46d092018-04-07 15:32:37 +0000349 frontend_compile_proc = popen(
Yang Guo5ebd8a72019-06-26 08:04:01 +0000350 java_exec + ['-jar', CLOSURE_RUNNER_JAR, '--compiler-args-file',
351 to_platform_path_exact(compiler_args_file_path)])
Blink Reformat4c46d092018-04-07 15:32:37 +0000352
353 print 'Compiling devtools_compatibility.js...'
354
Yang Guo5ebd8a72019-06-26 08:04:01 +0000355 closure_compiler_command = java_exec + ['-jar', CLOSURE_COMPILER_JAR] + common_closure_args
Blink Reformat4c46d092018-04-07 15:32:37 +0000356
357 devtools_js_compile_command = closure_compiler_command + [
Yang Guo5ebd8a72019-06-26 08:04:01 +0000358 '--externs',
359 to_platform_path(GLOBAL_EXTERNS_FILE), '--externs',
360 to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'host', 'InspectorFrontendHostAPI.js')),
361 '--jscomp_off=externsValidation', '--js',
362 to_platform_path(path.join(DEVTOOLS_FRONTEND_PATH, 'devtools_compatibility.js'))
Blink Reformat4c46d092018-04-07 15:32:37 +0000363 ]
364 devtools_js_compile_proc = popen(devtools_js_compile_command)
365
366 errors_found |= JSDocChecker(descriptors, java_exec).check()
367
368 (devtools_js_compile_out, _) = devtools_js_compile_proc.communicate()
369 print 'devtools_compatibility.js compilation output:%s' % os.linesep, devtools_js_compile_out
370 errors_found |= has_errors(devtools_js_compile_out)
371
372 (frontend_compile_out, _) = frontend_compile_proc.communicate()
373 print 'devtools frontend compilation output:'
374 for line in frontend_compile_out.splitlines():
375 if "@@ START_MODULE" in line or "@@ END_MODULE" in line:
376 continue
377 print line
378 errors_found |= has_errors(frontend_compile_out)
379
380 os.remove(protocol_externs_file)
381 os.remove(namespace_externs_path)
382 os.remove(compiler_args_file_path)
383 shutil.rmtree(temp_devtools_path, True)
384
385 if errors_found:
386 print 'ERRORS DETECTED'
387 sys.exit(1)
388 print 'DONE - compiled without errors'
389
390
391if __name__ == "__main__":
392 main()