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.
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).
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.
36 Consider a graph like the following:
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.
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|.
53 If the generator flag analyzer_output_path is specified, output is written
54 there. Otherwise output is written to stdout.
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".
66 import gyp.ninja_syntax as ninja_syntax
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)'
79 # MatchStatus is used indicate if and how a target depends upon the supplied
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
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.
93 generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
95 generator_wants_static_library_dependencies_adjusted = False
97 generator_default_variables = {
99 for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR',
100 'LIB_DIR', 'SHARED_LIB_DIR']:
101 generator_default_variables[dirname] = '!!!'
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] = ''
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('\\', '/')
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|."""
124 while path.startswith('../'):
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):
131 if depth == len(base_path_components):
133 return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \
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('$'):
148 # variable expansion may lead to //.
150 source = source[0] + source[1:].replace('//', '/')
151 if source.startswith('../'):
152 source = _ResolveParent(source, base_path_components)
154 result.append(source)
156 result.append(base_path + source)
158 print 'AddSource', org_source, result[len(result) - 1]
161 def _ExtractSourcesFromAction(action, base_path, base_path_components,
163 if 'inputs' in action:
164 _AddSources(action['inputs'], base_path, base_path_components, results)
167 def _ToLocalPath(toplevel_dir, path):
168 """Converts |path| to a path relative to |toplevel_dir|."""
169 if path == toplevel_dir:
171 if path.startswith(toplevel_dir + '/'):
172 return path[len(toplevel_dir) + len('/'):]
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('/')
183 # Add a trailing '/' so that _AddSources() can easily build paths.
188 print 'ExtractSources', target, base_path
191 if 'sources' in target_dict:
192 _AddSources(target_dict['sources'], base_path, base_path_components,
194 # Include the inputs from any actions. Any changes to these affect the
196 if 'actions' in target_dict:
197 for action in target_dict['actions']:
198 _ExtractSourcesFromAction(action, base_path, base_path_components,
200 if 'rules' in target_dict:
201 for rule in target_dict['rules']:
202 _ExtractSourcesFromAction(rule, base_path, base_path_components, results)
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):
228 self.match_status = MATCH_STATUS_TBD
229 self.back_deps = set()
231 # TODO(sky): I don't like hanging this off Target. This state is specific
232 # to certain functions and should be isolated there.
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
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."""
249 self.additional_compile_target_names = set()
250 self.test_target_names = set()
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)
260 f = open(config_path, 'r')
261 config = json.load(f)
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', []))
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:
281 print 'gyp file modified', build_file
284 # First element of included_files is the file itself.
285 if len(data[build_file]['included_files']) <= 1:
288 for include_file in data[build_file]['included_files'][1:]:
289 # |included_files| are relative to the directory of the |build_file|.
291 _ToGypPath(gyp.common.UnrelativePath(include_file, build_file))
292 if _ToLocalPath(toplevel_dir, rel_include_file) in files:
294 print 'included gyp file modified, gyp_file=', build_file, \
295 'included file=', rel_include_file
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
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'))
318 def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, 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.
331 # Targets that matched.
332 matching_targets = []
334 # Queue of targets to visit.
335 targets_to_visit = target_list[:]
337 # Maps from build file to a boolean indicating whether the build file is in
339 build_file_in_files = {}
341 # Root targets across all files.
344 # Set of Targets in |build_files|.
345 build_file_targets = set()
347 while len(targets_to_visit) > 0:
348 target_name = targets_to_visit.pop()
349 created_target, target = _GetOrCreateTargetByName(name_to_target,
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')
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)
370 if build_file in build_files:
371 build_file_targets.add(target)
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)
380 sources = _ExtractSources(target_name, target_dicts[target_name],
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)
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)
393 created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target,
395 if not created_dep_target:
396 roots.discard(dep_target)
398 target.deps.add(dep_target)
399 dep_target.back_deps.add(target)
401 return name_to_target, matching_targets, roots & build_file_targets
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."""
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]
420 return result, [x for x in to_find]
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:
430 if target.match_status == MATCH_STATUS_MATCHES or \
431 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY:
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
438 target.match_status = MATCH_STATUS_DOESNT_MATCH
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."""
448 print 'Targets that matched by dependency:'
449 for target in possible_targets:
450 if _DoesTargetDependOnMatchingTargets(target):
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."""
465 target.visited = True
466 target.in_roots = target in roots
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)
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
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
494 target.added_to_compile_targets = True
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."""
502 for target in matching_targets:
503 print 'finding compile targets for match', target.name
504 _AddCompileTargets(target, supplied_targets, True, result)
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']:
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']:
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']:
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']:
534 if 'test_targets' in values:
535 values['test_targets'].sort()
536 print 'Test targets:'
537 for target in values['test_targets']:
540 output_path = params.get('generator_flags', {}).get(
541 'analyzer_output_path', None)
543 print json.dumps(values)
546 f = open(output_path, 'w')
547 f.write(json.dumps(values) + '\n')
550 print 'Error writing to output file', output_path, str(e)
553 def _WasGypIncludeFileModified(params, files):
554 """Returns true if one of the files in |files| is in the set of included
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
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]
569 def _LookupTargets(names, mapping):
570 """Returns a list of the mapping[name] for each value in |names| that is in
572 return [mapping[name] for name in names if name in mapping]
575 def CalculateVariables(default_variables, params):
576 """Calculate additional variables for use in the build (called by gyp)."""
577 flavor = gyp.common.GetFlavor(params)
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', [])
590 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
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)
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()))
611 def _supplied_target_names(self):
612 return self._additional_compile_target_names | self._test_target_names
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')
620 def is_build_impacted(self):
621 """Returns true if the supplied files impact the build at all."""
622 return self._changed_targets
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))]
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')
665 return matching_target_names
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
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,
686 return [gyp.common.ParseQualifiedTarget(target.name)[1]
687 for target in compile_targets]
690 def GenerateOutput(target_list, target_dicts, data, params):
691 """Called by gyp as the final stage. Outputs results."""
697 raise Exception('Must specify files to analyze via config_path generator '
700 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir))
702 print 'toplevel_dir', toplevel_dir
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)
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,
721 'compile_targets': [] }
722 if calculator.invalid_targets:
723 result_dict['invalid_targets'] = calculator.invalid_targets
724 _WriteOutput(params, **result_dict)
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)
740 except Exception as e:
741 _WriteOutput(params, error=str(e))