| 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' | 
|  |