2 # Copyright (c) 2012 Google Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
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
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.
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.
31 import os.path as path
32 import generate_protocol_externs
41 import simplejson as json
43 scripts_path = path.dirname(path.abspath(__file__))
44 devtools_path = path.dirname(scripts_path)
45 inspector_path = path.join(path.dirname(devtools_path), "core", "inspector")
46 devtools_frontend_path = path.join(devtools_path, "front_end")
47 global_externs_file = path.join(devtools_frontend_path, "externs.js")
48 protocol_externs_file = path.join(devtools_frontend_path, "protocol_externs.js")
49 webgl_rendering_context_idl_path = path.join(path.dirname(devtools_path), "core", "html", "canvas", "WebGLRenderingContextBase.idl")
50 injected_script_source_name = path.join(inspector_path, "InjectedScriptSource.js")
51 canvas_injected_script_source_name = path.join(inspector_path, "InjectedScriptCanvasModuleSource.js")
52 closure_compiler_jar = path.join(scripts_path, "closure", "compiler.jar")
53 closure_runner_jar = path.join(scripts_path, "compiler-runner", "closure-runner.jar")
54 jsdoc_validator_jar = path.join(scripts_path, "jsdoc-validator", "jsdoc-validator.jar")
55 java_exec = "java -Xms1024m -server -XX:+TieredCompilation"
57 generate_protocol_externs.generate_protocol_externs(protocol_externs_file, path.join(devtools_path, "protocol.json"))
59 jsmodule_name_prefix = "jsmodule_"
60 js_modules_name = "frontend_modules.json"
63 with open(path.join(scripts_path, js_modules_name), "rt") as js_modules_file:
64 modules = json.loads(js_modules_file.read())
66 print "ERROR: Failed to read %s" % js_modules_name
69 # `importScript` function must not be used in any files
70 # except module headers. Refer to devtools.gyp file for
71 # the module header list.
72 allowed_import_statements_files = [
73 "search/AdvancedSearchView.js",
74 "console/ConsolePanel.js",
75 "elements/ElementsPanel.js",
76 "resources/ResourcesPanel.js",
77 "network/NetworkPanel.js",
78 "sources/SourcesPanel.js",
79 "timeline/TimelinePanel.js",
80 "profiler/ProfilesPanel.js",
81 "audits/AuditsPanel.js",
82 "layers/LayersPanel.js",
83 "extensions/ExtensionServer.js",
84 "source_frame/SourceFrame.js",
87 type_checked_jsdoc_tags_list = ["param", "return", "type", "enum"]
89 type_checked_jsdoc_tags_or = "|".join(type_checked_jsdoc_tags_list)
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).
92 invalid_type_regex = re.compile(r"@(?:" + type_checked_jsdoc_tags_or + r")\s*\{.*(?<![!?:.A-Za-z0-9])([A-Z][A-Za-z0-9.]+[A-Za-z0-9])[^/]*\}")
94 invalid_type_designator_regex = re.compile(r"@(?:" + type_checked_jsdoc_tags_or + r")\s*.*([?!])=?\}")
96 importscript_regex = re.compile(r"importScript\(\s*[\"']")
97 error_warning_regex = re.compile(r"(?:WARNING|ERROR)")
102 def hasErrors(output):
103 return re.search(error_warning_regex, output) != None
106 def verify_importScript_usage():
108 for module in modules:
109 for file_name in module['sources']:
110 if file_name in allowed_import_statements_files:
113 with open(path.join(devtools_frontend_path, file_name), "r") as sourceFile:
114 source = sourceFile.read()
115 if re.search(importscript_regex, source):
116 print "ERROR: importScript function call is allowed in module header files only (found in %s)" % file_name
119 print "ERROR: Failed to access %s" % file_name
124 def dump_all_checked_files():
126 for module in modules:
127 for source in module["sources"]:
128 files[path.join(devtools_frontend_path, source)] = True
129 return " ".join(files.keys())
132 def verify_jsdoc_extra():
133 return subprocess.Popen("%s -jar %s %s" % (java_exec, jsdoc_validator_jar, dump_all_checked_files()), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
138 for module in modules:
139 for file_name in module["sources"]:
141 full_file_name = path.join(devtools_frontend_path, file_name)
142 with open(full_file_name, "r") as sourceFile:
143 for line in sourceFile:
148 if verify_jsdoc_line(full_file_name, lineIndex, line):
153 def verify_jsdoc_line(fileName, lineIndex, line):
154 def print_error(message, errorPosition):
155 print "%s:%s: ERROR - %s\n%s\n%s\n" % (fileName, lineIndex, message, line, " " * errorPosition + "^")
158 match = re.search(invalid_type_regex, line)
160 print_error("Type '%s' nullability not marked explicitly with '?' (nullable) or '!' (non-nullable)" % match.group(1), match.start(1))
163 match = re.search(invalid_type_designator_regex, line)
165 print_error("Type nullability indicator misplaced, should precede type", match.start(1))
170 def check_java_path():
171 proc = subprocess.Popen("which java", stdout=subprocess.PIPE, shell=True)
172 (javaPath, _) = proc.communicate()
174 if proc.returncode != 0:
175 print "Cannot find java ('which java' return code = %d, should be 0)" % proc.returncode
177 print "Java executable: " + re.sub(r"\n$", "", javaPath)
181 print "Verifying 'importScript' function usage..."
182 errors_found |= verify_importScript_usage()
184 print "Verifying JSDoc comments..."
185 errors_found |= verify_jsdoc()
186 jsdocValidatorProc = verify_jsdoc_extra()
188 modules_dir = tempfile.mkdtemp()
189 common_closure_args = " --summary_detail_level 3 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE --language_in ECMASCRIPT5 --accept_const_keyword --module_output_path_prefix %s/" % modules_dir
191 compiler_args_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
192 closure_runner_command = "%s -jar %s --compiler-args-file %s" % (java_exec, closure_runner_jar, compiler_args_file.name)
194 spawned_compiler_command = "%s -jar %s %s \\\n" % (java_exec, closure_compiler_jar, common_closure_args)
197 standalone_modules_by_name = {}
198 dependents_by_module_name = {}
200 for module in modules:
201 name = module["name"]
202 modules_by_name[name] = module
203 if "standalone" in module:
204 standalone_modules_by_name[name] = module
205 for dep in module["dependencies"]:
206 list = dependents_by_module_name.get(dep)
209 dependents_by_module_name[dep] = list
213 def verify_standalone_modules():
214 for module in modules:
215 for dependency in module["dependencies"]:
216 if dependency in standalone_modules_by_name:
217 print "ERROR: Standalone module %s may not be present among the dependencies of %s" % (dependency, module["name"])
220 verify_standalone_modules()
223 def check_duplicate_files():
225 def check_module(module, seen_files, seen_modules):
226 name = module["name"]
227 seen_modules[name] = True
228 for dep_name in module["dependencies"]:
229 if not dep_name in seen_modules:
230 check_module(modules_by_name[dep_name], seen_files, seen_modules)
231 for source in module["sources"]:
232 referencing_module = seen_files.get(source)
233 if referencing_module:
234 print "ERROR: Duplicate use of %s in '%s' (previously seen in '%s')" % (source, name, referencing_module)
235 seen_files[source] = name
237 for module_name in standalone_modules_by_name:
238 check_module(standalone_modules_by_name[module_name], {}, {})
240 print "Checking duplicate files across modules..."
241 check_duplicate_files()
244 def dump_module(name, recursively, processed_modules):
245 if name in processed_modules:
247 processed_modules[name] = True
248 module = modules_by_name[name]
251 for dependency in module["dependencies"]:
252 command += dump_module(dependency, recursively, processed_modules)
253 command += " --module " + jsmodule_name_prefix + module["name"] + ":"
254 command += str(len(module["sources"]))
255 firstDependency = True
256 for dependency in module["dependencies"]:
261 firstDependency = False
262 command += jsmodule_name_prefix + dependency
263 for script in module["sources"]:
264 command += " --js " + path.join(devtools_frontend_path, script)
267 print "Compiling frontend..."
269 for module in modules:
270 closure_args = common_closure_args
271 closure_args += " --externs " + global_externs_file
272 closure_args += " --externs " + protocol_externs_file
273 closure_args += dump_module(module["name"], True, {})
274 compiler_args_file.write("%s %s\n" % (module["name"], closure_args))
276 compiler_args_file.close()
277 modular_compiler_proc = subprocess.Popen(closure_runner_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
280 def unclosure_injected_script(sourceFileName, outFileName):
281 sourceFile = open(sourceFileName, "r")
282 source = sourceFile.read()
285 def replace_function(matchobj):
286 return re.sub(r"@param", "param", matchobj.group(1) or "") + "\n//" + matchobj.group(2)
288 # Comment out the closure function and its jsdocs
289 source = re.sub(r"(/\*\*(?:[\s\n]*\*\s*@param[^\n]+\n)+\s*\*/\s*)?\n(\(function)", replace_function, source, count=1)
291 # Comment out its return statement
292 source = re.sub(r"\n(\s*return\s+[^;]+;\s*\n\}\)\s*)$", "\n/*\\1*/", source)
294 outFileName = open(outFileName, "w")
295 outFileName.write(source)
298 injectedScriptSourceTmpFile = path.join(inspector_path, "InjectedScriptSourceTmp.js")
299 injectedScriptCanvasModuleSourceTmpFile = path.join(inspector_path, "InjectedScriptCanvasModuleSourceTmp.js")
301 unclosure_injected_script(injected_script_source_name, injectedScriptSourceTmpFile)
302 unclosure_injected_script(canvas_injected_script_source_name, injectedScriptCanvasModuleSourceTmpFile)
304 print "Compiling InjectedScriptSource.js and InjectedScriptCanvasModuleSource.js..."
305 command = spawned_compiler_command
306 command += " --externs " + path.join(inspector_path, "InjectedScriptExterns.js") + " \\\n"
307 command += " --externs " + protocol_externs_file + " \\\n"
308 command += " --module " + jsmodule_name_prefix + "injected_script" + ":1" + " \\\n"
309 command += " --js " + injectedScriptSourceTmpFile + " \\\n"
310 command += " --module " + jsmodule_name_prefix + "injected_canvas_script" + ":1:" + jsmodule_name_prefix + "injected_script" + " \\\n"
311 command += " --js " + injectedScriptCanvasModuleSourceTmpFile + " \\\n"
314 injectedScriptCompileProc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
316 print "Checking generated code in InjectedScriptCanvasModuleSource.js..."
317 check_injected_webgl_calls_command = "%s/check_injected_webgl_calls_info.py %s %s" % (scripts_path, webgl_rendering_context_idl_path, canvas_injected_script_source_name)
318 canvasModuleCompileProc = subprocess.Popen(check_injected_webgl_calls_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
320 print "Validating InjectedScriptSource.js..."
321 check_injected_script_command = "%s/check_injected_script_source.py %s" % (scripts_path, injected_script_source_name)
322 validateInjectedScriptProc = subprocess.Popen(check_injected_script_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
326 (jsdocValidatorOut, _) = jsdocValidatorProc.communicate()
327 if jsdocValidatorOut:
328 print ("JSDoc validator output:\n%s" % jsdocValidatorOut)
331 (moduleCompileOut, _) = modular_compiler_proc.communicate()
332 print "Modular compilation output:"
334 start_module_regex = re.compile(r"^@@ START_MODULE:(.+) @@$")
335 end_module_regex = re.compile(r"^@@ END_MODULE @@$")
341 def skip_dependents(module_name):
342 for skipped_module in dependents_by_module_name.get(module_name, []):
343 skipped_modules[skipped_module] = True
345 # pylint: disable=E1103
346 for line in moduleCompileOut.splitlines():
348 match = re.search(start_module_regex, line)
352 module_error_count = 0
354 module_name = match.group(1)
355 skip_module = skipped_modules.get(module_name)
357 skip_dependents(module_name)
359 match = re.search(end_module_regex, line)
362 module_output.append(line)
365 module_error_count += 1
366 skip_dependents(module_name)
371 print "Skipping module %s..." % module_name
372 elif not module_error_count:
373 print "Module %s compiled successfully: %s" % (module_name, module_output[0])
375 print "Module %s compile failed: %s errors\n" % (module_name, module_error_count)
376 print os.linesep.join(module_output)
379 print "Total Closure errors: %d\n" % error_count
382 (injectedScriptCompileOut, _) = injectedScriptCompileProc.communicate()
383 print "InjectedScriptSource.js and InjectedScriptCanvasModuleSource.js compilation output:\n", injectedScriptCompileOut
384 errors_found |= hasErrors(injectedScriptCompileOut)
386 (canvasModuleCompileOut, _) = canvasModuleCompileProc.communicate()
387 print "InjectedScriptCanvasModuleSource.js generated code check output:\n", canvasModuleCompileOut
388 errors_found |= hasErrors(canvasModuleCompileOut)
390 (validateInjectedScriptOut, _) = validateInjectedScriptProc.communicate()
391 print "Validate InjectedScriptSource.js output:\n", (validateInjectedScriptOut if validateInjectedScriptOut else "<empty>")
392 errors_found |= hasErrors(validateInjectedScriptOut)
395 print "ERRORS DETECTED"
397 os.remove(injectedScriptSourceTmpFile)
398 os.remove(injectedScriptCanvasModuleSourceTmpFile)
399 os.remove(compiler_args_file.name)
400 os.remove(protocol_externs_file)
401 shutil.rmtree(modules_dir, True)