Index: pylib/gyp/generator/analyzer.py |
diff --git a/pylib/gyp/generator/analyzer.py b/pylib/gyp/generator/analyzer.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..007d17d17e183f470664b1d3f1fcf1fce13b9ced |
--- /dev/null |
+++ b/pylib/gyp/generator/analyzer.py |
@@ -0,0 +1,192 @@ |
+# Copyright (c) 2014 Google Inc. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+""" |
+This script is intended for use as a GYP_GENERATOR. It takes as input (by way of |
+the generator flag file_path) the list of relative file paths to consider. If |
+any target has at least one of the paths as a source (or input to an action or |
+rule) then 'Found dependency' is output, otherwise 'No dependencies' is output. |
+""" |
+ |
+import gyp.common |
+import gyp.ninja_syntax as ninja_syntax |
+import os |
+import posixpath |
+ |
+generator_supports_multiple_toolsets = True |
+ |
+generator_wants_static_library_dependencies_adjusted = False |
+ |
+generator_default_variables = { |
+} |
+for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', |
+ 'LIB_DIR', 'SHARED_LIB_DIR']: |
+ generator_default_variables[dirname] = '!!!' |
+ |
+for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', |
+ 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', |
+ 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', |
+ 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', |
+ 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', |
+ 'CONFIGURATION_NAME']: |
+ generator_default_variables[unused] = '' |
+ |
+def __MakeRelativeTargetName(path): |
+ """Converts a gyp target name into a relative name. For example, the path to a |
+ gyp file may be something like c:\foo\bar.gyp:target, this converts it to |
+ bar.gyp. |
+ """ |
+ prune_path = os.getcwd() |
+ if path.startswith(prune_path): |
+ path = path[len(prune_path):] |
+ # Gyp paths are always posix style. |
+ path = path.replace('\\', '/') |
+ if path.endswith('#target'): |
+ path = path[0:len(path) - len('#target')] |
+ return path |
+ |
+def __ExtractBasePath(target): |
+ """Extracts the path components of the specified gyp target path.""" |
+ last_index = target.rfind('/') |
+ if last_index == -1: |
+ return '' |
+ return target[0:(last_index + 1)] |
+ |
+def __AddSources(sources, base_path, base_path_components, result): |
+ """Extracts valid sources from |sources| and adds them to |result|. Each |
+ source file is relative to |base_path|, but may contain '..'. To make |
+ resolving '..' easier |base_path_components| contains each of the |
+ directories in |base_path|. Additionally each source may contain variables. |
+ Such sources are ignored as it is assumed dependencies on them are expressed |
+ and tracked in some other means.""" |
+ # NOTE: gyp paths are always posix style. |
+ for source in sources: |
+ if not len(source) or source.startswith('!!!') or source.startswith('$'): |
+ continue |
+ # variable expansion may lead to //. |
+ source = source[0] + source[1:].replace('//', '/') |
+ if source.startswith('../'): |
+ path_components = base_path_components[:] |
+ # Resolve relative paths. |
+ while source.startswith('../'): |
+ path_components.pop(len(path_components) - 1) |
+ source = source[3:] |
+ result.append('/'.join(path_components) + source) |
+ continue |
+ result.append(base_path + source) |
+ |
+def __ExtractSourcesFromAction(action, base_path, base_path_components, |
+ results): |
+ if 'inputs' in action: |
+ __AddSources(action['inputs'], base_path, base_path_components, results) |
+ |
+def __ExtractSources(target, target_dict): |
+ base_path = posixpath.dirname(target) |
+ base_path_components = base_path.split('/') |
+ # Add a trailing '/' so that __AddSources() can easily build paths. |
+ if len(base_path): |
+ base_path += '/' |
+ results = [] |
+ if 'sources' in target_dict: |
+ __AddSources(target_dict['sources'], base_path, base_path_components, |
+ results) |
+ # Include the inputs from any actions. Any changes to these effect the |
+ # resulting output. |
+ if 'actions' in target_dict: |
+ for action in target_dict['actions']: |
+ __ExtractSourcesFromAction(action, base_path, base_path_components, |
+ results) |
+ if 'rules' in target_dict: |
+ for rule in target_dict['rules']: |
+ __ExtractSourcesFromAction(rule, base_path, base_path_components, results) |
+ |
+ return results |
+ |
+class Target(object): |
+ """Holds information about a particular target: |
+ sources: set of source files defined by this target. This includes inputs to |
+ actions and rules. |
+ deps: list of direct dependencies.""" |
+ def __init__(self): |
+ self.sources = [] |
+ self.deps = [] |
+ |
+def __GenerateTargets(target_list, target_dicts): |
+ """Generates a dictionary with the key the name of a target and the value a |
+ Target.""" |
+ targets = {} |
+ |
+ # Queue of targets to visit. |
+ targets_to_visit = target_list[:] |
+ |
+ while len(targets_to_visit) > 0: |
+ absolute_target_name = targets_to_visit.pop() |
+ # |absolute_target| may be an absolute path and may include #target. |
+ # References to targets are relative, so we need to clean the name. |
+ relative_target_name = __MakeRelativeTargetName(absolute_target_name) |
+ if relative_target_name in targets: |
+ continue |
+ |
+ target = Target() |
+ targets[relative_target_name] = target |
+ target.sources.extend(__ExtractSources(relative_target_name, |
+ target_dicts[absolute_target_name])) |
+ |
+ for dep in target_dicts[absolute_target_name].get('dependencies', []): |
+ targets[relative_target_name].deps.append(__MakeRelativeTargetName(dep)) |
+ targets_to_visit.append(dep) |
+ |
+ return targets |
+ |
+def __GetFiles(params): |
+ """Returns the list of files to analyze, or None if none specified.""" |
+ generator_flags = params.get('generator_flags', {}) |
+ file_path = generator_flags.get('file_path', None) |
+ if not file_path: |
+ return None |
+ try: |
+ f = open(file_path, 'r') |
+ result = [] |
+ for file_name in f: |
+ if file_name.endswith('\n'): |
+ file_name = file_name[0:len(file_name) - 1] |
+ if len(file_name): |
+ result.append(file_name) |
+ f.close() |
+ return result |
+ except IOError: |
+ print 'Unable to open file', file_path |
+ return None |
+ |
+def CalculateVariables(default_variables, params): |
+ """Calculate additional variables for use in the build (called by gyp).""" |
+ flavor = gyp.common.GetFlavor(params) |
+ if flavor == 'mac': |
+ default_variables.setdefault('OS', 'mac') |
+ elif flavor == 'win': |
+ default_variables.setdefault('OS', 'win') |
+ else: |
+ operating_system = flavor |
+ if flavor == 'android': |
+ operating_system = 'linux' # Keep this legacy behavior for now. |
+ default_variables.setdefault('OS', operating_system) |
+ |
+def GenerateOutput(target_list, target_dicts, data, params): |
+ """Called by gyp as the final stage. Outputs results.""" |
+ files = __GetFiles(params) |
+ if not files: |
+ print 'Must specify files to analyze via file_path generator flag' |
+ return |
+ |
+ targets = __GenerateTargets(target_list, target_dicts) |
+ |
+ files_set = frozenset(files) |
+ found_in_all_sources = 0 |
+ for target_name, target in targets.iteritems(): |
+ sources = files_set.intersection(target.sources) |
+ if len(sources): |
+ print 'Found dependency' |
+ return |
+ |
+ print 'No dependencies' |