"Fossies" - the Fresh Open Source Software Archive 
Member "node-v19.8.1-win-x64/node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py" (16 Feb 2023, 32492 Bytes) of package /windows/www/node-v19.8.1-win-x64.zip:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
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
66 import gyp.common
67 import json
68 import os
69 import posixpath
70
71 debug = False
72
73 found_dependency_string = "Found dependency"
74 no_dependency_string = "No dependencies"
75 # Status when it should be assumed that everything has changed.
76 all_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.
81 MATCH_STATUS_MATCHES = 1
82 # The target has a dependency on another target that contains one of the
83 # supplied paths.
84 MATCH_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.
87 MATCH_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.
90 MATCH_STATUS_TBD = 4
91
92 generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
93
94 generator_wants_static_library_dependencies_adjusted = False
95
96 generator_default_variables = {}
97 for 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
106 for 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
123 def _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
130 def _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
151 def _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("!!!") 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
175 def _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
180 def _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
189 def _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
218 class 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
254 class 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
289 def _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
319 def _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
330 def _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
340 def _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 = (
382 target_type == "executable" or target_type == "shared_library"
383 )
384
385 build_file = gyp.common.ParseQualifiedTarget(target_name)[0]
386 if build_file not in build_file_in_files:
387 build_file_in_files[build_file] = _WasBuildFileModified(
388 build_file, data, files, toplevel_dir
389 )
390
391 if build_file in build_files:
392 build_file_targets.add(target)
393
394 # If a build file (or any of its included files) is modified we assume all
395 # targets in the file are modified.
396 if build_file_in_files[build_file]:
397 print("matching target from modified build file", target_name)
398 target.match_status = MATCH_STATUS_MATCHES
399 matching_targets.append(target)
400 else:
401 sources = _ExtractSources(
402 target_name, target_dicts[target_name], toplevel_dir
403 )
404 for source in sources:
405 if _ToGypPath(os.path.normpath(source)) in files:
406 print("target", target_name, "matches", source)
407 target.match_status = MATCH_STATUS_MATCHES
408 matching_targets.append(target)
409 break
410
411 # Add dependencies to visit as well as updating back pointers for deps.
412 for dep in target_dicts[target_name].get("dependencies", []):
413 targets_to_visit.append(dep)
414
415 created_dep_target, dep_target = _GetOrCreateTargetByName(
416 name_to_target, dep
417 )
418 if not created_dep_target:
419 roots.discard(dep_target)
420
421 target.deps.add(dep_target)
422 dep_target.back_deps.add(target)
423
424 return name_to_target, matching_targets, roots & build_file_targets
425
426
427 def _GetUnqualifiedToTargetMapping(all_targets, to_find):
428 """Returns a tuple of the following:
429 . mapping (dictionary) from unqualified name to Target for all the
430 Targets in |to_find|.
431 . any target names not found. If this is empty all targets were found."""
432 result = {}
433 if not to_find:
434 return {}, []
435 to_find = set(to_find)
436 for target_name in all_targets.keys():
437 extracted = gyp.common.ParseQualifiedTarget(target_name)
438 if len(extracted) > 1 and extracted[1] in to_find:
439 to_find.remove(extracted[1])
440 result[extracted[1]] = all_targets[target_name]
441 if not to_find:
442 return result, []
443 return result, [x for x in to_find]
444
445
446 def _DoesTargetDependOnMatchingTargets(target):
447 """Returns true if |target| or any of its dependencies is one of the
448 targets containing the files supplied as input to analyzer. This updates
449 |matches| of the Targets as it recurses.
450 target: the Target to look for."""
451 if target.match_status == MATCH_STATUS_DOESNT_MATCH:
452 return False
453 if (
454 target.match_status == MATCH_STATUS_MATCHES
455 or target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY
456 ):
457 return True
458 for dep in target.deps:
459 if _DoesTargetDependOnMatchingTargets(dep):
460 target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY
461 print("\t", target.name, "matches by dep", dep.name)
462 return True
463 target.match_status = MATCH_STATUS_DOESNT_MATCH
464 return False
465
466
467 def _GetTargetsDependingOnMatchingTargets(possible_targets):
468 """Returns the list of Targets in |possible_targets| that depend (either
469 directly on indirectly) on at least one of the targets containing the files
470 supplied as input to analyzer.
471 possible_targets: targets to search from."""
472 found = []
473 print("Targets that matched by dependency:")
474 for target in possible_targets:
475 if _DoesTargetDependOnMatchingTargets(target):
476 found.append(target)
477 return found
478
479
480 def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
481 """Recurses through all targets that depend on |target|, adding all targets
482 that need to be built (and are in |roots|) to |result|.
483 roots: set of root targets.
484 add_if_no_ancestor: If true and there are no ancestors of |target| then add
485 |target| to |result|. |target| must still be in |roots|.
486 result: targets that need to be built are added here."""
487 if target.visited:
488 return
489
490 target.visited = True
491 target.in_roots = target in roots
492
493 for back_dep_target in target.back_deps:
494 _AddCompileTargets(back_dep_target, roots, False, result)
495 target.added_to_compile_targets |= back_dep_target.added_to_compile_targets
496 target.in_roots |= back_dep_target.in_roots
497 target.is_or_has_linked_ancestor |= back_dep_target.is_or_has_linked_ancestor
498
499 # Always add 'executable' targets. Even though they may be built by other
500 # targets that depend upon them it makes detection of what is going to be
501 # built easier.
502 # And always add static_libraries that have no dependencies on them from
503 # linkables. This is necessary as the other dependencies on them may be
504 # static libraries themselves, which are not compile time dependencies.
505 if target.in_roots and (
506 target.is_executable
507 or (
508 not target.added_to_compile_targets
509 and (add_if_no_ancestor or target.requires_build)
510 )
511 or (
512 target.is_static_library
513 and add_if_no_ancestor
514 and not target.is_or_has_linked_ancestor
515 )
516 ):
517 print(
518 "\t\tadding to compile targets",
519 target.name,
520 "executable",
521 target.is_executable,
522 "added_to_compile_targets",
523 target.added_to_compile_targets,
524 "add_if_no_ancestor",
525 add_if_no_ancestor,
526 "requires_build",
527 target.requires_build,
528 "is_static_library",
529 target.is_static_library,
530 "is_or_has_linked_ancestor",
531 target.is_or_has_linked_ancestor,
532 )
533 result.add(target)
534 target.added_to_compile_targets = True
535
536
537 def _GetCompileTargets(matching_targets, supplied_targets):
538 """Returns the set of Targets that require a build.
539 matching_targets: targets that changed and need to be built.
540 supplied_targets: set of targets supplied to analyzer to search from."""
541 result = set()
542 for target in matching_targets:
543 print("finding compile targets for match", target.name)
544 _AddCompileTargets(target, supplied_targets, True, result)
545 return result
546
547
548 def _WriteOutput(params, **values):
549 """Writes the output, either to stdout or a file is specified."""
550 if "error" in values:
551 print("Error:", values["error"])
552 if "status" in values:
553 print(values["status"])
554 if "targets" in values:
555 values["targets"].sort()
556 print("Supplied targets that depend on changed files:")
557 for target in values["targets"]:
558 print("\t", target)
559 if "invalid_targets" in values:
560 values["invalid_targets"].sort()
561 print("The following targets were not found:")
562 for target in values["invalid_targets"]:
563 print("\t", target)
564 if "build_targets" in values:
565 values["build_targets"].sort()
566 print("Targets that require a build:")
567 for target in values["build_targets"]:
568 print("\t", target)
569 if "compile_targets" in values:
570 values["compile_targets"].sort()
571 print("Targets that need to be built:")
572 for target in values["compile_targets"]:
573 print("\t", target)
574 if "test_targets" in values:
575 values["test_targets"].sort()
576 print("Test targets:")
577 for target in values["test_targets"]:
578 print("\t", target)
579
580 output_path = params.get("generator_flags", {}).get("analyzer_output_path", None)
581 if not output_path:
582 print(json.dumps(values))
583 return
584 try:
585 f = open(output_path, "w")
586 f.write(json.dumps(values) + "\n")
587 f.close()
588 except OSError as e:
589 print("Error writing to output file", output_path, str(e))
590
591
592 def _WasGypIncludeFileModified(params, files):
593 """Returns true if one of the files in |files| is in the set of included
594 files."""
595 if params["options"].includes:
596 for include in params["options"].includes:
597 if _ToGypPath(os.path.normpath(include)) in files:
598 print("Include file modified, assuming all changed", include)
599 return True
600 return False
601
602
603 def _NamesNotIn(names, mapping):
604 """Returns a list of the values in |names| that are not in |mapping|."""
605 return [name for name in names if name not in mapping]
606
607
608 def _LookupTargets(names, mapping):
609 """Returns a list of the mapping[name] for each value in |names| that is in
610 |mapping|."""
611 return [mapping[name] for name in names if name in mapping]
612
613
614 def CalculateVariables(default_variables, params):
615 """Calculate additional variables for use in the build (called by gyp)."""
616 flavor = gyp.common.GetFlavor(params)
617 if flavor == "mac":
618 default_variables.setdefault("OS", "mac")
619 elif flavor == "win":
620 default_variables.setdefault("OS", "win")
621 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
622 else:
623 operating_system = flavor
624 if flavor == "android":
625 operating_system = "linux" # Keep this legacy behavior for now.
626 default_variables.setdefault("OS", operating_system)
627
628
629 class TargetCalculator:
630 """Calculates the matching test_targets and matching compile_targets."""
631
632 def __init__(
633 self,
634 files,
635 additional_compile_target_names,
636 test_target_names,
637 data,
638 target_list,
639 target_dicts,
640 toplevel_dir,
641 build_files,
642 ):
643 self._additional_compile_target_names = set(additional_compile_target_names)
644 self._test_target_names = set(test_target_names)
645 (
646 self._name_to_target,
647 self._changed_targets,
648 self._root_targets,
649 ) = _GenerateTargets(
650 data, target_list, target_dicts, toplevel_dir, frozenset(files), build_files
651 )
652 (
653 self._unqualified_mapping,
654 self.invalid_targets,
655 ) = _GetUnqualifiedToTargetMapping(
656 self._name_to_target, self._supplied_target_names_no_all()
657 )
658
659 def _supplied_target_names(self):
660 return self._additional_compile_target_names | self._test_target_names
661
662 def _supplied_target_names_no_all(self):
663 """Returns the supplied test targets without 'all'."""
664 result = self._supplied_target_names()
665 result.discard("all")
666 return result
667
668 def is_build_impacted(self):
669 """Returns true if the supplied files impact the build at all."""
670 return self._changed_targets
671
672 def find_matching_test_target_names(self):
673 """Returns the set of output test targets."""
674 assert self.is_build_impacted()
675 # Find the test targets first. 'all' is special cased to mean all the
676 # root targets. To deal with all the supplied |test_targets| are expanded
677 # to include the root targets during lookup. If any of the root targets
678 # match, we remove it and replace it with 'all'.
679 test_target_names_no_all = set(self._test_target_names)
680 test_target_names_no_all.discard("all")
681 test_targets_no_all = _LookupTargets(
682 test_target_names_no_all, self._unqualified_mapping
683 )
684 test_target_names_contains_all = "all" in self._test_target_names
685 if test_target_names_contains_all:
686 test_targets = [
687 x for x in (set(test_targets_no_all) | set(self._root_targets))
688 ]
689 else:
690 test_targets = [x for x in test_targets_no_all]
691 print("supplied test_targets")
692 for target_name in self._test_target_names:
693 print("\t", target_name)
694 print("found test_targets")
695 for target in test_targets:
696 print("\t", target.name)
697 print("searching for matching test targets")
698 matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets)
699 matching_test_targets_contains_all = test_target_names_contains_all and set(
700 matching_test_targets
701 ) & set(self._root_targets)
702 if matching_test_targets_contains_all:
703 # Remove any of the targets for all that were not explicitly supplied,
704 # 'all' is subsequentely added to the matching names below.
705 matching_test_targets = [
706 x for x in (set(matching_test_targets) & set(test_targets_no_all))
707 ]
708 print("matched test_targets")
709 for target in matching_test_targets:
710 print("\t", target.name)
711 matching_target_names = [
712 gyp.common.ParseQualifiedTarget(target.name)[1]
713 for target in matching_test_targets
714 ]
715 if matching_test_targets_contains_all:
716 matching_target_names.append("all")
717 print("\tall")
718 return matching_target_names
719
720 def find_matching_compile_target_names(self):
721 """Returns the set of output compile targets."""
722 assert self.is_build_impacted()
723 # Compile targets are found by searching up from changed targets.
724 # Reset the visited status for _GetBuildTargets.
725 for target in self._name_to_target.values():
726 target.visited = False
727
728 supplied_targets = _LookupTargets(
729 self._supplied_target_names_no_all(), self._unqualified_mapping
730 )
731 if "all" in self._supplied_target_names():
732 supplied_targets = [
733 x for x in (set(supplied_targets) | set(self._root_targets))
734 ]
735 print("Supplied test_targets & compile_targets")
736 for target in supplied_targets:
737 print("\t", target.name)
738 print("Finding compile targets")
739 compile_targets = _GetCompileTargets(self._changed_targets, supplied_targets)
740 return [
741 gyp.common.ParseQualifiedTarget(target.name)[1]
742 for target in compile_targets
743 ]
744
745
746 def GenerateOutput(target_list, target_dicts, data, params):
747 """Called by gyp as the final stage. Outputs results."""
748 config = Config()
749 try:
750 config.Init(params)
751
752 if not config.files:
753 raise Exception(
754 "Must specify files to analyze via config_path generator " "flag"
755 )
756
757 toplevel_dir = _ToGypPath(os.path.abspath(params["options"].toplevel_dir))
758 if debug:
759 print("toplevel_dir", toplevel_dir)
760
761 if _WasGypIncludeFileModified(params, config.files):
762 result_dict = {
763 "status": all_changed_string,
764 "test_targets": list(config.test_target_names),
765 "compile_targets": list(
766 config.additional_compile_target_names | config.test_target_names
767 ),
768 }
769 _WriteOutput(params, **result_dict)
770 return
771
772 calculator = TargetCalculator(
773 config.files,
774 config.additional_compile_target_names,
775 config.test_target_names,
776 data,
777 target_list,
778 target_dicts,
779 toplevel_dir,
780 params["build_files"],
781 )
782 if not calculator.is_build_impacted():
783 result_dict = {
784 "status": no_dependency_string,
785 "test_targets": [],
786 "compile_targets": [],
787 }
788 if calculator.invalid_targets:
789 result_dict["invalid_targets"] = calculator.invalid_targets
790 _WriteOutput(params, **result_dict)
791 return
792
793 test_target_names = calculator.find_matching_test_target_names()
794 compile_target_names = calculator.find_matching_compile_target_names()
795 found_at_least_one_target = compile_target_names or test_target_names
796 result_dict = {
797 "test_targets": test_target_names,
798 "status": found_dependency_string
799 if found_at_least_one_target
800 else no_dependency_string,
801 "compile_targets": list(set(compile_target_names) | set(test_target_names)),
802 }
803 if calculator.invalid_targets:
804 result_dict["invalid_targets"] = calculator.invalid_targets
805 _WriteOutput(params, **result_dict)
806
807 except Exception as e:
808 _WriteOutput(params, error=str(e))