blob: 921c1a6b714328b73a081f2a02fac41d6d65d1d7 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001# 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
65import gyp.common
66import gyp.ninja_syntax as ninja_syntax
67import json
68import os
69import posixpath
70import sys
71
72debug = False
73
74found_dependency_string = 'Found dependency'
75no_dependency_string = 'No dependencies'
76# Status when it should be assumed that everything has changed.
77all_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.
82MATCH_STATUS_MATCHES = 1
83# The target has a dependency on another target that contains one of the
84# supplied paths.
85MATCH_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.
88MATCH_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.
91MATCH_STATUS_TBD = 4
92
93generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
94
95generator_wants_static_library_dependencies_adjusted = False
96
97generator_default_variables = {
98}
99for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR',
100 'LIB_DIR', 'SHARED_LIB_DIR']:
101 generator_default_variables[dirname] = '!!!'
102
103for 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
112def _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
119def _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
137def _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
161def _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
167def _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
176def _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
207class 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
242class 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
275def _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
300def _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
311def _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
318def _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
404def _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
423def _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
442def _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
455def _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
497def _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
508def _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
553def _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
564def _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
569def _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
575def 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
598class 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
690def 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))