| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | # Copyright 2012 Google Inc. All Rights Reserved. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """Closure compiler rules for the build system.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Contains the following rules: | 
					
						
							|  |  |  | closure_js_lint | 
					
						
							|  |  |  | closure_js_fixstyle | 
					
						
							|  |  |  | closure_js_library | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Assumes Closure Linter is installed and on the path. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __author__ = 'benvanik@google.com (Ben Vanik)' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  | import io | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | from anvil.context import RuleContext | 
					
						
							|  |  |  | from anvil.rule import Rule, build_rule | 
					
						
							| 
									
										
										
										
											2012-05-03 17:23:45 +08:00
										 |  |  | from anvil.task import (Task, ExecutableTask, JavaExecutableTask, | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |     WriteFileTask) | 
					
						
							| 
									
										
										
										
											2012-05-05 10:58:11 +08:00
										 |  |  | import anvil.util | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @build_rule('closure_js_lint') | 
					
						
							|  |  |  | class ClosureJsLintRule(Rule): | 
					
						
							|  |  |  |   """A set of linted JS files.
 | 
					
						
							|  |  |  |   Passes all source files through the Closure style linter. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Similar to file_set, all source files are globbed together and de-duplicated | 
					
						
							|  |  |  |   before being passed on as outputs. If a src_filter is provided then it is used | 
					
						
							|  |  |  |   to filter all sources. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Inputs: | 
					
						
							|  |  |  |     srcs: Source JS file paths. | 
					
						
							|  |  |  |     namespaces: A list of Closurized namespaces. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Outputs: | 
					
						
							|  |  |  |     All of the source file paths, passed-through unmodified. | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |   def __init__(self, name, namespaces, *args, **kwargs): | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     """Initializes a Closure JS lint rule.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       name: Rule name. | 
					
						
							|  |  |  |       namespaces: A list of Closurized namespaces. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |     super(ClosureJsLintRule, self).__init__(name, *args, **kwargs) | 
					
						
							| 
									
										
										
										
											2012-05-02 19:59:51 +08:00
										 |  |  |     self.src_filter = '*.js' | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self._command = 'gjslint' | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |     self._extra_args = ['--nobeep',] | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self.namespaces = [] | 
					
						
							|  |  |  |     self.namespaces.extend(namespaces) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   class _Context(RuleContext): | 
					
						
							|  |  |  |     def begin(self): | 
					
						
							|  |  |  |       super(ClosureJsLintRule._Context, self).begin() | 
					
						
							|  |  |  |       self._append_output_paths(self.src_paths) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-20 12:21:59 +08:00
										 |  |  |       # Skip if cache hit | 
					
						
							|  |  |  |       if self._check_if_cached(): | 
					
						
							|  |  |  |         self._succeed() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |       namespaces = ','.join(self.rule.namespaces) | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |       args = [ | 
					
						
							|  |  |  |           '--strict', | 
					
						
							|  |  |  |           '--jslint_error=all', | 
					
						
							|  |  |  |           '--closurized_namespaces=%s' % (namespaces), | 
					
						
							|  |  |  |           ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-01 15:52:29 +08:00
										 |  |  |       # TODO(benvanik): only changed paths | 
					
						
							| 
									
										
										
										
											2012-05-07 13:28:20 +08:00
										 |  |  |       # Exclude any path containing build-* | 
					
						
							| 
									
										
										
										
											2012-09-20 12:21:59 +08:00
										 |  |  |       for src_path in self.file_delta.changed_files: | 
					
						
							| 
									
										
										
										
											2012-05-23 10:10:47 +08:00
										 |  |  |         if (src_path.find('build-out%s' % os.sep) == -1 and | 
					
						
							|  |  |  |             src_path.find('build-gen%s' % os.sep) == -1): | 
					
						
							| 
									
										
										
										
											2012-05-07 13:28:20 +08:00
										 |  |  |           args.append(src_path) | 
					
						
							| 
									
										
										
										
											2012-05-01 15:52:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-03 17:23:45 +08:00
										 |  |  |       d = self._run_task_async(ExecutableTask( | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |           self.build_env, self.rule._command, args)) | 
					
						
							|  |  |  |       # TODO(benvanik): pull out errors? | 
					
						
							|  |  |  |       self._chain(d) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @build_rule('closure_js_fixstyle') | 
					
						
							|  |  |  | class ClosureJsFixStyleRule(ClosureJsLintRule): | 
					
						
							|  |  |  |   """A set of style-corrected JS files.
 | 
					
						
							|  |  |  |   Passes all source files through the Closure style fixer. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Similar to file_set, all source files are globbed together and de-duplicated | 
					
						
							|  |  |  |   before being passed on as outputs. If a src_filter is provided then it is used | 
					
						
							|  |  |  |   to filter all sources. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   This rule is special in that it fixes the style of the source files in-place, | 
					
						
							|  |  |  |   overwriting them in their source path. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Inputs: | 
					
						
							|  |  |  |     srcs: Source JS file paths. | 
					
						
							|  |  |  |     namespaces: A list of Closurized namespaces. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Outputs: | 
					
						
							|  |  |  |     All of the source file paths, passed-through post-correction. | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def __init__(self, name, namespaces, *args, **kwargs): | 
					
						
							|  |  |  |     """Initializes a Closure JS style fixing rule.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       name: Rule name. | 
					
						
							|  |  |  |       namespaces: A list of Closurized namespaces. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     super(ClosureJsFixStyleRule, self).__init__(name, namespaces, | 
					
						
							|  |  |  |         *args, **kwargs) | 
					
						
							|  |  |  |     self._command = 'fixjsstyle' | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |     self._extra_args = [] | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # TODO(benvanik): support non-closure code | 
					
						
							|  |  |  | # TODO(benvanik): support AMD modules | 
					
						
							|  |  |  | @build_rule('closure_js_library') | 
					
						
							|  |  |  | class ClosureJsLibraryRule(Rule): | 
					
						
							|  |  |  |   """A Closure compiler JavaScript library.
 | 
					
						
							|  |  |  |   Uses the Closure compiler to build a library from the given source files. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Processes input files using the Closure compiler in the given mode. | 
					
						
							|  |  |  |   goog.provide and goog.require are used to order the files when concatenated. | 
					
						
							|  |  |  |   In SIMPLE and ADVANCED modes dependencies are used to remove dead code. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |   If UNCOMPILED mode is used, only a -deps.js file is generated. This is a fast | 
					
						
							|  |  |  |   operation and can be used when interactively running code in a browser in | 
					
						
							|  |  |  |   uncompiled mode. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |   In DEPS mode then only a -deps.js file is generated. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |   A compiler JAR must be provided. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |   Inputs: | 
					
						
							|  |  |  |     srcs: All source JS files. | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |     mode: Compilation mode, one of ['DEPS', UNCOMPILED', 'SIMPLE', 'ADVANCED']. | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |     compiler_jar: Path to a compiler .jar file. | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |     entry_points: A list of entry points, such as 'myapp.start'. | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     pretty_print: True to pretty print the output. | 
					
						
							|  |  |  |     debug: True to enable Closure DEBUG consts. | 
					
						
							|  |  |  |     compiler_flags: A list of string compiler flags. | 
					
						
							|  |  |  |     externs: Additional extern .js files. | 
					
						
							| 
									
										
										
										
											2012-05-05 10:46:08 +08:00
										 |  |  |     wrap_with_global: Wrap all output in a closure and call with the given | 
					
						
							|  |  |  |           global object. | 
					
						
							|  |  |  |           Example - 'global' -> (function(){...code...}).call(global); | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     out: Optional output name. If none is provided than the rule name will be | 
					
						
							|  |  |  |         used. | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |     deps_out: Base name for -deps.js file. | 
					
						
							|  |  |  |         Example: 'library' -> 'library-deps.js' | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   Outputs: | 
					
						
							|  |  |  |     A single compiled JS file. If no out is specified a file with the name of | 
					
						
							|  |  |  |     the rule will be created. | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |   def __init__(self, name, mode, compiler_jar, entry_points, | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |         pretty_print=False, debug=False, | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |         compiler_flags=None, externs=None, wrap_with_global=None, | 
					
						
							|  |  |  |         out=None, deps_out=None, | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |         *args, **kwargs): | 
					
						
							|  |  |  |     """Initializes a Closure JS library rule.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       name: Rule name. | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       mode: Compilation mode, one of ['UNCOMPILED', 'SIMPLE', 'ADVANCED']. | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |       compiler_jar: Path to a compiler .jar file. | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       entry_points: A list of entry points, such as 'myapp.start'. | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |       pretty_print: True to pretty print the output. | 
					
						
							|  |  |  |       debug: True to enable Closure DEBUG consts. | 
					
						
							|  |  |  |       compiler_flags: A list of string compiler flags. | 
					
						
							|  |  |  |       externs: Additional extern .js files. | 
					
						
							| 
									
										
										
										
											2012-05-05 10:46:08 +08:00
										 |  |  |       wrap_with_global: Wrap all output in a closure and call with the given | 
					
						
							|  |  |  |           global object. | 
					
						
							|  |  |  |           Example - 'global' -> (function(){...code...}).call(global); | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |       out: Optional output name. | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |       deps_out: Base name for -deps.js file. | 
					
						
							|  |  |  |           Example: 'library' -> 'library-deps.js' | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     super(ClosureJsLibraryRule, self).__init__(name, *args, **kwargs) | 
					
						
							| 
									
										
										
										
											2012-05-01 20:18:39 +08:00
										 |  |  |     self.src_filter = '*.js' | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self.mode = mode | 
					
						
							| 
									
										
										
										
											2012-05-01 15:45:51 +08:00
										 |  |  |     self.compiler_jar = compiler_jar | 
					
						
							|  |  |  |     self._append_dependent_paths([self.compiler_jar]) | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self.pretty_print = pretty_print | 
					
						
							|  |  |  |     self.debug = debug | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |     self.entry_points = [] | 
					
						
							|  |  |  |     if isinstance(entry_points, str): | 
					
						
							|  |  |  |       self.entry_points.append(entry_points) | 
					
						
							|  |  |  |     elif entry_points: | 
					
						
							|  |  |  |       self.entry_points.extend(entry_points) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self.compiler_flags = [] | 
					
						
							|  |  |  |     if compiler_flags: | 
					
						
							|  |  |  |       self.compiler_flags.extend(compiler_flags) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.externs = [] | 
					
						
							|  |  |  |     if externs: | 
					
						
							|  |  |  |       self.externs.extend(externs) | 
					
						
							|  |  |  |     self._append_dependent_paths(self.externs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-05 10:46:08 +08:00
										 |  |  |     self.wrap_with_global = wrap_with_global | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |     self.out = out | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |     self.deps_out = deps_out | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   class _Context(RuleContext): | 
					
						
							|  |  |  |     def begin(self): | 
					
						
							|  |  |  |       super(ClosureJsLibraryRule._Context, self).begin() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       jar_path = self._resolve_input_files([self.rule.compiler_jar])[0] | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |       args = [ | 
					
						
							| 
									
										
										
										
											2012-05-01 20:18:39 +08:00
										 |  |  |           '--process_closure_primitives', | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |           '--generate_exports', | 
					
						
							|  |  |  |           '--summary_detail_level=3', | 
					
						
							|  |  |  |           '--warning_level=VERBOSE', | 
					
						
							|  |  |  |           ] | 
					
						
							|  |  |  |       args.extend(self.rule.compiler_flags) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |       deps_only = False | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       compiling = False | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |       if self.rule.mode == 'DEPS': | 
					
						
							|  |  |  |         deps_only = True | 
					
						
							|  |  |  |       elif self.rule.mode == 'UNCOMPILED': | 
					
						
							|  |  |  |         compiling = True | 
					
						
							|  |  |  |         args.append('--compilation_level=WHITESPACE_ONLY') | 
					
						
							|  |  |  |         args.append('--formatting=PRETTY_PRINT') | 
					
						
							|  |  |  |         args.append('--formatting=PRINT_INPUT_DELIMITER') | 
					
						
							|  |  |  |       elif self.rule.mode == 'SIMPLE': | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |         compiling = True | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |         args.append('--compilation_level=SIMPLE_OPTIMIZATIONS') | 
					
						
							|  |  |  |       elif self.rule.mode == 'ADVANCED': | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |         compiling = True | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |         args.append('--compilation_level=ADVANCED_OPTIMIZATIONS') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if self.rule.pretty_print: | 
					
						
							|  |  |  |         args.append('--formatting=PRETTY_PRINT') | 
					
						
							|  |  |  |         args.append('--formatting=PRINT_INPUT_DELIMITER') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if not self.rule.debug: | 
					
						
							|  |  |  |         args.append('--define=goog.DEBUG=false') | 
					
						
							|  |  |  |         args.append('--define=goog.asserts.ENABLE_ASSERTS=false') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-05 10:46:08 +08:00
										 |  |  |       if self.rule.wrap_with_global: | 
					
						
							|  |  |  |         args.append('--output_wrapper="(function(){%%output%%}).call(%s);"' % ( | 
					
						
							|  |  |  |             self.rule.wrap_with_global)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  |       extern_paths = self._resolve_input_files(self.rule.externs) | 
					
						
							|  |  |  |       for extern_path in extern_paths: | 
					
						
							| 
									
										
										
										
											2012-05-05 12:24:31 +08:00
										 |  |  |         args.append('--externs=%s' % (extern_path)) | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       for entry_point in self.rule.entry_points: | 
					
						
							|  |  |  |         args.append('--closure_entry_point=%s' % (entry_point)) | 
					
						
							| 
									
										
										
										
											2012-04-30 14:46:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |       # Main js library | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       if compiling: | 
					
						
							|  |  |  |         output_path = self._get_out_path(name=self.rule.out, suffix='.js') | 
					
						
							|  |  |  |         self._ensure_output_exists(os.path.dirname(output_path)) | 
					
						
							|  |  |  |         self._append_output_paths([output_path]) | 
					
						
							|  |  |  |         args.append('--js_output_file=%s' % (output_path)) | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       # deps.js file | 
					
						
							|  |  |  |       deps_name = self.rule.deps_out or self.rule.out | 
					
						
							|  |  |  |       deps_js_path = self._get_out_path(name=deps_name, suffix='-deps.js') | 
					
						
							|  |  |  |       self._ensure_output_exists(os.path.dirname(deps_js_path)) | 
					
						
							|  |  |  |       self._append_output_paths([deps_js_path]) | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-20 12:21:59 +08:00
										 |  |  |       # Skip if cache hit | 
					
						
							|  |  |  |       if self._check_if_cached(): | 
					
						
							|  |  |  |         self._succeed() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       # Issue dependency scanning to build the deps graph | 
					
						
							|  |  |  |       d = self._run_task_async(_ScanJsDependenciesTask( | 
					
						
							|  |  |  |           self.build_env, self.src_paths)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Launch the compilation and deps.js gen tasks when scanning completes | 
					
						
							|  |  |  |       def _deps_scanned(dep_graph): | 
					
						
							|  |  |  |         ds = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Grab all used files | 
					
						
							|  |  |  |         used_paths = dep_graph.get_transitive_closure(self.rule.entry_points) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Generate/write JS deps | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |         ds.append(self._run_task_async(WriteFileTask( | 
					
						
							|  |  |  |             self.build_env, dep_graph.get_deps_js(), deps_js_path))) | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Compile main lib | 
					
						
							|  |  |  |         if compiling: | 
					
						
							|  |  |  |           for src_path in used_paths: | 
					
						
							|  |  |  |             args.append('--js=%s' % (src_path)) | 
					
						
							|  |  |  |           ds.append(self._run_task_async(JavaExecutableTask( | 
					
						
							|  |  |  |               self.build_env, jar_path, args))) | 
					
						
							|  |  |  |           # TODO(benvanik): pull out (stdout, stderr) from result and the | 
					
						
							|  |  |  |           #     exception to get better error logging | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2012-09-15 02:05:59 +08:00
										 |  |  |           # deps-only - pass along all inputs as outputs | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |           self._append_output_paths(used_paths) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._chain(ds) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Kickoff chain | 
					
						
							|  |  |  |       d.add_callback_fn(_deps_scanned) | 
					
						
							|  |  |  |       self._chain_errback(d) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _ScanJsDependenciesTask(Task): | 
					
						
							|  |  |  |   def __init__(self, build_env, src_paths, *args, **kwargs): | 
					
						
							|  |  |  |     super(_ScanJsDependenciesTask, self).__init__(build_env, *args, **kwargs) | 
					
						
							|  |  |  |     self.src_paths = src_paths | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def execute(self): | 
					
						
							|  |  |  |     deps_graph = JsDependencyGraph(self.build_env, self.src_paths) | 
					
						
							|  |  |  |     return deps_graph | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class JsDependencyFile(object): | 
					
						
							|  |  |  |   """A single source JS file.
 | 
					
						
							|  |  |  |   Parses the file and finds all goog.provide and goog.require statements. | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _PROVIDEREQURE_REGEX = re.compile( | 
					
						
							|  |  |  |       'goog\.(provide|require)\(\s*[\'"](.+)[\'"]\s*\)') | 
					
						
							|  |  |  |   _GOOG_BASE_LINE = ( | 
					
						
							|  |  |  |       'var goog = goog || {}; // Identifies this file as the Closure base.\n') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def __init__(self, src_path): | 
					
						
							|  |  |  |     """Initializes a JS dependency file.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       src_path: Source JS file path. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     self.src_path = src_path | 
					
						
							|  |  |  |     self.provides = [] | 
					
						
							|  |  |  |     self.requires = [] | 
					
						
							|  |  |  |     self.is_base_js = False | 
					
						
							|  |  |  |     self.is_css_rename_map = False | 
					
						
							| 
									
										
										
										
											2012-05-18 12:55:41 +08:00
										 |  |  |     with io.open(self.src_path, 'rb') as f: | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       self._scan(f) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _scan(self, f): | 
					
						
							|  |  |  |     """Scans the given file for provides/requires and returns the results.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       f: Input file. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     provides = set() | 
					
						
							|  |  |  |     requires = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for line in f.readlines(): | 
					
						
							|  |  |  |       match = self._PROVIDEREQURE_REGEX.match(line) | 
					
						
							|  |  |  |       if match: | 
					
						
							|  |  |  |         if match.group(1) == 'provide': | 
					
						
							|  |  |  |           provides.add(str(match.group(2))) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |           requires.add(str(match.group(2))) | 
					
						
							|  |  |  |       elif line == self._GOOG_BASE_LINE: | 
					
						
							|  |  |  |         provides.add('goog') | 
					
						
							|  |  |  |         self.is_base_js = True | 
					
						
							|  |  |  |       elif line.startswith('goog.setCssNameMapping('): | 
					
						
							|  |  |  |         self.is_css_rename_map = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.provides = list(provides) | 
					
						
							|  |  |  |     self.provides.sort() | 
					
						
							|  |  |  |     self.requires = list(requires) | 
					
						
							|  |  |  |     self.requires.sort() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class JsDependencyGraph(object): | 
					
						
							|  |  |  |   """Represents a JS dependency graph.
 | 
					
						
							|  |  |  |   This scans all source JavaScript files to identify their dependencies. | 
					
						
							|  |  |  |   The result is a queryable list | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def __init__(self, build_env, src_paths, *args, **kwargs): | 
					
						
							|  |  |  |     """Initializes a JS dependency graph.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       build_env: BuildEnvironment. | 
					
						
							|  |  |  |       src_paths: A list of source JS paths. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     self.build_env = build_env | 
					
						
							|  |  |  |     self.src_paths = list(src_paths) | 
					
						
							|  |  |  |     self.dep_files = {} | 
					
						
							|  |  |  |     self._provide_map = {} | 
					
						
							|  |  |  |     self.base_dep_file = None | 
					
						
							|  |  |  |     self.base_js_path = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Scan all files | 
					
						
							|  |  |  |     for src_path in self.src_paths: | 
					
						
							|  |  |  |       dep_file = JsDependencyFile(src_path) | 
					
						
							|  |  |  |       self.dep_files[src_path] = dep_file | 
					
						
							|  |  |  |       if dep_file.is_base_js: | 
					
						
							|  |  |  |         self.base_dep_file = dep_file | 
					
						
							|  |  |  |         self.base_js_path = os.path.dirname(dep_file.src_path) | 
					
						
							|  |  |  |       for provide in dep_file.provides: | 
					
						
							|  |  |  |         assert not provide in self._provide_map | 
					
						
							|  |  |  |         self._provide_map[provide] = dep_file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def get_deps_js(self): | 
					
						
							|  |  |  |     """Generates the contents of a deps.js file from the dependency graph.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |       A string containing all of the lines of a deps.js file. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # Path that all dependencies will be relative from | 
					
						
							|  |  |  |     base_path = self.build_env.root_path | 
					
						
							|  |  |  |     if self.base_js_path: | 
					
						
							|  |  |  |       base_path = self.base_js_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     lines = [ | 
					
						
							|  |  |  |         '// Automatically generated by anvil-build - do not modify', | 
					
						
							|  |  |  |         '', | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Write in path order (to make the file easier to debug) | 
					
						
							|  |  |  |     src_paths = self.dep_files.keys() | 
					
						
							|  |  |  |     src_paths.sort() | 
					
						
							|  |  |  |     for src_path in src_paths: | 
					
						
							|  |  |  |       dep_file = self.dep_files[src_path] | 
					
						
							|  |  |  |       rel_path = os.path.relpath(dep_file.src_path, base_path) | 
					
						
							| 
									
										
										
										
											2012-05-05 10:58:11 +08:00
										 |  |  |       rel_path = anvil.util.strip_build_paths(rel_path) | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       lines.append('goog.addDependency(\'%s\', %s, %s);' % ( | 
					
						
							| 
									
										
										
										
											2012-05-18 13:15:06 +08:00
										 |  |  |           anvil.util.ensure_forwardslashes(rel_path), | 
					
						
							|  |  |  |           dep_file.provides, dep_file.requires)) | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |     return u'\n'.join(lines) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def get_transitive_closure(self, entry_points): | 
					
						
							|  |  |  |     """Identifies the transitive closure of the dependency graph for the given
 | 
					
						
							|  |  |  |     entry points. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |       entry_points: Closure entry points, such as 'my.start'. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |       A list of all file paths required by the given entry points. | 
					
						
							|  |  |  |       The files are sorted in proper dependency order. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     deps_list = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Always base.js first | 
					
						
							|  |  |  |     if self.base_dep_file: | 
					
						
							|  |  |  |       deps_list.append(self.base_dep_file.src_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Followed by all files in the transitive closure, in order | 
					
						
							|  |  |  |     for entry_point in entry_points: | 
					
						
							|  |  |  |       self._add_dependencies(deps_list, entry_point) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # And finally, any files that look special | 
					
						
							|  |  |  |     for dep_file in self.dep_files.values(): | 
					
						
							|  |  |  |       if dep_file.is_css_rename_map: | 
					
						
							|  |  |  |         deps_list.append(dep_file.src_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return deps_list | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _add_dependencies(self, deps_list, namespace): | 
					
						
							| 
									
										
										
										
											2012-05-05 11:17:35 +08:00
										 |  |  |     if not namespace in self._provide_map: | 
					
						
							|  |  |  |       print 'Namespace %s not provided' % (namespace) | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |     assert namespace in self._provide_map | 
					
						
							|  |  |  |     dep_file = self._provide_map[namespace] | 
					
						
							|  |  |  |     if dep_file.src_path in deps_list: | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     for require in dep_file.requires: | 
					
						
							| 
									
										
										
										
											2012-07-18 04:51:14 +08:00
										 |  |  |       if require in dep_file.provides: | 
					
						
							|  |  |  |         print 'Namespace %s both provided and required in the same file' % ( | 
					
						
							|  |  |  |             require) | 
					
						
							|  |  |  |       assert not require in dep_file.provides | 
					
						
							| 
									
										
										
										
											2012-05-02 15:33:14 +08:00
										 |  |  |       self._add_dependencies(deps_list, require) | 
					
						
							|  |  |  |     deps_list.append(dep_file.src_path) |