Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / scripts / compile_frontend.py
1 #!/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
30 import os
31 import os.path as path
32 import generate_protocol_externs
33 import re
34 import shutil
35 import subprocess
36 import sys
37 import tempfile
38 try:
39     import json
40 except ImportError:
41     import simplejson as json
42
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"
56
57 generate_protocol_externs.generate_protocol_externs(protocol_externs_file, path.join(devtools_path, "protocol.json"))
58
59 jsmodule_name_prefix = "jsmodule_"
60 js_modules_name = "frontend_modules.json"
61
62 try:
63     with open(path.join(scripts_path, js_modules_name), "rt") as js_modules_file:
64         modules = json.loads(js_modules_file.read())
65 except:
66     print "ERROR: Failed to read %s" % js_modules_name
67     raise
68
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",
85 ]
86
87 type_checked_jsdoc_tags_list = ["param", "return", "type", "enum"]
88
89 type_checked_jsdoc_tags_or = "|".join(type_checked_jsdoc_tags_list)
90
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])[^/]*\}")
93
94 invalid_type_designator_regex = re.compile(r"@(?:" + type_checked_jsdoc_tags_or + r")\s*.*([?!])=?\}")
95
96 importscript_regex = re.compile(r"importScript\(\s*[\"']")
97 error_warning_regex = re.compile(r"(?:WARNING|ERROR)")
98
99 errors_found = False
100
101
102 def hasErrors(output):
103     return re.search(error_warning_regex, output) != None
104
105
106 def verify_importScript_usage():
107     errors_found = False
108     for module in modules:
109         for file_name in module['sources']:
110             if file_name in allowed_import_statements_files:
111                 continue
112             try:
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
117                         errors_found = True
118             except:
119                 print "ERROR: Failed to access %s" % file_name
120                 raise
121     return errors_found
122
123
124 def dump_all_checked_files():
125     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())
130
131
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)
134
135
136 def verify_jsdoc():
137     errors_found = False
138     for module in modules:
139         for file_name in module["sources"]:
140             lineIndex = 0
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:
144                     line = line.rstrip()
145                     lineIndex += 1
146                     if not line:
147                         continue
148                     if verify_jsdoc_line(full_file_name, lineIndex, line):
149                         errors_found = True
150     return errors_found
151
152
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 + "^")
156
157     errors_found = False
158     match = re.search(invalid_type_regex, line)
159     if match:
160         print_error("Type '%s' nullability not marked explicitly with '?' (nullable) or '!' (non-nullable)" % match.group(1), match.start(1))
161         errors_found = True
162
163     match = re.search(invalid_type_designator_regex, line)
164     if (match):
165         print_error("Type nullability indicator misplaced, should precede type", match.start(1))
166         errors_found = True
167     return errors_found
168
169
170 def check_java_path():
171     proc = subprocess.Popen("which java", stdout=subprocess.PIPE, shell=True)
172     (javaPath, _) = proc.communicate()
173
174     if proc.returncode != 0:
175         print "Cannot find java ('which java' return code = %d, should be 0)" % proc.returncode
176         sys.exit(1)
177     print "Java executable: " + re.sub(r"\n$", "", javaPath)
178
179 check_java_path()
180
181 print "Verifying 'importScript' function usage..."
182 errors_found |= verify_importScript_usage()
183
184 print "Verifying JSDoc comments..."
185 errors_found |= verify_jsdoc()
186 jsdocValidatorProc = verify_jsdoc_extra()
187
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
190
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)
193
194 spawned_compiler_command = "%s -jar %s %s \\\n" % (java_exec, closure_compiler_jar, common_closure_args)
195
196 modules_by_name = {}
197 standalone_modules_by_name = {}
198 dependents_by_module_name = {}
199
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)
207         if not list:
208             list = []
209             dependents_by_module_name[dep] = list
210         list.append(name)
211
212
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"])
218                 errors_found = True
219
220 verify_standalone_modules()
221
222
223 def check_duplicate_files():
224
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
236
237     for module_name in standalone_modules_by_name:
238         check_module(standalone_modules_by_name[module_name], {}, {})
239
240 print "Checking duplicate files across modules..."
241 check_duplicate_files()
242
243
244 def dump_module(name, recursively, processed_modules):
245     if name in processed_modules:
246         return ""
247     processed_modules[name] = True
248     module = modules_by_name[name]
249     command = ""
250     if recursively:
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"]:
257         if firstDependency:
258             command += ":"
259         else:
260             command += ","
261         firstDependency = False
262         command += jsmodule_name_prefix + dependency
263     for script in module["sources"]:
264         command += " --js " + path.join(devtools_frontend_path, script)
265     return command
266
267 print "Compiling frontend..."
268
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))
275
276 compiler_args_file.close()
277 modular_compiler_proc = subprocess.Popen(closure_runner_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
278
279
280 def unclosure_injected_script(sourceFileName, outFileName):
281     sourceFile = open(sourceFileName, "r")
282     source = sourceFile.read()
283     sourceFile.close()
284
285     def replace_function(matchobj):
286         return re.sub(r"@param", "param", matchobj.group(1) or "") + "\n//" + matchobj.group(2)
287
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)
290
291     # Comment out its return statement
292     source = re.sub(r"\n(\s*return\s+[^;]+;\s*\n\}\)\s*)$", "\n/*\\1*/", source)
293
294     outFileName = open(outFileName, "w")
295     outFileName.write(source)
296     outFileName.close()
297
298 injectedScriptSourceTmpFile = path.join(inspector_path, "InjectedScriptSourceTmp.js")
299 injectedScriptCanvasModuleSourceTmpFile = path.join(inspector_path, "InjectedScriptCanvasModuleSourceTmp.js")
300
301 unclosure_injected_script(injected_script_source_name, injectedScriptSourceTmpFile)
302 unclosure_injected_script(canvas_injected_script_source_name, injectedScriptCanvasModuleSourceTmpFile)
303
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"
312 command += "\n"
313
314 injectedScriptCompileProc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
315
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)
319
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)
323
324 print
325
326 (jsdocValidatorOut, _) = jsdocValidatorProc.communicate()
327 if jsdocValidatorOut:
328     print ("JSDoc validator output:\n%s" % jsdocValidatorOut)
329     errors_found = True
330
331 (moduleCompileOut, _) = modular_compiler_proc.communicate()
332 print "Modular compilation output:"
333
334 start_module_regex = re.compile(r"^@@ START_MODULE:(.+) @@$")
335 end_module_regex = re.compile(r"^@@ END_MODULE @@$")
336
337 in_module = False
338 skipped_modules = {}
339 error_count = 0
340
341 def skip_dependents(module_name):
342     for skipped_module in dependents_by_module_name.get(module_name, []):
343         skipped_modules[skipped_module] = True
344
345 # pylint: disable=E1103
346 for line in moduleCompileOut.splitlines():
347     if not in_module:
348         match = re.search(start_module_regex, line)
349         if not match:
350             continue
351         in_module = True
352         module_error_count = 0
353         module_output = []
354         module_name = match.group(1)
355         skip_module = skipped_modules.get(module_name)
356         if skip_module:
357             skip_dependents(module_name)
358     else:
359         match = re.search(end_module_regex, line)
360         if not match:
361             if not skip_module:
362                 module_output.append(line)
363                 if hasErrors(line):
364                     error_count += 1
365                     module_error_count += 1
366                     skip_dependents(module_name)
367             continue
368
369         in_module = False
370         if skip_module:
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])
374         else:
375             print "Module %s compile failed: %s errors\n" % (module_name, module_error_count)
376             print os.linesep.join(module_output)
377
378 if error_count:
379     print "Total Closure errors: %d\n" % error_count
380     errors_found = True
381
382 (injectedScriptCompileOut, _) = injectedScriptCompileProc.communicate()
383 print "InjectedScriptSource.js and InjectedScriptCanvasModuleSource.js compilation output:\n", injectedScriptCompileOut
384 errors_found |= hasErrors(injectedScriptCompileOut)
385
386 (canvasModuleCompileOut, _) = canvasModuleCompileProc.communicate()
387 print "InjectedScriptCanvasModuleSource.js generated code check output:\n", canvasModuleCompileOut
388 errors_found |= hasErrors(canvasModuleCompileOut)
389
390 (validateInjectedScriptOut, _) = validateInjectedScriptProc.communicate()
391 print "Validate InjectedScriptSource.js output:\n", (validateInjectedScriptOut if validateInjectedScriptOut else "<empty>")
392 errors_found |= hasErrors(validateInjectedScriptOut)
393
394 if errors_found:
395     print "ERRORS DETECTED"
396
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)