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