diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2011-08-22 17:08:16 -0700 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2011-08-22 17:09:57 -0700 |
commit | 80dd8182907314ae99f5572798c86176e2086c47 (patch) | |
tree | 3dddb582141ef135a93789a288c25eed543f32f9 /tools/gyp | |
parent | c2ae39b8d60407261d1963461087798458b31358 (diff) | |
download | node-new-80dd8182907314ae99f5572798c86176e2086c47.tar.gz |
Upgrade GYP to r1010
Diffstat (limited to 'tools/gyp')
-rw-r--r-- | tools/gyp/.gitignore | 1 | ||||
-rwxr-xr-x | tools/gyp/gyptest.py | 4 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/MSVSNew.py | 10 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/MSVSSettings_test.py | 2 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/easy_xml_test.py | 2 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/make.py | 260 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/msvs.py | 17 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/msvs_test.py | 35 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/generator/ninja.py | 626 | ||||
-rw-r--r-- | tools/gyp/pylib/gyp/ninja_syntax.py | 98 |
10 files changed, 963 insertions, 92 deletions
diff --git a/tools/gyp/.gitignore b/tools/gyp/.gitignore new file mode 100644 index 0000000000..0d20b6487c --- /dev/null +++ b/tools/gyp/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/tools/gyp/gyptest.py b/tools/gyp/gyptest.py index 6b95a9dde7..0cf38b15c5 100755 --- a/tools/gyp/gyptest.py +++ b/tools/gyp/gyptest.py @@ -210,8 +210,8 @@ def main(argv=None): 'freebsd8': ['make'], 'cygwin': ['msvs'], 'win32': ['msvs'], - 'linux2': ['make'], - 'linux3': ['make'], + 'linux2': ['make', 'ninja'], + 'linux3': ['make', 'ninja'], 'darwin': ['make', 'xcode'], }[sys.platform] diff --git a/tools/gyp/pylib/gyp/MSVSNew.py b/tools/gyp/pylib/gyp/MSVSNew.py index 1277d4a508..9b9b848fe7 100644 --- a/tools/gyp/pylib/gyp/MSVSNew.py +++ b/tools/gyp/pylib/gyp/MSVSNew.py @@ -213,20 +213,16 @@ class MSVSSolution: IndexError: An entry appears multiple times. """ # Walk the entry tree and collect all the folders and projects. - all_entries = [] + all_entries = set() entries_to_check = self.entries[:] while entries_to_check: - # Pop from the beginning of the list to preserve the user's order. e = entries_to_check.pop(0) - # A project or folder can only appear once in the solution's folder tree. - # This also protects from cycles. + # If this entry has been visited, nothing to do. if e in all_entries: - #raise IndexError('Entry "%s" appears more than once in solution' % - # e.name) continue - all_entries.append(e) + all_entries.add(e) # If this is a folder, check its entries too. if isinstance(e, MSVSFolder): diff --git a/tools/gyp/pylib/gyp/MSVSSettings_test.py b/tools/gyp/pylib/gyp/MSVSSettings_test.py index 2ae0dd23bc..199f98b1b3 100644 --- a/tools/gyp/pylib/gyp/MSVSSettings_test.py +++ b/tools/gyp/pylib/gyp/MSVSSettings_test.py @@ -8,7 +8,7 @@ import StringIO import unittest -import MSVSSettings +import gyp.MSVSSettings as MSVSSettings class TestSequenceFunctions(unittest.TestCase): diff --git a/tools/gyp/pylib/gyp/easy_xml_test.py b/tools/gyp/pylib/gyp/easy_xml_test.py index a8f32a0cd5..9e59559818 100644 --- a/tools/gyp/pylib/gyp/easy_xml_test.py +++ b/tools/gyp/pylib/gyp/easy_xml_test.py @@ -6,7 +6,7 @@ """ Unit tests for the easy_xml.py file. """ -import easy_xml +import gyp.easy_xml as easy_xml import unittest import StringIO diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index 5ebc5c96c7..b8914785bf 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -29,7 +29,6 @@ import gyp.system_test import os.path import os import sys -import stat # Debugging-related imports -- remove me once we're solid. import code @@ -374,6 +373,7 @@ prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?)) # do_cmd: run a command via the above cmd_foo names, if necessary. # Should always run for a given target to handle command-line changes. # Second argument, if non-zero, makes it do asm/C/C++ dependency munging. +# Third argument, if non-zero, makes it do POSTBUILDS processing. # Note: We intentionally do NOT call dirx for depfile, since it contains """ + \ SPACE_REPLACEMENT + """ for # spaces already and dirx strips the """ + SPACE_REPLACEMENT + \ @@ -389,6 +389,9 @@ $(if $(or $(command_changed),$(prereq_changed)), ) @$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile) @$(if $(2),$(fixup_dep)) + $(if $(and $(3), $(POSTBUILDS)), + @for p in $(POSTBUILDS); do eval $$p; done + ) ) endef @@ -422,12 +425,13 @@ quiet_cmd_pch_mm = CXX($(TOOLSET)) $@ cmd_pch_mm = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $< # gyp-mac-tool is written next to the root Makefile by gyp. -# Use #(3) for the command, since $(2) is used as flag by do_cmd already. -quiet_cmd_mac_tool = MACTOOL $(3) $< -cmd_mac_tool = ./gyp-mac-tool $(3) $< "$@" +# Use $(4) for the command, since $(2) and $(3) are used as flag by do_cmd +# already. +quiet_cmd_mac_tool = MACTOOL $(4) $< +cmd_mac_tool = ./gyp-mac-tool $(4) $< "$@" quiet_cmd_mac_package_framework = PACKAGE FRAMEWORK $@ -cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(3) +cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(4) """ @@ -676,9 +680,9 @@ class XcodeSettings(object): return os.path.join(self.GetBundleContentsFolderPath(), 'Resources', 'Info.plist') - def GetBundleBinaryPath(self): - """Returns the directory name of the bundle represented by this target. E.g. - Chromium.app/Contents/MacOS/Chromium. Only valid for bundles.""" + def _GetBundleBinaryPath(self): + """Returns the name of the bundle binary of by this target. + E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles.""" assert self._IsBundle() if self.spec['type'] in ('loadable_module', 'shared_library'): path = self.GetBundleContentsFolderPath() @@ -687,6 +691,53 @@ class XcodeSettings(object): return os.path.join(path, self.spec.get('product_name', self.spec['target_name'])) + def _GetStandaloneExecutableSuffix(self): + if 'product_extension' in self.spec: + return '.' + self.spec['product_extension'] + return { + 'executable': '', + 'static_library': '.a', + 'shared_library': '.dylib', + 'loadable_module': '.so', + }[self.spec['type']] + + def _GetStandaloneExecutablePrefix(self): + return self.spec.get('product_prefix', { + 'executable': '', + 'static_library': 'lib', + 'shared_library': 'lib', + # Non-bundled loadable_modules are called foo.so for some reason + # (that is, .so and no prefix) with the xcode build -- match that. + 'loadable_module': '', + }[self.spec['type']]) + + def _GetStandaloneBinaryPath(self): + """Returns the name of the non-bundle binary represented by this target. + E.g. hello_world. Only valid for non-bundles.""" + assert not self._IsBundle() + assert self.spec['type'] in ( + 'executable', 'shared_library', 'static_library', 'loadable_module') + target = self.spec['target_name'] + if self.spec['type'] == 'static_library': + if target[:3] == 'lib': + target = target[3:] + elif self.spec['type'] in ('loadable_module', 'shared_library'): + if target[:3] == 'lib': + target = target[3:] + + target_prefix = self._GetStandaloneExecutablePrefix() + target = self.spec.get('product_name', target) + target_ext = self._GetStandaloneExecutableSuffix() + return target_prefix + target + target_ext + + def GetExecutablePath(self): + """Returns the directory name of the bundle represented by this target. E.g. + Chromium.app/Contents/MacOS/Chromium.""" + if self._IsBundle(): + return self._GetBundleBinaryPath() + else: + return self._GetStandaloneBinaryPath() + def GetCflags(self, configname): """Returns flags that need to be added to .c, .cc, .m, and .mm compilations.""" @@ -838,6 +889,10 @@ class XcodeSettings(object): ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s') self._Appendf( 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') for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): ldflags.append('-L' + library_path) @@ -855,37 +910,47 @@ class XcodeSettings(object): ldflags.append('-L' + generator_default_variables['PRODUCT_DIR']) install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME') + install_base = self.GetPerTargetSetting('DYLIB_INSTALL_NAME_BASE') + default_install_name = \ + '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)' + if not install_name and install_base: + install_name = default_install_name + if install_name: # Hardcode support for the variables used in chromium for now, to unblock # people using the make build. if '$' in install_name: - assert install_name == ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/' - '$(WRAPPER_NAME)/$(PRODUCT_NAME)'), ( + assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/' + '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), ( 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported yet' ' in target \'%s\' (got \'%s\')' % (self.spec['target_name'], install_name)) - install_base = self.GetPerTargetSetting('DYLIB_INSTALL_NAME_BASE') # I'm not quite sure what :standardizepath does. Just call normpath(), - # but don't let @executable_path/../foo collapse to foo - prefix, rest = '', install_base - if install_base.startswith('@'): - prefix, rest = install_base.split('/', 1) - rest = os.path.normpath(rest) # :standardizepath - install_base = os.path.join(prefix, rest) + # but don't let @executable_path/../foo collapse to foo. + if '/' in install_base: + prefix, rest = '', install_base + if install_base.startswith('@'): + prefix, rest = install_base.split('/', 1) + rest = os.path.normpath(rest) # :standardizepath + install_base = os.path.join(prefix, rest) install_name = install_name.replace( '$(DYLIB_INSTALL_NAME_BASE:standardizepath)', install_base) + if self._IsBundle(): + # These are only valid for bundles, hence the |if|. + install_name = install_name.replace( + '$(WRAPPER_NAME)', self.GetWrapperName()) + install_name = install_name.replace( + '$(PRODUCT_NAME)', self.GetProductName()) + else: + assert '$(WRAPPER_NAME)' not in install_name + assert '$(PRODUCT_NAME)' not in install_name + install_name = install_name.replace( - '$(WRAPPER_NAME)', self.GetWrapperName()) - install_name = install_name.replace( - '$(PRODUCT_NAME)', self.GetProductName()) + '$(EXECUTABLE_PATH)', self.GetExecutablePath()) install_name = QuoteSpaces(install_name) ldflags.append('-install_name ' + install_name) - elif self.GetPerTargetSetting('DYLIB_INSTALL_NAME_BASE'): - # LD_DYLIB_INSTALL_NAME defaults to - # $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH). - print 'Warning: DYLIB_INSTALL_NAME_BASE is not fully implemented.' self.configname = None return ldflags @@ -1453,7 +1518,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD if output.endswith('.xib'): output = output[0:-3] + 'nib' - self.WriteDoCmd([output], [path], 'mac_tool,,copy-bundle-resource', + self.WriteDoCmd([output], [path], 'mac_tool,,,copy-bundle-resource', part_of_all=True) bundle_deps.append(output) @@ -1467,7 +1532,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD dest_plist = os.path.join(path, self.xcode_settings.GetBundlePlistPath()) dest_plist = QuoteSpaces(dest_plist) self.WriteXcodeEnv(dest_plist, spec) # plists can contain envvars. - self.WriteDoCmd([dest_plist], [info_plist], 'mac_tool,,copy-info-plist', + self.WriteDoCmd([dest_plist], [info_plist], 'mac_tool,,,copy-info-plist', part_of_all=True) bundle_deps.append(dest_plist) @@ -1593,18 +1658,21 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn() - def ComputeOutput(self, spec): - """Return the 'output' (full output path) of a gyp spec. + def ComputeOutputBasename(self, spec): + """Return the 'output basename' of a gyp spec. E.g., the loadable module 'foobar' in directory 'baz' will produce - '$(obj)/baz/libfoobar.so' + 'libfoobar.so' """ assert not self.is_mac_bundle + if self.flavor == 'mac' and self.type in ( + 'static_library', 'executable', 'shared_library', 'loadable_module'): + return self.xcode_settings.GetExecutablePath() + target = spec['target_name'] target_prefix = '' target_ext = '' - path = os.path.join('$(obj).' + self.toolset, self.path) if self.type == 'static_library': if target[:3] == 'lib': target = target[3:] @@ -1615,31 +1683,37 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD target = target[3:] target_prefix = 'lib' target_ext = '.so' - if self.flavor == 'mac': - if self.type == 'shared_library': - target_ext = '.dylib' - else: - # Non-bundled loadable_modules are called foo.so for some reason - # (that is, .so and no prefix) with the xcode build -- match that. - target_prefix = '' elif self.type == 'none': target = '%s.stamp' % target - elif self.type == 'settings': - return '' # Doesn't have any output. - elif self.type == 'executable': - path = os.path.join('$(builddir)') - else: + elif self.type != 'executable': print ("ERROR: What output file should be generated?", "type", self.type, "target", target) - path = spec.get('product_dir', path) target_prefix = spec.get('product_prefix', target_prefix) target = spec.get('product_name', target) product_ext = spec.get('product_extension') if product_ext: target_ext = '.' + product_ext - return os.path.join(path, target_prefix + target + target_ext) + return target_prefix + target + target_ext + + + def ComputeOutput(self, spec): + """Return the 'output' (full output path) of a gyp spec. + + E.g., the loadable module 'foobar' in directory 'baz' will produce + '$(obj)/baz/libfoobar.so' + """ + assert not self.is_mac_bundle + + if self.type == 'settings': + return '' # Doesn't have any output. + + path = os.path.join('$(obj).' + self.toolset, self.path) + if self.type == 'executable': + path = '$(builddir)' + path = spec.get('product_dir', path) + return os.path.join(path, self.ComputeOutputBasename(spec)) def ComputeMacBundleOutput(self, spec): @@ -1652,7 +1726,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD def ComputeMacBundleBinaryOutput(self, spec): """Return the 'output' (full output path) to the binary in a bundle.""" path = generator_default_variables['PRODUCT_DIR'] - return os.path.join(path, self.xcode_settings.GetBundleBinaryPath()) + return os.path.join(path, self.xcode_settings.GetExecutablePath()) def ComputeDeps(self, spec): @@ -1731,6 +1805,21 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD '%s: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))' % self.output_binary) self.WriteLn('%s: LIBS := $(LIBS)' % self.output_binary) + postbuilds = [] + if self.flavor == 'mac': + # Postbuild actions. Like actions, but implicitly depend on the target's + # output. + for postbuild in spec.get('postbuilds', []): + postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( + self.target, postbuild['postbuild_name'])) + shell_list = postbuild['action'] + # The first element is the command. If it's a relative path, it's + # a script in the source tree relative to the gyp file and needs to be + # absolutified. Else, it's in the PATH (e.g. install_name_tool, ln). + if os.path.sep in shell_list[0]: + shell_list[0] = self.Absolutify(shell_list[0]) + postbuilds.append('%s' % gyp.common.EncodePOSIXShellList(shell_list)) + # A bundle directory depends on its dependencies such as bundle resources # and bundle binary. When all dependencies have been built, the bundle # needs to be packaged. @@ -1749,22 +1838,14 @@ $(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,%s)' % + self.WriteLn('\t@$(call do_cmd,mac_package_framework,0,0,%s)' % self.xcode_settings.GetFrameworkVersion()) - # Postbuild actions. Like actions, but implicitly depend on the output - # framework. - for postbuild in spec.get('postbuilds', []): - self.WriteLn('\t@echo POSTBUILD %s' % postbuild['postbuild_name']) - shell_list = postbuild['action'] - # The first element is the command. If it's a relative path, it's - # a script in the source tree relative to the gyp file and needs to be - # absolutified. Else, it's in the PATH (e.g. install_name_tool, ln). - if os.path.sep in shell_list[0]: - shell_list[0] = self.Absolutify(shell_list[0]) - # TODO: Honor V=1 etc. Not using do_cmd because since this is part of - # the framework rule, there's no need for .d file processing here. - self.WriteLn('\t@%s' % gyp.common.EncodePOSIXShellList(shell_list)) + # Bundle postbuilds can depend on the whole bundle, so run them after + # the bundle is packaged, not already after the bundle binary is done. + for postbuild in postbuilds: + self.WriteLn('\t@' + postbuild) + postbuilds = [] # Don't write postbuilds for target's output. # Needed by test/mac/gyptest-rebuild.py. self.WriteLn('\t@true # No-op, used by tests') @@ -1775,32 +1856,43 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # on every build (expensive, especially with postbuilds), expliclity # update the time on the framework directory. self.WriteLn('\t@touch -c %s' % self.output) - elif 'postbuilds' in spec: - print ("Warning: 'postbuild' support for non-bundles " - "isn't implemented yet (target '%s)'." % self.target) + + if postbuilds: + assert not self.is_mac_bundle, ('Postbuilds for bundles should be done ' + 'on the bundle, not the binary (target \'%s\')' % self.target) + self.WriteXcodeEnv(self.output_binary, spec) # For postbuilds + postbuilds = [EscapeShellArgument(p) for p in postbuilds] + self.WriteLn('%s: builddir := $(abs_builddir)' % self.output_binary) + self.WriteLn('%s: POSTBUILDS := %s' % ( + self.output_binary, ' '.join(postbuilds))) if self.type == 'executable': self.WriteLn( '%s: LD_INPUTS := %s' % (self.output_binary, ' '.join(link_deps))) - self.WriteDoCmd([self.output_binary], link_deps, 'link', part_of_all) + self.WriteDoCmd([self.output_binary], link_deps, 'link', part_of_all, + postbuilds=postbuilds) elif self.type == 'static_library': for link_dep in link_deps: assert ' ' not in link_dep, ( "Spaces in alink input filenames not supported (%s)" % link_dep) - self.WriteDoCmd([self.output_binary], link_deps, 'alink', part_of_all) + self.WriteDoCmd([self.output_binary], link_deps, 'alink', part_of_all, + postbuilds=postbuilds) elif self.type == 'shared_library': self.WriteLn( '%s: LD_INPUTS := %s' % (self.output_binary, ' '.join(link_deps))) - self.WriteDoCmd([self.output_binary], link_deps, 'solink', part_of_all) + self.WriteDoCmd([self.output_binary], link_deps, 'solink', part_of_all, + postbuilds=postbuilds) elif self.type == 'loadable_module': for link_dep in link_deps: assert ' ' not in link_dep, ( "Spaces in module input filenames not supported (%s)" % link_dep) self.WriteDoCmd( - [self.output_binary], link_deps, 'solink_module', part_of_all) + [self.output_binary], link_deps, 'solink_module', part_of_all, + postbuilds=postbuilds) elif self.type == 'none': # Write a stamp line. - self.WriteDoCmd([self.output_binary], deps, 'touch', part_of_all) + self.WriteDoCmd([self.output_binary], deps, 'touch', part_of_all, + postbuilds=postbuilds) elif self.type == 'settings': # Only used for passing flags around. pass @@ -1867,14 +1959,19 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.fp.write("\n\n") - def WriteDoCmd(self, outputs, inputs, command, part_of_all, comment=None): + def WriteDoCmd(self, outputs, inputs, command, part_of_all, comment=None, + postbuilds=False): """Write a Makefile rule that uses do_cmd. This makes the outputs dependent on the command line that was run, as well as support the V= make command line flag. """ + suffix = '' + if postbuilds: + assert ',' not in command + suffix = ',,1' # Tell do_cmd to honor $POSTBUILDS self.WriteMakeRule(outputs, inputs, - actions = ['$(call do_cmd,%s)' % command], + actions = ['$(call do_cmd,%s%s)' % (command, suffix)], comment = comment, force = True) # Add our outputs to the list of targets we read depfiles from. @@ -2042,6 +2139,18 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD 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)'):]) + built_products_dir = generator_default_variables['PRODUCT_DIR'] srcroot = self.path if target_relative_path: @@ -2052,6 +2161,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD 'BUILT_PRODUCTS_DIR' : built_products_dir, 'CONFIGURATION' : '$(BUILDTYPE)', 'PRODUCT_NAME' : product_name, + # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME + 'FULL_PRODUCT_NAME' : product_name, 'SRCROOT' : srcroot, # This is not true for static libraries, but currently the env is only # written for bundles: @@ -2060,13 +2171,9 @@ $(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) - # Can't use self.output_binary here because it's not in the products dir. - # We really care about the final location of the dylib anyway. - env['EXECUTABLE_PATH'] = StripProductDir( - self._InstallableTargetInstallPath()) + if self.type in ('executable', 'shared_library', 'loadable_module'): + env['EXECUTABLE_PATH'] = self.xcode_settings.GetExecutablePath() if self.is_mac_bundle: - # Overwrite this to point to the binary _in_ the bundle. - env['EXECUTABLE_PATH'] = self.xcode_settings.GetBundleBinaryPath() env['CONTENTS_FOLDER_PATH'] = \ self.xcode_settings.GetBundleContentsFolderPath() env['INFOPLIST_PATH'] = self.xcode_settings.GetBundlePlistPath() @@ -2312,8 +2419,7 @@ def GenerateOutput(target_list, target_dicts, data, params): if os.path.exists(mactool_path): os.remove(mactool_path) CopyMacTool(mactool_path) - os.chmod(mactool_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) # Make file executable. + os.chmod(mactool_path, 0o755) # Make file executable. # Find the list of targets that derive from the gyp file(s) being built. needed_targets = set() diff --git a/tools/gyp/pylib/gyp/generator/msvs.py b/tools/gyp/pylib/gyp/generator/msvs.py index c4893b4758..37d5527dfe 100644 --- a/tools/gyp/pylib/gyp/generator/msvs.py +++ b/tools/gyp/pylib/gyp/generator/msvs.py @@ -878,7 +878,7 @@ def _GenerateMSVSProject(project, options, version): spec, options, gyp_dir, sources, excluded_sources)) # Add in files. - # _VerifySourcesExist(sources, gyp_dir) + _VerifySourcesExist(sources, gyp_dir) p.AddFiles(sources) _AddToolFilesToMSVS(p, spec) @@ -1071,7 +1071,17 @@ def _GetLibraries(spec): libraries = spec.get('libraries', []) # Strip out -l, as it is not used on windows (but is needed so we can pass # in libraries that are assumed to be in the default library path). - return [re.sub('^(\-l)', '', lib) for lib in libraries] + # Also remove duplicate entries, leaving only the last duplicate, while + # preserving order. + found = set() + unique_libraries_list = [] + for entry in reversed(libraries): + library = re.sub('^\-l', '', entry) + if library not in found: + found.add(library) + unique_libraries_list.append(library) + unique_libraries_list.reverse() + return unique_libraries_list def _GetOutputFilePathAndTool(spec): @@ -1643,7 +1653,6 @@ def _ShardTargets(target_list, target_dicts): shards = int(target_dicts[t].get('msvs_shard', 0)) if shards: targets_to_shard[t] = shards - print targets_to_shard # Shard target_list. new_target_list = [] for t in target_list: @@ -2721,7 +2730,7 @@ def _GenerateMSBuildProject(project, options, version): _GenerateMSBuildFiltersFile(project.path + '.filters', sources, extension_to_rule_name) - # _VerifySourcesExist(sources, gyp_dir) + _VerifySourcesExist(sources, gyp_dir) for (_, configuration) in configurations.iteritems(): _FinalizeMSBuildSettings(spec, configuration) diff --git a/tools/gyp/pylib/gyp/generator/msvs_test.py b/tools/gyp/pylib/gyp/generator/msvs_test.py new file mode 100644 index 0000000000..60d25abe09 --- /dev/null +++ b/tools/gyp/pylib/gyp/generator/msvs_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +# Copyright (c) 2011 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. + +""" Unit tests for the msvs.py file. """ + +import gyp.generator.msvs as msvs +import unittest +import StringIO + + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + self.stderr = StringIO.StringIO() + + def test_GetLibraries(self): + self.assertEqual( + msvs._GetLibraries({}), + []) + self.assertEqual( + msvs._GetLibraries({'libraries': []}), + []) + self.assertEqual( + msvs._GetLibraries({'other':'foo', 'libraries': ['a.lib']}), + ['a.lib']) + self.assertEqual( + msvs._GetLibraries({'libraries': ['a.lib', 'b.lib', 'c.lib', '-lb.lib', + '-lb.lib', 'd.lib', 'a.lib']}), + ['c.lib', 'b.lib', 'd.lib', 'a.lib']) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py new file mode 100644 index 0000000000..8a8e2900ce --- /dev/null +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -0,0 +1,626 @@ +#!/usr/bin/python + +# Copyright (c) 2011 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. + +import gyp +import gyp.common +import gyp.system_test +import os.path +import pprint +import subprocess +import sys + +import gyp.ninja_syntax as ninja_syntax + +generator_default_variables = { + 'OS': 'linux', + + 'EXECUTABLE_PREFIX': '', + 'EXECUTABLE_SUFFIX': '', + 'STATIC_LIB_PREFIX': '', + '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', + + # Special variables that may be used by gyp 'rule' targets. + # We generate definitions for these variables on the fly when processing a + # rule. + 'RULE_INPUT_ROOT': '$root', + 'RULE_INPUT_PATH': '$source', + 'RULE_INPUT_EXT': '$ext', + '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 + +""" + + +def StripPrefix(arg, prefix): + if arg.startswith(prefix): + return arg[len(prefix):] + return arg + + +def QuoteShellArgument(arg): + return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'" + + +def MaybeQuoteShellArgument(arg): + if '"' in arg or ' ' in arg: + return QuoteShellArgument(arg) + return arg + + +# A small discourse on paths as used within the Ninja build: +# +# 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. +# +# 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. +# +# We translate between these two notions of paths with two helper +# functions: +# +# - GypPathToNinja translates a gyp path (i.e. relative to the .gyp file) +# into the equivalent ninja path. +# +# - GypPathToUniqueOutput translates a gyp path into a ninja path to write +# an output file; the result can be namespaced such that is unique +# to the input file name as well as the output target name. + +class NinjaWriter: + def __init__(self, target_outputs, base_dir, output_file): + 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.ninja = ninja_syntax.Writer(output_file) + + 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)) + + def GypPathToUniqueOutput(self, path, qualified=False): + """Translate a gyp path to a ninja path for writing output. + + If qualified is True, qualify the resulting filename with the name + of the target. This is necessary when e.g. compiling the same + path twice for two separate output targets. + + 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('$') + + # 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) + # + # 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. + 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, + path_basename)) + + def StampPath(self, name): + """Return a path for a stamp file with a particular name. + + Stamp files are used to collapse a dependency on a bunch of files + into a single file.""" + return self.GypPathToUniqueOutput(name + '.stamp', qualified=True) + + def WriteSpec(self, spec, config): + """The main entry point for NinjaWriter: write the build rules for a spec. + + Returns the path to the build output, or None.""" + + if spec['type'] == 'settings': + # TODO: 'settings' is not actually part of gyp; it was + # accidentally introduced somehow into just the Linux build files. + return None + + self.name = spec['target_name'] + + # Compute predepends for all rules. + # prebuild is the dependencies this target depends on before + # running any of its internal steps. + prebuild = [] + if 'dependencies' in spec: + prebuild_deps = [] + for dep in spec['dependencies']: + if dep in self.target_outputs: + prebuild_deps.append(self.target_outputs[dep][0]) + if prebuild_deps: + stamp = self.StampPath('predepends') + prebuild = self.ninja.build(stamp, 'stamp', prebuild_deps) + self.ninja.newline() + + # Write out actions, rules, and copies. These must happen before we + # compile any sources, so compute a list of predependencies for sources + # while we do it. + extra_sources = [] + sources_predepends = self.WriteActionsRulesCopies(spec, extra_sources, + prebuild) + + # Write out the compilation steps, if any. + link_deps = [] + sources = spec.get('sources', []) + extra_sources + if sources: + 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')] + + # The final output of our target depends on the last output of the + # above steps. + final_deps = link_deps or sources_predepends or prebuild + if final_deps: + return self.WriteTarget(spec, config, final_deps) + + def WriteActionsRulesCopies(self, spec, extra_sources, prebuild): + """Write out the Actions, Rules, and Copies steps. Return any outputs + of these steps (or a stamp file if there are lots of outputs).""" + outputs = [] + + if 'actions' in spec: + outputs += self.WriteActions(spec['actions'], extra_sources, prebuild) + if 'rules' in spec: + outputs += self.WriteRules(spec['rules'], extra_sources, prebuild) + if 'copies' in spec: + outputs += self.WriteCopies(spec['copies'], prebuild) + + # To simplify downstream build edges, ensure we generate a single + # stamp file that represents the results of all of the above. + if len(outputs) > 1: + stamp = self.StampPath('actions_rules_copies') + outputs = self.ninja.build(stamp, 'stamp', outputs) + + return outputs + + 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']) + rule_name = self.WriteNewNinjaRule(name, action['action'], description) + + inputs = [self.GypPathToNinja(i) for i in action['inputs']] + if int(action.get('process_outputs_as_sources', False)): + extra_sources += action['outputs'] + outputs = [self.GypPathToNinja(o) for o in action['outputs']] + + # Then write out an edge using the rule. + self.ninja.build(outputs, rule_name, inputs, + order_only=prebuild) + all_outputs += outputs + + self.ninja.newline() + + return all_outputs + + def WriteRules(self, rules, extra_sources, prebuild): + all_outputs = [] + for rule in rules: + # 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) + rule_name = self.WriteNewNinjaRule(name, args, description) + + # TODO: if the command references the outputs directly, we should + # simplify it to just use $out. + + # Rules can potentially make use of some special variables which + # must vary per source file. + # Compute the list of variables we'll need to provide. + special_locals = ('source', 'root', 'ext', 'name') + needed_variables = set(['source']) + for argument in args: + for var in special_locals: + if '$' + var in argument: + needed_variables.add(var) + + # For each source file, write an edge that generates all the outputs. + for source in rule.get('rule_sources', []): + basename = os.path.basename(source) + root, ext = os.path.splitext(basename) + source = self.GypPathToNinja(source) + + outputs = [] + for output in rule['outputs']: + outputs.append(output.replace('$root', root)) + + extra_bindings = [] + for var in needed_variables: + if var == 'root': + extra_bindings.append(('root', root)) + elif var == 'source': + extra_bindings.append(('source', source)) + elif var == 'ext': + extra_bindings.append(('ext', ext)) + elif var == 'name': + extra_bindings.append(('name', basename)) + else: + assert var == None, repr(var) + + inputs = map(self.GypPathToNinja, rule.get('inputs', [])) + self.ninja.build(outputs, rule_name, 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 + + def WriteCopies(self, copies, prebuild): + outputs = [] + for copy in copies: + for path in copy['files']: + # Normalize the path so trailing slashes don't confuse us. + path = os.path.normpath(path) + basename = os.path.split(path)[1] + src = self.GypPathToNinja(path) + dst = self.GypPathToNinja(os.path.join(copy['destination'], basename)) + outputs += self.ninja.build(dst, 'copy', src, + order_only=prebuild) + + return outputs + + def WriteSources(self, config, sources, predepends): + """Write build rules to compile all of |sources|.""" + self.WriteVariableList('defines', + ['-D' + MaybeQuoteShellArgument(ninja_syntax.escape(d)) + for d in config.get('defines', [])]) + self.WriteVariableList('includes', + ['-I' + self.GypPathToNinja(i) + for i in config.get('include_dirs', [])]) + self.WriteVariableList('cflags', config.get('cflags')) + self.WriteVariableList('cflags_c', config.get('cflags_c')) + self.WriteVariableList('cflags_cc', config.get('cflags_cc')) + self.ninja.newline() + outputs = [] + for source in sources: + filename, ext = os.path.splitext(source) + ext = ext[1:] + if ext in ('cc', 'cpp', 'cxx'): + command = 'cxx' + elif ext in ('c', 's', 'S'): + command = 'cc' + else: + # TODO: should we assert here on unexpected extensions? + continue + input = self.GypPathToNinja(source) + output = self.GypPathToUniqueOutput(filename + '.o', qualified=True) + self.ninja.build(output, command, input, + order_only=predepends) + outputs.append(output) + self.ninja.newline() + return outputs + + def WriteTarget(self, spec, config, final_deps): + output = self.ComputeOutput(spec) + + output_uses_linker = spec['type'] in ('executable', 'loadable_module', + 'shared_library') + + implicit_deps = set() + if 'dependencies' in spec: + # Two kinds of dependencies: + # - Linkable dependencies (like a .a or a .so): add them to the link line. + # - Non-linkable dependencies (like a rule that generates a file + # and writes a stamp file): add them to implicit_deps + if output_uses_linker: + extra_deps = set() + for dep in spec['dependencies']: + input, linkable = self.target_outputs.get(dep, (None, False)) + if not input: + continue + if linkable: + extra_deps.add(input) + 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: + implicit_deps.add(input) + final_deps.extend(list(extra_deps)) + command_map = { + 'executable': 'link', + 'static_library': 'alink', + 'loadable_module': 'solink', + 'shared_library': 'solink', + 'none': 'stamp', + } + command = command_map[spec['type']] + + if output_uses_linker: + self.WriteVariableList('ldflags', + gyp.common.uniquer(config.get('ldflags', []))) + self.WriteVariableList('libs', + gyp.common.uniquer(spec.get('libraries', []))) + + extra_bindings = [] + if command == 'solink': + extra_bindings.append(('soname', os.path.split(output)[1])) + + self.ninja.build(output, command, final_deps, + 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): + """Compute the filename of the final output for the current target.""" + + # Compute filename prefix: the product prefix, or a default for + # the product type. + DEFAULT_PREFIX = { + 'loadable_module': 'lib', + 'shared_library': 'lib', + } + prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(spec['type'], '')) + + # Compute filename extension: the product extension, or a default + # for the product type. + DEFAULT_EXTENSION = { + 'static_library': 'a', + 'loadable_module': 'so', + 'shared_library': 'so', + } + extension = spec.get('product_extension', + DEFAULT_EXTENSION.get(spec['type'], '')) + if extension: + extension = '.' + extension + + if 'product_name' in spec: + # If we were given an explicit name, use that. + target = spec['product_name'] + else: + # Otherwise, derive a name from the target name. + target = spec['target_name'] + if prefix == 'lib': + # Snip out an extra 'lib' from libs if appropriate. + target = StripPrefix(target, 'lib') + + if spec['type'] in ('static_library', 'loadable_module', 'shared_library', + 'executable'): + return '%s%s%s' % (prefix, target, extension) + elif spec['type'] == 'none': + return '%s.stamp' % target + elif spec['type'] == 'settings': + return None + else: + raise 'Unhandled output type', spec['type'] + + def ComputeOutput(self, spec): + """Compute the path for the final output of the spec.""" + + filename = self.ComputeOutputFileName(spec) + + if 'product_dir' in spec: + path = os.path.join(spec['product_dir'], filename) + return 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) + elif spec['type'] == 'shared_library': + return os.path.join('$b/lib', filename) + else: + return self.GypPathToUniqueOutput(filename) + + def WriteVariableList(self, var, values): + if values is None: + values = [] + self.ninja.variable(var, ' '.join(values)) + + def WriteNewNinjaRule(self, name, args, description): + """Write out a new ninja "rule" statement for a given command. + + Returns the name of the new rule.""" + + # 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(' ', '_') + + 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 + + command = cd + gyp.common.EncodePOSIXShellList(args) + self.ninja.rule(rule_name, command, description) + self.ninja.newline() + + return rule_name + + +def CalculateVariables(default_variables, params): + """Calculate additional variables for use in the build (called by gyp).""" + cc_target = os.environ.get('CC.target', os.environ.get('CC', 'cc')) + default_variables['LINKER_SUPPORTS_ICF'] = \ + gyp.system_test.TestLinkerSupportsICF(cc_command=cc_target) + + +def OpenOutput(path): + """Open |path| for writing, creating directories if necessary.""" + try: + os.makedirs(os.path.dirname(path)) + except OSError: + pass + return open(path, 'w') + + +def GenerateOutput(target_list, target_dicts, data, params): + options = params['options'] + generator_flags = params.get('generator_flags', {}) + + if options.generator_output: + raise NotImplementedError, "--generator_output not implemented for ninja" + + config_name = generator_flags.get('config', None) + if config_name is None: + # Guess which config we want to use: pick the first one from the + # first target. + config_name = target_dicts[target_list[0]]['default_configuration'] + + # builddir: relative path from source root to our output files. + # 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++'), + }) + + all_targets = set() + for build_file in params['build_files']: + for target in gyp.common.AllTargets(target_list, target_dicts, build_file): + 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) + + # 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') + spec = target_dicts[qualified_target] + config = spec['configurations'][config_name] + + writer = NinjaWriter(target_outputs, base_path, + OpenOutput(os.path.join(options.toplevel_dir, + output_file))) + subninjas.add(output_file) + + output = writer.WriteSpec(spec, config) + if output: + linkable = spec['type'] in ('static_library', 'shared_library') + target_outputs[qualified_target] = (output, linkable) + + 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() diff --git a/tools/gyp/pylib/gyp/ninja_syntax.py b/tools/gyp/pylib/gyp/ninja_syntax.py new file mode 100644 index 0000000000..e2dca2dc5b --- /dev/null +++ b/tools/gyp/pylib/gyp/ninja_syntax.py @@ -0,0 +1,98 @@ +#!/usr/bin/python + +# This file comes from +# https://github.com/martine/ninja/blob/master/misc/ninja_syntax.py +# Do not edit! Edit the upstream one instead. + +"""Python module for generating .ninja files. + +Note that this is emphatically not a required piece of Ninja; it's +just a helpful utility for build-file-generation systems that already +use Python. +""" + +import textwrap + +class Writer(object): + def __init__(self, output, width=78): + self.output = output + self.width = width + + def newline(self): + self.output.write('\n') + + def comment(self, text): + for line in textwrap.wrap(text, self.width - 2): + self.output.write('# ' + line + '\n') + + def variable(self, key, value, indent=0): + self._line('%s = %s' % (key, value), indent) + + def rule(self, name, command, description=None, depfile=None): + self._line('rule %s' % name) + self.variable('command', command, indent=1) + if description: + self.variable('description', description, indent=1) + if depfile: + self.variable('depfile', depfile, indent=1) + + def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, + variables=None): + outputs = self._as_list(outputs) + all_inputs = self._as_list(inputs)[:] + + if implicit: + all_inputs.append('|') + all_inputs.extend(self._as_list(implicit)) + if order_only: + all_inputs.append('||') + all_inputs.extend(self._as_list(order_only)) + + self._line('build %s: %s %s' % (' '.join(outputs), + rule, + ' '.join(all_inputs))) + + if variables: + for key, val in variables: + self.variable(key, val, indent=1) + + return outputs + + def _line(self, text, indent=0): + """Write 'text' word-wrapped at self.width characters.""" + leading_space = ' ' * indent + while len(text) > self.width: + # The text is too wide; wrap if possible. + + # Find the rightmost space that would obey our width constraint. + available_space = self.width - len(leading_space) - len(' $') + space = text.rfind(' ', 0, available_space) + if space < 0: + # No such space; just use the first space we can find. + space = text.find(' ', available_space) + if space < 0: + # Give up on breaking. + break + + self.output.write(leading_space + text[0:space] + ' $\n') + text = text[space+1:] + + # Subsequent lines are continuations, so indent them. + leading_space = ' ' * (indent+2) + + self.output.write(leading_space + text + '\n') + + def _as_list(self, input): + if input is None: + return [] + if isinstance(input, list): + return input + return [input] + + +def escape(string): + """Escape a string such that it can be embedded into a Ninja file without + further interpretation.""" + assert '\n' not in string, 'Ninja syntax does not allow newlines' + # We only have one special metacharacter: '$'. + return string.replace('$', '$$') |