• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
65from __future__ import print_function
66
67import gyp.common
68import json
69import os
70import posixpath
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 = {}
98for dirname in [
99    "INTERMEDIATE_DIR",
100    "SHARED_INTERMEDIATE_DIR",
101    "PRODUCT_DIR",
102    "LIB_DIR",
103    "SHARED_LIB_DIR",
104]:
105    generator_default_variables[dirname] = "!!!"
106
107for unused in [
108    "RULE_INPUT_PATH",
109    "RULE_INPUT_ROOT",
110    "RULE_INPUT_NAME",
111    "RULE_INPUT_DIRNAME",
112    "RULE_INPUT_EXT",
113    "EXECUTABLE_PREFIX",
114    "EXECUTABLE_SUFFIX",
115    "STATIC_LIB_PREFIX",
116    "STATIC_LIB_SUFFIX",
117    "SHARED_LIB_PREFIX",
118    "SHARED_LIB_SUFFIX",
119    "CONFIGURATION_NAME",
120]:
121    generator_default_variables[unused] = ""
122
123
124def _ToGypPath(path):
125    """Converts a path to the format used by gyp."""
126    if os.sep == "\\" and os.altsep == "/":
127        return path.replace("\\", "/")
128    return path
129
130
131def _ResolveParent(path, base_path_components):
132    """Resolves |path|, which starts with at least one '../'. Returns an empty
133  string if the path shouldn't be considered. See _AddSources() for a
134  description of |base_path_components|."""
135    depth = 0
136    while path.startswith("../"):
137        depth += 1
138        path = path[3:]
139    # Relative includes may go outside the source tree. For example, an action may
140    # have inputs in /usr/include, which are not in the source tree.
141    if depth > len(base_path_components):
142        return ""
143    if depth == len(base_path_components):
144        return path
145    return (
146        "/".join(base_path_components[0 : len(base_path_components) - depth])
147        + "/"
148        + path
149    )
150
151
152def _AddSources(sources, base_path, base_path_components, result):
153    """Extracts valid sources from |sources| and adds them to |result|. Each
154  source file is relative to |base_path|, but may contain '..'. To make
155  resolving '..' easier |base_path_components| contains each of the
156  directories in |base_path|. Additionally each source may contain variables.
157  Such sources are ignored as it is assumed dependencies on them are expressed
158  and tracked in some other means."""
159    # NOTE: gyp paths are always posix style.
160    for source in sources:
161        if not len(source) or source.startswith("!!!") or source.startswith("$"):
162            continue
163        # variable expansion may lead to //.
164        org_source = source
165        source = source[0] + source[1:].replace("//", "/")
166        if source.startswith("../"):
167            source = _ResolveParent(source, base_path_components)
168            if len(source):
169                result.append(source)
170            continue
171        result.append(base_path + source)
172        if debug:
173            print("AddSource", org_source, result[len(result) - 1])
174
175
176def _ExtractSourcesFromAction(action, base_path, base_path_components, results):
177    if "inputs" in action:
178        _AddSources(action["inputs"], base_path, base_path_components, results)
179
180
181def _ToLocalPath(toplevel_dir, path):
182    """Converts |path| to a path relative to |toplevel_dir|."""
183    if path == toplevel_dir:
184        return ""
185    if path.startswith(toplevel_dir + "/"):
186        return path[len(toplevel_dir) + len("/") :]
187    return path
188
189
190def _ExtractSources(target, target_dict, toplevel_dir):
191    # |target| is either absolute or relative and in the format of the OS. Gyp
192    # source paths are always posix. Convert |target| to a posix path relative to
193    # |toplevel_dir_|. This is done to make it easy to build source paths.
194    base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target)))
195    base_path_components = base_path.split("/")
196
197    # Add a trailing '/' so that _AddSources() can easily build paths.
198    if len(base_path):
199        base_path += "/"
200
201    if debug:
202        print("ExtractSources", target, base_path)
203
204    results = []
205    if "sources" in target_dict:
206        _AddSources(target_dict["sources"], base_path, base_path_components, results)
207    # Include the inputs from any actions. Any changes to these affect the
208    # resulting output.
209    if "actions" in target_dict:
210        for action in target_dict["actions"]:
211            _ExtractSourcesFromAction(action, base_path, base_path_components, results)
212    if "rules" in target_dict:
213        for rule in target_dict["rules"]:
214            _ExtractSourcesFromAction(rule, base_path, base_path_components, results)
215
216    return results
217
218
219class Target(object):
220    """Holds information about a particular target:
221  deps: set of Targets this Target depends upon. This is not recursive, only the
222    direct dependent Targets.
223  match_status: one of the MatchStatus values.
224  back_deps: set of Targets that have a dependency on this Target.
225  visited: used during iteration to indicate whether we've visited this target.
226    This is used for two iterations, once in building the set of Targets and
227    again in _GetBuildTargets().
228  name: fully qualified name of the target.
229  requires_build: True if the target type is such that it needs to be built.
230    See _DoesTargetTypeRequireBuild for details.
231  added_to_compile_targets: used when determining if the target was added to the
232    set of targets that needs to be built.
233  in_roots: true if this target is a descendant of one of the root nodes.
234  is_executable: true if the type of target is executable.
235  is_static_library: true if the type of target is static_library.
236  is_or_has_linked_ancestor: true if the target does a link (eg executable), or
237    if there is a target in back_deps that does a link."""
238
239    def __init__(self, name):
240        self.deps = set()
241        self.match_status = MATCH_STATUS_TBD
242        self.back_deps = set()
243        self.name = name
244        # TODO(sky): I don't like hanging this off Target. This state is specific
245        # to certain functions and should be isolated there.
246        self.visited = False
247        self.requires_build = False
248        self.added_to_compile_targets = False
249        self.in_roots = False
250        self.is_executable = False
251        self.is_static_library = False
252        self.is_or_has_linked_ancestor = False
253
254
255class Config(object):
256    """Details what we're looking for
257  files: set of files to search for
258  targets: see file description for details."""
259
260    def __init__(self):
261        self.files = []
262        self.targets = set()
263        self.additional_compile_target_names = set()
264        self.test_target_names = set()
265
266    def Init(self, params):
267        """Initializes Config. This is a separate method as it raises an exception
268    if there is a parse error."""
269        generator_flags = params.get("generator_flags", {})
270        config_path = generator_flags.get("config_path", None)
271        if not config_path:
272            return
273        try:
274            f = open(config_path, "r")
275            config = json.load(f)
276            f.close()
277        except IOError:
278            raise Exception("Unable to open file " + config_path)
279        except ValueError as e:
280            raise Exception("Unable to parse config file " + config_path + str(e))
281        if not isinstance(config, dict):
282            raise Exception("config_path must be a JSON file containing a dictionary")
283        self.files = config.get("files", [])
284        self.additional_compile_target_names = set(
285            config.get("additional_compile_targets", [])
286        )
287        self.test_target_names = set(config.get("test_targets", []))
288
289
290def _WasBuildFileModified(build_file, data, files, toplevel_dir):
291    """Returns true if the build file |build_file| is either in |files| or
292  one of the files included by |build_file| is in |files|. |toplevel_dir| is
293  the root of the source tree."""
294    if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files:
295        if debug:
296            print("gyp file modified", build_file)
297        return True
298
299    # First element of included_files is the file itself.
300    if len(data[build_file]["included_files"]) <= 1:
301        return False
302
303    for include_file in data[build_file]["included_files"][1:]:
304        # |included_files| are relative to the directory of the |build_file|.
305        rel_include_file = _ToGypPath(
306            gyp.common.UnrelativePath(include_file, build_file)
307        )
308        if _ToLocalPath(toplevel_dir, rel_include_file) in files:
309            if debug:
310                print(
311                    "included gyp file modified, gyp_file=",
312                    build_file,
313                    "included file=",
314                    rel_include_file,
315                )
316            return True
317    return False
318
319
320def _GetOrCreateTargetByName(targets, target_name):
321    """Creates or returns the Target at targets[target_name]. If there is no
322  Target for |target_name| one is created. Returns a tuple of whether a new
323  Target was created and the Target."""
324    if target_name in targets:
325        return False, targets[target_name]
326    target = Target(target_name)
327    targets[target_name] = target
328    return True, target
329
330
331def _DoesTargetTypeRequireBuild(target_dict):
332    """Returns true if the target type is such that it needs to be built."""
333    # If a 'none' target has rules or actions we assume it requires a build.
334    return bool(
335        target_dict["type"] != "none"
336        or target_dict.get("actions")
337        or target_dict.get("rules")
338    )
339
340
341def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build_files):
342    """Returns a tuple of the following:
343  . A dictionary mapping from fully qualified name to Target.
344  . A list of the targets that have a source file in |files|.
345  . Targets that constitute the 'all' target. See description at top of file
346    for details on the 'all' target.
347  This sets the |match_status| of the targets that contain any of the source
348  files in |files| to MATCH_STATUS_MATCHES.
349  |toplevel_dir| is the root of the source tree."""
350    # Maps from target name to Target.
351    name_to_target = {}
352
353    # Targets that matched.
354    matching_targets = []
355
356    # Queue of targets to visit.
357    targets_to_visit = target_list[:]
358
359    # Maps from build file to a boolean indicating whether the build file is in
360    # |files|.
361    build_file_in_files = {}
362
363    # Root targets across all files.
364    roots = set()
365
366    # Set of Targets in |build_files|.
367    build_file_targets = set()
368
369    while len(targets_to_visit) > 0:
370        target_name = targets_to_visit.pop()
371        created_target, target = _GetOrCreateTargetByName(name_to_target, target_name)
372        if created_target:
373            roots.add(target)
374        elif target.visited:
375            continue
376
377        target.visited = True
378        target.requires_build = _DoesTargetTypeRequireBuild(target_dicts[target_name])
379        target_type = target_dicts[target_name]["type"]
380        target.is_executable = target_type == "executable"
381        target.is_static_library = target_type == "static_library"
382        target.is_or_has_linked_ancestor = (
383            target_type == "executable" or target_type == "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.keys():
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, [x for x in 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 (
455        target.match_status == MATCH_STATUS_MATCHES
456        or target.match_status == 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 IOError 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(object):
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 = [
688                x for x in (set(test_targets_no_all) | set(self._root_targets))
689            ]
690        else:
691            test_targets = [x for x in test_targets_no_all]
692        print("supplied test_targets")
693        for target_name in self._test_target_names:
694            print("\t", target_name)
695        print("found test_targets")
696        for target in test_targets:
697            print("\t", target.name)
698        print("searching for matching test targets")
699        matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets)
700        matching_test_targets_contains_all = test_target_names_contains_all and set(
701            matching_test_targets
702        ) & set(self._root_targets)
703        if matching_test_targets_contains_all:
704            # Remove any of the targets for all that were not explicitly supplied,
705            # 'all' is subsequentely added to the matching names below.
706            matching_test_targets = [
707                x for x in (set(matching_test_targets) & set(test_targets_no_all))
708            ]
709        print("matched test_targets")
710        for target in matching_test_targets:
711            print("\t", target.name)
712        matching_target_names = [
713            gyp.common.ParseQualifiedTarget(target.name)[1]
714            for target in matching_test_targets
715        ]
716        if matching_test_targets_contains_all:
717            matching_target_names.append("all")
718            print("\tall")
719        return matching_target_names
720
721    def find_matching_compile_target_names(self):
722        """Returns the set of output compile targets."""
723        assert self.is_build_impacted()
724        # Compile targets are found by searching up from changed targets.
725        # Reset the visited status for _GetBuildTargets.
726        for target in self._name_to_target.values():
727            target.visited = False
728
729        supplied_targets = _LookupTargets(
730            self._supplied_target_names_no_all(), self._unqualified_mapping
731        )
732        if "all" in self._supplied_target_names():
733            supplied_targets = [
734                x for x in (set(supplied_targets) | set(self._root_targets))
735            ]
736        print("Supplied test_targets & compile_targets")
737        for target in supplied_targets:
738            print("\t", target.name)
739        print("Finding compile targets")
740        compile_targets = _GetCompileTargets(self._changed_targets, supplied_targets)
741        return [
742            gyp.common.ParseQualifiedTarget(target.name)[1]
743            for target in compile_targets
744        ]
745
746
747def GenerateOutput(target_list, target_dicts, data, params):
748    """Called by gyp as the final stage. Outputs results."""
749    config = Config()
750    try:
751        config.Init(params)
752
753        if not config.files:
754            raise Exception(
755                "Must specify files to analyze via config_path generator " "flag"
756            )
757
758        toplevel_dir = _ToGypPath(os.path.abspath(params["options"].toplevel_dir))
759        if debug:
760            print("toplevel_dir", toplevel_dir)
761
762        if _WasGypIncludeFileModified(params, config.files):
763            result_dict = {
764                "status": all_changed_string,
765                "test_targets": list(config.test_target_names),
766                "compile_targets": list(
767                    config.additional_compile_target_names | config.test_target_names
768                ),
769            }
770            _WriteOutput(params, **result_dict)
771            return
772
773        calculator = TargetCalculator(
774            config.files,
775            config.additional_compile_target_names,
776            config.test_target_names,
777            data,
778            target_list,
779            target_dicts,
780            toplevel_dir,
781            params["build_files"],
782        )
783        if not calculator.is_build_impacted():
784            result_dict = {
785                "status": no_dependency_string,
786                "test_targets": [],
787                "compile_targets": [],
788            }
789            if calculator.invalid_targets:
790                result_dict["invalid_targets"] = calculator.invalid_targets
791            _WriteOutput(params, **result_dict)
792            return
793
794        test_target_names = calculator.find_matching_test_target_names()
795        compile_target_names = calculator.find_matching_compile_target_names()
796        found_at_least_one_target = compile_target_names or test_target_names
797        result_dict = {
798            "test_targets": test_target_names,
799            "status": found_dependency_string
800            if found_at_least_one_target
801            else no_dependency_string,
802            "compile_targets": list(set(compile_target_names) | set(test_target_names)),
803        }
804        if calculator.invalid_targets:
805            result_dict["invalid_targets"] = calculator.invalid_targets
806        _WriteOutput(params, **result_dict)
807
808    except Exception as e:
809        _WriteOutput(params, error=str(e))
810