tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

closurebuilder.py (9626B)


      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2009 The Closure Library Authors. All Rights Reserved.
      4 #
      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
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     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.
     16 
     17 """Utility for Closure Library dependency calculation.
     18 
     19 ClosureBuilder scans source files to build dependency info.  From the
     20 dependencies, the script can produce a manifest in dependency order,
     21 a concatenated script, or compiled output from the Closure Compiler.
     22 
     23 Paths to files can be expressed as individual arguments to the tool (intended
     24 for use with find and xargs).  As a convenience, --root can be used to specify
     25 all JS files below a directory.
     26 
     27 usage: %prog [options] [file1.js file2.js ...]
     28 """
     29 
     30 __author__ = 'nnaze@google.com (Nathan Naze)'
     31 
     32 
     33 import logging
     34 import optparse
     35 import os
     36 import sys
     37 
     38 import depstree
     39 import jscompiler
     40 import source
     41 import treescan
     42 
     43 
     44 def _GetOptionsParser():
     45  """Get the options parser."""
     46 
     47  parser = optparse.OptionParser(__doc__)
     48  parser.add_option('-i',
     49                    '--input',
     50                    dest='inputs',
     51                    action='append',
     52                    default=[],
     53                    help='One or more input files to calculate dependencies '
     54                    'for.  The namespaces in this file will be combined with '
     55                    'those given with the -n flag to form the set of '
     56                    'namespaces to find dependencies for.')
     57  parser.add_option('-n',
     58                    '--namespace',
     59                    dest='namespaces',
     60                    action='append',
     61                    default=[],
     62                    help='One or more namespaces to calculate dependencies '
     63                    'for.  These namespaces will be combined with those given '
     64                    'with the -i flag to form the set of namespaces to find '
     65                    'dependencies for.  A Closure namespace is a '
     66                    'dot-delimited path expression declared with a call to '
     67                    'goog.provide() (e.g. "goog.array" or "foo.bar").')
     68  parser.add_option('--root',
     69                    dest='roots',
     70                    action='append',
     71                    default=[],
     72                    help='The paths that should be traversed to build the '
     73                    'dependencies.')
     74  parser.add_option('-o',
     75                    '--output_mode',
     76                    dest='output_mode',
     77                    type='choice',
     78                    action='store',
     79                    choices=['list', 'script', 'compiled'],
     80                    default='list',
     81                    help='The type of output to generate from this script. '
     82                    'Options are "list" for a list of filenames, "script" '
     83                    'for a single script containing the contents of all the '
     84                    'files, or "compiled" to produce compiled output with '
     85                    'the Closure Compiler.  Default is "list".')
     86  parser.add_option('-c',
     87                    '--compiler_jar',
     88                    dest='compiler_jar',
     89                    action='store',
     90                    help='The location of the Closure compiler .jar file.')
     91  parser.add_option('-f',
     92                    '--compiler_flags',
     93                    dest='compiler_flags',
     94                    default=[],
     95                    action='append',
     96                    help='Additional flags to pass to the Closure compiler. '
     97                    'To pass multiple flags, --compiler_flags has to be '
     98                    'specified multiple times.')
     99  parser.add_option('-j',
    100                    '--jvm_flags',
    101                    dest='jvm_flags',
    102                    default=[],
    103                    action='append',
    104                    help='Additional flags to pass to the JVM compiler. '
    105                    'To pass multiple flags, --jvm_flags has to be '
    106                    'specified multiple times.')
    107  parser.add_option('--output_file',
    108                    dest='output_file',
    109                    action='store',
    110                    help=('If specified, write output to this path instead of '
    111                          'writing to standard output.'))
    112 
    113  return parser
    114 
    115 
    116 def _GetInputByPath(path, sources):
    117  """Get the source identified by a path.
    118 
    119  Args:
    120    path: str, A path to a file that identifies a source.
    121    sources: An iterable collection of source objects.
    122 
    123  Returns:
    124    The source from sources identified by path, if found.  Converts to
    125    real paths for comparison.
    126  """
    127  for js_source in sources:
    128    # Convert both to real paths for comparison.
    129    if os.path.realpath(path) == os.path.realpath(js_source.GetPath()):
    130      return js_source
    131 
    132 
    133 def _GetClosureBaseFile(sources):
    134  """Given a set of sources, returns the one base.js file.
    135 
    136  Note that if zero or two or more base.js files are found, an error message
    137  will be written and the program will be exited.
    138 
    139  Args:
    140    sources: An iterable of _PathSource objects.
    141 
    142  Returns:
    143    The _PathSource representing the base Closure file.
    144  """
    145  base_files = [
    146      js_source for js_source in sources if _IsClosureBaseFile(js_source)]
    147 
    148  if not base_files:
    149    logging.error('No Closure base.js file found.')
    150    sys.exit(1)
    151  if len(base_files) > 1:
    152    logging.error('More than one Closure base.js files found at these paths:')
    153    for base_file in base_files:
    154      logging.error(base_file.GetPath())
    155    sys.exit(1)
    156  return base_files[0]
    157 
    158 
    159 def _IsClosureBaseFile(js_source):
    160  """Returns true if the given _PathSource is the Closure base.js source."""
    161  return (os.path.basename(js_source.GetPath()) == 'base.js' and
    162          js_source.provides == set(['goog']))
    163 
    164 
    165 class _PathSource(source.Source):
    166  """Source file subclass that remembers its file path."""
    167 
    168  def __init__(self, path):
    169    """Initialize a source.
    170 
    171    Args:
    172      path: str, Path to a JavaScript file.  The source string will be read
    173        from this file.
    174    """
    175    super(_PathSource, self).__init__(source.GetFileContents(path))
    176 
    177    self._path = path
    178 
    179  def __str__(self):
    180    return 'PathSource %s' % self._path
    181 
    182  def GetPath(self):
    183    """Returns the path."""
    184    return self._path
    185 
    186 
    187 def _WrapGoogModuleSource(src):
    188  return ('goog.loadModule(function(exports) {{'
    189          '"use strict";'
    190          '{0}'
    191          '\n'  # terminate any trailing single line comment.
    192          ';return exports'
    193          '}});\n').format(src)
    194 
    195 
    196 def main():
    197  logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
    198                      level=logging.INFO)
    199  options, args = _GetOptionsParser().parse_args()
    200 
    201  # Make our output pipe.
    202  if options.output_file:
    203    out = open(options.output_file, 'w')
    204  else:
    205    out = sys.stdout
    206 
    207  sources = set()
    208 
    209  logging.info('Scanning paths...')
    210  for path in options.roots:
    211    for js_path in treescan.ScanTreeForJsFiles(path):
    212      sources.add(_PathSource(js_path))
    213 
    214  # Add scripts specified on the command line.
    215  for js_path in args:
    216    sources.add(_PathSource(js_path))
    217 
    218  logging.info('%s sources scanned.', len(sources))
    219 
    220  # Though deps output doesn't need to query the tree, we still build it
    221  # to validate dependencies.
    222  logging.info('Building dependency tree..')
    223  tree = depstree.DepsTree(sources)
    224 
    225  input_namespaces = set()
    226  inputs = options.inputs or []
    227  for input_path in inputs:
    228    js_input = _GetInputByPath(input_path, sources)
    229    if not js_input:
    230      logging.error('No source matched input %s', input_path)
    231      sys.exit(1)
    232    input_namespaces.update(js_input.provides)
    233 
    234  input_namespaces.update(options.namespaces)
    235 
    236  if not input_namespaces:
    237    logging.error('No namespaces found. At least one namespace must be '
    238                  'specified with the --namespace or --input flags.')
    239    sys.exit(2)
    240 
    241  # The Closure Library base file must go first.
    242  base = _GetClosureBaseFile(sources)
    243  deps = [base] + tree.GetDependencies(input_namespaces)
    244 
    245  output_mode = options.output_mode
    246  if output_mode == 'list':
    247    out.writelines([js_source.GetPath() + '\n' for js_source in deps])
    248  elif output_mode == 'script':
    249    for js_source in deps:
    250      src = js_source.GetSource()
    251      if js_source.is_goog_module:
    252        src = _WrapGoogModuleSource(src)
    253      out.write(src + '\n')
    254  elif output_mode == 'compiled':
    255    logging.warning("""\
    256 Closure Compiler now natively understands and orders Closure dependencies and
    257 is prefererred over using this script for performing JavaScript compilation.
    258 
    259 Please migrate your codebase.
    260 
    261 See:
    262 https://github.com/google/closure-compiler/wiki/Manage-Closure-Dependencies
    263 """)
    264 
    265    # Make sure a .jar is specified.
    266    if not options.compiler_jar:
    267      logging.error('--compiler_jar flag must be specified if --output is '
    268                    '"compiled"')
    269      sys.exit(2)
    270 
    271    # Will throw an error if the compilation fails.
    272    compiled_source = jscompiler.Compile(
    273        options.compiler_jar,
    274        [js_source.GetPath() for js_source in deps],
    275        jvm_flags=options.jvm_flags,
    276        compiler_flags=options.compiler_flags)
    277 
    278    logging.info('JavaScript compilation succeeded.')
    279    out.write(compiled_source)
    280 
    281  else:
    282    logging.error('Invalid value for --output flag.')
    283    sys.exit(2)
    284 
    285 
    286 if __name__ == '__main__':
    287  main()