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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2
3 # Copyright 2014 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 '''Produces various output formats from a set of JavaScript files with
8 closure style require/provide calls.
9
10 Scans one or more directory trees for JavaScript files. Then, from a
11 given list of top-level files, sorts all required input files topologically.
12 The top-level files are appended to the sorted list in the order specified
13 on the command line. If no root directories are specified, the source
14 files are assumed to be ordered already and no dependency analysis is
15 performed. The resulting file list can then be used in one of the following
16 ways:
17
18 - list: a plain list of files, one per line is output.
19
20 - html: a series of html <script> tags with src attributes containing paths
21 is output.
22
23 - bundle: a concatenation of all the files, separated by newlines is output.
24
25 - compressed_bundle: A bundle where non-significant whitespace, including
26 comments, has been stripped is output.
27
28 - copy: the files are copied, or hard linked if possible, to the destination
29 directory. In this case, no output is generated.
30 '''
31
32
33 import optparse
34 import os
35 import shutil
36 import sys
37
38 _SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
39 _CHROME_SOURCE = os.path.realpath(
40 os.path.join(_SCRIPT_DIR, *[os.path.pardir] * 6))
41 sys.path.insert(0, os.path.join(
42 _CHROME_SOURCE, 'third_party/WebKit/Source/build/scripts'))
43 sys.path.insert(0, os.path.join(
44 _CHROME_SOURCE, ('chrome/third_party/chromevox/third_party/' +
45 'closure-library/closure/bin/build')))
46 import depstree
47 import rjsmin
48 import source
49 import treescan
50
51
52 def Die(message):
53 '''Prints an error message and exit the program.'''
54 print >>sys.stderr, message
55 sys.exit(1)
56
57
58 class SourceWithPaths(source.Source):
59 '''A source.Source object with its relative input and output paths'''
60
61 def __init__(self, content, in_path, out_path):
62 super(SourceWithPaths, self).__init__(content)
63 self._in_path = in_path
64 self._out_path = out_path
65
66 def GetInPath(self):
67 return self._in_path
68
69 def GetOutPath(self):
70 return self._out_path
71
72
73 class Bundle():
74 '''An ordered list of sources without duplicates.'''
75
76 def __init__(self):
77 self._added_paths = set()
78 self._added_sources = []
79
80 def Add(self, sources):
81 '''Appends one or more source objects the list if it doesn't already
82 exist.
83
84 Args:
85 sources: A SourceWithPath or an iterable of such objects.
86 '''
87 if isinstance(sources, SourceWithPaths):
88 sources = [sources]
89 for source in sources:
90 path = source.GetInPath()
91 if path not in self._added_paths:
92 self._added_paths.add(path)
93 self._added_sources.append(source)
94
95 def GetOutPaths(self):
96 return (source.GetOutPath() for source in self._added_sources)
97
98 def GetSources(self):
99 return self._added_sources
100
101 def GetUncompressedSource(self):
102 return '\n'.join((s.GetSource() for s in self._added_sources))
103
104 def GetCompressedSource(self):
105 return rjsmin.jsmin(self.GetUncompressedSource())
106
107
108 class PathRewriter():
109 '''A list of simple path rewrite rules to map relative input paths to
110 relative output paths.
111 '''
112
113 def __init__(self, specs):
114 '''Args:
115 specs: A list of mappings, each consisting of the input prefix and
116 the corresponding output prefix separated by colons.
117 '''
118 self._prefix_map = []
119 for spec in specs:
120 parts = spec.split(':')
121 if len(parts) != 2:
122 Die('Invalid prefix rewrite spec %s' % spec)
123 if not parts[0].endswith('/'):
124 parts[0] += '/'
125 self._prefix_map.append(parts)
126
127 def RewritePath(self, in_path):
128 '''Rewrites an input path according to the list of rules.
129
130 Args:
131 in_path, str: The input path to rewrite.
132 Returns:
133 str: The corresponding output path.
134 '''
135 for in_prefix, out_prefix in self._prefix_map:
136 if in_path.startswith(in_prefix):
137 return os.path.join(out_prefix, in_path[len(in_prefix):])
138 return in_path
139
140
141 def ReadSources(options, args):
142 '''Reads all source specified on the command line, including sources
143 included by --root options.
144 '''
145
146 def EnsureSourceLoaded(in_path, sources, path_rewriter):
147 if in_path not in sources:
148 out_path = path_rewriter.RewritePath(in_path)
149 sources[in_path] = SourceWithPaths(source.GetFileContents(in_path),
150 in_path, out_path)
151
152 # Only read the actual source file if we will do a dependency analysis or
153 # if we'll need it for the output.
154 need_source_text = (len(options.roots) > 0 or
155 options.mode in ('bundle', 'compressed_bundle'))
156 path_rewriter = PathRewriter(options.prefix_map)
157 sources = {}
158 for root in options.roots:
159 for name in treescan.ScanTreeForJsFiles(root):
160 EnsureSourceLoaded(name, sources, path_rewriter)
161 for path in args:
162 if need_source_text:
163 EnsureSourceLoaded(path, sources, path_rewriter)
164 else:
165 # Just add an empty representation of the source.
166 sources[path] = SourceWithPaths(
167 '', path, path_rewriter.RewritePath(path))
168 return sources
169
170
171 def CalcDeps(bundle, sources, top_level):
172 '''Calculates dependencies for a set of top-level files.
173
174 Args:
175 bundle: Bundle to add the sources to.
176 sources, dict: Mapping from input path to SourceWithPaths objects.
177 top_level, list: List of top-level input paths to calculate dependencies
178 for.
179 '''
180 def GetBase(sources):
181 for source in sources.itervalues():
182 if (os.path.basename(source.GetInPath()) == 'base.js' and
183 'goog' in source.provides):
184 return source
185 Die('goog.base not provided by any file')
186
187 providers = [s for s in sources.itervalues() if len(s.provides) > 0]
188 deps = depstree.DepsTree(providers)
189 namespaces = []
190 for path in top_level:
191 namespaces.extend(sources[path].requires)
192 # base.js is an implicit dependency that always goes first.
193 bundle.Add(GetBase(sources))
194 bundle.Add(deps.GetDependencies(namespaces))
195
196
197 def LinkOrCopyFiles(sources, dest_dir):
198 '''Copies a list of sources to a destination directory.'''
199
200 def LinkOrCopyOneFile(src, dst):
201 if not os.path.exists(os.path.dirname(dst)):
202 os.makedirs(os.path.dirname(dst))
203 if os.path.exists(dst):
204 # Avoid clobbering the inode if source and destination refer to the
205 # same file already.
206 if os.path.samefile(src, dst):
207 return
208 os.unlink(dst)
209 try:
210 os.link(src, dst)
211 except:
212 shutil.copy(src, dst)
213
214 for source in sources:
215 LinkOrCopyOneFile(source.GetInPath(),
216 os.path.join(dest_dir, source.GetOutPath()))
217
218
219 def WriteOutput(bundle, format, out_file, dest_dir):
220 '''Writes output in the specified format.
221
222 Args:
223 bundle: The ordered bundle iwth all sources already added.
224 format: Output format, one of list, html, bundle, compressed_bundle.
225 out_file: File object to receive the output.
226 dest_dir: Prepended to each path mentioned in the output, if applicable.
227 '''
228 if format == 'list':
229 paths = bundle.GetOutPaths()
230 if dest_dir:
231 paths = (os.path.join(dest_dir, p) for p in paths)
232 paths = (os.path.normpath(p) for p in paths)
233 out_file.write('\n'.join(paths))
234 elif format == 'html':
235 HTML_TEMPLATE = '<script src=\'%s\'>'
236 script_lines = (HTML_TEMPLATE % p for p in bundle.GetOutPaths())
237 out_file.write('\n'.join(script_lines))
238 elif format == 'bundle':
239 out_file.write(bundle.GetUncompressedSource())
240 elif format == 'compressed_bundle':
241 out_file.write(bundle.GetCompressedSource())
242 out_file.write('\n')
243
244
245 def CreateOptionParser():
246 parser = optparse.OptionParser(description=__doc__)
247 parser.usage = '%prog [options] <top_level_file>...'
248 parser.add_option('-d', '--dest_dir', action='store', metavar='DIR',
249 help=('Destination directory. Used when translating ' +
250 'input paths to output paths and when copying '
251 'files.'))
252 parser.add_option('-o', '--output_file', action='store', metavar='FILE',
253 help=('File to output result to for modes that output '
254 'a single file.'))
255 parser.add_option('-r', '--root', dest='roots', action='append', default=[],
256 metavar='ROOT',
257 help='Roots of directory trees to scan for sources.')
258 parser.add_option('-w', '--rewrite_prefix', action='append', default=[],
259 dest='prefix_map', metavar='SPEC',
260 help=('Two path prefixes, separated by colons ' +
261 'specifying that a file whose (relative) path ' +
262 'name starts with the first prefix should have ' +
263 'that prefix replaced by the second prefix to ' +
264 'form a path relative to the output directory.'))
265 parser.add_option('-m', '--mode', type='choice', action='store',
266 choices=['list', 'html', 'bundle',
267 'compressed_bundle', 'copy'],
268 default='list', metavar='MODE',
269 help=("Otput mode. One of 'list', 'html', 'bundle', " +
270 "'compressed_bundle' or 'copy'."))
271 return parser
272
273
274 def main():
275 options, args = CreateOptionParser().parse_args()
276 if len(args) < 1:
277 Die('At least one top-level source file must be specified.')
278 sources = ReadSources(options, args)
279 bundle = Bundle()
280 if len(options.roots) > 0:
281 CalcDeps(bundle, sources, args)
282 bundle.Add((sources[name] for name in args))
283 if options.mode == 'copy':
284 if options.dest_dir is None:
285 Die('Must specify --dest_dir when copying.')
286 LinkOrCopyFiles(bundle.GetSources(), options.dest_dir)
287 else:
288 if options.output_file:
289 out_file = open(options.output_file, 'w')
290 else:
291 out_file = sys.stdout
292 try:
293 WriteOutput(bundle, options.mode, out_file, options.dest_dir)
294 finally:
295 if options.output_file:
296 out_file.close()
297
298
299 if __name__ == '__main__':
300 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698