"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))