Chromium Code Reviews| Index: pylib/gyp/generator/analyzer.py |
| diff --git a/pylib/gyp/generator/analyzer.py b/pylib/gyp/generator/analyzer.py |
| index c093f1c6bda4fdcd466775a7af7755eec43f86ab..d49052718bdcecf3b33650bc884f03438dfb0191 100644 |
| --- a/pylib/gyp/generator/analyzer.py |
| +++ b/pylib/gyp/generator/analyzer.py |
| @@ -7,17 +7,27 @@ This script is intended for use as a GYP_GENERATOR. It takes as input (by way of |
| the generator flag config_path) the path of a json file that dictates the files |
| and targets to search for. The following keys are supported: |
| files: list of paths (relative) of the files to search for. |
| -targets: list of targets to search for. The target names are unqualified. |
| +test_targets: unqualified target names to search for. Any target in this list |
| +that depends upon a file in |files| is output regardless of the type of target |
| +or chain of dependencies. |
| +compile_targets: Additional unqualified targets to search for. Targets in this |
|
Dirk Pranke
2015/11/11 03:51:16
"Additional" is perhaps a bit confusing in this co
sky
2015/11/11 18:38:10
I thought we agreed on compile_targets as the name
|
| +list that depend upon a file in |files| are not necessarily output. For |
| +example, if the target is out type none then the target is not output (but one |
| +of the descendants of the target will be). |
| The following is output: |
| error: only supplied if there is an error. |
| -build_targets: minimal set of targets that directly depend on the changed |
| - files and need to be built. The expectation is this set of targets is passed |
| - into a build step. The returned values are either values in the supplied |
| - targets, or have a dependency on one of the supplied targets. |
| +compile_targets: minimal set of targets that directly or indirectly depend on |
| + the files in |files| and is one of the supplied targets or a target that one |
| + of the supplied targets depends on. |
|
Dirk Pranke
2015/11/11 03:51:16
maybe note that the second clause only applies if
sky
2015/11/11 18:38:10
Done.
|
| + The expectation is this set of targets is passed into a build step. |
| +test_targets: set of targets from the supplied |test_targets| that either |
| + directly or indictly depend upon a file in |files|. This list if useful |
|
Dirk Pranke
2015/11/11 03:51:16
typo: s/indictly/indirectly .
sky
2015/11/11 18:38:10
Done.
|
| + if additional processing needs to be done for certain targets after the |
| + build, such as running tests. |
| status: outputs one of three values: none of the supplied files were found, |
| one of the include files changed so that it should be assumed everything |
| - changed (in this case targets and build_targets are not output) or at |
| + changed (in this case test_targets and compile_targets are not output) or at |
| least one file was found. |
| invalid_targets: list of supplied targets thare were not found. |
|
Dirk Pranke
2015/11/11 03:51:16
typo: s/thare/that/
sky
2015/11/11 18:38:10
Done.
|
| @@ -28,14 +38,16 @@ Consider a graph like the following: |
| B C |
| A depends upon both B and C, A is of type none and B and C are executables. |
| D is an executable, has no dependencies and nothing depends on it. |
| -If |targets| = ["A"] and files = ["b.cc", "d.cc"] (B depends upon b.cc and D |
| -depends upon d.cc), then the following is output: |
| -|build_targets| = ["B"] B must built as it depends upon the changed file b.cc |
| +If |compile_targets| = ["A"], |test_targets| = ["B", "C"] and |
| +files = ["b.cc", "d.cc"] (B depends upon b.cc and D depends upon d.cc), then |
| +the following is output: |
| +|compile_targets| = ["B"] B must built as it depends upon the changed file b.cc |
| and the supplied target A depends upon it. A is not output as a build_target |
| as it is of type none with no rules and actions. |
| +|test_targets| = ["B"] B directly depends upon the change file b.cc. |
| Even though the file d.cc, which D depends upon, has changed D is not output |
| -as none of the supplied targets (A) depend upon D. |
| +as it was not supplied by way of |compile_targets| or |test_targets|. |
| If the generator flag analyzer_output_path is specified, output is written |
| there. Otherwise output is written to stdout. |
| @@ -225,6 +237,10 @@ class Config(object): |
| def __init__(self): |
| self.files = [] |
| self.targets = set() |
| + self.compile_targets = set() |
| + self.test_targets = set() |
| + # Needed until recipes is updated. |
|
Dirk Pranke
2015/11/11 03:51:16
Nit: "are updated" :).
sky
2015/11/11 18:38:10
Done.
|
| + self.deprecated_mode = False |
| def Init(self, params): |
| """Initializes Config. This is a separate method as it raises an exception |
| @@ -244,7 +260,12 @@ class Config(object): |
| if not isinstance(config, dict): |
| raise Exception('config_path must be a JSON file containing a dictionary') |
| self.files = config.get('files', []) |
| - self.targets = set(config.get('targets', [])) |
| + if config.get('targets'): |
| + self.targets = set(config.get('targets')) |
| + self.deprecated_mode = True |
| + else: |
| + self.compile_targets = set(config.get('compile_targets', [])) |
| + self.test_targets = set(config.get('test_targets', [])) |
| def _WasBuildFileModified(build_file, data, files, toplevel_dir): |
| @@ -375,11 +396,13 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, |
| def _GetUnqualifiedToTargetMapping(all_targets, to_find): |
| - """Returns a mapping (dictionary) from unqualified name to Target for all the |
| - Targets in |to_find|.""" |
| + """Returns a tuple of the following: |
| + . mapping (dictionary) from unqualified name to Target for all the |
| + Targets in |to_find|. |
| + . any target names not found. If this is empty all targets were found.""" |
| result = {} |
| if not to_find: |
| - return result |
| + return {}, [] |
| to_find = set(to_find) |
| for target_name in all_targets.keys(): |
| extracted = gyp.common.ParseQualifiedTarget(target_name) |
| @@ -387,11 +410,11 @@ def _GetUnqualifiedToTargetMapping(all_targets, to_find): |
| to_find.remove(extracted[1]) |
| result[extracted[1]] = all_targets[target_name] |
| if not to_find: |
| - return result |
| - return result |
| + return result, [] |
| + return result, [x for x in to_find] |
| -def _AddBuildTargets(target, roots, result): |
| +def _AddBuildTargetsDeprecated(target, roots, result): |
| """Recurses through all targets that depend on |target|, adding all targets |
| that need to be built (and are in |roots|) to |result|. |
| roots: set of root targets. |
| @@ -403,7 +426,7 @@ def _AddBuildTargets(target, roots, result): |
| target.in_roots = target in roots |
| for back_dep_target in target.back_deps: |
| - _AddBuildTargets(back_dep_target, roots, result) |
| + _AddBuildTargetsDeprecated(back_dep_target, roots, result) |
| target.added_to_compile_targets |= back_dep_target.added_to_compile_targets |
| target.in_roots |= back_dep_target.in_roots |
| target.is_or_has_linked_ancestor |= ( |
| @@ -429,14 +452,97 @@ def _AddBuildTargets(target, roots, result): |
| target.added_to_compile_targets = True |
| -def _GetBuildTargets(matching_targets, roots): |
| +def _GetBuildTargetsDeprecated(matching_targets, roots): |
| """Returns the set of Targets that require a build. |
| matching_targets: targets that changed and need to be built. |
| roots: set of root targets in the build files to search from.""" |
| result = set() |
| for target in matching_targets: |
| print '\tfinding build targets for match', target.name |
| - _AddBuildTargets(target, roots, result) |
| + _AddBuildTargetsDeprecated(target, roots, result) |
| + return result |
| + |
| + |
| +def _DoesTargetDependOn(target): |
|
sky
2015/11/10 18:41:31
This function and the next are exactly the same as
|
| + """Returns true if |target| or any of its dependencies matches the supplied |
| + set of paths. This updates |matches| of the Targets as it recurses. |
|
Dirk Pranke
2015/11/11 03:51:16
"the supplied set of paths" is unclear (to me) her
sky
2015/11/11 18:38:10
I updated the docs and changed names.
As to the d
|
| + target: the Target to look for.""" |
| + if target.match_status == MATCH_STATUS_DOESNT_MATCH: |
| + return False |
| + if target.match_status == MATCH_STATUS_MATCHES or \ |
| + target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: |
| + return True |
| + for dep in target.deps: |
| + if _DoesTargetDependOn(dep): |
| + target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY |
| + print '\t', target.name, 'matches by dep', dep.name |
| + return True |
| + target.match_status = MATCH_STATUS_DOESNT_MATCH |
| + return False |
| + |
| + |
| +def _GetTargetsDependingOn(possible_targets): |
|
Dirk Pranke
2015/11/11 03:51:16
See comments above for _DoesTargetDependOn() (as r
|
| + """Returns the list of Targets in |possible_targets| that depend (either |
| + directly on indirectly) on the matched targets. |
| + possible_targets: targets to search from.""" |
| + found = [] |
| + print 'Targets that matched by dependency:' |
| + for target in possible_targets: |
| + if _DoesTargetDependOn(target): |
| + found.append(target) |
| + return found |
| + |
| + |
| +def _AddCompileTargets(target, roots, add_if_no_ancestor, result): |
|
sky
2015/11/10 18:41:31
And this is _AddBuildTargets from before https://c
|
| + """Recurses through all targets that depend on |target|, adding all targets |
| + that need to be built (and are in |roots|) to |result|. |
| + roots: set of root targets. |
| + add_if_no_ancestor: If true and there are no ancestors of |target| then add |
| + |target| to |result|. |target| must still be in |roots|. |
| + result: targets that need to be built are added here.""" |
| + if target.visited: |
| + return |
| + |
| + target.visited = True |
| + target.in_roots = target in roots |
| + |
| + for back_dep_target in target.back_deps: |
| + _AddCompileTargets(back_dep_target, roots, False, result) |
| + target.added_to_compile_targets |= back_dep_target.added_to_compile_targets |
| + target.in_roots |= back_dep_target.in_roots |
| + target.is_or_has_linked_ancestor |= ( |
| + back_dep_target.is_or_has_linked_ancestor) |
| + |
| + # Always add 'executable' targets. Even though they may be built by other |
| + # targets that depend upon them it makes detection of what is going to be |
| + # built easier. |
| + # And always add static_libraries that have no dependencies on them from |
| + # linkables. This is necessary as the other dependencies on them may be |
| + # static libraries themselves, which are not compile time dependencies. |
| + if target.in_roots and \ |
| + (target.is_executable or |
| + (not target.added_to_compile_targets and |
| + (add_if_no_ancestor or target.requires_build)) or |
| + (target.is_static_library and add_if_no_ancestor and |
| + not target.is_or_has_linked_ancestor)): |
| + print '\t\tadding to compile targets', target.name, 'executable', \ |
| + target.is_executable, 'added_to_compile_targets', \ |
| + target.added_to_compile_targets, 'add_if_no_ancestor', \ |
| + add_if_no_ancestor, 'requires_build', target.requires_build, \ |
| + 'is_static_library', target.is_static_library, \ |
| + 'is_or_has_linked_ancestor', target.is_or_has_linked_ancestor |
| + result.add(target) |
| + target.added_to_compile_targets = True |
| + |
| + |
| +def _GetCompileTargets(matching_targets, roots): |
| + """Returns the set of Targets that require a build. |
| + matching_targets: targets that changed and need to be built. |
| + roots: set of root targets in the build files to search from.""" |
|
Dirk Pranke
2015/11/11 03:51:16
calling this 'roots' is also a bit confusing, beca
sky
2015/11/11 18:38:10
Done.
|
| + result = set() |
| + for target in matching_targets: |
| + print 'finding compile targets for match', target.name |
| + _AddCompileTargets(target, roots, True, result) |
| return result |
| @@ -461,6 +567,16 @@ def _WriteOutput(params, **values): |
| print 'Targets that require a build:' |
| for target in values['build_targets']: |
| print '\t', target |
| + if 'compile_targets' in values: |
| + values['compile_targets'].sort() |
| + print 'Targets that need to be built:' |
| + for target in values['compile_targets']: |
| + print '\t', target |
| + if 'test_targets' in values: |
| + values['test_targets'].sort() |
| + print 'Test targets:' |
| + for target in values['test_targets']: |
| + print '\t', target |
| output_path = params.get('generator_flags', {}).get( |
| 'analyzer_output_path', None) |
| @@ -520,58 +636,149 @@ def CalculateVariables(default_variables, params): |
| default_variables.setdefault('OS', operating_system) |
| +def _GenerateOutputDeprecated(target_list, target_dicts, data, params, config): |
| + """Old deprecated behavior, will be nuked shortly.""" |
| + toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) |
| + |
| + if _WasGypIncludeFileModified(params, config.files): |
| + result_dict = { 'status': all_changed_string, |
| + 'targets': list(config.targets) } |
|
Dirk Pranke
2015/11/11 03:51:16
this is probably a nit, since this is the way the
sky
2015/11/11 18:38:10
I believe the recipe code ignores the targets if a
|
| + _WriteOutput(params, **result_dict) |
| + return |
| + |
| + all_targets, matching_targets, root_targets = _GenerateTargets( |
| + data, target_list, target_dicts, toplevel_dir, frozenset(config.files), |
| + params['build_files']) |
| + |
| + unqualified_mapping, invalid_targets = _GetUnqualifiedToTargetMapping( |
| + all_targets, config.targets) |
| + |
| + if matching_targets: |
| + search_targets = _LookupTargets(config.targets, unqualified_mapping) |
| + print 'supplied targets' |
| + for target in config.targets: |
| + print '\t', target |
| + print 'expanded supplied targets' |
| + for target in search_targets: |
| + print '\t', target.name |
| + # Reset the visited status for _GetBuildTargets. |
| + for target in all_targets.itervalues(): |
| + target.visited = False |
| + build_targets = _GetBuildTargetsDeprecated(matching_targets, search_targets) |
| + build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] |
| + for target in build_targets] |
| + else: |
| + build_targets = [] |
| + |
| + result_dict = { 'targets': build_targets, |
| + 'status': found_dependency_string if matching_targets else |
| + no_dependency_string, |
| + 'build_targets': build_targets} |
| + if invalid_targets: |
| + result_dict['invalid_targets'] = invalid_targets |
| + _WriteOutput(params, **result_dict) |
| + |
| + |
| def GenerateOutput(target_list, target_dicts, data, params): |
| """Called by gyp as the final stage. Outputs results.""" |
| config = Config() |
| try: |
| config.Init(params) |
| + |
| if not config.files: |
| raise Exception('Must specify files to analyze via config_path generator ' |
| 'flag') |
| + if config.deprecated_mode: |
| + _GenerateOutputDeprecated(target_list, target_dicts, data, params, |
| + config) |
| + return |
| + |
| toplevel_dir = _ToGypPath(os.path.abspath(params['options'].toplevel_dir)) |
| if debug: |
| print 'toplevel_dir', toplevel_dir |
| if _WasGypIncludeFileModified(params, config.files): |
| result_dict = { 'status': all_changed_string, |
| - 'targets': list(config.targets) } |
| + 'test_targets': list(config.test_targets), |
| + 'compile_targets': list(config.compile_targets) } |
| _WriteOutput(params, **result_dict) |
| return |
|
Dirk Pranke
2015/11/11 03:51:16
General comments on the rest of this function:
It
sky
2015/11/11 18:38:10
I moved all of this off into a class. Hopefully it
|
| - all_targets, matching_targets, _ = _GenerateTargets( |
| + all_targets, changed_targets, root_targets = _GenerateTargets( |
|
Dirk Pranke
2015/11/11 03:51:16
'changed_targets' is the list of targets *directly
|
| data, target_list, target_dicts, toplevel_dir, frozenset(config.files), |
| params['build_files']) |
| - unqualified_mapping = _GetUnqualifiedToTargetMapping(all_targets, |
| - config.targets) |
| - invalid_targets = None |
| - if len(unqualified_mapping) != len(config.targets): |
| - invalid_targets = _NamesNotIn(config.targets, unqualified_mapping) |
| - |
| - if matching_targets: |
| - search_targets = _LookupTargets(config.targets, unqualified_mapping) |
| - print 'supplied targets' |
| - for target in config.targets: |
| - print '\t', target |
| - print 'expanded supplied targets' |
| - for target in search_targets: |
| + supplied_target_names = config.compile_targets | config.test_targets |
|
Dirk Pranke
2015/11/11 03:51:16
We discussed this earlier (as to how this was safe
|
| + # 'all' is added back below. |
| + supplied_targets_contains_all = 'all' in supplied_target_names |
| + supplied_target_names.discard('all') |
| + unqualified_mapping, invalid_targets = _GetUnqualifiedToTargetMapping( |
| + all_targets, supplied_target_names) |
| + |
| + if changed_targets: |
| + # Find the test targets first. 'all' is special cased to mean all the |
| + # root targets. To deal with all the supplied |test_targets| are expanded |
| + # to include the root targets during lookup. If any of the root targets |
| + # match, we remove it and replace it with 'all'. |
|
Dirk Pranke
2015/11/11 03:51:16
Same concerns about 'all' and 'root targets' here
|
| + test_target_names = set(config.test_targets) |
| + test_target_names_contains_all = 'all' in test_target_names |
| + test_target_names.discard('all') |
| + test_targets = _LookupTargets(test_target_names, unqualified_mapping) |
| + original_test_targets = set(test_targets) |
| + if test_target_names_contains_all: |
| + test_targets = [x for x in (set(test_targets) | set(root_targets))] |
|
Dirk Pranke
2015/11/11 03:51:16
Rewrite as
test_targets = list(set(test_targets)
|
| + print 'supplied test_targets' |
| + for target_name in config.test_targets: |
| + print '\t', target_name |
| + print 'found test_targets' |
| + for target in test_targets: |
| + print '\t', target.name |
| + print 'searching for matching test targets' |
| + test_targets = _GetTargetsDependingOn(test_targets) |
|
Dirk Pranke
2015/11/11 03:51:16
maybe instead something like:
affected_test_targe
|
| + test_targets_contains_all = (test_target_names_contains_all and |
| + set(test_targets) & set(root_targets)) |
|
Dirk Pranke
2015/11/11 03:51:16
and then rewriting this as something like:
affe
|
| + if test_targets_contains_all: |
| + # Remove any of the targets for all that were not explicitly supplied, |
| + # 'all' is subsequentely added to the matching names below. |
| + test_targets = [x for x in (set(test_targets) & |
| + set(original_test_targets))] |
|
Dirk Pranke
2015/11/11 03:51:16
affected_test_targets = list(set(affected_test_tar
|
| + |
| + print 'matched test_targets' |
| + for target in test_targets: |
| print '\t', target.name |
| + test_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] |
| + for target in test_targets] |
| + if test_targets_contains_all: |
| + test_target_names.append('all') |
| + print '\tall' |
| + |
| + # Compile targets are found by searching up from changed targets. |
| # Reset the visited status for _GetBuildTargets. |
| for target in all_targets.itervalues(): |
| target.visited = False |
| - print 'Finding build targets' |
| - build_targets = _GetBuildTargets(matching_targets, search_targets) |
| - build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] |
| - for target in build_targets] |
| - else: |
| - build_targets = [] |
| - # TODO(sky): nuke 'targets'. |
| - result_dict = { 'targets': build_targets, |
| - 'status': found_dependency_string if matching_targets else |
| - no_dependency_string, |
| - 'build_targets': build_targets} |
| + supplied_targets = _LookupTargets(supplied_target_names, |
|
Dirk Pranke
2015/11/11 03:51:16
see comments above re: supplied_target_names being
|
| + unqualified_mapping) |
| + if supplied_targets_contains_all: |
| + supplied_targets = [x for x in (set(supplied_targets) | |
| + set(root_targets))] |
| + print 'Supplied test_targets & compile_targets' |
| + for target in supplied_targets: |
| + print '\t', target.name |
| + print 'Finding compile targets' |
| + compile_targets = _GetCompileTargets(changed_targets, supplied_targets) |
| + compile_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] |
| + for target in compile_targets] |
| + else: |
| + compile_target_names = [] |
| + test_target_names = [] |
| + |
| + found_at_least_one_target = compile_target_names or test_target_names |
| + result_dict = { 'test_targets': test_target_names, |
| + 'status': found_dependency_string if |
| + found_at_least_one_target else no_dependency_string, |
| + 'compile_targets': compile_target_names} |
| if invalid_targets: |
| result_dict['invalid_targets'] = invalid_targets |
|
Dirk Pranke
2015/11/11 03:51:16
Looks like you *don't* return an error if there ar
|
| _WriteOutput(params, **result_dict) |