diff --git a/gyp/AUTHORS b/gyp/AUTHORS index 9389ca0a23..fecf84a1c4 100644 --- a/gyp/AUTHORS +++ b/gyp/AUTHORS @@ -9,3 +9,4 @@ Steven Knight Ryan Norton David J. Sankel Eric N. Vander Weele +Tom Freudenberg diff --git a/gyp/pylib/gyp/common.py b/gyp/pylib/gyp/common.py index d482a20df3..256e3f3a6b 100644 --- a/gyp/pylib/gyp/common.py +++ b/gyp/pylib/gyp/common.py @@ -425,6 +425,8 @@ def GetFlavor(params): return 'freebsd' if sys.platform.startswith('openbsd'): return 'openbsd' + if sys.platform.startswith('netbsd'): + return 'netbsd' if sys.platform.startswith('aix'): return 'aix' diff --git a/gyp/pylib/gyp/generator/analyzer.py b/gyp/pylib/gyp/generator/analyzer.py index f403d4e266..921c1a6b71 100644 --- a/gyp/pylib/gyp/generator/analyzer.py +++ b/gyp/pylib/gyp/generator/analyzer.py @@ -7,23 +7,59 @@ 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. +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. -targets: the set of targets passed in via targets that either directly or - indirectly depend upon the set of paths supplied in files. -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. +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 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. +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 @@ -210,6 +246,8 @@ class Config(object): 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 @@ -229,7 +267,9 @@ def Init(self, params): 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', [])) + 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): @@ -280,12 +320,13 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, 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|. - . Set of root Targets reachable from the the files |build_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. - targets = {} + name_to_target = {} # Targets that matched. matching_targets = [] @@ -305,7 +346,8 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, while len(targets_to_visit) > 0: target_name = targets_to_visit.pop() - created_target, target = _GetOrCreateTargetByName(targets, target_name) + created_target, target = _GetOrCreateTargetByName(name_to_target, + target_name) if created_target: roots.add(target) elif target.visited: @@ -348,22 +390,25 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, for dep in target_dicts[target_name].get('dependencies', []): targets_to_visit.append(dep) - created_dep_target, dep_target = _GetOrCreateTargetByName(targets, 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 targets, matching_targets, roots & build_file_targets + return name_to_target, matching_targets, roots & build_file_targets 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) @@ -371,13 +416,14 @@ 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 _DoesTargetDependOn(target): - """Returns true if |target| or any of its dependencies matches the supplied - set of paths. This updates |matches| of the Targets as it recurses. +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 @@ -385,7 +431,7 @@ def _DoesTargetDependOn(target): target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY: return True for dep in target.deps: - if _DoesTargetDependOn(dep): + if _DoesTargetDependOnMatchingTargets(dep): target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY print '\t', target.name, 'matches by dep', dep.name return True @@ -393,19 +439,20 @@ def _DoesTargetDependOn(target): return False -def _GetTargetsDependingOn(possible_targets): +def _GetTargetsDependingOnMatchingTargets(possible_targets): """Returns the list of Targets in |possible_targets| that depend (either - directly on indirectly) on the matched targets. + 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 _DoesTargetDependOn(target): + if _DoesTargetDependOnMatchingTargets(target): found.append(target) return found -def _AddBuildTargets(target, roots, add_if_no_ancestor, result): +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. @@ -416,10 +463,10 @@ def _AddBuildTargets(target, roots, add_if_no_ancestor, result): return target.visited = True - target.in_roots = not target.back_deps and target in roots + target.in_roots = target in roots for back_dep_target in target.back_deps: - _AddBuildTargets(back_dep_target, roots, False, result) + _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 |= ( @@ -437,7 +484,7 @@ def _AddBuildTargets(target, roots, add_if_no_ancestor, result): (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 build targets', target.name, 'executable', \ + 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, \ @@ -447,14 +494,14 @@ def _AddBuildTargets(target, roots, add_if_no_ancestor, result): target.added_to_compile_targets = True -def _GetBuildTargets(matching_targets, roots): +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. - roots: set of root targets in the build files to search from.""" + supplied_targets: set of targets supplied to analyzer to search from.""" result = set() for target in matching_targets: - print '\tfinding build targets for match', target.name - _AddBuildTargets(target, roots, True, result) + print 'finding compile targets for match', target.name + _AddCompileTargets(target, supplied_targets, True, result) return result @@ -479,6 +526,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) @@ -538,11 +595,104 @@ def CalculateVariables(default_variables, params): 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') @@ -553,55 +703,38 @@ def GenerateOutput(target_list, target_dicts, data, params): if _WasGypIncludeFileModified(params, config.files): result_dict = { 'status': all_changed_string, - 'targets': list(config.targets) } + 'test_targets': list(config.test_target_names), + 'compile_targets': list( + config.additional_compile_target_names | + config.test_target_names) } _WriteOutput(params, **result_dict) return - all_targets, matching_targets, roots = _GenerateTargets( - data, target_list, target_dicts, toplevel_dir, frozenset(config.files), - params['build_files']) - - print 'roots:' - for root in roots: - print '\t', root.name - - 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: - print '\t', target.name - matched_search_targets = _GetTargetsDependingOn(search_targets) - print 'raw matched search targets:' - for target in matched_search_targets: - print '\t', target.name - # Reset the visited status for _GetBuildTargets. - for target in all_targets.itervalues(): - target.visited = False - print 'Finding build targets' - build_targets = _GetBuildTargets(matching_targets, roots) - matched_search_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] - for target in matched_search_targets] - build_targets = [gyp.common.ParseQualifiedTarget(target.name)[1] - for target in build_targets] - else: - matched_search_targets = [] - build_targets = [] - - result_dict = { 'targets': matched_search_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 + 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: diff --git a/gyp/pylib/gyp/generator/cmake.py b/gyp/pylib/gyp/generator/cmake.py index eece6ea98d..17f5e6396c 100644 --- a/gyp/pylib/gyp/generator/cmake.py +++ b/gyp/pylib/gyp/generator/cmake.py @@ -55,7 +55,7 @@ 'CONFIGURATION_NAME': '${configuration}', } -FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}') +FULL_PATH_VARS = ('${CMAKE_CURRENT_LIST_DIR}', '${builddir}', '${obj}') generator_supports_multiple_toolsets = True generator_wants_static_library_dependencies_adjusted = True @@ -103,7 +103,7 @@ def NormjoinPathForceCMakeSource(base_path, rel_path): if any([rel_path.startswith(var) for var in FULL_PATH_VARS]): return rel_path # TODO: do we need to check base_path for absolute variables as well? - return os.path.join('${CMAKE_SOURCE_DIR}', + return os.path.join('${CMAKE_CURRENT_LIST_DIR}', os.path.normpath(os.path.join(base_path, rel_path))) @@ -293,7 +293,7 @@ def WriteActions(target_name, actions, extra_sources, extra_deps, WriteVariable(output, inputs_name) output.write('\n') - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -398,9 +398,9 @@ def WriteRules(target_name, rules, extra_sources, extra_deps, output.write(NormjoinPath(path_to_gyp, rule_source)) output.write('\n') - # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives. + # CMAKE_CURRENT_LIST_DIR is where the CMakeLists.txt lives. # The cwd is the current build directory. - output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -522,7 +522,7 @@ def __init__(self, ext, command): WriteVariable(output, copy.inputs_name, ' ') output.write('\n') - output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') + output.write('WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/') output.write(path_to_gyp) output.write('\n') @@ -1126,7 +1126,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, if cc: SetVariable(output, 'CMAKE_ASM_COMPILER', cc) - SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}') + SetVariable(output, 'builddir', '${CMAKE_CURRENT_BINARY_DIR}') SetVariable(output, 'obj', '${builddir}/obj') output.write('\n') diff --git a/gyp/pylib/gyp/generator/make.py b/gyp/pylib/gyp/generator/make.py index 4139eb7399..aefdac787c 100644 --- a/gyp/pylib/gyp/generator/make.py +++ b/gyp/pylib/gyp/generator/make.py @@ -1579,7 +1579,7 @@ def WriteTarget(self, spec, configs, deps, link_deps, bundle_deps, for link_dep in link_deps: assert ' ' not in link_dep, ( "Spaces in alink input filenames not supported (%s)" % link_dep) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.WriteDoCmd([self.output_binary], link_deps, 'alink_thin', part_of_all, postbuilds=postbuilds) @@ -2046,6 +2046,11 @@ def CalculateMakefilePath(build_file, base_name): header_params.update({ 'flock': 'lockf', }) + elif flavor == 'openbsd': + copy_archive_arguments = '-pPRf' + header_params.update({ + 'copy_archive_args': copy_archive_arguments, + }) elif flavor == 'aix': copy_archive_arguments = '-pPRf' header_params.update({ diff --git a/gyp/pylib/gyp/generator/ninja.py b/gyp/pylib/gyp/generator/ninja.py index 416d83eaec..841067ed34 100644 --- a/gyp/pylib/gyp/generator/ninja.py +++ b/gyp/pylib/gyp/generator/ninja.py @@ -139,8 +139,11 @@ def __init__(self, type): self.bundle = None # On Windows, incremental linking requires linking against all the .objs # that compose a .lib (rather than the .lib itself). That list is stored - # here. + # here. In this case, we also need to save the compile_deps for the target, + # so that the the target that directly depends on the .objs can also depend + # on those. self.component_objs = None + self.compile_deps = None # Windows only. The import .lib is the output of a build step, but # because dependents only link against the lib (not both the lib and the # dll) we keep track of the import library here. @@ -474,16 +477,17 @@ def WriteSpec(self, spec, config_name, generator_flags): elif self.flavor == 'mac' and len(self.archs) > 1: link_deps = collections.defaultdict(list) - + compile_deps = self.target.actions_stamp or actions_depends if self.flavor == 'win' and self.target.type == 'static_library': self.target.component_objs = link_deps + self.target.compile_deps = compile_deps # Write out a link step, if needed. output = None is_empty_bundle = not link_deps and not mac_bundle_depends if link_deps or self.target.actions_stamp or actions_depends: output = self.WriteTarget(spec, config_name, config, link_deps, - self.target.actions_stamp or actions_depends) + compile_deps) if self.is_mac_bundle: mac_bundle_depends.append(output) @@ -1093,6 +1097,7 @@ def WriteLinkForArch(self, ninja_file, spec, config_name, config, implicit_deps = set() solibs = set() + order_deps = set() if 'dependencies' in spec: # Two kinds of dependencies: @@ -1111,6 +1116,8 @@ def WriteLinkForArch(self, ninja_file, spec, config_name, config, target.component_objs and self.msvs_settings.IsUseLibraryDependencyInputs(config_name)): new_deps = target.component_objs + if target.compile_deps: + order_deps.add(target.compile_deps) elif self.flavor == 'win' and target.import_lib: new_deps = [target.import_lib] elif target.UsesToc(self.flavor): @@ -1249,6 +1256,7 @@ def WriteLinkForArch(self, ninja_file, spec, config_name, config, ninja_file.build(output, command + command_suffix, link_deps, implicit=list(implicit_deps), + order_only=list(order_deps), variables=extra_bindings) return linked_binary @@ -1263,7 +1271,7 @@ def WriteTarget(self, spec, config_name, config, link_deps, compile_deps): self.target.type = 'none' elif spec['type'] == 'static_library': self.target.binary = self.ComputeOutput(spec) - if (self.flavor not in ('mac', 'openbsd', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not self.is_standalone_static_library): self.ninja.build(self.target.binary, 'alink_thin', link_deps, order_only=compile_deps) diff --git a/gyp/pylib/gyp/generator/xcode.py b/gyp/pylib/gyp/generator/xcode.py index 482b53ac8a..0e3fb9301e 100644 --- a/gyp/pylib/gyp/generator/xcode.py +++ b/gyp/pylib/gyp/generator/xcode.py @@ -87,6 +87,8 @@ 'mac_framework_private_headers', ] +generator_filelist_paths = None + # Xcode's standard set of library directories, which don't need to be duplicated # in LIBRARY_SEARCH_PATHS. This list is not exhaustive, but that's okay. xcode_standard_library_dirs = frozenset([ @@ -578,6 +580,26 @@ def PerformBuild(data, configurations, params): subprocess.check_call(arguments) +def CalculateGeneratorInputInfo(params): + toplevel = params['options'].toplevel_dir + if params.get('flavor') == 'ninja': + generator_dir = os.path.relpath(params['options'].generator_output or '.') + output_dir = params.get('generator_flags', {}).get('output_dir', 'out') + output_dir = os.path.normpath(os.path.join(generator_dir, output_dir)) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles-xcode-ninja')) + else: + output_dir = os.path.normpath(os.path.join(toplevel, 'xcodebuild')) + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, output_dir, 'gypfiles')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + + def GenerateOutput(target_list, target_dicts, data, params): # Optionally configure each spec to use ninja as the external builder. ninja_wrapper = params.get('flavor') == 'ninja' @@ -590,6 +612,15 @@ def GenerateOutput(target_list, target_dicts, data, params): parallel_builds = generator_flags.get('xcode_parallel_builds', True) serialize_all_tests = \ generator_flags.get('xcode_serialize_all_test_runs', True) + upgrade_check_project_version = \ + generator_flags.get('xcode_upgrade_check_project_version', None) + + # Format upgrade_check_project_version with leading zeros as needed. + if upgrade_check_project_version: + upgrade_check_project_version = str(upgrade_check_project_version) + while len(upgrade_check_project_version) < 4: + upgrade_check_project_version = '0' + upgrade_check_project_version + skip_excluded_files = \ not generator_flags.get('xcode_list_excluded_files', True) xcode_projects = {} @@ -604,9 +635,17 @@ def GenerateOutput(target_list, target_dicts, data, params): xcode_projects[build_file] = xcp pbxp = xcp.project + # Set project-level attributes from multiple options + project_attributes = {}; if parallel_builds: - pbxp.SetProperty('attributes', - {'BuildIndependentTargetsInParallel': 'YES'}) + project_attributes['BuildIndependentTargetsInParallel'] = 'YES' + if upgrade_check_project_version: + project_attributes['LastUpgradeCheck'] = upgrade_check_project_version + project_attributes['LastTestingUpgradeCheck'] = \ + upgrade_check_project_version + project_attributes['LastSwiftUpdateCheck'] = \ + upgrade_check_project_version + pbxp.SetProperty('attributes', project_attributes) # Add gyp/gypi files to project if not generator_flags.get('standalone'): @@ -648,6 +687,7 @@ def GenerateOutput(target_list, target_dicts, data, params): 'loadable_module': 'com.googlecode.gyp.xcode.bundle', 'shared_library': 'com.apple.product-type.library.dynamic', 'static_library': 'com.apple.product-type.library.static', + 'mac_kernel_extension': 'com.apple.product-type.kernel-extension', 'executable+bundle': 'com.apple.product-type.application', 'loadable_module+bundle': 'com.apple.product-type.bundle', 'loadable_module+xctest': 'com.apple.product-type.bundle.unit-test', @@ -655,7 +695,9 @@ def GenerateOutput(target_list, target_dicts, data, params): 'executable+extension+bundle': 'com.apple.product-type.app-extension', 'executable+watch+extension+bundle': 'com.apple.product-type.watchkit-extension', - 'executable+watch+bundle': 'com.apple.product-type.application.watchapp', + 'executable+watch+bundle': + 'com.apple.product-type.application.watchapp', + 'mac_kernel_extension+bundle': 'com.apple.product-type.kernel-extension', } target_properties = { diff --git a/gyp/pylib/gyp/input.py b/gyp/pylib/gyp/input.py index bc68c3765d..20178672b2 100644 --- a/gyp/pylib/gyp/input.py +++ b/gyp/pylib/gyp/input.py @@ -28,7 +28,12 @@ # A list of types that are treated as linkable. -linkable_types = ['executable', 'shared_library', 'loadable_module'] +linkable_types = [ + 'executable', + 'shared_library', + 'loadable_module', + 'mac_kernel_extension', +] # A list of sections that contain links to other targets. dependency_sections = ['dependencies', 'export_dependent_settings'] @@ -1724,11 +1729,12 @@ def _LinkDependenciesInternal(self, targets, include_shared_libraries, dependencies.add(self.ref) return dependencies - # Executables and loadable modules are already fully and finally linked. - # Nothing else can be a link dependency of them, there can only be - # dependencies in the sense that a dependent target might run an - # executable or load the loadable_module. - if not initial and target_type in ('executable', 'loadable_module'): + # Executables, mac kernel extensions and loadable modules are already fully + # and finally linked. Nothing else can be a link dependency of them, there + # can only be dependencies in the sense that a dependent target might run + # an executable or load the loadable_module. + if not initial and target_type in ('executable', 'loadable_module', + 'mac_kernel_extension'): return dependencies # Shared libraries are already fully linked. They should only be included @@ -2479,7 +2485,7 @@ def ValidateTargetType(target, target_dict): """ VALID_TARGET_TYPES = ('executable', 'loadable_module', 'static_library', 'shared_library', - 'none') + 'mac_kernel_extension', 'none') target_type = target_dict.get('type', None) if target_type not in VALID_TARGET_TYPES: raise GypError("Target %s has an invalid target type '%s'. " diff --git a/gyp/pylib/gyp/xcode_emulation.py b/gyp/pylib/gyp/xcode_emulation.py index 407ead074b..b06bdc4e8b 100644 --- a/gyp/pylib/gyp/xcode_emulation.py +++ b/gyp/pylib/gyp/xcode_emulation.py @@ -1452,6 +1452,7 @@ def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, # These are filled in on a as-needed basis. env = { + 'BUILT_FRAMEWORKS_DIR' : built_products_dir, 'BUILT_PRODUCTS_DIR' : built_products_dir, 'CONFIGURATION' : configuration, 'PRODUCT_NAME' : xcode_settings.GetProductName(), diff --git a/gyp/pylib/gyp/xcodeproj_file.py b/gyp/pylib/gyp/xcodeproj_file.py index 034a0d2d4f..d08b7f7770 100644 --- a/gyp/pylib/gyp/xcodeproj_file.py +++ b/gyp/pylib/gyp/xcodeproj_file.py @@ -1492,6 +1492,7 @@ def __init__(self, properties=None, id=None, parent=None): 'icns': 'image.icns', 'java': 'sourcecode.java', 'js': 'sourcecode.javascript', + 'kext': 'wrapper.kext', 'm': 'sourcecode.c.objc', 'mm': 'sourcecode.cpp.objcpp', 'nib': 'wrapper.nib', @@ -1951,6 +1952,7 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # path_tree_to_subfolder maps names of Xcode variables to the associated # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object. path_tree_to_subfolder = { + 'BUILT_FRAMEWORKS_DIR': 10, # Frameworks Directory 'BUILT_PRODUCTS_DIR': 16, # Products Directory # Other types that can be chosen via the Xcode UI. # TODO(mark): Map Xcode variable names to these. @@ -1958,7 +1960,6 @@ class PBXCopyFilesBuildPhase(XCBuildPhase): # : 6, # Executables: 6 # : 7, # Resources # : 15, # Java Resources - # : 10, # Frameworks # : 11, # Shared Frameworks # : 12, # Shared Support # : 13, # PlugIns @@ -2262,6 +2263,8 @@ class PBXNativeTarget(XCTarget): '', '.xctest'], 'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib', '', '.so'], + 'com.apple.product-type.kernel-extension': ['wrapper.kext', + '', '.kext'], } def __init__(self, properties=None, id=None, parent=None,