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