| # Copyright (c) 2014 Google Inc. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| 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. |
| 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. |
| additional_compile_targets: Unqualified targets to search for in addition to |
| test_targets. Targets in the combined list that depend upon a file in |files| |
| are not necessarily output. For example, if the target is of 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. |
| compile_targets: minimal set of targets that directly or indirectly (for |
| targets of type none) depend on the files in |files| and is one of the |
| supplied targets or a target that one of the supplied targets depends on. |
| The expectation is this set of targets is passed into a build step. This list |
| always contains the output of test_targets as well. |
| test_targets: set of targets from the supplied |test_targets| that either |
| directly or indirectly depend upon a file in |files|. This list if useful |
| 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 test_targets and compile_targets are not output) or at |
| least one file was found. |
| invalid_targets: list of supplied targets that were not found. |
| |
| Example: |
| Consider a graph like the following: |
| A D |
| / \ |
| 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 |additional_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 it was not supplied by way of |additional_compile_targets| or |test_targets|. |
| |
| If the generator flag analyzer_output_path is specified, output is written |
| there. Otherwise output is written to stdout. |
| |
| In Gyp the "all" target is shorthand for the root targets in the files passed |
| to gyp. For example, if file "a.gyp" contains targets "a1" and |
| "a2", and file "b.gyp" contains targets "b1" and "b2" and "a2" has a dependency |
| on "b2" and gyp is supplied "a.gyp" then "all" consists of "a1" and "a2". |
| Notice that "b1" and "b2" are not in the "all" target as "b.gyp" was not |
| directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp |
| then the "all" target includes "b1" and "b2". |
| """ |
| |
| import gyp.common |
| import gyp.ninja_syntax as ninja_syntax |
| import json |
| import os |
| import posixpath |
| import sys |
| |
| debug = False |
| |
| found_dependency_string = 'Found dependency' |
| no_dependency_string = 'No dependencies' |
| # Status when it should be assumed that everything has changed. |
| all_changed_string = 'Found dependency (all)' |
| |
| # MatchStatus is used indicate if and how a target depends upon the supplied |
| # sources. |
| # The target's sources contain one of the supplied paths. |
| MATCH_STATUS_MATCHES = 1 |
| # The target has a dependency on another target that contains one of the |
| # supplied paths. |
| MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2 |
| # The target's sources weren't in the supplied paths and none of the target's |
| # dependencies depend upon a target that matched. |
| MATCH_STATUS_DOESNT_MATCH = 3 |
| # The target doesn't contain the source, but the dependent targets have not yet |
| # been visited to determine a more specific status yet. |
| MATCH_STATUS_TBD = 4 |
| |
| generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() |
| |
| generator_wants_static_library_dependencies_adjusted = False |
| |
| generator_default_variables = { |
| } |
| for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', |
| 'LIB_DIR', 'SHARED_LIB_DIR']: |
| generator_default_variables[dirname] = '!!!' |
| |
| for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', |
| 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', |
| 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', |
| 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', |
| 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', |
| 'CONFIGURATION_NAME']: |
| generator_default_variables[unused] = '' |
| |
| |
| def _ToGypPath(path): |
| """Converts a path to the format used by gyp.""" |
| if os.sep == '\\' and os.altsep == '/': |
| return path.replace('\\', '/') |
| return path |
| |
| |
| def _ResolveParent(path, base_path_components): |
| """Resolves |path|, which starts with at least one '../'. Returns an empty |
| string if the path shouldn't be considered. See _AddSources() for a |
| description of |base_path_components|.""" |
| depth = 0 |
| while path.startswith('../'): |
| depth += 1 |
| path = path[3:] |
| # Relative includes may go outside the source tree. For example, an action may |
| # have inputs in /usr/include, which are not in the source tree. |
| if depth > len(base_path_components): |
| return '' |
| if depth == len(base_path_components): |
| return path |
| return '/'.join(base_path_components[0:len(base_path_components) - depth]) + \ |
| '/' + path |
| |
| |
| def _AddSources(sources, base_path, base_path_components, result): |
| """Extracts valid sources from |sources| and adds them to |result|. Each |
| source file is relative to |base_path|, but may contain '..'. To make |
| resolving '..' easier |base_path_components| contains each of the |
| directories in |base_path|. Additionally each source may contain variables. |
| Such sources are ignored as it is assumed dependencies on them are expressed |
| and tracked in some other means.""" |
| # NOTE: gyp paths are always posix style. |
| for source in sources: |
| if not len(source) or source.startswith('!!!') or source.startswith('$'): |
| continue |
| # variable expansion may lead to //. |
| org_source = source |
| source = source[0] + source[1:].replace('//', '/') |
| if source.startswith('../'): |
| source = _ResolveParent(source, base_path_components) |
| if len(source): |
| result.append(source) |
| continue |
| result.append(base_path + source) |
| if debug: |
| print 'AddSource', org_source, result[len(result) - 1] |
| |
| |
| def _ExtractSourcesFromAction(action, base_path, base_path_components, |
| results): |
| if 'inputs' in action: |
| _AddSources(action['inputs'], base_path, base_path_components, results) |
| |
| |
| def _ToLocalPath(toplevel_dir, path): |
| """Converts |path| to a path relative to |toplevel_dir|.""" |
| if path == toplevel_dir: |
| return '' |
| if path.startswith(toplevel_dir + '/'): |
| return path[len(toplevel_dir) + len('/'):] |
| return path |
| |
| |
| def _ExtractSources(target, target_dict, toplevel_dir): |
| # |target| is either absolute or relative and in the format of the OS. Gyp |
| # source paths are always posix. Convert |target| to a posix path relative to |
| # |toplevel_dir_|. This is done to make it easy to build source paths. |
| base_path = posixpath.dirname(_ToLocalPath(toplevel_dir, _ToGypPath(target))) |
| base_path_components = base_path.split('/') |
| |
| # Add a trailing '/' so that _AddSources() can easily build paths. |
| if len(base_path): |
| base_path += '/' |
| |
| if debug: |
| print 'ExtractSources', target, base_path |
| |
| results = [] |
| if 'sources' in target_dict: |
| _AddSources(target_dict['sources'], base_path, base_path_components, |
| results) |
| # Include the inputs from any actions. Any changes to these affect the |
| # resulting output. |
| if 'actions' in target_dict: |
| for action in target_dict['actions']: |
| _ExtractSourcesFromAction(action, base_path, base_path_components, |
| results) |
| if 'rules' in target_dict: |
| for rule in target_dict['rules']: |
| _ExtractSourcesFromAction(rule, base_path, base_path_components, results) |
| |
| return results |
| |
| |
| class Target(object): |
| """Holds information about a particular target: |
| deps: set of Targets this Target depends upon. This is not recursive, only the |
| direct dependent Targets. |
| match_status: one of the MatchStatus values. |
| back_deps: set of Targets that have a dependency on this Target. |
| visited: used during iteration to indicate whether we've visited this target. |
| This is used for two iterations, once in building the set of Targets and |
| again in _GetBuildTargets(). |
| name: fully qualified name of the target. |
| requires_build: True if the target type is such that it needs to be built. |
| See _DoesTargetTypeRequireBuild for details. |
| added_to_compile_targets: used when determining if the target was added to the |
| set of targets that needs to be built. |
| in_roots: true if this target is a descendant of one of the root nodes. |
| is_executable: true if the type of target is executable. |
| is_static_library: true if the type of target is static_library. |
| is_or_has_linked_ancestor: true if the target does a link (eg executable), or |
| if there is a target in back_deps that does a link.""" |
| def __init__(self, name): |
| self.deps = set() |
| self.match_status = MATCH_STATUS_TBD |
| self.back_deps = set() |
| self.name = name |
| # TODO(sky): I don't like hanging this off Target. This state is specific |
| # to certain functions and should be isolated there. |
| self.visited = False |
| self.requires_build = False |
| self.added_to_compile_targets = False |
| self.in_roots = False |
| self.is_executable = False |
| self.is_static_library = False |
| self.is_or_has_linked_ancestor = False |
| |
| |
| class Config(object): |
| """Details what we're looking for |
| files: set of files to search for |
| targets: see file description for details.""" |
| def __init__(self): |
| self.files = [] |
| self.targets = set() |
| self.additional_compile_target_names = set() |
| self.test_target_names = set() |
| |
| def Init(self, params): |
| """Initializes Config. This is a separate method as it raises an exception |
| if there is a parse error.""" |
| generator_flags = params.get('generator_flags', {}) |
| config_path = generator_flags.get('config_path', None) |
| if not config_path: |
| return |
| try: |
| f = open(config_path, 'r') |
| config = json.load(f) |
| f.close() |
| except IOError: |
| raise Exception('Unable to open file ' + config_path) |
| except ValueError as e: |
| raise Exception('Unable to parse config file ' + config_path + str(e)) |
| if not isinstance(config, dict): |
| raise Exception('config_path must be a JSON file containing a dictionary') |
| self.files = config.get('files', []) |
| self.additional_compile_target_names = set( |
| config.get('additional_compile_targets', [])) |
| self.test_target_names = set(config.get('test_targets', [])) |
| |
| |
| def _WasBuildFileModified(build_file, data, files, toplevel_dir): |
| """Returns true if the build file |build_file| is either in |files| or |
| one of the files included by |build_file| is in |files|. |toplevel_dir| is |
| the root of the source tree.""" |
| if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files: |
| if debug: |
| print 'gyp file modified', build_file |
| return True |
| |
| # First element of included_files is the file itself. |
| if len(data[build_file]['included_files']) <= 1: |
| return False |
| |
| for include_file in data[build_file]['included_files'][1:]: |
| # |included_files| are relative to the directory of the |build_file|. |
| rel_include_file = \ |
| _ToGypPath(gyp.common.UnrelativePath(include_file, build_file)) |
| if _ToLocalPath(toplevel_dir, rel_include_file) in files: |
| if debug: |
| print 'included gyp file modified, gyp_file=', build_file, \ |
| 'included file=', rel_include_file |
| return True |
| return False |
| |
| |
| def _GetOrCreateTargetByName(targets, target_name): |
| """Creates or returns the Target at targets[target_name]. If there is no |
| Target for |target_name| one is created. Returns a tuple of whether a new |
| Target was created and the Target.""" |
| if target_name in targets: |
| return False, targets[target_name] |
| target = Target(target_name) |
| targets[target_name] = target |
| return True, target |
| |
| |
| def _DoesTargetTypeRequireBuild(target_dict): |
| """Returns true if the target type is such that it needs to be built.""" |
| # If a 'none' target has rules or actions we assume it requires a build. |
| return bool(target_dict['type'] != 'none' or |
| target_dict.get('actions') or target_dict.get('rules')) |
| |
| |
| def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, |
| build_files): |
| """Returns a tuple of the following: |
| . A dictionary mapping from fully qualified name to Target. |
| . A list of the targets that have a source file in |files|. |
| . Targets that constitute the 'all' target. See description at top of file |
| for details on the 'all' target. |
| This sets the |match_status| of the targets that contain any of the source |
| files in |files| to MATCH_STATUS_MATCHES. |
| |toplevel_dir| is the root of the source tree.""" |
| # Maps from target name to Target. |
| name_to_target = {} |
| |
| # Targets that matched. |
| matching_targets = [] |
| |
| # Queue of targets to visit. |
| targets_to_visit = target_list[:] |
| |
| # Maps from build file to a boolean indicating whether the build file is in |
| # |files|. |
| build_file_in_files = {} |
| |
| # Root targets across all files. |
| roots = set() |
| |
| # Set of Targets in |build_files|. |
| build_file_targets = set() |
| |
| while len(targets_to_visit) > 0: |
| target_name = targets_to_visit.pop() |
| created_target, target = _GetOrCreateTargetByName(name_to_target, |
| target_name) |
| if created_target: |
| roots.add(target) |
| elif target.visited: |
| continue |
| |
| target.visited = True |
| target.requires_build = _DoesTargetTypeRequireBuild( |
| target_dicts[target_name]) |
| target_type = target_dicts[target_name]['type'] |
| target.is_executable = target_type == 'executable' |
| target.is_static_library = target_type == 'static_library' |
| target.is_or_has_linked_ancestor = (target_type == 'executable' or |
| target_type == 'shared_library') |
| |
| build_file = gyp.common.ParseQualifiedTarget(target_name)[0] |
| if not build_file in build_file_in_files: |
| build_file_in_files[build_file] = \ |
| _WasBuildFileModified(build_file, data, files, toplevel_dir) |
| |
| if build_file in build_files: |
| build_file_targets.add(target) |
| |
| # If a build file (or any of its included files) is modified we assume all |
| # targets in the file are modified. |
| if build_file_in_files[build_file]: |
| print 'matching target from modified build file', target_name |
| target.match_status = MATCH_STATUS_MATCHES |
| matching_targets.append(target) |
| else: |
| sources = _ExtractSources(target_name, target_dicts[target_name], |
| toplevel_dir) |
| for source in sources: |
| if _ToGypPath(os.path.normpath(source)) in files: |
| print 'target', target_name, 'matches', source |
| target.match_status = MATCH_STATUS_MATCHES |
| matching_targets.append(target) |
| break |
| |
| # Add dependencies to visit as well as updating back pointers for deps. |
| for dep in target_dicts[target_name].get('dependencies', []): |
| targets_to_visit.append(dep) |
| |
| created_dep_target, dep_target = _GetOrCreateTargetByName(name_to_target, |
| dep) |
| if not created_dep_target: |
| roots.discard(dep_target) |
| |
| target.deps.add(dep_target) |
| dep_target.back_deps.add(target) |
| |
| return name_to_target, matching_targets, roots & build_file_targets |
| |
| |
| def _GetUnqualifiedToTargetMapping(all_targets, 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 {}, [] |
| to_find = set(to_find) |
| for target_name in all_targets.keys(): |
| extracted = gyp.common.ParseQualifiedTarget(target_name) |
| if len(extracted) > 1 and extracted[1] in to_find: |
| to_find.remove(extracted[1]) |
| result[extracted[1]] = all_targets[target_name] |
| if not to_find: |
| return result, [] |
| return result, [x for x in to_find] |
| |
| |
| def _DoesTargetDependOnMatchingTargets(target): |
| """Returns true if |target| or any of its dependencies is one of the |
| targets containing the files supplied as input to analyzer. This updates |
| |matches| of the Targets as it recurses. |
| 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 _DoesTargetDependOnMatchingTargets(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 _GetTargetsDependingOnMatchingTargets(possible_targets): |
| """Returns the list of Targets in |possible_targets| that depend (either |
| directly on indirectly) on at least one of the targets containing the files |
| supplied as input to analyzer. |
| possible_targets: targets to search from.""" |
| found = [] |
| print 'Targets that matched by dependency:' |
| for target in possible_targets: |
| if _DoesTargetDependOnMatchingTargets(target): |
| found.append(target) |
| return found |
| |
| |
| def _AddCompileTargets(target, roots, add_if_no_ancestor, 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. |
| 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, supplied_targets): |
| """Returns the set of Targets that require a build. |
| matching_targets: targets that changed and need to be built. |
| supplied_targets: set of targets supplied to analyzer to search from.""" |
| result = set() |
| for target in matching_targets: |
| print 'finding compile targets for match', target.name |
| _AddCompileTargets(target, supplied_targets, True, result) |
| return result |
| |
| |
| def _WriteOutput(params, **values): |
| """Writes the output, either to stdout or a file is specified.""" |
| if 'error' in values: |
| print 'Error:', values['error'] |
| if 'status' in values: |
| print values['status'] |
| if 'targets' in values: |
| values['targets'].sort() |
| print 'Supplied targets that depend on changed files:' |
| for target in values['targets']: |
| print '\t', target |
| if 'invalid_targets' in values: |
| values['invalid_targets'].sort() |
| print 'The following targets were not found:' |
| for target in values['invalid_targets']: |
| print '\t', target |
| if 'build_targets' in values: |
| values['build_targets'].sort() |
| 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) |
| if not output_path: |
| print json.dumps(values) |
| return |
| try: |
| f = open(output_path, 'w') |
| f.write(json.dumps(values) + '\n') |
| f.close() |
| except IOError as e: |
| print 'Error writing to output file', output_path, str(e) |
| |
| |
| def _WasGypIncludeFileModified(params, files): |
| """Returns true if one of the files in |files| is in the set of included |
| files.""" |
| if params['options'].includes: |
| for include in params['options'].includes: |
| if _ToGypPath(os.path.normpath(include)) in files: |
| print 'Include file modified, assuming all changed', include |
| return True |
| return False |
| |
| |
| def _NamesNotIn(names, mapping): |
| """Returns a list of the values in |names| that are not in |mapping|.""" |
| return [name for name in names if name not in mapping] |
| |
| |
| def _LookupTargets(names, mapping): |
| """Returns a list of the mapping[name] for each value in |names| that is in |
| |mapping|.""" |
| return [mapping[name] for name in names if name in mapping] |
| |
| |
| def CalculateVariables(default_variables, params): |
| """Calculate additional variables for use in the build (called by gyp).""" |
| flavor = gyp.common.GetFlavor(params) |
| if flavor == 'mac': |
| default_variables.setdefault('OS', 'mac') |
| elif flavor == 'win': |
| default_variables.setdefault('OS', 'win') |
| # Copy additional generator configuration data from VS, which is shared |
| # by the Windows Ninja generator. |
| import gyp.generator.msvs as msvs_generator |
| generator_additional_non_configuration_keys = getattr(msvs_generator, |
| 'generator_additional_non_configuration_keys', []) |
| generator_additional_path_sections = getattr(msvs_generator, |
| 'generator_additional_path_sections', []) |
| |
| gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) |
| else: |
| operating_system = flavor |
| if flavor == 'android': |
| operating_system = 'linux' # Keep this legacy behavior for now. |
| default_variables.setdefault('OS', operating_system) |
| |
| |
| class TargetCalculator(object): |
| """Calculates the matching test_targets and matching compile_targets.""" |
| def __init__(self, files, additional_compile_target_names, test_target_names, |
| data, target_list, target_dicts, toplevel_dir, build_files): |
| self._additional_compile_target_names = set(additional_compile_target_names) |
| self._test_target_names = set(test_target_names) |
| self._name_to_target, self._changed_targets, self._root_targets = ( |
| _GenerateTargets(data, target_list, target_dicts, toplevel_dir, |
| frozenset(files), build_files)) |
| self._unqualified_mapping, self.invalid_targets = ( |
| _GetUnqualifiedToTargetMapping(self._name_to_target, |
| self._supplied_target_names_no_all())) |
| |
| def _supplied_target_names(self): |
| return self._additional_compile_target_names | self._test_target_names |
| |
| def _supplied_target_names_no_all(self): |
| """Returns the supplied test targets without 'all'.""" |
| result = self._supplied_target_names(); |
| result.discard('all') |
| return result |
| |
| def is_build_impacted(self): |
| """Returns true if the supplied files impact the build at all.""" |
| return self._changed_targets |
| |
| def find_matching_test_target_names(self): |
| """Returns the set of output test targets.""" |
| assert self.is_build_impacted() |
| # 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'. |
| test_target_names_no_all = set(self._test_target_names) |
| test_target_names_no_all.discard('all') |
| test_targets_no_all = _LookupTargets(test_target_names_no_all, |
| self._unqualified_mapping) |
| test_target_names_contains_all = 'all' in self._test_target_names |
| if test_target_names_contains_all: |
| test_targets = [x for x in (set(test_targets_no_all) | |
| set(self._root_targets))] |
| else: |
| test_targets = [x for x in test_targets_no_all] |
| print 'supplied test_targets' |
| for target_name in self._test_target_names: |
| print '\t', target_name |
| print 'found test_targets' |
| for target in test_targets: |
| print '\t', target.name |
| print 'searching for matching test targets' |
| matching_test_targets = _GetTargetsDependingOnMatchingTargets(test_targets) |
| matching_test_targets_contains_all = (test_target_names_contains_all and |
| set(matching_test_targets) & |
| set(self._root_targets)) |
| if matching_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. |
| matching_test_targets = [x for x in (set(matching_test_targets) & |
| set(test_targets_no_all))] |
| print 'matched test_targets' |
| for target in matching_test_targets: |
| print '\t', target.name |
| matching_target_names = [gyp.common.ParseQualifiedTarget(target.name)[1] |
| for target in matching_test_targets] |
| if matching_test_targets_contains_all: |
| matching_target_names.append('all') |
| print '\tall' |
| return matching_target_names |
| |
| def find_matching_compile_target_names(self): |
| """Returns the set of output compile targets.""" |
| assert self.is_build_impacted(); |
| # Compile targets are found by searching up from changed targets. |
| # Reset the visited status for _GetBuildTargets. |
| for target in self._name_to_target.itervalues(): |
| target.visited = False |
| |
| supplied_targets = _LookupTargets(self._supplied_target_names_no_all(), |
| self._unqualified_mapping) |
| if 'all' in self._supplied_target_names(): |
| supplied_targets = [x for x in (set(supplied_targets) | |
| set(self._root_targets))] |
| print 'Supplied test_targets & compile_targets' |
| for target in supplied_targets: |
| print '\t', target.name |
| print 'Finding compile targets' |
| compile_targets = _GetCompileTargets(self._changed_targets, |
| supplied_targets) |
| return [gyp.common.ParseQualifiedTarget(target.name)[1] |
| for target in compile_targets] |
| |
| |
| 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') |
| |
| 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, |
| 'test_targets': list(config.test_target_names), |
| 'compile_targets': list( |
| config.additional_compile_target_names | |
| config.test_target_names) } |
| _WriteOutput(params, **result_dict) |
| return |
| |
| calculator = TargetCalculator(config.files, |
| config.additional_compile_target_names, |
| config.test_target_names, data, |
| target_list, target_dicts, toplevel_dir, |
| params['build_files']) |
| if not calculator.is_build_impacted(): |
| result_dict = { 'status': no_dependency_string, |
| 'test_targets': [], |
| 'compile_targets': [] } |
| if calculator.invalid_targets: |
| result_dict['invalid_targets'] = calculator.invalid_targets |
| _WriteOutput(params, **result_dict) |
| return |
| |
| test_target_names = calculator.find_matching_test_target_names() |
| compile_target_names = calculator.find_matching_compile_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': list( |
| set(compile_target_names) | |
| set(test_target_names)) } |
| if calculator.invalid_targets: |
| result_dict['invalid_targets'] = calculator.invalid_targets |
| _WriteOutput(params, **result_dict) |
| |
| except Exception as e: |
| _WriteOutput(params, error=str(e)) |