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 65 66import gyp.common 67import json 68import os 69import posixpath 70 71debug = False 72 73found_dependency_string = "Found dependency" 74no_dependency_string = "No dependencies" 75# Status when it should be assumed that everything has changed. 76all_changed_string = "Found dependency (all)" 77 78# MatchStatus is used indicate if and how a target depends upon the supplied 79# sources. 80# The target's sources contain one of the supplied paths. 81MATCH_STATUS_MATCHES = 1 82# The target has a dependency on another target that contains one of the 83# supplied paths. 84MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 85# The target's sources weren't in the supplied paths and none of the target's 86# dependencies depend upon a target that matched. 87MATCH_STATUS_DOESNT_MATCH = 3 88# The target doesn't contain the source, but the dependent targets have not yet 89# been visited to determine a more specific status yet. 90MATCH_STATUS_TBD = 4 91 92generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() 93 94generator_wants_static_library_dependencies_adjusted = False 95 96generator_default_variables = {} 97for dirname in [ 98 "INTERMEDIATE_DIR", 99 "SHARED_INTERMEDIATE_DIR", 100 "PRODUCT_DIR", 101 "LIB_DIR", 102 "SHARED_LIB_DIR", 103]: 104 generator_default_variables[dirname] = "!!!" 105 106for unused in [ 107 "RULE_INPUT_PATH", 108 "RULE_INPUT_ROOT", 109 "RULE_INPUT_NAME", 110 "RULE_INPUT_DIRNAME", 111 "RULE_INPUT_EXT", 112 "EXECUTABLE_PREFIX", 113 "EXECUTABLE_SUFFIX", 114 "STATIC_LIB_PREFIX", 115 "STATIC_LIB_SUFFIX", 116 "SHARED_LIB_PREFIX", 117 "SHARED_LIB_SUFFIX", 118 "CONFIGURATION_NAME", 119]: 120 generator_default_variables[unused] = "" 121 122 123def _ToGypPath(path): 124 """Converts a path to the format used by gyp.""" 125 if os.sep == "\\" and os.altsep == "/": 126 return path.replace("\\", "/") 127 return path 128 129 130def _ResolveParent(path, base_path_components): 131 """Resolves |path|, which starts with at least one '../'. Returns an empty 132 string if the path shouldn't be considered. See _AddSources() for a 133 description of |base_path_components|.""" 134 depth = 0 135 while path.startswith("../"): 136 depth += 1 137 path = path[3:] 138 # Relative includes may go outside the source tree. For example, an action may 139 # have inputs in /usr/include, which are not in the source tree. 140 if depth > len(base_path_components): 141 return "" 142 if depth == len(base_path_components): 143 return path 144 return ( 145 "/".join(base_path_components[0 : len(base_path_components) - depth]) 146 + "/" 147 + path 148 ) 149 150 151def _AddSources(sources, base_path, base_path_components, result): 152 """Extracts valid sources from |sources| and adds them to |result|. Each 153 source file is relative to |base_path|, but may contain '..'. To make 154 resolving '..' easier |base_path_components| contains each of the 155 directories in |base_path|. Additionally each source may contain variables. 156 Such sources are ignored as it is assumed dependencies on them are expressed 157 and tracked in some other means.""" 158 # NOTE: gyp paths are always posix style. 159 for source in sources: 160 if not len(source) or source.startswith("!!!") or source.startswith("$"): 161 continue 162 # variable expansion may lead to //. 163 org_source = source 164 source = source[0] + source[1:].replace("//", "/") 165 if source.startswith("../"): 166 source = _ResolveParent(source, base_path_components) 167 if len(source): 168 result.append(source) 169 continue 170 result.append(base_path + source) 171 if debug: 172 print("AddSource", org_source, result[len(result) - 1]) 173 174 175def _ExtractSourcesFromAction(action, base_path, base_path_components, results): 176 if "inputs" in action: 177 _AddSources(action["inputs"], base_path, base_path_components, results) 178 179 180def _ToLocalPath(toplevel_dir, path): 181 """Converts |path| to a path relative to |toplevel_dir|.""" 182 if path == toplevel_dir: 183 return "" 184 if path.startswith(toplevel_dir + "/"): 185 return path[len(toplevel_dir) + len("/") :] 186 return path 187 188 189def _ExtractSources(target, target_dict, toplevel_dir): 190 # |target| is either absolute or relative and in the format of the OS. Gyp 191 # source paths are always posix. Convert |target| to a posix path relative to 192 # |toplevel_dir_|. This is done to make it easy to build source paths. 193 base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target))) 194 base_path_components = base_path.split("/") 195 196 # Add a trailing '/' so that _AddSources() can easily build paths. 197 if len(base_path): 198 base_path += "/" 199 200 if debug: 201 print("ExtractSources", target, base_path) 202 203 results = [] 204 if "sources" in target_dict: 205 _AddSources(target_dict["sources"], base_path, base_path_components, results) 206 # Include the inputs from any actions. Any changes to these affect the 207 # resulting output. 208 if "actions" in target_dict: 209 for action in target_dict["actions"]: 210 _ExtractSourcesFromAction(action, base_path, base_path_components, results) 211 if "rules" in target_dict: 212 for rule in target_dict["rules"]: 213 _ExtractSourcesFromAction(rule, base_path, base_path_components, results) 214 215 return results 216 217 218class Target: 219 """Holds information about a particular target: 220 deps: set of Targets this Target depends upon. This is not recursive, only the 221 direct dependent Targets. 222 match_status: one of the MatchStatus values. 223 back_deps: set of Targets that have a dependency on this Target. 224 visited: used during iteration to indicate whether we've visited this target. 225 This is used for two iterations, once in building the set of Targets and 226 again in _GetBuildTargets(). 227 name: fully qualified name of the target. 228 requires_build: True if the target type is such that it needs to be built. 229 See _DoesTargetTypeRequireBuild for details. 230 added_to_compile_targets: used when determining if the target was added to the 231 set of targets that needs to be built. 232 in_roots: true if this target is a descendant of one of the root nodes. 233 is_executable: true if the type of target is executable. 234 is_static_library: true if the type of target is static_library. 235 is_or_has_linked_ancestor: true if the target does a link (eg executable), or 236 if there is a target in back_deps that does a link.""" 237 238 def __init__(self, name): 239 self.deps = set() 240 self.match_status = MATCH_STATUS_TBD 241 self.back_deps = set() 242 self.name = name 243 # TODO(sky): I don't like hanging this off Target. This state is specific 244 # to certain functions and should be isolated there. 245 self.visited = False 246 self.requires_build = False 247 self.added_to_compile_targets = False 248 self.in_roots = False 249 self.is_executable = False 250 self.is_static_library = False 251 self.is_or_has_linked_ancestor = False 252 253 254class Config: 255 """Details what we're looking for 256 files: set of files to search for 257 targets: see file description for details.""" 258 259 def __init__(self): 260 self.files = [] 261 self.targets = set() 262 self.additional_compile_target_names = set() 263 self.test_target_names = set() 264 265 def Init(self, params): 266 """Initializes Config. This is a separate method as it raises an exception 267 if there is a parse error.""" 268 generator_flags = params.get("generator_flags", {}) 269 config_path = generator_flags.get("config_path", None) 270 if not config_path: 271 return 272 try: 273 f = open(config_path) 274 config = json.load(f) 275 f.close() 276 except OSError: 277 raise Exception("Unable to open file " + config_path) 278 except ValueError as e: 279 raise Exception("Unable to parse config file " + config_path + str(e)) 280 if not isinstance(config, dict): 281 raise Exception("config_path must be a JSON file containing a dictionary") 282 self.files = config.get("files", []) 283 self.additional_compile_target_names = set( 284 config.get("additional_compile_targets", []) 285 ) 286 self.test_target_names = set(config.get("test_targets", [])) 287 288 289def _WasBuildFileModified(build_file, data, files, toplevel_dir): 290 """Returns true if the build file |build_file| is either in |files| or 291 one of the files included by |build_file| is in |files|. |toplevel_dir| is 292 the root of the source tree.""" 293 if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files: 294 if debug: 295 print("gyp file modified", build_file) 296 return True 297 298 # First element of included_files is the file itself. 299 if len(data[build_file]["included_files"]) <= 1: 300 return False 301 302 for include_file in data[build_file]["included_files"][1:]: 303 # |included_files| are relative to the directory of the |build_file|. 304 rel_include_file = _ToGypPath( 305 gyp.common.UnrelativePath(include_file, build_file) 306 ) 307 if _ToLocalPath(toplevel_dir, rel_include_file) in files: 308 if debug: 309 print( 310 "included gyp file modified, gyp_file=", 311 build_file, 312 "included file=", 313 rel_include_file, 314 ) 315 return True 316 return False 317 318 319def _GetOrCreateTargetByName(targets, target_name): 320 """Creates or returns the Target at targets[target_name]. If there is no 321 Target for |target_name| one is created. Returns a tuple of whether a new 322 Target was created and the Target.""" 323 if target_name in targets: 324 return False, targets[target_name] 325 target = Target(target_name) 326 targets[target_name] = target 327 return True, target 328 329 330def _DoesTargetTypeRequireBuild(target_dict): 331 """Returns true if the target type is such that it needs to be built.""" 332 # If a 'none' target has rules or actions we assume it requires a build. 333 return bool( 334 target_dict["type"] != "none" 335 or target_dict.get("actions") 336 or target_dict.get("rules") 337 ) 338 339 340def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build_files): 341 """Returns a tuple of the following: 342 . A dictionary mapping from fully qualified name to Target. 343 . A list of the targets that have a source file in |files|. 344 . Targets that constitute the 'all' target. See description at top of file 345 for details on the 'all' target. 346 This sets the |match_status| of the targets that contain any of the source 347 files in |files| to MATCH_STATUS_MATCHES. 348 |toplevel_dir| is the root of the source tree.""" 349 # Maps from target name to Target. 350 name_to_target = {} 351 352 # Targets that matched. 353 matching_targets = [] 354 355 # Queue of targets to visit. 356 targets_to_visit = target_list[:] 357 358 # Maps from build file to a boolean indicating whether the build file is in 359 # |files|. 360 build_file_in_files = {} 361 362 # Root targets across all files. 363 roots = set() 364 365 # Set of Targets in |build_files|. 366 build_file_targets = set() 367 368 while len(targets_to_visit) > 0: 369 target_name = targets_to_visit.pop() 370 created_target, target = _GetOrCreateTargetByName(name_to_target, target_name) 371 if created_target: 372 roots.add(target) 373 elif target.visited: 374 continue 375 376 target.visited = True 377 target.requires_build = _DoesTargetTypeRequireBuild(target_dicts[target_name]) 378 target_type = target_dicts[target_name]["type"] 379 target.is_executable = target_type == "executable" 380 target.is_static_library = target_type == "static_library" 381 target.is_or_has_linked_ancestor = ( 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 427def _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 446def _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 467def _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 480def _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 537def _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 548def _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 592def _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 603def _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 608def _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 614def 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 629class 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 746def 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)) 809