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