Backed out changeset 7272b7396c78 (bug 1932758) for causing fenix debug failures...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / closure-library / closure / bin / calcdeps.py
blob9cb1a6db062e944a9a516af2c80a2f76e16d2e58
1 #!/usr/bin/env python
3 # Copyright 2006 The Closure Library Authors. All Rights Reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 """Calculates JavaScript dependencies without requiring Google's build system.
20 This tool is deprecated and is provided for legacy users.
21 See build/closurebuilder.py and build/depswriter.py for the current tools.
23 It iterates over a number of search paths and builds a dependency tree. With
24 the inputs provided, it walks the dependency tree and outputs all the files
25 required for compilation.
26 """
32 try:
33 import distutils.version
34 except ImportError:
35 # distutils is not available in all environments
36 distutils = None
38 import logging
39 import optparse
40 import os
41 import re
42 import subprocess
43 import sys
46 _BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
47 req_regex = re.compile(_BASE_REGEX_STRING % 'require')
48 prov_regex = re.compile(_BASE_REGEX_STRING % 'provide')
49 ns_regex = re.compile('^ns:((\w+\.)*(\w+))$')
50 version_regex = re.compile('[\.0-9]+')
53 def IsValidFile(ref):
54 """Returns true if the provided reference is a file and exists."""
55 return os.path.isfile(ref)
58 def IsJsFile(ref):
59 """Returns true if the provided reference is a Javascript file."""
60 return ref.endswith('.js')
63 def IsNamespace(ref):
64 """Returns true if the provided reference is a namespace."""
65 return re.match(ns_regex, ref) is not None
68 def IsDirectory(ref):
69 """Returns true if the provided reference is a directory."""
70 return os.path.isdir(ref)
73 def ExpandDirectories(refs):
74 """Expands any directory references into inputs.
76 Description:
77 Looks for any directories in the provided references. Found directories
78 are recursively searched for .js files, which are then added to the result
79 list.
81 Args:
82 refs: a list of references such as files, directories, and namespaces
84 Returns:
85 A list of references with directories removed and replaced by any
86 .js files that are found in them. Also, the paths will be normalized.
87 """
88 result = []
89 for ref in refs:
90 if IsDirectory(ref):
91 # Disable 'Unused variable' for subdirs
92 # pylint: disable=unused-variable
93 for (directory, subdirs, filenames) in os.walk(ref):
94 for filename in filenames:
95 if IsJsFile(filename):
96 result.append(os.path.join(directory, filename))
97 else:
98 result.append(ref)
99 return map(os.path.normpath, result)
102 class DependencyInfo(object):
103 """Represents a dependency that is used to build and walk a tree."""
105 def __init__(self, filename):
106 self.filename = filename
107 self.provides = []
108 self.requires = []
110 def __str__(self):
111 return '%s Provides: %s Requires: %s' % (self.filename,
112 repr(self.provides),
113 repr(self.requires))
116 def BuildDependenciesFromFiles(files):
117 """Build a list of dependencies from a list of files.
119 Description:
120 Takes a list of files, extracts their provides and requires, and builds
121 out a list of dependency objects.
123 Args:
124 files: a list of files to be parsed for goog.provides and goog.requires.
126 Returns:
127 A list of dependency objects, one for each file in the files argument.
129 result = []
130 filenames = set()
131 for filename in files:
132 if filename in filenames:
133 continue
135 # Python 3 requires the file encoding to be specified
136 if (sys.version_info[0] < 3):
137 file_handle = open(filename, 'r')
138 else:
139 file_handle = open(filename, 'r', encoding='utf8')
141 try:
142 dep = CreateDependencyInfo(filename, file_handle)
143 result.append(dep)
144 finally:
145 file_handle.close()
147 filenames.add(filename)
149 return result
152 def CreateDependencyInfo(filename, source):
153 """Create dependency info.
155 Args:
156 filename: Filename for source.
157 source: File-like object containing source.
159 Returns:
160 A DependencyInfo object with provides and requires filled.
162 dep = DependencyInfo(filename)
163 for line in source:
164 if re.match(req_regex, line):
165 dep.requires.append(re.search(req_regex, line).group(1))
166 if re.match(prov_regex, line):
167 dep.provides.append(re.search(prov_regex, line).group(1))
168 return dep
171 def BuildDependencyHashFromDependencies(deps):
172 """Builds a hash for searching dependencies by the namespaces they provide.
174 Description:
175 Dependency objects can provide multiple namespaces. This method enumerates
176 the provides of each dependency and adds them to a hash that can be used
177 to easily resolve a given dependency by a namespace it provides.
179 Args:
180 deps: a list of dependency objects used to build the hash.
182 Raises:
183 Exception: If a multiple files try to provide the same namepace.
185 Returns:
186 A hash table { namespace: dependency } that can be used to resolve a
187 dependency by a namespace it provides.
189 dep_hash = {}
190 for dep in deps:
191 for provide in dep.provides:
192 if provide in dep_hash:
193 raise Exception('Duplicate provide (%s) in (%s, %s)' % (
194 provide,
195 dep_hash[provide].filename,
196 dep.filename))
197 dep_hash[provide] = dep
198 return dep_hash
201 def CalculateDependencies(paths, inputs):
202 """Calculates the dependencies for given inputs.
204 Description:
205 This method takes a list of paths (files, directories) and builds a
206 searchable data structure based on the namespaces that each .js file
207 provides. It then parses through each input, resolving dependencies
208 against this data structure. The final output is a list of files,
209 including the inputs, that represent all of the code that is needed to
210 compile the given inputs.
212 Args:
213 paths: the references (files, directories) that are used to build the
214 dependency hash.
215 inputs: the inputs (files, directories, namespaces) that have dependencies
216 that need to be calculated.
218 Raises:
219 Exception: if a provided input is invalid.
221 Returns:
222 A list of all files, including inputs, that are needed to compile the given
223 inputs.
225 deps = BuildDependenciesFromFiles(paths + inputs)
226 search_hash = BuildDependencyHashFromDependencies(deps)
227 result_list = []
228 seen_list = []
229 for input_file in inputs:
230 if IsNamespace(input_file):
231 namespace = re.search(ns_regex, input_file).group(1)
232 if namespace not in search_hash:
233 raise Exception('Invalid namespace (%s)' % namespace)
234 input_file = search_hash[namespace].filename
235 if not IsValidFile(input_file) or not IsJsFile(input_file):
236 raise Exception('Invalid file (%s)' % input_file)
237 seen_list.append(input_file)
238 file_handle = open(input_file, 'r')
239 try:
240 for line in file_handle:
241 if re.match(req_regex, line):
242 require = re.search(req_regex, line).group(1)
243 ResolveDependencies(require, search_hash, result_list, seen_list)
244 finally:
245 file_handle.close()
246 result_list.append(input_file)
248 # All files depend on base.js, so put it first.
249 base_js_path = FindClosureBasePath(paths)
250 if base_js_path:
251 result_list.insert(0, base_js_path)
252 else:
253 logging.warning('Closure Library base.js not found.')
255 return result_list
258 def FindClosureBasePath(paths):
259 """Given a list of file paths, return Closure base.js path, if any.
261 Args:
262 paths: A list of paths.
264 Returns:
265 The path to Closure's base.js file including filename, if found.
268 for path in paths:
269 pathname, filename = os.path.split(path)
271 if filename == 'base.js':
272 f = open(path)
274 is_base = False
276 # Sanity check that this is the Closure base file. Check that this
277 # is where goog is defined. This is determined by the @provideGoog
278 # flag.
279 for line in f:
280 if '@provideGoog' in line:
281 is_base = True
282 break
284 f.close()
286 if is_base:
287 return path
289 def ResolveDependencies(require, search_hash, result_list, seen_list):
290 """Takes a given requirement and resolves all of the dependencies for it.
292 Description:
293 A given requirement may require other dependencies. This method
294 recursively resolves all dependencies for the given requirement.
296 Raises:
297 Exception: when require does not exist in the search_hash.
299 Args:
300 require: the namespace to resolve dependencies for.
301 search_hash: the data structure used for resolving dependencies.
302 result_list: a list of filenames that have been calculated as dependencies.
303 This variable is the output for this function.
304 seen_list: a list of filenames that have been 'seen'. This is required
305 for the dependency->dependant ordering.
307 if require not in search_hash:
308 raise Exception('Missing provider for (%s)' % require)
310 dep = search_hash[require]
311 if not dep.filename in seen_list:
312 seen_list.append(dep.filename)
313 for sub_require in dep.requires:
314 ResolveDependencies(sub_require, search_hash, result_list, seen_list)
315 result_list.append(dep.filename)
318 def GetDepsLine(dep, base_path):
319 """Returns a JS string for a dependency statement in the deps.js file.
321 Args:
322 dep: The dependency that we're printing.
323 base_path: The path to Closure's base.js including filename.
325 return 'goog.addDependency("%s", %s, %s);' % (
326 GetRelpath(dep.filename, base_path), dep.provides, dep.requires)
329 def GetRelpath(path, start):
330 """Return a relative path to |path| from |start|."""
331 # NOTE: Python 2.6 provides os.path.relpath, which has almost the same
332 # functionality as this function. Since we want to support 2.4, we have
333 # to implement it manually. :(
334 path_list = os.path.abspath(os.path.normpath(path)).split(os.sep)
335 start_list = os.path.abspath(
336 os.path.normpath(os.path.dirname(start))).split(os.sep)
338 common_prefix_count = 0
339 for i in range(0, min(len(path_list), len(start_list))):
340 if path_list[i] != start_list[i]:
341 break
342 common_prefix_count += 1
344 # Always use forward slashes, because this will get expanded to a url,
345 # not a file path.
346 return '/'.join(['..'] * (len(start_list) - common_prefix_count) +
347 path_list[common_prefix_count:])
350 def PrintLine(msg, out):
351 out.write(msg)
352 out.write('\n')
355 def PrintDeps(source_paths, deps, out):
356 """Print out a deps.js file from a list of source paths.
358 Args:
359 source_paths: Paths that we should generate dependency info for.
360 deps: Paths that provide dependency info. Their dependency info should
361 not appear in the deps file.
362 out: The output file.
364 Returns:
365 True on success, false if it was unable to find the base path
366 to generate deps relative to.
368 base_path = FindClosureBasePath(source_paths + deps)
369 if not base_path:
370 return False
372 PrintLine('// This file was autogenerated by calcdeps.py', out)
373 excludesSet = set(deps)
375 for dep in BuildDependenciesFromFiles(source_paths + deps):
376 if not dep.filename in excludesSet:
377 PrintLine(GetDepsLine(dep, base_path), out)
379 return True
382 def PrintScript(source_paths, out):
383 for index, dep in enumerate(source_paths):
384 PrintLine('// Input %d' % index, out)
385 f = open(dep, 'r')
386 PrintLine(f.read(), out)
387 f.close()
390 def GetJavaVersion():
391 """Returns the string for the current version of Java installed."""
392 proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE)
393 proc.wait()
394 version_line = proc.stderr.read().splitlines()[0]
395 return version_regex.search(version_line).group()
398 def FilterByExcludes(options, files):
399 """Filters the given files by the exlusions specified at the command line.
401 Args:
402 options: The flags to calcdeps.
403 files: The files to filter.
404 Returns:
405 A list of files.
407 excludes = []
408 if options.excludes:
409 excludes = ExpandDirectories(options.excludes)
411 excludesSet = set(excludes)
412 return [i for i in files if not i in excludesSet]
415 def GetPathsFromOptions(options):
416 """Generates the path files from flag options.
418 Args:
419 options: The flags to calcdeps.
420 Returns:
421 A list of files in the specified paths. (strings).
424 search_paths = options.paths
425 if not search_paths:
426 search_paths = ['.'] # Add default folder if no path is specified.
428 search_paths = ExpandDirectories(search_paths)
429 return FilterByExcludes(options, search_paths)
432 def GetInputsFromOptions(options):
433 """Generates the inputs from flag options.
435 Args:
436 options: The flags to calcdeps.
437 Returns:
438 A list of inputs (strings).
440 inputs = options.inputs
441 if not inputs: # Parse stdin
442 logging.info('No inputs specified. Reading from stdin...')
443 inputs = filter(None, [line.strip('\n') for line in sys.stdin.readlines()])
445 logging.info('Scanning files...')
446 inputs = ExpandDirectories(inputs)
448 return FilterByExcludes(options, inputs)
451 def Compile(compiler_jar_path, source_paths, out, flags=None):
452 """Prepares command-line call to Closure compiler.
454 Args:
455 compiler_jar_path: Path to the Closure compiler .jar file.
456 source_paths: Source paths to build, in order.
457 flags: A list of additional flags to pass on to Closure compiler.
459 args = ['java', '-jar', compiler_jar_path]
460 for path in source_paths:
461 args += ['--js', path]
463 if flags:
464 args += flags
466 logging.info('Compiling with the following command: %s', ' '.join(args))
467 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
468 (stdoutdata, stderrdata) = proc.communicate()
469 if proc.returncode != 0:
470 logging.error('JavaScript compilation failed.')
471 sys.exit(1)
472 else:
473 out.write(stdoutdata)
476 def main():
477 """The entrypoint for this script."""
479 logging.basicConfig(format='calcdeps.py: %(message)s', level=logging.INFO)
481 usage = 'usage: %prog [options] arg'
482 parser = optparse.OptionParser(usage)
483 parser.add_option('-i',
484 '--input',
485 dest='inputs',
486 action='append',
487 help='The inputs to calculate dependencies for. Valid '
488 'values can be files, directories, or namespaces '
489 '(ns:goog.net.XhrIo). Only relevant to "list" and '
490 '"script" output.')
491 parser.add_option('-p',
492 '--path',
493 dest='paths',
494 action='append',
495 help='The paths that should be traversed to build the '
496 'dependencies.')
497 parser.add_option('-d',
498 '--dep',
499 dest='deps',
500 action='append',
501 help='Directories or files that should be traversed to '
502 'find required dependencies for the deps file. '
503 'Does not generate dependency information for names '
504 'provided by these files. Only useful in "deps" mode.')
505 parser.add_option('-e',
506 '--exclude',
507 dest='excludes',
508 action='append',
509 help='Files or directories to exclude from the --path '
510 'and --input flags')
511 parser.add_option('-o',
512 '--output_mode',
513 dest='output_mode',
514 action='store',
515 default='list',
516 help='The type of output to generate from this script. '
517 'Options are "list" for a list of filenames, "script" '
518 'for a single script containing the contents of all the '
519 'file, "deps" to generate a deps.js file for all '
520 'paths, or "compiled" to produce compiled output with '
521 'the Closure compiler.')
522 parser.add_option('-c',
523 '--compiler_jar',
524 dest='compiler_jar',
525 action='store',
526 help='The location of the Closure compiler .jar file.')
527 parser.add_option('-f',
528 '--compiler_flag',
529 '--compiler_flags', # for backwards compatability
530 dest='compiler_flags',
531 action='append',
532 help='Additional flag to pass to the Closure compiler. '
533 'May be specified multiple times to pass multiple flags.')
534 parser.add_option('--output_file',
535 dest='output_file',
536 action='store',
537 help=('If specified, write output to this path instead of '
538 'writing to standard output.'))
540 (options, args) = parser.parse_args()
542 search_paths = GetPathsFromOptions(options)
544 if options.output_file:
545 out = open(options.output_file, 'w')
546 else:
547 out = sys.stdout
549 if options.output_mode == 'deps':
550 result = PrintDeps(search_paths, ExpandDirectories(options.deps or []), out)
551 if not result:
552 logging.error('Could not find Closure Library in the specified paths')
553 sys.exit(1)
555 return
557 inputs = GetInputsFromOptions(options)
559 logging.info('Finding Closure dependencies...')
560 deps = CalculateDependencies(search_paths, inputs)
561 output_mode = options.output_mode
563 if output_mode == 'script':
564 PrintScript(deps, out)
565 elif output_mode == 'list':
566 # Just print out a dep per line
567 for dep in deps:
568 PrintLine(dep, out)
569 elif output_mode == 'compiled':
570 # Make sure a .jar is specified.
571 if not options.compiler_jar:
572 logging.error('--compiler_jar flag must be specified if --output is '
573 '"compiled"')
574 sys.exit(1)
576 # User friendly version check.
577 if distutils and not (distutils.version.LooseVersion(GetJavaVersion()) >
578 distutils.version.LooseVersion('1.6')):
579 logging.error('Closure Compiler requires Java 1.6 or higher.')
580 logging.error('Please visit http://www.java.com/getjava')
581 sys.exit(1)
583 Compile(options.compiler_jar, deps, out, options.compiler_flags)
585 else:
586 logging.error('Invalid value for --output flag.')
587 sys.exit(1)
589 if __name__ == '__main__':
590 main()