Initial commit
[yaffs-website] / node_modules / node-gyp / gyp / pylib / gyp / generator / analyzer.py
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))