3 # Copyright 2009 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.
17 """Utility for Closure Library dependency calculation.
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.
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.
27 usage: %prog [options] [file1.js file2.js ...]
30 __author__
= 'nnaze@google.com (Nathan Naze)'
44 def _GetOptionsParser():
45 """Get the options parser."""
47 parser
= optparse
.OptionParser(__doc__
)
48 parser
.add_option('-i',
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',
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',
72 help='The paths that should be traversed to build the '
74 parser
.add_option('-o',
79 choices
=['list', 'script', 'compiled'],
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',
90 help='The location of the Closure compiler .jar file.')
91 parser
.add_option('-f',
93 dest
='compiler_flags',
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',
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',
110 help=('If specified, write output to this path instead of '
111 'writing to standard output.'))
116 def _GetInputByPath(path
, sources
):
117 """Get the source identified by a path.
120 path: str, A path to a file that identifies a source.
121 sources: An iterable collection of source objects.
124 The source from sources identified by path, if found. Converts to
125 real paths for comparison.
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()):
133 def _GetClosureBaseFile(sources
):
134 """Given a set of sources, returns the one base.js file.
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.
140 sources: An iterable of _PathSource objects.
143 The _PathSource representing the base Closure file.
146 js_source
for js_source
in sources
if _IsClosureBaseFile(js_source
)]
149 logging
.error('No Closure base.js file found.')
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())
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']))
165 class _PathSource(source
.Source
):
166 """Source file subclass that remembers its file path."""
168 def __init__(self
, path
):
169 """Initialize a source.
172 path: str, Path to a JavaScript file. The source string will be read
175 super(_PathSource
, self
).__init
__(source
.GetFileContents(path
))
180 return 'PathSource %s' % self
._path
183 """Returns the path."""
187 def _WrapGoogModuleSource(src
):
188 return ('goog.loadModule(function(exports) {{'
191 '\n' # terminate any trailing single line comment.
193 '}});\n').format(src
)
197 logging
.basicConfig(format
=(sys
.argv
[0] + ': %(message)s'),
199 options
, args
= _GetOptionsParser().parse_args()
201 # Make our output pipe.
202 if options
.output_file
:
203 out
= open(options
.output_file
, 'w')
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
))
214 # Add scripts specified on the command line.
216 sources
.add(_PathSource(js_path
))
218 logging
.info('%s sources scanned.', len(sources
))
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
)
225 input_namespaces
= set()
226 inputs
= options
.inputs
or []
227 for input_path
in inputs
:
228 js_input
= _GetInputByPath(input_path
, sources
)
230 logging
.error('No source matched input %s', input_path
)
232 input_namespaces
.update(js_input
.provides
)
234 input_namespaces
.update(options
.namespaces
)
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.')
241 # The Closure Library base file must go first.
242 base
= _GetClosureBaseFile(sources
)
243 deps
= [base
] + tree
.GetDependencies(input_namespaces
)
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':
256 Closure Compiler now natively understands and orders Closure dependencies and
257 is prefererred over using this script for performing JavaScript compilation.
259 Please migrate your codebase.
262 https://github.com/google/closure-compiler/wiki/Manage-Closure-Dependencies
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 '
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
)
278 logging
.info('JavaScript compilation succeeded.')
279 out
.write(compiled_source
)
282 logging
.error('Invalid value for --output flag.')
286 if __name__
== '__main__':