"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py" (11 Apr 2017, 30567 Bytes) of package /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright (c) 2014 Google Inc. All rights reserved.
    2 # Use of this source code is governed by a BSD-style license that can be
    3 # found in the LICENSE file.
    4 
    5 """
    6 This script is intended for use as a GYP_GENERATOR. It takes as input (by way of
    7 the generator flag config_path) the path of a json file that dictates the files
    8 and targets to search for. The following keys are supported:
    9 files: list of paths (relative) of the files to search for.
   10 test_targets: unqualified target names to search for. Any target in this list
   11 that depends upon a file in |files| is output regardless of the type of target
   12 or chain of dependencies.
   13 additional_compile_targets: Unqualified targets to search for in addition to
   14 test_targets. Targets in the combined list that depend upon a file in |files|
   15 are not necessarily output. For example, if the target is of type none then the
   16 target is not output (but one of the descendants of the target will be).
   17 
   18 The following is output:
   19 error: only supplied if there is an error.
   20 compile_targets: minimal set of targets that directly or indirectly (for
   21   targets of type none) depend on the files in |files| and is one of the
   22   supplied targets or a target that one of the supplied targets depends on.
   23   The expectation is this set of targets is passed into a build step. This list
   24   always contains the output of test_targets as well.
   25 test_targets: set of targets from the supplied |test_targets| that either
   26   directly or indirectly depend upon a file in |files|. This list if useful
   27   if additional processing needs to be done for certain targets after the
   28   build, such as running tests.
   29 status: outputs one of three values: none of the supplied files were found,
   30   one of the include files changed so that it should be assumed everything
   31   changed (in this case test_targets and compile_targets are not output) or at
   32   least one file was found.
   33 invalid_targets: list of supplied targets that were not found.
   34 
   35 Example:
   36 Consider a graph like the following:
   37   A     D
   38  / \
   39 B   C
   40 A depends upon both B and C, A is of type none and B and C are executables.
   41 D is an executable, has no dependencies and nothing depends on it.
   42 If |additional_compile_targets| = ["A"], |test_targets| = ["B", "C"] and
   43 files = ["b.cc", "d.cc"] (B depends upon b.cc and D depends upon d.cc), then
   44 the following is output:
   45 |compile_targets| = ["B"] B must built as it depends upon the changed file b.cc
   46 and the supplied target A depends upon it. A is not output as a build_target
   47 as it is of type none with no rules and actions.
   48 |test_targets| = ["B"] B directly depends upon the change file b.cc.
   49 
   50 Even though the file d.cc, which D depends upon, has changed D is not output
   51 as it was not supplied by way of |additional_compile_targets| or |test_targets|.
   52 
   53 If the generator flag analyzer_output_path is specified, output is written
   54 there. Otherwise output is written to stdout.
   55 
   56 In Gyp the "all" target is shorthand for the root targets in the files passed
   57 to gyp. For example, if file "a.gyp" contains targets "a1" and
   58 "a2", and file "b.gyp" contains targets "b1" and "b2" and "a2" has a dependency
   59 on "b2" and gyp is supplied "a.gyp" then "all" consists of "a1" and "a2".
   60 Notice that "b1" and "b2" are not in the "all" target as "b.gyp" was not
   61 directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp
   62 then the "all" target includes "b1" and "b2".
   63 """
   64 
   65 import gyp.common
   66 import gyp.ninja_syntax as ninja_syntax
   67 import json
   68 import os
   69 import posixpath
   70 import sys
   71 
   72 debug = False
   73 
   74 found_dependency_string = 'Found dependency'
   75 no_dependency_string = 'No dependencies'
   76 # Status when it should be assumed that everything has changed.
   77 all_changed_string = 'Found dependency (all)'
   78 
   79 # MatchStatus is used indicate if and how a target depends upon the supplied
   80 # sources.
   81 # The target's sources contain one of the supplied paths.
   82 MATCH_STATUS_MATCHES = 1
   83 # The target has a dependency on another target that contains one of the
   84 # supplied paths.
   85 MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2
   86 # The target's sources weren't in the supplied paths and none of the target's
   87 # dependencies depend upon a target that matched.
   88 MATCH_STATUS_DOESNT_MATCH = 3
   89 # The target doesn't contain the source, but the dependent targets have not yet
   90 # been visited to determine a more specific status yet.
   91 MATCH_STATUS_TBD = 4
   92 
   93 generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
   94 
   95 generator_wants_static_library_dependencies_adjusted = False
   96 
   97 generator_default_variables = {
   98 }
   99 for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR',
  100                 'LIB_DIR', 'SHARED_LIB_DIR']:
  101   generator_default_variables[dirname] = '!!!'
  102 
  103 for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME',
  104                'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT',
  105                'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX',
  106                'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX',
  107                'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX',
  108                'CONFIGURATION_NAME']:
  109   generator_default_variables[unused] = ''
  110 
  111 
  112 def _ToGypPath(path):
  113   """Converts a path to the format used by gyp."""
  114   if os.sep == '\\' and os.altsep == '/':
  115     return path.replace('\\', '/')
  116   return path
  117 
  118 
  119 def _ResolveParent(path, base_path_components):
  120   """Resolves |path|, which starts with at least one '../'. Returns an empty
  121   string if the path shouldn't be considered. See _AddSources() for a
  122   description of |base_path_components|."""
  123   depth = 0
  124   while path.startswith('../'):
  125     depth += 1
  126     path = path[3:]
  127   # Relative includes may go outside the source tree. For example, an action may
  128   # have inputs in /usr/include, which are not in the source tree.
  129   if depth > len(base_path_components):
  130     return ''
  131   if depth == len(base_path_components):
  132     return path
  133   return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \
  134       '/' + path
  135 
  136 
  137 def _AddSources(sources, base_path, base_path_components, result):
  138   """Extracts valid sources from |sources| and adds them to |result|. Each
  139   source file is relative to |base_path|, but may contain '..'. To make
  140   resolving '..' easier |base_path_components| contains each of the
  141   directories in |base_path|. Additionally each source may contain variables.
  142   Such sources are ignored as it is assumed dependencies on them are expressed
  143   and tracked in some other means."""
  144   # NOTE: gyp paths are always posix style.
  145   for source in sources:
  146     if not len(source) or source.startswith('!!!') or source.startswith('$'):
  147       continue
  148     # variable expansion may lead to //.
  149     org_source = source
  150     source = source[0] + source[1:].replace('//', '/')
  151     if source.startswith('../'):
  152       source = _ResolveParent(source, base_path_components)
  153       if len(source):
  154         result.append(source)
  155       continue
  156     result.append(base_path + source)
  157     if debug:
  158       print 'AddSource', org_source, result[len(result) - 1]
  159 
  160 
  161 def _ExtractSourcesFromAction(action, base_path, base_path_components,
  162                               results):
  163   if 'inputs' in action:
  164     _AddSources(action['inputs'], base_path, base_path_components, results)
  165 
  166 
  167 def _ToLocalPath(toplevel_dir, path):
  168   """Converts |path| to a path relative to |toplevel_dir|."""
  169   if path == toplevel_dir:
  170     return ''
  171   if path.startswith(toplevel_dir + '/'):
  172     return path[len(toplevel_dir) + len('/'):]
  173   return path
  174 
  175 
  176 def _ExtractSources(target, target_dict, toplevel_dir):
  177   # |target| is either absolute or relative and in the format of the OS. Gyp
  178   # source paths are always posix. Convert |target| to a posix path relative to
  179   # |toplevel_dir_|. This is done to make it easy to build source paths.
  180   base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target)))
  181   base_path_components = base_path.split('/')
  182 
  183   # Add a trailing '/' so that _AddSources() can easily build paths.
  184   if len(base_path):
  185     base_path += '/'
  186 
  187   if debug:
  188     print 'ExtractSources', target, base_path
  189 
  190   results = []
  191   if 'sources' in target_dict:
  192     _AddSources(target_dict['sources'], base_path, base_path_components,
  193                 results)
  194   # Include the inputs from any actions. Any changes to these affect the
  195   # resulting output.
  196   if 'actions' in target_dict:
  197     for action in target_dict['actions']:
  198       _ExtractSourcesFromAction(action, base_path, base_path_components,
  199                                 results)
  200   if 'rules' in target_dict:
  201     for rule in target_dict['rules']:
  202       _ExtractSourcesFromAction(rule, base_path, base_path_components, results)
  203 
  204   return results
  205 
  206 
  207 class Target(object):
  208   """Holds information about a particular target:
  209   deps: set of Targets this Target depends upon. This is not recursive, only the
  210     direct dependent Targets.
  211   match_status: one of the MatchStatus values.
  212   back_deps: set of Targets that have a dependency on this Target.
  213   visited: used during iteration to indicate whether we've visited this target.
  214     This is used for two iterations, once in building the set of Targets and
  215     again in _GetBuildTargets().
  216   name: fully qualified name of the target.
  217   requires_build: True if the target type is such that it needs to be built.
  218     See _DoesTargetTypeRequireBuild for details.
  219   added_to_compile_targets: used when determining if the target was added to the
  220     set of targets that needs to be built.
  221   in_roots: true if this target is a descendant of one of the root nodes.
  222   is_executable: true if the type of target is executable.
  223   is_static_library: true if the type of target is static_library.
  224   is_or_has_linked_ancestor: true if the target does a link (eg executable), or
  225     if there is a target in back_deps that does a link."""
  226   def __init__(self, name):
  227     self.deps = set()
  228     self.match_status = MATCH_STATUS_TBD
  229     self.back_deps = set()
  230     self.name = name
  231     # TODO(sky): I don't like hanging this off Target. This state is specific
  232     # to certain functions and should be isolated there.
  233     self.visited = False
  234     self.requires_build = False
  235     self.added_to_compile_targets = False
  236     self.in_roots = False
  237     self.is_executable = False
  238     self.is_static_library = False
  239     self.is_or_has_linked_ancestor = False
  240 
  241 
  242 class Config(object):
  243   """Details what we're looking for
  244   files: set of files to search for
  245   targets: see file description for details."""
  246   def __init__(self):
  247     self.files = []
  248     self.targets = set()
  249     self.additional_compile_target_names = set()
  250     self.test_target_names = set()
  251 
  252   def Init(self, params):
  253     """Initializes Config. This is a separate method as it raises an exception
  254     if there is a parse error."""
  255     generator_flags = params.get('generator_flags', {})
  256     config_path = generator_flags.get('config_path', None)
  257     if not config_path:
  258       return
  259     try:
  260       f = open(config_path, 'r')
  261       config = json.load(f)
  262       f.close()
  263     except IOError:
  264       raise Exception('Unable to open file ' + config_path)
  265     except ValueError as e:
  266       raise Exception('Unable to parse config file ' + config_path + str(e))
  267     if not isinstance(config, dict):
  268       raise Exception('config_path must be a JSON file containing a dictionary')
  269     self.files = config.get('files', [])
  270     self.additional_compile_target_names = set(
  271       config.get('additional_compile_targets', []))
  272     self.test_target_names = set(config.get('test_targets', []))
  273 
  274 
  275 def _WasBuildFileModified(build_file, data, files, toplevel_dir):
  276   """Returns true if the build file |build_file| is either in |files| or
  277   one of the files included by |build_file| is in |files|. |toplevel_dir| is
  278   the root of the source tree."""
  279   if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files:
  280     if debug:
  281       print 'gyp file modified', build_file
  282     return True
  283 
  284   # First element of included_files is the file itself.
  285   if len(data[build_file]['included_files']) <= 1:
  286     return False
  287 
  288   for include_file in data[build_file]['included_files'][1:]:
  289     # |included_files| are relative to the directory of the |build_file|.
  290     rel_include_file = \
  291         _ToGypPath(gyp.common.UnrelativePath(include_file, build_file))
  292     if _ToLocalPath(toplevel_dir, rel_include_file) in files:
  293       if debug:
  294         print 'included gyp file modified, gyp_file=', build_file, \
  295             'included file=', rel_include_file
  296       return True
  297   return False
  298 
  299 
  300 def _GetOrCreateTargetByName(targets, target_name):
  301   """Creates or returns the Target at targets[target_name]. If there is no
  302   Target for |target_name| one is created. Returns a tuple of whether a new
  303   Target was created and the Target."""
  304   if target_name in targets:
  305     return False, targets[target_name]
  306   target = Target(target_name)
  307   targets[target_name] = target
  308   return True, target
  309 
  310 
  311 def _DoesTargetTypeRequireBuild(target_dict):
  312   """Returns true if the target type is such that it needs to be built."""
  313   # If a 'none' target has rules or actions we assume it requires a build.
  314   return bool(target_dict['type'] != 'none' or
  315               target_dict.get('actions') or target_dict.get('rules'))
  316 
  317 
  318 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files,
  319                      build_files):
  320   """Returns a tuple of the following:
  321   . A dictionary mapping from fully qualified name to Target.
  322   . A list of the targets that have a source file in |files|.
  323   . Targets that constitute the 'all' target. See description at top of file
  324     for details on the 'all' target.
  325   This sets the |match_status| of the targets that contain any of the source
  326   files in |files| to MATCH_STATUS_MATCHES.
  327   |toplevel_dir| is the root of the source tree."""
  328   # Maps from target name to Target.
  329   name_to_target = {}
  330 
  331   # Targets that matched.
  332   matching_targets = []
  333 
  334   # Queue of targets to visit.
  335   targets_to_visit = target_list[:]
  336 
  337   # Maps from build file to a boolean indicating whether the build file is in
  338   # |files|.
  339   build_file_in_files = {}
  340 
  341   # Root targets across all files.
  342   roots = set()
  343 
  344   # Set of Targets in |build_files|.
  345   build_file_targets = set()
  346 
  347   while len(targets_to_visit) > 0:
  348     target_name = targets_to_visit.pop()
  349     created_target, target = _GetOrCreateTargetByName(name_to_target,
  350                                                       target_name)
  351     if created_target:
  352       roots.add(target)
  353     elif target.visited:
  354       continue
  355 
  356     target.visited = True
  357     target.requires_build = _DoesTargetTypeRequireBuild(
  358         target_dicts[target_name])
  359     target_type = target_dicts[target_name]['type']
  360     target.is_executable = target_type == 'executable'
  361     target.is_static_library = target_type == 'static_library'
  362     target.is_or_has_linked_ancestor = (target_type == 'executable' or
  363                                         target_type == 'shared_library')
  364 
  365     build_file = gyp.common.ParseQualifiedTarget(target_name)[0]
  366     if not build_file in build_file_in_files:
  367       build_file_in_files[build_file] = \
  368           _WasBuildFileModified(build_file, data, files, toplevel_dir)
  369 
  370     if build_file in build_files:
  371       build_file_targets.add(target)
  372 
  373     # If a build file (or any of its included files) is modified we assume all
  374     # targets in the file are modified.
  375     if build_file_in_files[build_file]:
  376       print 'matching target from modified build file', target_name
  377       target.match_status = MATCH_STATUS_MATCHES
  378       matching_targets.append(target)
  379     else:
  380       sources = _ExtractSources(target_name, target_dicts[target_name],
  381                                 toplevel_dir)
  382       for source in sources:
  383         if _ToGypPath(os.path.normpath(source)) in files:
  384           print 'target', target_name, 'matches', source
  385           target.match_status = MATCH_STATUS_MATCHES
  386           matching_targets.append(target)
  387           break
  388 
  389     # Add dependencies to visit as well as updating back pointers for deps.
  390     for dep in target_dicts[target_name].get('dependencies', []):
  391       targets_to_visit.append(dep)
  392 
  393       created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target,
  394                                                                 dep)
  395       if not created_dep_target:
  396         roots.discard(dep_target)
  397 
  398       target.deps.add(dep_target)
  399       dep_target.back_deps.add(target)
  400 
  401   return name_to_target, matching_targets, roots & build_file_targets
  402 
  403 
  404 def _GetUnqualifiedToTargetMapping(all_targets, to_find):
  405   """Returns a tuple of the following:
  406   . mapping (dictionary) from unqualified name to Target for all the
  407     Targets in |to_find|.
  408   . any target names not found. If this is empty all targets were found."""
  409   result = {}
  410   if not to_find:
  411     return {}, []
  412   to_find = set(to_find)
  413   for target_name in all_targets.keys():
  414     extracted = gyp.common.ParseQualifiedTarget(target_name)
  415     if len(extracted) > 1 and extracted[1] in to_find:
  416       to_find.remove(extracted[1])
  417       result[extracted[1]] = all_targets[target_name]
  418       if not to_find:
  419         return result, []
  420   return result, [x for x in to_find]
  421 
  422 
  423 def _DoesTargetDependOnMatchingTargets(target):
  424   """Returns true if |target| or any of its dependencies is one of the
  425   targets containing the files supplied as input to analyzer. This updates
  426   |matches| of the Targets as it recurses.
  427   target: the Target to look for."""
  428   if target.match_status == MATCH_STATUS_DOESNT_MATCH:
  429     return False
  430   if target.match_status == MATCH_STATUS_MATCHES or \
  431       target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY:
  432     return True
  433   for dep in target.deps:
  434     if _DoesTargetDependOnMatchingTargets(dep):
  435       target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY
  436       print '\t', target.name, 'matches by dep', dep.name
  437       return True
  438   target.match_status = MATCH_STATUS_DOESNT_MATCH
  439   return False
  440 
  441 
  442 def _GetTargetsDependingOnMatchingTargets(possible_targets):
  443   """Returns the list of Targets in |possible_targets| that depend (either
  444   directly on indirectly) on at least one of the targets containing the files
  445   supplied as input to analyzer.
  446   possible_targets: targets to search from."""
  447   found = []
  448   print 'Targets that matched by dependency:'
  449   for target in possible_targets:
  450     if _DoesTargetDependOnMatchingTargets(target):
  451       found.append(target)
  452   return found
  453 
  454 
  455 def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
  456   """Recurses through all targets that depend on |target|, adding all targets
  457   that need to be built (and are in |roots|) to |result|.
  458   roots: set of root targets.
  459   add_if_no_ancestor: If true and there are no ancestors of |target| then add
  460   |target| to |result|. |target| must still be in |roots|.
  461   result: targets that need to be built are added here."""
  462   if target.visited:
  463     return
  464 
  465   target.visited = True
  466   target.in_roots = target in roots
  467 
  468   for back_dep_target in target.back_deps:
  469     _AddCompileTargets(back_dep_target, roots, False, result)
  470     target.added_to_compile_targets |= back_dep_target.added_to_compile_targets
  471     target.in_roots |= back_dep_target.in_roots
  472     target.is_or_has_linked_ancestor |= (
  473       back_dep_target.is_or_has_linked_ancestor)
  474 
  475   # Always add 'executable' targets. Even though they may be built by other
  476   # targets that depend upon them it makes detection of what is going to be
  477   # built easier.
  478   # And always add static_libraries that have no dependencies on them from
  479   # linkables. This is necessary as the other dependencies on them may be
  480   # static libraries themselves, which are not compile time dependencies.
  481   if target.in_roots and \
  482         (target.is_executable or
  483          (not target.added_to_compile_targets and
  484           (add_if_no_ancestor or target.requires_build)) or
  485          (target.is_static_library and add_if_no_ancestor and
  486           not target.is_or_has_linked_ancestor)):
  487     print '\t\tadding to compile targets', target.name, 'executable', \
  488            target.is_executable, 'added_to_compile_targets', \
  489            target.added_to_compile_targets, 'add_if_no_ancestor', \
  490            add_if_no_ancestor, 'requires_build', target.requires_build, \
  491            'is_static_library', target.is_static_library, \
  492            'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor
  493     result.add(target)
  494     target.added_to_compile_targets = True
  495 
  496 
  497 def _GetCompileTargets(matching_targets, supplied_targets):
  498   """Returns the set of Targets that require a build.
  499   matching_targets: targets that changed and need to be built.
  500   supplied_targets: set of targets supplied to analyzer to search from."""
  501   result = set()
  502   for target in matching_targets:
  503     print 'finding compile targets for match', target.name
  504     _AddCompileTargets(target, supplied_targets, True, result)
  505   return result
  506 
  507 
  508 def _WriteOutput(params, **values):
  509   """Writes the output, either to stdout or a file is specified."""
  510   if 'error' in values:
  511     print 'Error:', values['error']
  512   if 'status' in values:
  513     print values['status']
  514   if 'targets' in values:
  515     values['targets'].sort()
  516     print 'Supplied targets that depend on changed files:'
  517     for target in values['targets']:
  518       print '\t', target
  519   if 'invalid_targets' in values:
  520     values['invalid_targets'].sort()
  521     print 'The following targets were not found:'
  522     for target in values['invalid_targets']:
  523       print '\t', target
  524   if 'build_targets' in values:
  525     values['build_targets'].sort()
  526     print 'Targets that require a build:'
  527     for target in values['build_targets']:
  528       print '\t', target
  529   if 'compile_targets' in values:
  530     values['compile_targets'].sort()
  531     print 'Targets that need to be built:'
  532     for target in values['compile_targets']:
  533       print '\t', target
  534   if 'test_targets' in values:
  535     values['test_targets'].sort()
  536     print 'Test targets:'
  537     for target in values['test_targets']:
  538       print '\t', target
  539 
  540   output_path = params.get('generator_flags', {}).get(
  541       'analyzer_output_path', None)
  542   if not output_path:
  543     print json.dumps(values)
  544     return
  545   try:
  546     f = open(output_path, 'w')
  547     f.write(json.dumps(values) + '\n')
  548     f.close()
  549   except IOError as e:
  550     print 'Error writing to output file', output_path, str(e)
  551 
  552 
  553 def _WasGypIncludeFileModified(params, files):
  554   """Returns true if one of the files in |files| is in the set of included
  555   files."""
  556   if params['options'].includes:
  557     for include in params['options'].includes:
  558       if _ToGypPath(os.path.normpath(include)) in files:
  559         print 'Include file modified, assuming all changed', include
  560         return True
  561   return False
  562 
  563 
  564 def _NamesNotIn(names, mapping):
  565   """Returns a list of the values in |names| that are not in |mapping|."""
  566   return [name for name in names if name not in mapping]
  567 
  568 
  569 def _LookupTargets(names, mapping):
  570   """Returns a list of the mapping[name] for each value in |names| that is in
  571   |mapping|."""
  572   return [mapping[name] for name in names if name in mapping]
  573 
  574 
  575 def CalculateVariables(default_variables, params):
  576   """Calculate additional variables for use in the build (called by gyp)."""
  577   flavor = gyp.common.GetFlavor(params)
  578   if flavor == 'mac':
  579     default_variables.setdefault('OS', 'mac')
  580   elif flavor == 'win':
  581     default_variables.setdefault('OS', 'win')
  582     # Copy additional generator configuration data from VS, which is shared
  583     # by the Windows Ninja generator.
  584     import gyp.generator.msvs as msvs_generator
  585     generator_additional_non_configuration_keys = getattr(msvs_generator,
  586         'generator_additional_non_configuration_keys', [])
  587     generator_additional_path_sections = getattr(msvs_generator,
  588         'generator_additional_path_sections', [])
  589 
  590     gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
  591   else:
  592     operating_system = flavor
  593     if flavor == 'android':
  594       operating_system = 'linux'  # Keep this legacy behavior for now.
  595     default_variables.setdefault('OS', operating_system)
  596 
  597 
  598 class TargetCalculator(object):
  599   """Calculates the matching test_targets and matching compile_targets."""
  600   def __init__(self, files, additional_compile_target_names, test_target_names,
  601                data, target_list, target_dicts, toplevel_dir, build_files):
  602     self._additional_compile_target_names = set(additional_compile_target_names)
  603     self._test_target_names = set(test_target_names)
  604     self._name_to_target, self._changed_targets, self._root_targets = (
  605       _GenerateTargets(data, target_list, target_dicts, toplevel_dir,
  606                        frozenset(files), build_files))
  607     self._unqualified_mapping, self.invalid_targets = (
  608       _GetUnqualifiedToTargetMapping(self._name_to_target,
  609                                      self._supplied_target_names_no_all()))
  610 
  611   def _supplied_target_names(self):
  612     return self._additional_compile_target_names | self._test_target_names
  613 
  614   def _supplied_target_names_no_all(self):
  615     """Returns the supplied test targets without 'all'."""
  616     result = self._supplied_target_names();
  617     result.discard('all')
  618     return result
  619 
  620   def is_build_impacted(self):
  621     """Returns true if the supplied files impact the build at all."""
  622     return self._changed_targets
  623 
  624   def find_matching_test_target_names(self):
  625     """Returns the set of output test targets."""
  626     assert self.is_build_impacted()
  627     # Find the test targets first. 'all' is special cased to mean all the
  628     # root targets. To deal with all the supplied |test_targets| are expanded
  629     # to include the root targets during lookup. If any of the root targets
  630     # match, we remove it and replace it with 'all'.
  631     test_target_names_no_all = set(self._test_target_names)
  632     test_target_names_no_all.discard('all')
  633     test_targets_no_all = _LookupTargets(test_target_names_no_all,
  634                                          self._unqualified_mapping)
  635     test_target_names_contains_all = 'all' in self._test_target_names
  636     if test_target_names_contains_all:
  637       test_targets = [x for x in (set(test_targets_no_all) |
  638                                   set(self._root_targets))]
  639     else:
  640       test_targets = [x for x in test_targets_no_all]
  641     print 'supplied test_targets'
  642     for target_name in self._test_target_names:
  643       print '\t', target_name
  644     print 'found test_targets'
  645     for target in test_targets:
  646       print '\t', target.name
  647     print 'searching for matching test targets'
  648     matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets)
  649     matching_test_targets_contains_all = (test_target_names_contains_all and
  650                                           set(matching_test_targets) &
  651                                           set(self._root_targets))
  652     if matching_test_targets_contains_all:
  653       # Remove any of the targets for all that were not explicitly supplied,
  654       # 'all' is subsequentely added to the matching names below.
  655       matching_test_targets = [x for x in (set(matching_test_targets) &
  656                                            set(test_targets_no_all))]
  657     print 'matched test_targets'
  658     for target in matching_test_targets:
  659       print '\t', target.name
  660     matching_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1]
  661                              for target in matching_test_targets]
  662     if matching_test_targets_contains_all:
  663       matching_target_names.append('all')
  664       print '\tall'
  665     return matching_target_names
  666 
  667   def find_matching_compile_target_names(self):
  668     """Returns the set of output compile targets."""
  669     assert self.is_build_impacted();
  670     # Compile targets are found by searching up from changed targets.
  671     # Reset the visited status for _GetBuildTargets.
  672     for target in self._name_to_target.itervalues():
  673       target.visited = False
  674 
  675     supplied_targets = _LookupTargets(self._supplied_target_names_no_all(),
  676                                       self._unqualified_mapping)
  677     if 'all' in self._supplied_target_names():
  678       supplied_targets = [x for x in (set(supplied_targets) |
  679                                       set(self._root_targets))]
  680     print 'Supplied test_targets & compile_targets'
  681     for target in supplied_targets:
  682       print '\t', target.name
  683     print 'Finding compile targets'
  684     compile_targets = _GetCompileTargets(self._changed_targets,
  685                                          supplied_targets)
  686     return [gyp.common.ParseQualifiedTarget(target.name)[1]
  687             for target in compile_targets]
  688 
  689 
  690 def GenerateOutput(target_list, target_dicts, data, params):
  691   """Called by gyp as the final stage. Outputs results."""
  692   config = Config()
  693   try:
  694     config.Init(params)
  695 
  696     if not config.files:
  697       raise Exception('Must specify files to analyze via config_path generator '
  698                       'flag')
  699 
  700     toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir))
  701     if debug:
  702       print 'toplevel_dir', toplevel_dir
  703 
  704     if _WasGypIncludeFileModified(params, config.files):
  705       result_dict = { 'status': all_changed_string,
  706                       'test_targets': list(config.test_target_names),
  707                       'compile_targets': list(
  708                         config.additional_compile_target_names |
  709                         config.test_target_names) }
  710       _WriteOutput(params, **result_dict)
  711       return
  712 
  713     calculator = TargetCalculator(config.files,
  714                                   config.additional_compile_target_names,
  715                                   config.test_target_names, data,
  716                                   target_list, target_dicts, toplevel_dir,
  717                                   params['build_files'])
  718     if not calculator.is_build_impacted():
  719       result_dict = { 'status': no_dependency_string,
  720                       'test_targets': [],
  721                       'compile_targets': [] }
  722       if calculator.invalid_targets:
  723         result_dict['invalid_targets'] = calculator.invalid_targets
  724       _WriteOutput(params, **result_dict)
  725       return
  726 
  727     test_target_names = calculator.find_matching_test_target_names()
  728     compile_target_names = calculator.find_matching_compile_target_names()
  729     found_at_least_one_target = compile_target_names or test_target_names
  730     result_dict = { 'test_targets': test_target_names,
  731                     'status': found_dependency_string if
  732                         found_at_least_one_target else no_dependency_string,
  733                     'compile_targets': list(
  734                         set(compile_target_names) |
  735                         set(test_target_names)) }
  736     if calculator.invalid_targets:
  737       result_dict['invalid_targets'] = calculator.invalid_targets
  738     _WriteOutput(params, **result_dict)
  739 
  740   except Exception as e:
  741     _WriteOutput(params, error=str(e))