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 gyp.ninja_syntax as ninja_syntax 69import json 70import os 71import posixpath 72import sys 73 74debug = False 75 76found_dependency_string = 'Found dependency' 77no_dependency_string = 'No dependencies' 78# Status when it should be assumed that everything has changed. 79all_changed_string = 'Found dependency (all)' 80 81# MatchStatus is used indicate if and how a target depends upon the supplied 82# sources. 83# The target's sources contain one of the supplied paths. 84MATCH_STATUS_MATCHES = 1 85# The target has a dependency on another target that contains one of the 86# supplied paths. 87MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 88# The target's sources weren't in the supplied paths and none of the target's 89# dependencies depend upon a target that matched. 90MATCH_STATUS_DOESNT_MATCH = 3 91# The target doesn't contain the source, but the dependent targets have not yet 92# been visited to determine a more specific status yet. 93MATCH_STATUS_TBD = 4 94 95generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() 96 97generator_wants_static_library_dependencies_adjusted = False 98 99generator_default_variables = { 100} 101for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', 102 'LIB_DIR', 'SHARED_LIB_DIR']: 103 generator_default_variables[dirname] = '!!!' 104 105for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', 106 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', 107 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', 108 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', 109 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', 110 'CONFIGURATION_NAME']: 111 generator_default_variables[unused] = '' 112 113 114def _ToGypPath(path): 115 """Converts a path to the format used by gyp.""" 116 if os.sep == '\\' and os.altsep == '/': 117 return path.replace('\\', '/') 118 return path 119 120 121def _ResolveParent(path, base_path_components): 122 """Resolves |path|, which starts with at least one '../'. Returns an empty 123 string if the path shouldn't be considered. See _AddSources() for a 124 description of |base_path_components|.""" 125 depth = 0 126 while path.startswith('../'): 127 depth += 1 128 path = path[3:] 129 # Relative includes may go outside the source tree. For example, an action may 130 # have inputs in /usr/include, which are not in the source tree. 131 if depth > len(base_path_components): 132 return '' 133 if depth == len(base_path_components): 134 return path 135 return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \ 136 '/' + path 137 138 139def _AddSources(sources, base_path, base_path_components, result): 140 """Extracts valid sources from |sources| and adds them to |result|. Each 141 source file is relative to |base_path|, but may contain '..'. To make 142 resolving '..' easier |base_path_components| contains each of the 143 directories in |base_path|. Additionally each source may contain variables. 144 Such sources are ignored as it is assumed dependencies on them are expressed 145 and tracked in some other means.""" 146 # NOTE: gyp paths are always posix style. 147 for source in sources: 148 if not len(source) or source.startswith('!!!') or source.startswith('$'): 149 continue 150 # variable expansion may lead to //. 151 org_source = source 152 source = source[0] + source[1:].replace('//', '/') 153 if source.startswith('../'): 154 source = _ResolveParent(source, base_path_components) 155 if len(source): 156 result.append(source) 157 continue 158 result.append(base_path + source) 159 if debug: 160 print('AddSource', org_source, result[len(result) - 1]) 161 162 163def _ExtractSourcesFromAction(action, base_path, base_path_components, 164 results): 165 if 'inputs' in action: 166 _AddSources(action['inputs'], base_path, base_path_components, results) 167 168 169def _ToLocalPath(toplevel_dir, path): 170 """Converts |path| to a path relative to |toplevel_dir|.""" 171 if path == toplevel_dir: 172 return '' 173 if path.startswith(toplevel_dir + '/'): 174 return path[len(toplevel_dir) + len('/'):] 175 return path 176 177 178def _ExtractSources(target, target_dict, toplevel_dir): 179 # |target| is either absolute or relative and in the format of the OS. Gyp 180 # source paths are always posix. Convert |target| to a posix path relative to 181 # |toplevel_dir_|. This is done to make it easy to build source paths. 182 base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target))) 183 base_path_components = base_path.split('/') 184 185 # Add a trailing '/' so that _AddSources() can easily build paths. 186 if len(base_path): 187 base_path += '/' 188 189 if debug: 190 print('ExtractSources', target, base_path) 191 192 results = [] 193 if 'sources' in target_dict: 194 _AddSources(target_dict['sources'], base_path, base_path_components, 195 results) 196 # Include the inputs from any actions. Any changes to these affect the 197 # resulting output. 198 if 'actions' in target_dict: 199 for action in target_dict['actions']: 200 _ExtractSourcesFromAction(action, base_path, base_path_components, 201 results) 202 if 'rules' in target_dict: 203 for rule in target_dict['rules']: 204 _ExtractSourcesFromAction(rule, base_path, base_path_components, results) 205 206 return results 207 208 209class Target(object): 210 """Holds information about a particular target: 211 deps: set of Targets this Target depends upon. This is not recursive, only the 212 direct dependent Targets. 213 match_status: one of the MatchStatus values. 214 back_deps: set of Targets that have a dependency on this Target. 215 visited: used during iteration to indicate whether we've visited this target. 216 This is used for two iterations, once in building the set of Targets and 217 again in _GetBuildTargets(). 218 name: fully qualified name of the target. 219 requires_build: True if the target type is such that it needs to be built. 220 See _DoesTargetTypeRequireBuild for details. 221 added_to_compile_targets: used when determining if the target was added to the 222 set of targets that needs to be built. 223 in_roots: true if this target is a descendant of one of the root nodes. 224 is_executable: true if the type of target is executable. 225 is_static_library: true if the type of target is static_library. 226 is_or_has_linked_ancestor: true if the target does a link (eg executable), or 227 if there is a target in back_deps that does a link.""" 228 def __init__(self, name): 229 self.deps = set() 230 self.match_status = MATCH_STATUS_TBD 231 self.back_deps = set() 232 self.name = name 233 # TODO(sky): I don't like hanging this off Target. This state is specific 234 # to certain functions and should be isolated there. 235 self.visited = False 236 self.requires_build = False 237 self.added_to_compile_targets = False 238 self.in_roots = False 239 self.is_executable = False 240 self.is_static_library = False 241 self.is_or_has_linked_ancestor = False 242 243 244class Config(object): 245 """Details what we're looking for 246 files: set of files to search for 247 targets: see file description for details.""" 248 def __init__(self): 249 self.files = [] 250 self.targets = set() 251 self.additional_compile_target_names = set() 252 self.test_target_names = set() 253 254 def Init(self, params): 255 """Initializes Config. This is a separate method as it raises an exception 256 if there is a parse error.""" 257 generator_flags = params.get('generator_flags', {}) 258 config_path = generator_flags.get('config_path', None) 259 if not config_path: 260 return 261 try: 262 f = open(config_path, 'r') 263 config = json.load(f) 264 f.close() 265 except IOError: 266 raise Exception('Unable to open file ' + config_path) 267 except ValueError as e: 268 raise Exception('Unable to parse config file ' + config_path + str(e)) 269 if not isinstance(config, dict): 270 raise Exception('config_path must be a JSON file containing a dictionary') 271 self.files = config.get('files', []) 272 self.additional_compile_target_names = set( 273 config.get('additional_compile_targets', [])) 274 self.test_target_names = set(config.get('test_targets', [])) 275 276 277def _WasBuildFileModified(build_file, data, files, toplevel_dir): 278 """Returns true if the build file |build_file| is either in |files| or 279 one of the files included by |build_file| is in |files|. |toplevel_dir| is 280 the root of the source tree.""" 281 if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files: 282 if debug: 283 print('gyp file modified', build_file) 284 return True 285 286 # First element of included_files is the file itself. 287 if len(data[build_file]['included_files']) <= 1: 288 return False 289 290 for include_file in data[build_file]['included_files'][1:]: 291 # |included_files| are relative to the directory of the |build_file|. 292 rel_include_file = \ 293 _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) 294 if _ToLocalPath(toplevel_dir, rel_include_file) in files: 295 if debug: 296 print('included gyp file modified, gyp_file=', build_file, \ 297 'included file=', rel_include_file) 298 return True 299 return False 300 301 302def _GetOrCreateTargetByName(targets, target_name): 303 """Creates or returns the Target at targets[target_name]. If there is no 304 Target for |target_name| one is created. Returns a tuple of whether a new 305 Target was created and the Target.""" 306 if target_name in targets: 307 return False, targets[target_name] 308 target = Target(target_name) 309 targets[target_name] = target 310 return True, target 311 312 313def _DoesTargetTypeRequireBuild(target_dict): 314 """Returns true if the target type is such that it needs to be built.""" 315 # If a 'none' target has rules or actions we assume it requires a build. 316 return bool(target_dict['type'] != 'none' or 317 target_dict.get('actions') or target_dict.get('rules')) 318 319 320def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, 321 build_files): 322 """Returns a tuple of the following: 323 . A dictionary mapping from fully qualified name to Target. 324 . A list of the targets that have a source file in |files|. 325 . Targets that constitute the 'all' target. See description at top of file 326 for details on the 'all' target. 327 This sets the |match_status| of the targets that contain any of the source 328 files in |files| to MATCH_STATUS_MATCHES. 329 |toplevel_dir| is the root of the source tree.""" 330 # Maps from target name to Target. 331 name_to_target = {} 332 333 # Targets that matched. 334 matching_targets = [] 335 336 # Queue of targets to visit. 337 targets_to_visit = target_list[:] 338 339 # Maps from build file to a boolean indicating whether the build file is in 340 # |files|. 341 build_file_in_files = {} 342 343 # Root targets across all files. 344 roots = set() 345 346 # Set of Targets in |build_files|. 347 build_file_targets = set() 348 349 while len(targets_to_visit) > 0: 350 target_name = targets_to_visit.pop() 351 created_target, target = _GetOrCreateTargetByName(name_to_target, 352 target_name) 353 if created_target: 354 roots.add(target) 355 elif target.visited: 356 continue 357 358 target.visited = True 359 target.requires_build = _DoesTargetTypeRequireBuild( 360 target_dicts[target_name]) 361 target_type = target_dicts[target_name]['type'] 362 target.is_executable = target_type == 'executable' 363 target.is_static_library = target_type == 'static_library' 364 target.is_or_has_linked_ancestor = (target_type == 'executable' or 365 target_type == 'shared_library') 366 367 build_file = gyp.common.ParseQualifiedTarget(target_name)[0] 368 if not build_file in build_file_in_files: 369 build_file_in_files[build_file] = \ 370 _WasBuildFileModified(build_file, data, files, toplevel_dir) 371 372 if build_file in build_files: 373 build_file_targets.add(target) 374 375 # If a build file (or any of its included files) is modified we assume all 376 # targets in the file are modified. 377 if build_file_in_files[build_file]: 378 print('matching target from modified build file', target_name) 379 target.match_status = MATCH_STATUS_MATCHES 380 matching_targets.append(target) 381 else: 382 sources = _ExtractSources(target_name, target_dicts[target_name], 383 toplevel_dir) 384 for source in sources: 385 if _ToGypPath(os.path.normpath(source)) in files: 386 print('target', target_name, 'matches', source) 387 target.match_status = MATCH_STATUS_MATCHES 388 matching_targets.append(target) 389 break 390 391 # Add dependencies to visit as well as updating back pointers for deps. 392 for dep in target_dicts[target_name].get('dependencies', []): 393 targets_to_visit.append(dep) 394 395 created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target, 396 dep) 397 if not created_dep_target: 398 roots.discard(dep_target) 399 400 target.deps.add(dep_target) 401 dep_target.back_deps.add(target) 402 403 return name_to_target, matching_targets, roots & build_file_targets 404 405 406def _GetUnqualifiedToTargetMapping(all_targets, to_find): 407 """Returns a tuple of the following: 408 . mapping (dictionary) from unqualified name to Target for all the 409 Targets in |to_find|. 410 . any target names not found. If this is empty all targets were found.""" 411 result = {} 412 if not to_find: 413 return {}, [] 414 to_find = set(to_find) 415 for target_name in all_targets.keys(): 416 extracted = gyp.common.ParseQualifiedTarget(target_name) 417 if len(extracted) > 1 and extracted[1] in to_find: 418 to_find.remove(extracted[1]) 419 result[extracted[1]] = all_targets[target_name] 420 if not to_find: 421 return result, [] 422 return result, [x for x in to_find] 423 424 425def _DoesTargetDependOnMatchingTargets(target): 426 """Returns true if |target| or any of its dependencies is one of the 427 targets containing the files supplied as input to analyzer. This updates 428 |matches| of the Targets as it recurses. 429 target: the Target to look for.""" 430 if target.match_status == MATCH_STATUS_DOESNT_MATCH: 431 return False 432 if target.match_status == MATCH_STATUS_MATCHES or \ 433 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: 434 return True 435 for dep in target.deps: 436 if _DoesTargetDependOnMatchingTargets(dep): 437 target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY 438 print('\t', target.name, 'matches by dep', dep.name) 439 return True 440 target.match_status = MATCH_STATUS_DOESNT_MATCH 441 return False 442 443 444def _GetTargetsDependingOnMatchingTargets(possible_targets): 445 """Returns the list of Targets in |possible_targets| that depend (either 446 directly on indirectly) on at least one of the targets containing the files 447 supplied as input to analyzer. 448 possible_targets: targets to search from.""" 449 found = [] 450 print('Targets that matched by dependency:') 451 for target in possible_targets: 452 if _DoesTargetDependOnMatchingTargets(target): 453 found.append(target) 454 return found 455 456 457def _AddCompileTargets(target, roots, add_if_no_ancestor, result): 458 """Recurses through all targets that depend on |target|, adding all targets 459 that need to be built (and are in |roots|) to |result|. 460 roots: set of root targets. 461 add_if_no_ancestor: If true and there are no ancestors of |target| then add 462 |target| to |result|. |target| must still be in |roots|. 463 result: targets that need to be built are added here.""" 464 if target.visited: 465 return 466 467 target.visited = True 468 target.in_roots = target in roots 469 470 for back_dep_target in target.back_deps: 471 _AddCompileTargets(back_dep_target, roots, False, result) 472 target.added_to_compile_targets |= back_dep_target.added_to_compile_targets 473 target.in_roots |= back_dep_target.in_roots 474 target.is_or_has_linked_ancestor |= ( 475 back_dep_target.is_or_has_linked_ancestor) 476 477 # Always add 'executable' targets. Even though they may be built by other 478 # targets that depend upon them it makes detection of what is going to be 479 # built easier. 480 # And always add static_libraries that have no dependencies on them from 481 # linkables. This is necessary as the other dependencies on them may be 482 # static libraries themselves, which are not compile time dependencies. 483 if target.in_roots and \ 484 (target.is_executable or 485 (not target.added_to_compile_targets and 486 (add_if_no_ancestor or target.requires_build)) or 487 (target.is_static_library and add_if_no_ancestor and 488 not target.is_or_has_linked_ancestor)): 489 print('\t\tadding to compile targets', target.name, 'executable', 490 target.is_executable, 'added_to_compile_targets', 491 target.added_to_compile_targets, 'add_if_no_ancestor', 492 add_if_no_ancestor, 'requires_build', target.requires_build, 493 'is_static_library', target.is_static_library, 494 'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor 495 ) 496 result.add(target) 497 target.added_to_compile_targets = True 498 499 500def _GetCompileTargets(matching_targets, supplied_targets): 501 """Returns the set of Targets that require a build. 502 matching_targets: targets that changed and need to be built. 503 supplied_targets: set of targets supplied to analyzer to search from.""" 504 result = set() 505 for target in matching_targets: 506 print('finding compile targets for match', target.name) 507 _AddCompileTargets(target, supplied_targets, True, result) 508 return result 509 510 511def _WriteOutput(params, **values): 512 """Writes the output, either to stdout or a file is specified.""" 513 if 'error' in values: 514 print('Error:', values['error']) 515 if 'status' in values: 516 print(values['status']) 517 if 'targets' in values: 518 values['targets'].sort() 519 print('Supplied targets that depend on changed files:') 520 for target in values['targets']: 521 print('\t', target) 522 if 'invalid_targets' in values: 523 values['invalid_targets'].sort() 524 print('The following targets were not found:') 525 for target in values['invalid_targets']: 526 print('\t', target) 527 if 'compile_targets' in values: 528 values['compile_targets'].sort() 529 print('Targets that need to be built:') 530 for target in values['compile_targets']: 531 print('\t', target) 532 if 'test_targets' in values: 533 values['test_targets'].sort() 534 print('Test targets:') 535 for target in values['test_targets']: 536 print('\t', target) 537 538 output_path = params.get('generator_flags', {}).get( 539 'analyzer_output_path', None) 540 if not output_path: 541 print(json.dumps(values)) 542 return 543 try: 544 f = open(output_path, 'w') 545 f.write(json.dumps(values) + '\n') 546 f.close() 547 except IOError as e: 548 print('Error writing to output file', output_path, str(e)) 549 550 551def _WasGypIncludeFileModified(params, files): 552 """Returns true if one of the files in |files| is in the set of included 553 files.""" 554 if params['options'].includes: 555 for include in params['options'].includes: 556 if _ToGypPath(os.path.normpath(include)) in files: 557 print('Include file modified, assuming all changed', include) 558 return True 559 return False 560 561 562def _NamesNotIn(names, mapping): 563 """Returns a list of the values in |names| that are not in |mapping|.""" 564 return [name for name in names if name not in mapping] 565 566 567def _LookupTargets(names, mapping): 568 """Returns a list of the mapping[name] for each value in |names| that is in 569 |mapping|.""" 570 return [mapping[name] for name in names if name in mapping] 571 572 573def CalculateVariables(default_variables, params): 574 """Calculate additional variables for use in the build (called by gyp).""" 575 flavor = gyp.common.GetFlavor(params) 576 if flavor == 'mac': 577 default_variables.setdefault('OS', 'mac') 578 elif flavor == 'win': 579 default_variables.setdefault('OS', 'win') 580 # Copy additional generator configuration data from VS, which is shared 581 # by the Windows Ninja generator. 582 import gyp.generator.msvs as msvs_generator 583 generator_additional_non_configuration_keys = getattr(msvs_generator, 584 'generator_additional_non_configuration_keys', []) 585 generator_additional_path_sections = getattr(msvs_generator, 586 'generator_additional_path_sections', []) 587 588 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) 589 else: 590 operating_system = flavor 591 if flavor == 'android': 592 operating_system = 'linux' # Keep this legacy behavior for now. 593 default_variables.setdefault('OS', operating_system) 594 595 596class TargetCalculator(object): 597 """Calculates the matching test_targets and matching compile_targets.""" 598 def __init__(self, files, additional_compile_target_names, test_target_names, 599 data, target_list, target_dicts, toplevel_dir, build_files): 600 self._additional_compile_target_names = set(additional_compile_target_names) 601 self._test_target_names = set(test_target_names) 602 self._name_to_target, self._changed_targets, self._root_targets = ( 603 _GenerateTargets(data, target_list, target_dicts, toplevel_dir, 604 frozenset(files), build_files)) 605 self._unqualified_mapping, self.invalid_targets = ( 606 _GetUnqualifiedToTargetMapping(self._name_to_target, 607 self._supplied_target_names_no_all())) 608 609 def _supplied_target_names(self): 610 return self._additional_compile_target_names | self._test_target_names 611 612 def _supplied_target_names_no_all(self): 613 """Returns the supplied test targets without 'all'.""" 614 result = self._supplied_target_names(); 615 result.discard('all') 616 return result 617 618 def is_build_impacted(self): 619 """Returns true if the supplied files impact the build at all.""" 620 return self._changed_targets 621 622 def find_matching_test_target_names(self): 623 """Returns the set of output test targets.""" 624 assert self.is_build_impacted() 625 # Find the test targets first. 'all' is special cased to mean all the 626 # root targets. To deal with all the supplied |test_targets| are expanded 627 # to include the root targets during lookup. If any of the root targets 628 # match, we remove it and replace it with 'all'. 629 test_target_names_no_all = set(self._test_target_names) 630 test_target_names_no_all.discard('all') 631 test_targets_no_all = _LookupTargets(test_target_names_no_all, 632 self._unqualified_mapping) 633 test_target_names_contains_all = 'all' in self._test_target_names 634 if test_target_names_contains_all: 635 test_targets = [x for x in (set(test_targets_no_all) | 636 set(self._root_targets))] 637 else: 638 test_targets = [x for x in test_targets_no_all] 639 print('supplied test_targets') 640 for target_name in self._test_target_names: 641 print('\t', target_name) 642 print('found test_targets') 643 for target in test_targets: 644 print('\t', target.name) 645 print('searching for matching test targets') 646 matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets) 647 matching_test_targets_contains_all = (test_target_names_contains_all and 648 set(matching_test_targets) & 649 set(self._root_targets)) 650 if matching_test_targets_contains_all: 651 # Remove any of the targets for all that were not explicitly supplied, 652 # 'all' is subsequentely added to the matching names below. 653 matching_test_targets = [x for x in (set(matching_test_targets) & 654 set(test_targets_no_all))] 655 print('matched test_targets') 656 for target in matching_test_targets: 657 print('\t', target.name) 658 matching_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] 659 for target in matching_test_targets] 660 if matching_test_targets_contains_all: 661 matching_target_names.append('all') 662 print('\tall') 663 return matching_target_names 664 665 def find_matching_compile_target_names(self): 666 """Returns the set of output compile targets.""" 667 assert self.is_build_impacted(); 668 # Compile targets are found by searching up from changed targets. 669 # Reset the visited status for _GetBuildTargets. 670 for target in self._name_to_target.values(): 671 target.visited = False 672 673 supplied_targets = _LookupTargets(self._supplied_target_names_no_all(), 674 self._unqualified_mapping) 675 if 'all' in self._supplied_target_names(): 676 supplied_targets = [x for x in (set(supplied_targets) | 677 set(self._root_targets))] 678 print('Supplied test_targets & compile_targets') 679 for target in supplied_targets: 680 print('\t', target.name) 681 print('Finding compile targets') 682 compile_targets = _GetCompileTargets(self._changed_targets, 683 supplied_targets) 684 return [gyp.common.ParseQualifiedTarget(target.name)[1] 685 for target in compile_targets] 686 687 688def GenerateOutput(target_list, target_dicts, data, params): 689 """Called by gyp as the final stage. Outputs results.""" 690 config = Config() 691 try: 692 config.Init(params) 693 694 if not config.files: 695 raise Exception('Must specify files to analyze via config_path generator ' 696 'flag') 697 698 toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) 699 if debug: 700 print('toplevel_dir', toplevel_dir) 701 702 if _WasGypIncludeFileModified(params, config.files): 703 result_dict = { 'status': all_changed_string, 704 'test_targets': list(config.test_target_names), 705 'compile_targets': list( 706 config.additional_compile_target_names | 707 config.test_target_names) } 708 _WriteOutput(params, **result_dict) 709 return 710 711 calculator = TargetCalculator(config.files, 712 config.additional_compile_target_names, 713 config.test_target_names, data, 714 target_list, target_dicts, toplevel_dir, 715 params['build_files']) 716 if not calculator.is_build_impacted(): 717 result_dict = { 'status': no_dependency_string, 718 'test_targets': [], 719 'compile_targets': [] } 720 if calculator.invalid_targets: 721 result_dict['invalid_targets'] = calculator.invalid_targets 722 _WriteOutput(params, **result_dict) 723 return 724 725 test_target_names = calculator.find_matching_test_target_names() 726 compile_target_names = calculator.find_matching_compile_target_names() 727 found_at_least_one_target = compile_target_names or test_target_names 728 result_dict = { 'test_targets': test_target_names, 729 'status': found_dependency_string if 730 found_at_least_one_target else no_dependency_string, 731 'compile_targets': list( 732 set(compile_target_names) | 733 set(test_target_names)) } 734 if calculator.invalid_targets: 735 result_dict['invalid_targets'] = calculator.invalid_targets 736 _WriteOutput(params, **result_dict) 737 738 except Exception as e: 739 _WriteOutput(params, error=str(e)) 740