diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2011-09-08 01:11:08 -0700 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2011-09-08 01:11:45 -0700 |
commit | ee2c12d48e52c066c74fd646d6a38e880b0bfa0a (patch) | |
tree | d579379ec18b28d2aa3b6091ad5df9ee719fc0e8 /tools | |
parent | 0bca54444a5bb869ddebaadc65cd0bf381bb1e81 (diff) | |
download | node-new-ee2c12d48e52c066c74fd646d6a38e880b0bfa0a.tar.gz |
Upgrade GYP to r1034
Diffstat (limited to 'tools')
-rw-r--r-- | tools/gyp/pylib/gyp/MSVSNew.py | 5 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/dump_dependency_json.py | 9 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/make.py | 200 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/msvs.py | 36 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/ninja.py | 375 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/input.py | 43 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/ninja_syntax.py | 6 |
7 files changed, 442 insertions, 232 deletions
diff --git a/tools/gyp/pylib/gyp/MSVSNew.py b/tools/gyp/pylib/gyp/MSVSNew.py index 9b9b848fe7..ae8cbee688 100644 --- a/tools/gyp/pylib/gyp/MSVSNew.py +++ b/tools/gyp/pylib/gyp/MSVSNew.py @@ -248,10 +248,13 @@ class MSVSSolution: sln_root = os.path.split(self.path)[0] for e in all_entries: relative_path = gyp.common.RelativePath(e.path, sln_root) + # msbuild does not accept an empty folder_name. + # use '.' in case relative_path is empty. + folder_name = relative_path.replace('/', '\\') or '.' f.write('Project("%s") = "%s", "%s", "%s"\r\n' % ( e.entry_type_guid, # Entry type GUID e.name, # Folder name - relative_path.replace('/', '\\'), # Folder name (again) + folder_name, # Folder name (again) e.get_guid(), # Entry GUID )) diff --git a/tools/gyp/pylib/gyp/generator/dump_dependency_json.py b/tools/gyp/pylib/gyp/generator/dump_dependency_json.py index aacf232c8b..ebb7ff97be 100644 --- a/tools/gyp/pylib/gyp/generator/dump_dependency_json.py +++ b/tools/gyp/pylib/gyp/generator/dump_dependency_json.py @@ -32,6 +32,15 @@ def CalculateVariables(default_variables, params): default_variables['OS'] = generator_flags.get('os', 'linux') +def CalculateGeneratorInputInfo(params): + """Calculate the generator specific info that gets fed to input (called by + gyp).""" + generator_flags = params.get('generator_flags', {}) + if generator_flags.get('adjust_static_libraries', False): + global generator_wants_static_library_dependencies_adjusted + generator_wants_static_library_dependencies_adjusted = True + + def GenerateOutput(target_list, target_dicts, data, params): # Map of target -> list of targets it depends on. edges = {} diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index b8914785bf..56a02eb06b 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -43,7 +43,6 @@ generator_default_variables = { 'INTERMEDIATE_DIR': '$(obj).$(TOOLSET)/geni', 'SHARED_INTERMEDIATE_DIR': '$(obj)/gen', 'PRODUCT_DIR': '$(builddir)', - 'LIB_DIR': '$(obj).$(TOOLSET)', 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python. 'RULE_INPUT_PATH': '$(abspath $<)', 'RULE_INPUT_EXT': '$(suffix $<)', @@ -76,6 +75,8 @@ def CalculateVariables(default_variables, params): default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib') default_variables.setdefault('SHARED_LIB_DIR', generator_default_variables['PRODUCT_DIR']) + default_variables.setdefault('LIB_DIR', + generator_default_variables['PRODUCT_DIR']) # Copy additional generator configuration data from Xcode, which is shared # by the Mac Make generator. @@ -95,6 +96,7 @@ def CalculateVariables(default_variables, params): default_variables.setdefault('OS', 'linux') default_variables.setdefault('SHARED_LIB_SUFFIX', '.so') default_variables.setdefault('SHARED_LIB_DIR','$(builddir)/lib.$(TOOLSET)') + default_variables.setdefault('LIB_DIR', '$(obj).$(TOOLSET)') def CalculateGeneratorInputInfo(params): @@ -230,8 +232,10 @@ all_deps := # # This will allow make to invoke N linker processes as specified in -jN. FLOCK ?= %(flock)s $(builddir)/linker.lock -LINK ?= $(FLOCK) $(CXX) +%(make_global_settings)s + +LINK ?= $(FLOCK) $(CXX) CC.target ?= $(CC) CFLAGS.target ?= $(CFLAGS) CXX.target ?= $(CXX) @@ -738,6 +742,14 @@ class XcodeSettings(object): else: return self._GetStandaloneBinaryPath() + def _SdkPath(self): + sdk_root = 'macosx10.5' + if 'SDKROOT' in self._Settings(): + sdk_root = self._Settings()['SDKROOT'] + if sdk_root.startswith('macosx'): + sdk_root = 'MacOSX' + sdk_root[len('macosx'):] + return '/Developer/SDKs/%s.sdk' % sdk_root + def GetCflags(self, configname): """Returns flags that need to be added to .c, .cc, .m, and .mm compilations.""" @@ -747,11 +759,9 @@ class XcodeSettings(object): self.configname = configname cflags = [] - sdk_root = 'Mac10.5' + sdk_root = self._SdkPath() if 'SDKROOT' in self._Settings(): - sdk_root = self._Settings()['SDKROOT'] - cflags.append('-isysroot /Developer/SDKs/%s.sdk' % sdk_root) - sdk_root_dir = '/Developer/SDKs/%s.sdk' % sdk_root + cflags.append('-isysroot %s' % sdk_root) if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'): cflags.append('-fasm-blocks') @@ -770,22 +780,21 @@ class XcodeSettings(object): self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s') - dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') - if dbg_format == 'none': - pass - elif dbg_format == 'dwarf': - cflags.append('-gdwarf-2') - elif dbg_format == 'stabs': - raise NotImplementedError('stabs debug format is not supported yet.') - elif dbg_format == 'dwarf-with-dsym': - # TODO(thakis): this is needed for mac_breakpad chromium builds, but not - # for regular chromium builds. - # -gdwarf-2 as well, but needs to invoke dsymutil after linking too: - # dsymutil build/Default/TestAppGyp.app/Contents/MacOS/TestAppGyp \ - # -o build/Default/TestAppGyp.app.dSYM - raise NotImplementedError('dsym debug format is not supported yet.') - else: - raise NotImplementedError('Unknown debug format %s' % dbg_format) + if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'): + dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') + if dbg_format == 'dwarf': + cflags.append('-gdwarf-2') + elif dbg_format == 'stabs': + raise NotImplementedError('stabs debug format is not supported yet.') + elif dbg_format == 'dwarf-with-dsym': + # TODO(thakis): this is needed for mac_breakpad chromium builds, but not + # for regular chromium builds. + # -gdwarf-2 as well, but needs to invoke dsymutil after linking too: + # dsymutil build/Default/TestAppGyp.app/Contents/MacOS/TestAppGyp \ + # -o build/Default/TestAppGyp.app.dSYM + raise NotImplementedError('dsym debug format is not supported yet.') + else: + raise NotImplementedError('Unknown debug format %s' % dbg_format) if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'): cflags.append('-fvisibility=hidden') @@ -802,6 +811,9 @@ class XcodeSettings(object): self._WarnUnimplemented('ARCHS') self._WarnUnimplemented('COPY_PHASE_STRIP') self._WarnUnimplemented('DEPLOYMENT_POSTPROCESSING') + self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS') + self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS') + self._WarnUnimplemented('GCC_ENABLE_OBJC_GC') self._WarnUnimplemented('INFOPLIST_PREPROCESS') self._WarnUnimplemented('INFOPLIST_PREPROCESSOR_DEFINITIONS') self._WarnUnimplemented('STRIPFLAGS') @@ -816,7 +828,7 @@ class XcodeSettings(object): config = self.spec['configurations'][self.configname] framework_dirs = config.get('mac_framework_dirs', []) for directory in framework_dirs: - cflags.append('-F ' + os.path.join(sdk_root_dir, directory)) + cflags.append('-F ' + os.path.join(sdk_root, directory)) self.configname = None return cflags @@ -891,8 +903,8 @@ class XcodeSettings(object): ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s') self._Appendf( ldflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') - self._Appendf( - ldflags, 'SDKROOT', '-isysroot /Developer/SDKs/%s.sdk') + if 'SDKROOT' in self._Settings(): + ldflags.append('-isysroot ' + self._SdkPath()) for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): ldflags.append('-L' + library_path) @@ -904,9 +916,7 @@ class XcodeSettings(object): # TODO: Do not hardcode arch. Supporting fat binaries will be annoying. ldflags.append('-arch i386') - # Xcode adds the product directory by default. It writes static libraries - # into the product directory. So add both. - ldflags.append('-L' + generator_default_variables['LIB_DIR']) + # Xcode adds the product directory by default. ldflags.append('-L' + generator_default_variables['PRODUCT_DIR']) install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME') @@ -955,6 +965,23 @@ class XcodeSettings(object): self.configname = None return ldflags + def GetPerTargetSettings(self): + """Gets a list of all the per-target settings. This will only fetch keys + whose values are the same across all configurations.""" + first_pass = True + result = {} + for configname in sorted(self.xcode_settings.keys()): + if first_pass: + result = dict(self.xcode_settings[configname]) + first_pass = False + else: + for key, value in self.xcode_settings[configname].iteritems(): + if key not in result: + continue + elif result[key] != value: + del result[key] + return result + def GetPerTargetSetting(self, setting, default=None): """Tries to get xcode_settings.setting from spec. Assumes that the setting has the same value in all configurations and throws otherwise.""" @@ -1531,7 +1558,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD path = generator_default_variables['PRODUCT_DIR'] dest_plist = os.path.join(path, self.xcode_settings.GetBundlePlistPath()) dest_plist = QuoteSpaces(dest_plist) - self.WriteXcodeEnv(dest_plist, spec) # plists can contain envvars. + extra_settings = self.xcode_settings.GetPerTargetSettings() + # plists can contain envvars and substitute them into the file.. + self.WriteXcodeEnv(dest_plist, spec, additional_settings=extra_settings) self.WriteDoCmd([dest_plist], [info_plist], 'mac_tool,,,copy-info-plist', part_of_all=True) bundle_deps.append(dest_plist) @@ -1698,6 +1727,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD return target_prefix + target + target_ext + def _InstallImmediately(self): + return self.toolset == 'target' and self.flavor == 'mac' and self.type in ( + 'static_library', 'executable', 'shared_library', 'loadable_module') + + def ComputeOutput(self, spec): """Return the 'output' (full output path) of a gyp spec. @@ -1710,7 +1744,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD return '' # Doesn't have any output. path = os.path.join('$(obj).' + self.toolset, self.path) - if self.type == 'executable': + if self.type == 'executable' or self._InstallImmediately(): path = '$(builddir)' path = spec.get('product_dir', path) return os.path.join(path, self.ComputeOutputBasename(spec)) @@ -1838,7 +1872,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # After the framework is built, package it. Needs to happen before # postbuilds, since postbuilds depend on this. if self.type in ('shared_library', 'loadable_module'): - self.WriteLn('\t@$(call do_cmd,mac_package_framework,0,0,%s)' % + self.WriteLn('\t@$(call do_cmd,mac_package_framework,,,%s)' % self.xcode_settings.GetFrameworkVersion()) # Bundle postbuilds can depend on the whole bundle, so run them after @@ -1860,6 +1894,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD if postbuilds: assert not self.is_mac_bundle, ('Postbuilds for bundles should be done ' 'on the bundle, not the binary (target \'%s\')' % self.target) + assert 'product_dir' not in spec, ('Postbuilds do not work with ' + 'custom product_dir') self.WriteXcodeEnv(self.output_binary, spec) # For postbuilds postbuilds = [EscapeShellArgument(p) for p in postbuilds] self.WriteLn('%s: builddir := $(abs_builddir)' % self.output_binary) @@ -1921,8 +1957,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD file_desc = 'executable' install_path = self._InstallableTargetInstallPath() installable_deps = [self.output] - if self.is_mac_bundle: - # Bundles are created in their install_path location immediately. + if self.flavor == 'mac' and not 'product_dir' in spec: + # On mac, products are created in install_path immediately. assert install_path == self.output, '%s != %s' % ( install_path, self.output) @@ -2102,10 +2138,15 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD modules.append(filename[len(prefix):-len(suffix)]) return modules + # Retrieve the default value of 'SHARED_LIB_SUFFIX' + params = {'flavor': 'linux'} + default_variables = {} + CalculateVariables(default_variables, params) + self.WriteList( DepsToModules(link_deps, generator_default_variables['SHARED_LIB_PREFIX'], - generator_default_variables['SHARED_LIB_SUFFIX']), + default_variables['SHARED_LIB_SUFFIX']), 'LOCAL_SHARED_LIBRARIES') self.WriteList( DepsToModules(link_deps, @@ -2132,26 +2173,17 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD for a full list.""" if self.flavor != 'mac': return {} + built_products_dir = generator_default_variables['PRODUCT_DIR'] def StripProductDir(s): - product_dir = generator_default_variables['PRODUCT_DIR'] - assert s.startswith(product_dir), s - return s[len(product_dir) + 1:] + assert s.startswith(built_products_dir), s + return s[len(built_products_dir) + 1:] product_name = spec.get('product_name', self.output) - # Some postbuilds try to read a build output file at - # ""${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}". Static libraries end up - # "$(obj).target", so - # BUILT_PRODUCTS_DIR is $(builddir) - # FULL_PRODUCT_NAME is $(out).target/path/to/lib.a - # Since $(obj) contains out/Debug already, the postbuild - # would get out/Debug/out/Debug/obj.target/path/to/lib.a. To prevent this, - # remove the "out/Debug" prefix from $(obj). - if product_name.startswith('$(obj)'): - product_name = ( - '$(subst $(builddir)/,,$(obj))' + product_name[len('$(obj)'):]) + if self._InstallImmediately(): + if product_name.startswith(built_products_dir): + product_name = StripProductDir(product_name) - built_products_dir = generator_default_variables['PRODUCT_DIR'] srcroot = self.path if target_relative_path: built_products_dir = os.path.relpath(built_products_dir, srcroot) @@ -2171,11 +2203,14 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD } if self.type in ('executable', 'shared_library'): env['EXECUTABLE_NAME'] = os.path.basename(self.output_binary) - if self.type in ('executable', 'shared_library', 'loadable_module'): + if self.type in ( + 'executable', 'static_library', 'shared_library', 'loadable_module'): env['EXECUTABLE_PATH'] = self.xcode_settings.GetExecutablePath() if self.is_mac_bundle: env['CONTENTS_FOLDER_PATH'] = \ self.xcode_settings.GetBundleContentsFolderPath() + env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \ + self.xcode_settings.GetBundleResourceFolder() env['INFOPLIST_PATH'] = self.xcode_settings.GetBundlePlistPath() # TODO(thakis): Remove this. @@ -2186,16 +2221,40 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD return env - def WriteXcodeEnv(self, target, spec, target_relative_path=False): - env = self.GetXcodeEnv(spec, target_relative_path) - # For - # foo := a\ b - # the escaped space does the right thing. For - # export foo := a\ b - # it does not -- the backslash is written to the env as literal character. - # Hence, unescape all spaces here. + def WriteXcodeEnv(self, + target, + spec, + target_relative_path=False, + additional_settings={}): + env = additional_settings + env.update(self.GetXcodeEnv(spec, target_relative_path)) + + # Keys whose values will not have $(builddir) replaced with $(abs_builddir). + # These have special substitution rules in some cases; see above in + # GetXcodeEnv() for the full rationale. + keys_to_not_absolutify = ('PRODUCT_NAME', 'FULL_PRODUCT_NAME') + + # Perform some transformations that are required to mimic Xcode behavior. for k in env: + # Values that are not strings but are, for example, lists or tuples such + # as LDFLAGS or CFLAGS, should not be written out because they are + # not needed and it's undefined how multi-valued keys should be written. + if not isinstance(env[k], str): + continue + + # For + # foo := a\ b + # the escaped space does the right thing. For + # export foo := a\ b + # it does not -- the backslash is written to the env as literal character. + # Hence, unescape all spaces here. v = env[k].replace(r'\ ', ' ') + + # Xcode works purely with absolute paths. When writing env variables to + # mimic its usage, replace $(builddir) with $(abs_builddir). + if k not in keys_to_not_absolutify: + v = v.replace('$(builddir)', '$(abs_builddir)') + self.WriteLn('%s: export %s := %s' % (target, k, v)) @@ -2399,6 +2458,26 @@ def GenerateOutput(target_list, target_dicts, data, params): }) header_params.update(RunSystemTests(flavor)) + build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) + make_global_settings_dict = data[build_file].get('make_global_settings', {}) + make_global_settings = '' + for key, value in make_global_settings_dict: + if value[0] != '$': + value = '$(abspath %s)' % value + if key == 'LINK': + make_global_settings += '%s ?= $(FLOCK) %s\n' % (key, value) + elif key in ['CC', 'CXX']: + make_global_settings += ( + 'ifneq (,$(filter $(origin %s), undefined default))\n' % key) + # Let gyp-time envvars win over global settings. + if key in os.environ: + value = os.environ[key] + make_global_settings += ' %s = %s\n' % (key, value) + make_global_settings += 'endif\n' + else: + make_global_settings += '%s ?= %s\n' % (key, value) + header_params['make_global_settings'] = make_global_settings + ensure_directory_exists(makefile_path) root_makefile = open(makefile_path, 'w') root_makefile.write(SHARED_HEADER % header_params) @@ -2433,6 +2512,11 @@ def GenerateOutput(target_list, target_dicts, data, params): for qualified_target in target_list: build_file, target, toolset = gyp.common.ParseQualifiedTarget( qualified_target) + + this_make_global_settings = data[build_file].get('make_global_settings', {}) + assert make_global_settings_dict == this_make_global_settings, ( + "make_global_settings needs to be the same for all targets.") + build_files.add(gyp.common.RelativePath(build_file, options.toplevel_dir)) included_files = data[build_file]['included_files'] for included_file in included_files: diff --git a/tools/gyp/pylib/gyp/generator/msvs.py b/tools/gyp/pylib/gyp/generator/msvs.py index 92cbf776a7..dfecbda786 100644 --- a/tools/gyp/pylib/gyp/generator/msvs.py +++ b/tools/gyp/pylib/gyp/generator/msvs.py @@ -56,7 +56,7 @@ generator_default_variables = { # of the warnings. # TODO(jeanluc) I had: 'LIB_DIR': '$(OutDir)lib', - #'LIB_DIR': '$(OutDir)/lib', + 'LIB_DIR': '$(OutDir)/lib', 'RULE_INPUT_ROOT': '$(InputName)', 'RULE_INPUT_EXT': '$(InputExt)', 'RULE_INPUT_NAME': '$(InputFileName)', @@ -575,18 +575,7 @@ def _GenerateExternalRules(rules, output_dir, spec, 'IntDir=$(IntDir)', '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}', '-f', filename] - - # Currently this weird argument munging is used to duplicate the way a - # python script would need to be run as part of the chrome tree. - # Eventually we should add some sort of rule_default option to set this - # per project. For now the behavior chrome needs is the default. - mcs = rule.get('msvs_cygwin_shell') - if mcs is None: - mcs = int(spec.get('msvs_cygwin_shell', 1)) - elif isinstance(mcs, str): - mcs = int(mcs) - quote_cmd = int(rule.get('msvs_quote_cmd', 1)) - cmd = _BuildCommandLineForRuleRaw(spec, cmd, mcs, False, quote_cmd) + cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True) # Insert makefile as 0'th input, so it gets the action attached there, # as this is easier to understand from in the IDE. all_inputs = list(all_inputs) @@ -1117,7 +1106,7 @@ def _GetOutputFilePathAndTool(spec): # TODO(jeanluc) If we want to avoid the MSB8012 warnings in # VisualStudio 2010, we will have to change the value of $(OutDir) # to contain the \lib suffix, rather than doing it as below. - 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)\\', '.lib'), + 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)\\lib\\', '.lib'), 'dummy_executable': ('VCLinkerTool', 'Link', '$(IntDir)\\', '.junk'), } output_file_props = output_file_map.get(spec['type']) @@ -2650,6 +2639,7 @@ def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, def _AddSources2(spec, sources, exclusions, grouped_sources, extension_to_rule_name, sources_handled_by_action): + extensions_excluded_from_precompile = [] for source in sources: if isinstance(source, MSVSProject.Filter): _AddSources2(spec, source.contents, exclusions, grouped_sources, @@ -2670,12 +2660,30 @@ def _AddSources2(spec, sources, exclusions, grouped_sources, for config_name, configuration in spec['configurations'].iteritems(): precompiled_source = configuration.get('msvs_precompiled_source', '') precompiled_source = _FixPath(precompiled_source) + if not extensions_excluded_from_precompile: + # If the precompiled header is generated by a C source, we must + # not try to use it for C++ sources, and vice versa. + basename, extension = os.path.splitext(precompiled_source) + if extension == '.c': + extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx'] + else: + extensions_excluded_from_precompile = ['.c'] + if precompiled_source == source: condition = _GetConfigurationCondition(config_name, configuration) detail.append(['PrecompiledHeader', {'Condition': condition}, 'Create' ]) + else: + # Turn off precompiled header usage for source files of a + # different type than the file that generated the + # precompiled header. + for extension in extensions_excluded_from_precompile: + if source.endswith(extension): + detail.append(['PrecompiledHeader', '']) + detail.append(['ForcedIncludeFiles', '']) + group, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name) grouped_sources[group].append([element, {'Include': source}] + detail) diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index 8a8e2900ce..b63e42cdb0 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -23,16 +23,20 @@ generator_default_variables = { 'STATIC_LIB_SUFFIX': '.a', 'SHARED_LIB_PREFIX': 'lib', 'SHARED_LIB_SUFFIX': '.so', - # TODO: intermediate dir should *not* be shared between different targets. - # Unfortunately, whatever we provide here gets written into many different - # places within the gyp spec so it's difficult to make it target-specific. - # Apparently we've made it this far with one global path for the make build - # we're safe for now. - 'INTERMEDIATE_DIR': '$b/geni', - 'SHARED_INTERMEDIATE_DIR': '$b/gen', - 'PRODUCT_DIR': '$b', - 'SHARED_LIB_DIR': '$b/lib', - 'LIB_DIR': '$b', + + # Gyp expects the following variables to be expandable by the build + # system to the appropriate locations. Ninja prefers paths to be + # known at compile time. To resolve this, introduce special + # variables starting with $! (which begin with a $ so gyp knows it + # should be treated as a path, but is otherwise an invalid + # ninja/shell variable) that are passed to gyp here but expanded + # before writing out into the target .ninja files; see + # ExpandSpecial. + 'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR', + 'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen', + 'PRODUCT_DIR': '$!PRODUCT_DIR', + 'SHARED_LIB_DIR': '$!PRODUCT_DIR/lib', + 'LIB_DIR': '', # Special variables that may be used by gyp 'rule' targets. # We generate definitions for these variables on the fly when processing a @@ -43,51 +47,11 @@ generator_default_variables = { 'RULE_INPUT_NAME': '$name', } -NINJA_BASE = """\ -builddir = %(builddir)s -# Short alias for builddir. -b = %(builddir)s - -cc = %(cc)s -cxx = %(cxx)s - -rule cc - depfile = $out.d - description = CC $out - command = $cc -MMD -MF $out.d $defines $includes $cflags $cflags_c $ - -c $in -o $out - -rule cxx - depfile = $out.d - description = CXX $out - command = $cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc $ - -c $in -o $out - -rule alink - description = AR $out - command = rm -f $out && ar rcsT $out $in - -rule solink - description = SOLINK $out - command = g++ -Wl,--threads -Wl,--thread-count=4 $ - -shared $ldflags -o $out -Wl,-soname=$soname $ - -Wl,--whole-archive $in -Wl,--no-whole-archive $libs - -rule link - description = LINK $out - command = g++ -Wl,--threads -Wl,--thread-count=4 $ - $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib $ - -Wl,--start-group $in -Wl,--end-group $libs - -rule stamp - description = STAMP $out - command = touch $out - -rule copy - description = COPY $in $out - command = ln -f $in $out 2>/dev/null || cp -af $in $out - -""" +# TODO: enable cross compiling once we figure out: +# - how to not build extra host objects in the non-cross-compile case. +# - how to decide what the host compiler is (should not just be $cc). +# - need ld_host as well. +generator_supports_multiple_toolsets = False def StripPrefix(arg, prefix): @@ -106,19 +70,34 @@ def MaybeQuoteShellArgument(arg): return arg +def InvertRelativePath(path): + """Given a relative path like foo/bar, return the inverse relative path: + the path from the relative path back to the origin dir. + + E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path))) + should always produce the empty string.""" + + if not path: + return path + # Only need to handle relative paths into subdirectories for now. + assert '..' not in path, path + depth = len(path.split('/')) + return '/'.join(['..'] * depth) + + # A small discourse on paths as used within the Ninja build: +# All files we produce (both at gyp and at build time) appear in the +# build directory (e.g. out/Debug). # # Paths within a given .gyp file are always relative to the directory # containing the .gyp file. Call these "gyp paths". This includes # sources as well as the starting directory a given gyp rule/action -# expects to be run from. We call this directory "base_dir" within -# the per-.gyp-file NinjaWriter code. +# expects to be run from. We call the path from the source root to +# the gyp file the "base directory" within the per-.gyp-file +# NinjaWriter code. # -# All paths as written into the .ninja files are relative to the root -# of the tree. Call these paths "ninja paths". We set up the ninja -# variable "$b" to be the path to the root of the build output, -# e.g. out/Debug/. All files we produce (both at gyp and at build -# time) appear in that output directory. +# All paths as written into the .ninja files are relative to the build +# directory. Call these paths "ninja paths". # # We translate between these two notions of paths with two helper # functions: @@ -131,24 +110,59 @@ def MaybeQuoteShellArgument(arg): # to the input file name as well as the output target name. class NinjaWriter: - def __init__(self, target_outputs, base_dir, output_file): + def __init__(self, target_outputs, base_dir, build_dir, output_file): + """ + base_dir: path from source root to directory containing this gyp file, + by gyp semantics, all input paths are relative to this + build_dir: path from source root to build output + """ + self.target_outputs = target_outputs - # The root-relative path to the source .gyp file; by gyp - # semantics, all input paths are relative to this. self.base_dir = base_dir + self.build_dir = build_dir self.ninja = ninja_syntax.Writer(output_file) + # Relative path from build output dir to base dir. + self.build_to_base = os.path.join(InvertRelativePath(build_dir), base_dir) + # Relative path from base dir to build dir. + self.base_to_build = os.path.join(InvertRelativePath(base_dir), build_dir) + + def ExpandSpecial(self, path, product_dir=None): + """Expand specials like $!PRODUCT_DIR in |path|. + + If |product_dir| is None, assumes the cwd is already the product + dir. Otherwise, |product_dir| is the relative path to the product + dir. + """ + + PRODUCT_DIR = '$!PRODUCT_DIR' + if PRODUCT_DIR in path: + if product_dir: + path = path.replace(PRODUCT_DIR, product_dir) + else: + path = path.replace(PRODUCT_DIR + '/', '') + path = path.replace(PRODUCT_DIR, '.') + + INTERMEDIATE_DIR = '$!INTERMEDIATE_DIR' + if INTERMEDIATE_DIR in path: + int_dir = self.GypPathToUniqueOutput('gen') + # GypPathToUniqueOutput generates a path relative to the product dir, + # so insert product_dir in front if it is provided. + path = path.replace(INTERMEDIATE_DIR, + os.path.join(product_dir or '', int_dir)) + + return path + def GypPathToNinja(self, path): """Translate a gyp path to a ninja path. See the above discourse on path conversions.""" - if path.startswith('$'): - # If the path contains a reference to a ninja variable, we know - # it's already relative to the source root. - return path - return os.path.normpath(os.path.join(self.base_dir, path)) + if path.startswith('$!'): + return self.ExpandSpecial(path) + assert '$' not in path, path + return os.path.normpath(os.path.join(self.build_to_base, path)) - def GypPathToUniqueOutput(self, path, qualified=False): + def GypPathToUniqueOutput(self, path, qualified=True): """Translate a gyp path to a ninja path for writing output. If qualified is True, qualify the resulting filename with the name @@ -157,30 +171,28 @@ class NinjaWriter: See the above discourse on path conversions.""" - # It may seem strange to discard components of the path, but we are just - # attempting to produce a known-unique output filename; we don't want to - # reuse any global directory. - genvars = generator_default_variables - assert not genvars['SHARED_INTERMEDIATE_DIR'].startswith( - genvars['INTERMEDIATE_DIR']) - path = StripPrefix(path, genvars['INTERMEDIATE_DIR']) - path = StripPrefix(path, genvars['SHARED_INTERMEDIATE_DIR']) - path = StripPrefix(path, '/') - assert not path.startswith('$') + path = self.ExpandSpecial(path) + assert not path.startswith('$'), path # Translate the path following this scheme: # Input: foo/bar.gyp, target targ, references baz/out.o - # Output: $b/obj/foo/baz/targ.out.o (if qualified) - # $b/obj/foo/baz/out.o (otherwise) + # Output: obj/foo/baz/targ.out.o (if qualified) + # obj/foo/baz/out.o (otherwise) + # (and obj.host instead of obj for cross-compiles) # # Why this scheme and not some other one? # 1) for a given input, you can compute all derived outputs by matching # its path, even if the input is brought via a gyp file with '..'. # 2) simple files like libraries and stamps have a simple filename. + + obj = 'obj' + if self.toolset != 'target': + obj += '.' + self.toolset + path_dir, path_basename = os.path.split(path) if qualified: path_basename = self.name + '.' + path_basename - return os.path.normpath(os.path.join('$b/obj', self.base_dir, path_dir, + return os.path.normpath(os.path.join(obj, self.base_dir, path_dir, path_basename)) def StampPath(self, name): @@ -188,7 +200,7 @@ class NinjaWriter: Stamp files are used to collapse a dependency on a bunch of files into a single file.""" - return self.GypPathToUniqueOutput(name + '.stamp', qualified=True) + return self.GypPathToUniqueOutput(name + '.stamp') def WriteSpec(self, spec, config): """The main entry point for NinjaWriter: write the build rules for a spec. @@ -201,6 +213,7 @@ class NinjaWriter: return None self.name = spec['target_name'] + self.toolset = spec['toolset'] # Compute predepends for all rules. # prebuild is the dependencies this target depends on before @@ -230,13 +243,20 @@ class NinjaWriter: link_deps = self.WriteSources(config, sources, sources_predepends or prebuild) # Some actions/rules output 'sources' that are already object files. - link_deps += [f for f in sources if f.endswith('.o')] + link_deps += [self.GypPathToNinja(f) for f in sources if f.endswith('.o')] # The final output of our target depends on the last output of the # above steps. + output = None final_deps = link_deps or sources_predepends or prebuild if final_deps: - return self.WriteTarget(spec, config, final_deps) + output = self.WriteTarget(spec, config, final_deps) + if self.name != output and self.toolset == 'target': + # Write a short name to build this target. This benefits both the + # "build chrome" case as well as the gyp tests, which expect to be + # able to run actions and build libraries by their short name. + self.ninja.build(self.name, 'phony', output) + return output def WriteActionsRulesCopies(self, spec, extra_sources, prebuild): """Write out the Actions, Rules, and Copies steps. Return any outputs @@ -258,15 +278,28 @@ class NinjaWriter: return outputs + def GenerateDescription(self, verb, message, fallback): + """Generate and return a description of a build step. + + |verb| is the short summary, e.g. ACTION or RULE. + |message| is a hand-written description, or None if not available. + |fallback| is the gyp-level name of the step, usable as a fallback. + """ + if self.toolset != 'target': + verb += '(%s)' % self.toolset + if message: + return '%s %s' % (verb, self.ExpandSpecial(message)) + else: + return '%s %s: %s' % (verb, self.name, fallback) + def WriteActions(self, actions, extra_sources, prebuild): all_outputs = [] for action in actions: # First write out a rule for the action. name = action['action_name'] - if 'message' in action: - description = 'ACTION ' + action['message'] - else: - description = 'ACTION %s: %s' % (self.name, action['action_name']) + description = self.GenerateDescription('ACTION', + action.get('message', None), + name) rule_name = self.WriteNewNinjaRule(name, action['action'], description) inputs = [self.GypPathToNinja(i) for i in action['inputs']] @@ -289,10 +322,9 @@ class NinjaWriter: # First write out a rule for the rule action. name = rule['rule_name'] args = rule['action'] - if 'message' in rule: - description = 'RULE ' + rule['message'] - else: - description = 'RULE %s: %s $source' % (self.name, name) + description = self.GenerateDescription('RULE', + rule.get('message', None), + '%s $source' % name) rule_name = self.WriteNewNinjaRule(name, args, description) # TODO: if the command references the outputs directly, we should @@ -312,18 +344,25 @@ class NinjaWriter: for source in rule.get('rule_sources', []): basename = os.path.basename(source) root, ext = os.path.splitext(basename) - source = self.GypPathToNinja(source) + # Gather the list of outputs, expanding $vars if possible. outputs = [] for output in rule['outputs']: outputs.append(output.replace('$root', root)) + if int(rule.get('process_outputs_as_sources', False)): + extra_sources += outputs + extra_bindings = [] for var in needed_variables: if var == 'root': extra_bindings.append(('root', root)) elif var == 'source': - extra_bindings.append(('source', source)) + # '$source' is a parameter to the rule action, which means + # it shouldn't be converted to a Ninja path. But we don't + # want $!PRODUCT_DIR in there either. + source_expanded = self.ExpandSpecial(source, self.base_to_build) + extra_bindings.append(('source', source_expanded)) elif var == 'ext': extra_bindings.append(('ext', ext)) elif var == 'name': @@ -332,14 +371,12 @@ class NinjaWriter: assert var == None, repr(var) inputs = map(self.GypPathToNinja, rule.get('inputs', [])) - self.ninja.build(outputs, rule_name, source, + outputs = map(self.GypPathToNinja, outputs) + self.ninja.build(outputs, rule_name, self.GypPathToNinja(source), implicit=inputs, order_only=prebuild, variables=extra_bindings) - if int(rule.get('process_outputs_as_sources', False)): - extra_sources += outputs - all_outputs.extend(outputs) return all_outputs @@ -360,6 +397,10 @@ class NinjaWriter: def WriteSources(self, config, sources, predepends): """Write build rules to compile all of |sources|.""" + if self.toolset == 'host': + self.ninja.variable('cc', '$cc_host') + self.ninja.variable('cxx', '$cxx_host') + self.WriteVariableList('defines', ['-D' + MaybeQuoteShellArgument(ninja_syntax.escape(d)) for d in config.get('defines', [])]) @@ -382,7 +423,7 @@ class NinjaWriter: # TODO: should we assert here on unexpected extensions? continue input = self.GypPathToNinja(source) - output = self.GypPathToUniqueOutput(filename + '.o', qualified=True) + output = self.GypPathToUniqueOutput(filename + '.o') self.ninja.build(output, command, input, order_only=predepends) outputs.append(output) @@ -390,6 +431,14 @@ class NinjaWriter: return outputs def WriteTarget(self, spec, config, final_deps): + if spec['type'] == 'none': + # This target doesn't have any explicit final output, but is instead + # used for its effects before the final output (e.g. copies steps). + # Reuse the existing output if it's easy. + if len(final_deps) == 1: + return final_deps[0] + # Otherwise, fall through to writing out a stamp file. + output = self.ComputeOutput(spec) output_uses_linker = spec['type'] in ('executable', 'loadable_module', @@ -412,7 +461,7 @@ class NinjaWriter: else: # TODO: Chrome-specific HACK. Chrome runs this lastchange rule on # every build, but we don't want to rebuild when it runs. - if 'lastchange.stamp' not in input: + if 'lastchange' not in input: implicit_deps.add(input) final_deps.extend(list(extra_deps)) command_map = { @@ -426,9 +475,11 @@ class NinjaWriter: if output_uses_linker: self.WriteVariableList('ldflags', - gyp.common.uniquer(config.get('ldflags', []))) + gyp.common.uniquer(map(self.ExpandSpecial, + config.get('ldflags', [])))) self.WriteVariableList('libs', - gyp.common.uniquer(spec.get('libraries', []))) + gyp.common.uniquer(map(self.ExpandSpecial, + spec.get('libraries', [])))) extra_bindings = [] if command == 'solink': @@ -438,11 +489,6 @@ class NinjaWriter: implicit=list(implicit_deps), variables=extra_bindings) - # Write a short name to build this target. This benefits both the - # "build chrome" case as well as the gyp tests, which expect to be - # able to run actions and build libraries by their short name. - self.ninja.build(self.name, 'phony', output) - return output def ComputeOutputFileName(self, spec): @@ -495,17 +541,20 @@ class NinjaWriter: if 'product_dir' in spec: path = os.path.join(spec['product_dir'], filename) - return path + return self.ExpandSpecial(path) # Executables and loadable modules go into the output root, # libraries go into shared library dir, and everything else # goes into the normal place. if spec['type'] in ('executable', 'loadable_module'): - return os.path.join('$b', filename) + return filename elif spec['type'] == 'shared_library': - return os.path.join('$b/lib', filename) + libdir = 'lib' + if self.toolset != 'target': + libdir = 'lib/%s' % self.toolset + return os.path.join(libdir, filename) else: - return self.GypPathToUniqueOutput(filename) + return self.GypPathToUniqueOutput(filename, qualified=False) def WriteVariableList(self, var, values): if values is None: @@ -520,20 +569,19 @@ class NinjaWriter: # TODO: we shouldn't need to qualify names; we do it because # currently the ninja rule namespace is global, but it really # should be scoped to the subninja. - rule_name = ('%s.%s' % (self.name, name)).replace(' ', '_') + rule_name = self.name + if self.toolset == 'target': + rule_name += '.' + self.toolset + rule_name += '.' + name + rule_name = rule_name.replace(' ', '_') - cd = '' args = args[:] - if self.base_dir: - # gyp dictates that commands are run from the base directory. - # cd into the directory before running, and adjust all paths in - # the arguments point to the proper locations. - cd = 'cd %s; ' % self.base_dir - cdup = '../' * len(self.base_dir.split('/')) - for i, arg in enumerate(args): - arg = arg.replace('$b', cdup + '$b') - arg = arg.replace('$source', cdup + '$source') - args[i] = arg + + # gyp dictates that commands are run from the base directory. + # cd into the directory before running, and adjust paths in + # the arguments to point to the proper locations. + cd = 'cd %s; ' % self.build_to_base + args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args] command = cd + gyp.common.EncodePOSIXShellList(args) self.ninja.rule(rule_name, command, description) @@ -575,13 +623,53 @@ def GenerateOutput(target_list, target_dicts, data, params): # e.g. "out/Debug" builddir = os.path.join(generator_flags.get('output_dir', 'out'), config_name) - master_ninja = OpenOutput(os.path.join(options.toplevel_dir, builddir, - 'build.ninja')) - master_ninja.write(NINJA_BASE % { - 'builddir': builddir, - 'cc': os.environ.get('CC', 'gcc'), - 'cxx': os.environ.get('CXX', 'g++'), - }) + master_ninja = ninja_syntax.Writer( + OpenOutput(os.path.join(options.toplevel_dir, builddir, 'build.ninja')), + width=120) + + # TODO: compute cc/cxx/ld/etc. by command-line arguments and system tests. + master_ninja.variable('cc', os.environ.get('CC', 'gcc')) + master_ninja.variable('cxx', os.environ.get('CXX', 'g++')) + master_ninja.variable('ld', '$cxx -Wl,--threads -Wl,--thread-count=4') + master_ninja.variable('cc_host', '$cc') + master_ninja.variable('cxx_host', '$cxx') + master_ninja.newline() + + master_ninja.rule( + 'cc', + description='CC $out', + command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c ' + '-c $in -o $out'), + depfile='$out.d') + master_ninja.rule( + 'cxx', + description='CXX $out', + command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc ' + '-c $in -o $out'), + depfile='$out.d') + master_ninja.rule( + 'alink', + description='AR $out', + command='rm -f $out && ar rcsT $out $in') + master_ninja.rule( + 'solink', + description='SOLINK $out', + command=('$ld -shared $ldflags -o $out -Wl,-soname=$soname ' + '-Wl,--whole-archive $in -Wl,--no-whole-archive $libs')) + master_ninja.rule( + 'link', + description='LINK $out', + command=('$ld $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib ' + '-Wl,--start-group $in -Wl,--end-group $libs')) + master_ninja.rule( + 'stamp', + description='STAMP $out', + command='touch $out') + master_ninja.rule( + 'copy', + description='COPY $in $out', + command='ln -f $in $out 2>/dev/null || cp -af $in $out') + master_ninja.newline() all_targets = set() for build_file in params['build_files']: @@ -589,25 +677,29 @@ def GenerateOutput(target_list, target_dicts, data, params): all_targets.add(target) all_outputs = set() - subninjas = set() target_outputs = {} for qualified_target in target_list: # qualified_target is like: third_party/icu/icu.gyp:icui18n#target - build_file, target, _ = gyp.common.ParseQualifiedTarget(qualified_target) + build_file, name, toolset = \ + gyp.common.ParseQualifiedTarget(qualified_target) # TODO: what is options.depth and how is it different than # options.toplevel_dir? build_file = gyp.common.RelativePath(build_file, options.depth) base_path = os.path.dirname(build_file) - output_file = os.path.join(builddir, 'obj', base_path, target + '.ninja') + obj = 'obj' + if toolset != 'target': + obj += '.' + toolset + output_file = os.path.join(obj, base_path, name + '.ninja') spec = target_dicts[qualified_target] config = spec['configurations'][config_name] - writer = NinjaWriter(target_outputs, base_path, + writer = NinjaWriter(target_outputs, base_path, builddir, OpenOutput(os.path.join(options.toplevel_dir, + builddir, output_file))) - subninjas.add(output_file) + master_ninja.subninja(output_file) output = writer.WriteSpec(spec, config) if output: @@ -617,10 +709,5 @@ def GenerateOutput(target_list, target_dicts, data, params): if qualified_target in all_targets: all_outputs.add(output) - for ninja in subninjas: - print >>master_ninja, 'subninja', ninja - if all_outputs: - print >>master_ninja, 'build all: phony ||' + ' '.join(all_outputs) - - master_ninja.close() + master_ninja.build('all', 'phony', list(all_outputs)) diff --git a/tools/gyp/pylib/gyp/input.py b/tools/gyp/pylib/gyp/input.py index 314b5c66de..d4eeebc0fc 100644 --- a/tools/gyp/pylib/gyp/input.py +++ b/tools/gyp/pylib/gyp/input.py @@ -1525,26 +1525,39 @@ def AdjustStaticLibraryDependencies(flat_list, targets, dependency_nodes, target_dict['dependencies_original'] = target_dict.get( 'dependencies', [])[:] + # A static library should not depend on another static library unless + # the dependency relationship is "hard," which should only be done when + # a dependent relies on some side effect other than just the build + # product, like a rule or action output. Further, if a target has a + # non-hard dependency, but that dependency exports a hard dependency, + # the non-hard dependency can safely be removed, but the exported hard + # dependency must be added to the target to keep the same dependency + # ordering. + dependencies = \ + dependency_nodes[target].DirectAndImportedDependencies(targets) index = 0 - while index < len(target_dict['dependencies']): - dependency = target_dict['dependencies'][index] + while index < len(dependencies): + dependency = dependencies[index] dependency_dict = targets[dependency] - if dependency_dict['type'] == 'static_library' and \ - (not 'hard_dependency' in dependency_dict or \ - not dependency_dict['hard_dependency']): - # A static library should not depend on another static library unless - # the dependency relationship is "hard," which should only be done - # when a dependent relies on some side effect other than just the - # build product, like a rule or action output. Take the dependency - # out of the list, and don't increment index because the next - # dependency to analyze will shift into the index formerly occupied - # by the one being removed. - del target_dict['dependencies'][index] + + # Remove every non-hard static library dependency and remove every + # non-static library dependency that isn't a direct dependency. + if (dependency_dict['type'] == 'static_library' and \ + not dependency_dict.get('hard_dependency', False)) or \ + (dependency_dict['type'] != 'static_library' and \ + not dependency in target_dict['dependencies']): + # Take the dependency out of the list, and don't increment index + # because the next dependency to analyze will shift into the index + # formerly occupied by the one being removed. + del dependencies[index] else: index = index + 1 - # If the dependencies list is empty, it's not needed, so unhook it. - if len(target_dict['dependencies']) == 0: + # Update the dependencies. If the dependencies list is empty, it's not + # needed, so unhook it. + if len(dependencies) > 0: + target_dict['dependencies'] = dependencies + else: del target_dict['dependencies'] elif target_type in linkable_types: diff --git a/tools/gyp/pylib/gyp/ninja_syntax.py b/tools/gyp/pylib/gyp/ninja_syntax.py index e2dca2dc5b..a8735225b1 100644 --- a/tools/gyp/pylib/gyp/ninja_syntax.py +++ b/tools/gyp/pylib/gyp/ninja_syntax.py @@ -58,6 +58,12 @@ class Writer(object): return outputs + def include(self, path): + self._line('include %s' % path) + + def subninja(self, path): + self._line('subninja %s' % path) + def _line(self, text, indent=0): """Write 'text' word-wrapped at self.width characters.""" leading_space = ' ' * indent |