Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5972)

Unified Diff: chrome/browser/resources/chromeos/chromevox/tools/jsbundler.py

Issue 299703003: Build ChromeVox using gyp. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@lkgr
Patch Set: Address nits from Dominic. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/chromeos/chromevox/tools/jsbundler.py
diff --git a/chrome/browser/resources/chromeos/chromevox/tools/jsbundler.py b/chrome/browser/resources/chromeos/chromevox/tools/jsbundler.py
new file mode 100755
index 0000000000000000000000000000000000000000..051813e4066897685c432dbb05ce9b678180b5ed
--- /dev/null
+++ b/chrome/browser/resources/chromeos/chromevox/tools/jsbundler.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python
+
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+'''Produces various output formats from a set of JavaScript files with
+closure style require/provide calls.
+
+Scans one or more directory trees for JavaScript files. Then, from a
+given list of top-level files, sorts all required input files topologically.
+The top-level files are appended to the sorted list in the order specified
+on the command line. If no root directories are specified, the source
+files are assumed to be ordered already and no dependency analysis is
+performed. The resulting file list can then be used in one of the following
+ways:
+
+- list: a plain list of files, one per line is output.
+
+- html: a series of html <script> tags with src attributes containing paths
+ is output.
+
+- bundle: a concatenation of all the files, separated by newlines is output.
+
+- compressed_bundle: A bundle where non-significant whitespace, including
+ comments, has been stripped is output.
+
+- copy: the files are copied, or hard linked if possible, to the destination
+ directory. In this case, no output is generated.
+'''
+
+
+import optparse
+import os
+import shutil
+import sys
+
+_SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+_CHROME_SOURCE = os.path.realpath(
+ os.path.join(_SCRIPT_DIR, *[os.path.pardir] * 6))
+sys.path.insert(0, os.path.join(
+ _CHROME_SOURCE, 'third_party/WebKit/Source/build/scripts'))
+sys.path.insert(0, os.path.join(
+ _CHROME_SOURCE, ('chrome/third_party/chromevox/third_party/' +
+ 'closure-library/closure/bin/build')))
+import depstree
+import rjsmin
+import source
+import treescan
+
+
+def Die(message):
+ '''Prints an error message and exit the program.'''
+ print >>sys.stderr, message
+ sys.exit(1)
+
+
+class SourceWithPaths(source.Source):
+ '''A source.Source object with its relative input and output paths'''
+
+ def __init__(self, content, in_path, out_path):
+ super(SourceWithPaths, self).__init__(content)
+ self._in_path = in_path
+ self._out_path = out_path
+
+ def GetInPath(self):
+ return self._in_path
+
+ def GetOutPath(self):
+ return self._out_path
+
+
+class Bundle():
+ '''An ordered list of sources without duplicates.'''
+
+ def __init__(self):
+ self._added_paths = set()
+ self._added_sources = []
+
+ def Add(self, sources):
+ '''Appends one or more source objects the list if it doesn't already
+ exist.
+
+ Args:
+ sources: A SourceWithPath or an iterable of such objects.
+ '''
+ if isinstance(sources, SourceWithPaths):
+ sources = [sources]
+ for source in sources:
+ path = source.GetInPath()
+ if path not in self._added_paths:
+ self._added_paths.add(path)
+ self._added_sources.append(source)
+
+ def GetOutPaths(self):
+ return (source.GetOutPath() for source in self._added_sources)
+
+ def GetSources(self):
+ return self._added_sources
+
+ def GetUncompressedSource(self):
+ return '\n'.join((s.GetSource() for s in self._added_sources))
+
+ def GetCompressedSource(self):
+ return rjsmin.jsmin(self.GetUncompressedSource())
+
+
+class PathRewriter():
+ '''A list of simple path rewrite rules to map relative input paths to
+ relative output paths.
+ '''
+
+ def __init__(self, specs):
+ '''Args:
+ specs: A list of mappings, each consisting of the input prefix and
+ the corresponding output prefix separated by colons.
+ '''
+ self._prefix_map = []
+ for spec in specs:
+ parts = spec.split(':')
+ if len(parts) != 2:
+ Die('Invalid prefix rewrite spec %s' % spec)
+ if not parts[0].endswith('/'):
+ parts[0] += '/'
+ self._prefix_map.append(parts)
+
+ def RewritePath(self, in_path):
+ '''Rewrites an input path according to the list of rules.
+
+ Args:
+ in_path, str: The input path to rewrite.
+ Returns:
+ str: The corresponding output path.
+ '''
+ for in_prefix, out_prefix in self._prefix_map:
+ if in_path.startswith(in_prefix):
+ return os.path.join(out_prefix, in_path[len(in_prefix):])
+ return in_path
+
+
+def ReadSources(options, args):
+ '''Reads all source specified on the command line, including sources
+ included by --root options.
+ '''
+
+ def EnsureSourceLoaded(in_path, sources, path_rewriter):
+ if in_path not in sources:
+ out_path = path_rewriter.RewritePath(in_path)
+ sources[in_path] = SourceWithPaths(source.GetFileContents(in_path),
+ in_path, out_path)
+
+ # Only read the actual source file if we will do a dependency analysis or
+ # if we'll need it for the output.
+ need_source_text = (len(options.roots) > 0 or
+ options.mode in ('bundle', 'compressed_bundle'))
+ path_rewriter = PathRewriter(options.prefix_map)
+ sources = {}
+ for root in options.roots:
+ for name in treescan.ScanTreeForJsFiles(root):
+ EnsureSourceLoaded(name, sources, path_rewriter)
+ for path in args:
+ if need_source_text:
+ EnsureSourceLoaded(path, sources, path_rewriter)
+ else:
+ # Just add an empty representation of the source.
+ sources[path] = SourceWithPaths(
+ '', path, path_rewriter.RewritePath(path))
+ return sources
+
+
+def CalcDeps(bundle, sources, top_level):
+ '''Calculates dependencies for a set of top-level files.
+
+ Args:
+ bundle: Bundle to add the sources to.
+ sources, dict: Mapping from input path to SourceWithPaths objects.
+ top_level, list: List of top-level input paths to calculate dependencies
+ for.
+ '''
+ def GetBase(sources):
+ for source in sources.itervalues():
+ if (os.path.basename(source.GetInPath()) == 'base.js' and
+ 'goog' in source.provides):
+ return source
+ Die('goog.base not provided by any file')
+
+ providers = [s for s in sources.itervalues() if len(s.provides) > 0]
+ deps = depstree.DepsTree(providers)
+ namespaces = []
+ for path in top_level:
+ namespaces.extend(sources[path].requires)
+ # base.js is an implicit dependency that always goes first.
+ bundle.Add(GetBase(sources))
+ bundle.Add(deps.GetDependencies(namespaces))
+
+
+def LinkOrCopyFiles(sources, dest_dir):
+ '''Copies a list of sources to a destination directory.'''
+
+ def LinkOrCopyOneFile(src, dst):
+ if not os.path.exists(os.path.dirname(dst)):
+ os.makedirs(os.path.dirname(dst))
+ if os.path.exists(dst):
+ # Avoid clobbering the inode if source and destination refer to the
+ # same file already.
+ if os.path.samefile(src, dst):
+ return
+ os.unlink(dst)
+ try:
+ os.link(src, dst)
+ except:
+ shutil.copy(src, dst)
+
+ for source in sources:
+ LinkOrCopyOneFile(source.GetInPath(),
+ os.path.join(dest_dir, source.GetOutPath()))
+
+
+def WriteOutput(bundle, format, out_file, dest_dir):
+ '''Writes output in the specified format.
+
+ Args:
+ bundle: The ordered bundle iwth all sources already added.
+ format: Output format, one of list, html, bundle, compressed_bundle.
+ out_file: File object to receive the output.
+ dest_dir: Prepended to each path mentioned in the output, if applicable.
+ '''
+ if format == 'list':
+ paths = bundle.GetOutPaths()
+ if dest_dir:
+ paths = (os.path.join(dest_dir, p) for p in paths)
+ paths = (os.path.normpath(p) for p in paths)
+ out_file.write('\n'.join(paths))
+ elif format == 'html':
+ HTML_TEMPLATE = '<script src=\'%s\'>'
+ script_lines = (HTML_TEMPLATE % p for p in bundle.GetOutPaths())
+ out_file.write('\n'.join(script_lines))
+ elif format == 'bundle':
+ out_file.write(bundle.GetUncompressedSource())
+ elif format == 'compressed_bundle':
+ out_file.write(bundle.GetCompressedSource())
+ out_file.write('\n')
+
+
+def CreateOptionParser():
+ parser = optparse.OptionParser(description=__doc__)
+ parser.usage = '%prog [options] <top_level_file>...'
+ parser.add_option('-d', '--dest_dir', action='store', metavar='DIR',
+ help=('Destination directory. Used when translating ' +
+ 'input paths to output paths and when copying '
+ 'files.'))
+ parser.add_option('-o', '--output_file', action='store', metavar='FILE',
+ help=('File to output result to for modes that output '
+ 'a single file.'))
+ parser.add_option('-r', '--root', dest='roots', action='append', default=[],
+ metavar='ROOT',
+ help='Roots of directory trees to scan for sources.')
+ parser.add_option('-w', '--rewrite_prefix', action='append', default=[],
+ dest='prefix_map', metavar='SPEC',
+ help=('Two path prefixes, separated by colons ' +
+ 'specifying that a file whose (relative) path ' +
+ 'name starts with the first prefix should have ' +
+ 'that prefix replaced by the second prefix to ' +
+ 'form a path relative to the output directory.'))
+ parser.add_option('-m', '--mode', type='choice', action='store',
+ choices=['list', 'html', 'bundle',
+ 'compressed_bundle', 'copy'],
+ default='list', metavar='MODE',
+ help=("Otput mode. One of 'list', 'html', 'bundle', " +
+ "'compressed_bundle' or 'copy'."))
+ return parser
+
+
+def main():
+ options, args = CreateOptionParser().parse_args()
+ if len(args) < 1:
+ Die('At least one top-level source file must be specified.')
+ sources = ReadSources(options, args)
+ bundle = Bundle()
+ if len(options.roots) > 0:
+ CalcDeps(bundle, sources, args)
+ bundle.Add((sources[name] for name in args))
+ if options.mode == 'copy':
+ if options.dest_dir is None:
+ Die('Must specify --dest_dir when copying.')
+ LinkOrCopyFiles(bundle.GetSources(), options.dest_dir)
+ else:
+ if options.output_file:
+ out_file = open(options.output_file, 'w')
+ else:
+ out_file = sys.stdout
+ try:
+ WriteOutput(bundle, options.mode, out_file, options.dest_dir)
+ finally:
+ if options.output_file:
+ out_file.close()
+
+
+if __name__ == '__main__':
+ main()

Powered by Google App Engine
This is Rietveld 408576698