Browse Source

Upgrade GYP to r1034

Ryan Dahl 14 years ago
parent
commit
ee2c12d48e
  1. 5
      tools/gyp/pylib/gyp/MSVSNew.py
  2. 9
      tools/gyp/pylib/gyp/generator/dump_dependency_json.py
  3. 164
      tools/gyp/pylib/gyp/generator/make.py
  4. 36
      tools/gyp/pylib/gyp/generator/msvs.py
  5. 373
      tools/gyp/pylib/gyp/generator/ninja.py
  6. 43
      tools/gyp/pylib/gyp/input.py
  7. 6
      tools/gyp/pylib/gyp/ninja_syntax.py

5
tools/gyp/pylib/gyp/MSVSNew.py

@ -248,10 +248,13 @@ class MSVSSolution:
sln_root = os.path.split(self.path)[0] sln_root = os.path.split(self.path)[0]
for e in all_entries: for e in all_entries:
relative_path = gyp.common.RelativePath(e.path, sln_root) 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' % ( f.write('Project("%s") = "%s", "%s", "%s"\r\n' % (
e.entry_type_guid, # Entry type GUID e.entry_type_guid, # Entry type GUID
e.name, # Folder name e.name, # Folder name
relative_path.replace('/', '\\'), # Folder name (again) folder_name, # Folder name (again)
e.get_guid(), # Entry GUID e.get_guid(), # Entry GUID
)) ))

9
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') 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): def GenerateOutput(target_list, target_dicts, data, params):
# Map of target -> list of targets it depends on. # Map of target -> list of targets it depends on.
edges = {} edges = {}

164
tools/gyp/pylib/gyp/generator/make.py

@ -43,7 +43,6 @@ generator_default_variables = {
'INTERMEDIATE_DIR': '$(obj).$(TOOLSET)/geni', 'INTERMEDIATE_DIR': '$(obj).$(TOOLSET)/geni',
'SHARED_INTERMEDIATE_DIR': '$(obj)/gen', 'SHARED_INTERMEDIATE_DIR': '$(obj)/gen',
'PRODUCT_DIR': '$(builddir)', 'PRODUCT_DIR': '$(builddir)',
'LIB_DIR': '$(obj).$(TOOLSET)',
'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python. 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python.
'RULE_INPUT_PATH': '$(abspath $<)', 'RULE_INPUT_PATH': '$(abspath $<)',
'RULE_INPUT_EXT': '$(suffix $<)', '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_SUFFIX', '.dylib')
default_variables.setdefault('SHARED_LIB_DIR', default_variables.setdefault('SHARED_LIB_DIR',
generator_default_variables['PRODUCT_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 # Copy additional generator configuration data from Xcode, which is shared
# by the Mac Make generator. # by the Mac Make generator.
@ -95,6 +96,7 @@ def CalculateVariables(default_variables, params):
default_variables.setdefault('OS', 'linux') default_variables.setdefault('OS', 'linux')
default_variables.setdefault('SHARED_LIB_SUFFIX', '.so') default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
default_variables.setdefault('SHARED_LIB_DIR','$(builddir)/lib.$(TOOLSET)') default_variables.setdefault('SHARED_LIB_DIR','$(builddir)/lib.$(TOOLSET)')
default_variables.setdefault('LIB_DIR', '$(obj).$(TOOLSET)')
def CalculateGeneratorInputInfo(params): def CalculateGeneratorInputInfo(params):
@ -230,8 +232,10 @@ all_deps :=
# #
# This will allow make to invoke N linker processes as specified in -jN. # This will allow make to invoke N linker processes as specified in -jN.
FLOCK ?= %(flock)s $(builddir)/linker.lock FLOCK ?= %(flock)s $(builddir)/linker.lock
LINK ?= $(FLOCK) $(CXX)
%(make_global_settings)s
LINK ?= $(FLOCK) $(CXX)
CC.target ?= $(CC) CC.target ?= $(CC)
CFLAGS.target ?= $(CFLAGS) CFLAGS.target ?= $(CFLAGS)
CXX.target ?= $(CXX) CXX.target ?= $(CXX)
@ -738,6 +742,14 @@ class XcodeSettings(object):
else: else:
return self._GetStandaloneBinaryPath() 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): def GetCflags(self, configname):
"""Returns flags that need to be added to .c, .cc, .m, and .mm """Returns flags that need to be added to .c, .cc, .m, and .mm
compilations.""" compilations."""
@ -747,11 +759,9 @@ class XcodeSettings(object):
self.configname = configname self.configname = configname
cflags = [] cflags = []
sdk_root = 'Mac10.5' sdk_root = self._SdkPath()
if 'SDKROOT' in self._Settings(): if 'SDKROOT' in self._Settings():
sdk_root = self._Settings()['SDKROOT'] cflags.append('-isysroot %s' % sdk_root)
cflags.append('-isysroot /Developer/SDKs/%s.sdk' % sdk_root)
sdk_root_dir = '/Developer/SDKs/%s.sdk' % sdk_root
if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'): if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
cflags.append('-fasm-blocks') cflags.append('-fasm-blocks')
@ -770,10 +780,9 @@ class XcodeSettings(object):
self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s') self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s')
if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'):
dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf')
if dbg_format == 'none': if dbg_format == 'dwarf':
pass
elif dbg_format == 'dwarf':
cflags.append('-gdwarf-2') cflags.append('-gdwarf-2')
elif dbg_format == 'stabs': elif dbg_format == 'stabs':
raise NotImplementedError('stabs debug format is not supported yet.') raise NotImplementedError('stabs debug format is not supported yet.')
@ -802,6 +811,9 @@ class XcodeSettings(object):
self._WarnUnimplemented('ARCHS') self._WarnUnimplemented('ARCHS')
self._WarnUnimplemented('COPY_PHASE_STRIP') self._WarnUnimplemented('COPY_PHASE_STRIP')
self._WarnUnimplemented('DEPLOYMENT_POSTPROCESSING') 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_PREPROCESS')
self._WarnUnimplemented('INFOPLIST_PREPROCESSOR_DEFINITIONS') self._WarnUnimplemented('INFOPLIST_PREPROCESSOR_DEFINITIONS')
self._WarnUnimplemented('STRIPFLAGS') self._WarnUnimplemented('STRIPFLAGS')
@ -816,7 +828,7 @@ class XcodeSettings(object):
config = self.spec['configurations'][self.configname] config = self.spec['configurations'][self.configname]
framework_dirs = config.get('mac_framework_dirs', []) framework_dirs = config.get('mac_framework_dirs', [])
for directory in 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 self.configname = None
return cflags return cflags
@ -891,8 +903,8 @@ class XcodeSettings(object):
ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s') ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
self._Appendf( self._Appendf(
ldflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') ldflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s')
self._Appendf( if 'SDKROOT' in self._Settings():
ldflags, 'SDKROOT', '-isysroot /Developer/SDKs/%s.sdk') ldflags.append('-isysroot ' + self._SdkPath())
for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
ldflags.append('-L' + library_path) ldflags.append('-L' + library_path)
@ -904,9 +916,7 @@ class XcodeSettings(object):
# TODO: Do not hardcode arch. Supporting fat binaries will be annoying. # TODO: Do not hardcode arch. Supporting fat binaries will be annoying.
ldflags.append('-arch i386') ldflags.append('-arch i386')
# Xcode adds the product directory by default. It writes static libraries # Xcode adds the product directory by default.
# into the product directory. So add both.
ldflags.append('-L' + generator_default_variables['LIB_DIR'])
ldflags.append('-L' + generator_default_variables['PRODUCT_DIR']) ldflags.append('-L' + generator_default_variables['PRODUCT_DIR'])
install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME') install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME')
@ -955,6 +965,23 @@ class XcodeSettings(object):
self.configname = None self.configname = None
return ldflags 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): def GetPerTargetSetting(self, setting, default=None):
"""Tries to get xcode_settings.setting from spec. Assumes that the setting """Tries to get xcode_settings.setting from spec. Assumes that the setting
has the same value in all configurations and throws otherwise.""" 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'] path = generator_default_variables['PRODUCT_DIR']
dest_plist = os.path.join(path, self.xcode_settings.GetBundlePlistPath()) dest_plist = os.path.join(path, self.xcode_settings.GetBundlePlistPath())
dest_plist = QuoteSpaces(dest_plist) 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', self.WriteDoCmd([dest_plist], [info_plist], 'mac_tool,,,copy-info-plist',
part_of_all=True) part_of_all=True)
bundle_deps.append(dest_plist) bundle_deps.append(dest_plist)
@ -1698,6 +1727,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
return target_prefix + target + target_ext 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): def ComputeOutput(self, spec):
"""Return the 'output' (full output path) of a gyp 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. return '' # Doesn't have any output.
path = os.path.join('$(obj).' + self.toolset, self.path) path = os.path.join('$(obj).' + self.toolset, self.path)
if self.type == 'executable': if self.type == 'executable' or self._InstallImmediately():
path = '$(builddir)' path = '$(builddir)'
path = spec.get('product_dir', path) path = spec.get('product_dir', path)
return os.path.join(path, self.ComputeOutputBasename(spec)) 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 # After the framework is built, package it. Needs to happen before
# postbuilds, since postbuilds depend on this. # postbuilds, since postbuilds depend on this.
if self.type in ('shared_library', 'loadable_module'): 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()) self.xcode_settings.GetFrameworkVersion())
# Bundle postbuilds can depend on the whole bundle, so run them after # 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: if postbuilds:
assert not self.is_mac_bundle, ('Postbuilds for bundles should be done ' assert not self.is_mac_bundle, ('Postbuilds for bundles should be done '
'on the bundle, not the binary (target \'%s\')' % self.target) '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 self.WriteXcodeEnv(self.output_binary, spec) # For postbuilds
postbuilds = [EscapeShellArgument(p) for p in postbuilds] postbuilds = [EscapeShellArgument(p) for p in postbuilds]
self.WriteLn('%s: builddir := $(abs_builddir)' % self.output_binary) 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' file_desc = 'executable'
install_path = self._InstallableTargetInstallPath() install_path = self._InstallableTargetInstallPath()
installable_deps = [self.output] installable_deps = [self.output]
if self.is_mac_bundle: if self.flavor == 'mac' and not 'product_dir' in spec:
# Bundles are created in their install_path location immediately. # On mac, products are created in install_path immediately.
assert install_path == self.output, '%s != %s' % ( assert install_path == self.output, '%s != %s' % (
install_path, self.output) install_path, self.output)
@ -2102,10 +2138,15 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
modules.append(filename[len(prefix):-len(suffix)]) modules.append(filename[len(prefix):-len(suffix)])
return modules return modules
# Retrieve the default value of 'SHARED_LIB_SUFFIX'
params = {'flavor': 'linux'}
default_variables = {}
CalculateVariables(default_variables, params)
self.WriteList( self.WriteList(
DepsToModules(link_deps, DepsToModules(link_deps,
generator_default_variables['SHARED_LIB_PREFIX'], generator_default_variables['SHARED_LIB_PREFIX'],
generator_default_variables['SHARED_LIB_SUFFIX']), default_variables['SHARED_LIB_SUFFIX']),
'LOCAL_SHARED_LIBRARIES') 'LOCAL_SHARED_LIBRARIES')
self.WriteList( self.WriteList(
DepsToModules(link_deps, DepsToModules(link_deps,
@ -2132,26 +2173,17 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
for a full list.""" for a full list."""
if self.flavor != 'mac': return {} if self.flavor != 'mac': return {}
built_products_dir = generator_default_variables['PRODUCT_DIR']
def StripProductDir(s): def StripProductDir(s):
product_dir = generator_default_variables['PRODUCT_DIR'] assert s.startswith(built_products_dir), s
assert s.startswith(product_dir), s return s[len(built_products_dir) + 1:]
return s[len(product_dir) + 1:]
product_name = spec.get('product_name', self.output) product_name = spec.get('product_name', self.output)
# Some postbuilds try to read a build output file at if self._InstallImmediately():
# ""${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}". Static libraries end up if product_name.startswith(built_products_dir):
# "$(obj).target", so product_name = StripProductDir(product_name)
# 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 srcroot = self.path
if target_relative_path: if target_relative_path:
built_products_dir = os.path.relpath(built_products_dir, srcroot) 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'): if self.type in ('executable', 'shared_library'):
env['EXECUTABLE_NAME'] = os.path.basename(self.output_binary) 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() env['EXECUTABLE_PATH'] = self.xcode_settings.GetExecutablePath()
if self.is_mac_bundle: if self.is_mac_bundle:
env['CONTENTS_FOLDER_PATH'] = \ env['CONTENTS_FOLDER_PATH'] = \
self.xcode_settings.GetBundleContentsFolderPath() self.xcode_settings.GetBundleContentsFolderPath()
env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \
self.xcode_settings.GetBundleResourceFolder()
env['INFOPLIST_PATH'] = self.xcode_settings.GetBundlePlistPath() env['INFOPLIST_PATH'] = self.xcode_settings.GetBundlePlistPath()
# TODO(thakis): Remove this. # TODO(thakis): Remove this.
@ -2186,16 +2221,40 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
return env return env
def WriteXcodeEnv(self, target, spec, target_relative_path=False): def WriteXcodeEnv(self,
env = self.GetXcodeEnv(spec, target_relative_path) 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 # For
# foo := a\ b # foo := a\ b
# the escaped space does the right thing. For # the escaped space does the right thing. For
# export foo := a\ b # export foo := a\ b
# it does not -- the backslash is written to the env as literal character. # it does not -- the backslash is written to the env as literal character.
# Hence, unescape all spaces here. # Hence, unescape all spaces here.
for k in env:
v = env[k].replace(r'\ ', ' ') 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)) 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)) 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) ensure_directory_exists(makefile_path)
root_makefile = open(makefile_path, 'w') root_makefile = open(makefile_path, 'w')
root_makefile.write(SHARED_HEADER % header_params) 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: for qualified_target in target_list:
build_file, target, toolset = gyp.common.ParseQualifiedTarget( build_file, target, toolset = gyp.common.ParseQualifiedTarget(
qualified_target) 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)) build_files.add(gyp.common.RelativePath(build_file, options.toplevel_dir))
included_files = data[build_file]['included_files'] included_files = data[build_file]['included_files']
for included_file in included_files: for included_file in included_files:

36
tools/gyp/pylib/gyp/generator/msvs.py

@ -56,7 +56,7 @@ generator_default_variables = {
# of the warnings. # of the warnings.
# TODO(jeanluc) I had: 'LIB_DIR': '$(OutDir)lib', # TODO(jeanluc) I had: 'LIB_DIR': '$(OutDir)lib',
#'LIB_DIR': '$(OutDir)/lib', 'LIB_DIR': '$(OutDir)/lib',
'RULE_INPUT_ROOT': '$(InputName)', 'RULE_INPUT_ROOT': '$(InputName)',
'RULE_INPUT_EXT': '$(InputExt)', 'RULE_INPUT_EXT': '$(InputExt)',
'RULE_INPUT_NAME': '$(InputFileName)', 'RULE_INPUT_NAME': '$(InputFileName)',
@ -575,18 +575,7 @@ def _GenerateExternalRules(rules, output_dir, spec,
'IntDir=$(IntDir)', 'IntDir=$(IntDir)',
'-j', '${NUMBER_OF_PROCESSORS_PLUS_1}', '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
'-f', filename] '-f', filename]
cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True)
# 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)
# Insert makefile as 0'th input, so it gets the action attached there, # Insert makefile as 0'th input, so it gets the action attached there,
# as this is easier to understand from in the IDE. # as this is easier to understand from in the IDE.
all_inputs = list(all_inputs) all_inputs = list(all_inputs)
@ -1117,7 +1106,7 @@ def _GetOutputFilePathAndTool(spec):
# TODO(jeanluc) If we want to avoid the MSB8012 warnings in # TODO(jeanluc) If we want to avoid the MSB8012 warnings in
# VisualStudio 2010, we will have to change the value of $(OutDir) # VisualStudio 2010, we will have to change the value of $(OutDir)
# to contain the \lib suffix, rather than doing it as below. # 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'), 'dummy_executable': ('VCLinkerTool', 'Link', '$(IntDir)\\', '.junk'),
} }
output_file_props = output_file_map.get(spec['type']) 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, def _AddSources2(spec, sources, exclusions, grouped_sources,
extension_to_rule_name, sources_handled_by_action): extension_to_rule_name, sources_handled_by_action):
extensions_excluded_from_precompile = []
for source in sources: for source in sources:
if isinstance(source, MSVSProject.Filter): if isinstance(source, MSVSProject.Filter):
_AddSources2(spec, source.contents, exclusions, grouped_sources, _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(): for config_name, configuration in spec['configurations'].iteritems():
precompiled_source = configuration.get('msvs_precompiled_source', '') precompiled_source = configuration.get('msvs_precompiled_source', '')
precompiled_source = _FixPath(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: if precompiled_source == source:
condition = _GetConfigurationCondition(config_name, configuration) condition = _GetConfigurationCondition(config_name, configuration)
detail.append(['PrecompiledHeader', detail.append(['PrecompiledHeader',
{'Condition': condition}, {'Condition': condition},
'Create' '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, group, element = _MapFileToMsBuildSourceType(source,
extension_to_rule_name) extension_to_rule_name)
grouped_sources[group].append([element, {'Include': source}] + detail) grouped_sources[group].append([element, {'Include': source}] + detail)

373
tools/gyp/pylib/gyp/generator/ninja.py

@ -23,16 +23,20 @@ generator_default_variables = {
'STATIC_LIB_SUFFIX': '.a', 'STATIC_LIB_SUFFIX': '.a',
'SHARED_LIB_PREFIX': 'lib', 'SHARED_LIB_PREFIX': 'lib',
'SHARED_LIB_SUFFIX': '.so', 'SHARED_LIB_SUFFIX': '.so',
# TODO: intermediate dir should *not* be shared between different targets.
# Unfortunately, whatever we provide here gets written into many different # Gyp expects the following variables to be expandable by the build
# places within the gyp spec so it's difficult to make it target-specific. # system to the appropriate locations. Ninja prefers paths to be
# Apparently we've made it this far with one global path for the make build # known at compile time. To resolve this, introduce special
# we're safe for now. # variables starting with $! (which begin with a $ so gyp knows it
'INTERMEDIATE_DIR': '$b/geni', # should be treated as a path, but is otherwise an invalid
'SHARED_INTERMEDIATE_DIR': '$b/gen', # ninja/shell variable) that are passed to gyp here but expanded
'PRODUCT_DIR': '$b', # before writing out into the target .ninja files; see
'SHARED_LIB_DIR': '$b/lib', # ExpandSpecial.
'LIB_DIR': '$b', '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. # Special variables that may be used by gyp 'rule' targets.
# We generate definitions for these variables on the fly when processing a # We generate definitions for these variables on the fly when processing a
@ -43,51 +47,11 @@ generator_default_variables = {
'RULE_INPUT_NAME': '$name', 'RULE_INPUT_NAME': '$name',
} }
NINJA_BASE = """\ # TODO: enable cross compiling once we figure out:
builddir = %(builddir)s # - how to not build extra host objects in the non-cross-compile case.
# Short alias for builddir. # - how to decide what the host compiler is (should not just be $cc).
b = %(builddir)s # - need ld_host as well.
generator_supports_multiple_toolsets = False
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): def StripPrefix(arg, prefix):
@ -106,19 +70,34 @@ def MaybeQuoteShellArgument(arg):
return 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: # 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 # Paths within a given .gyp file are always relative to the directory
# containing the .gyp file. Call these "gyp paths". This includes # containing the .gyp file. Call these "gyp paths". This includes
# sources as well as the starting directory a given gyp rule/action # sources as well as the starting directory a given gyp rule/action
# expects to be run from. We call this directory "base_dir" within # expects to be run from. We call the path from the source root to
# the per-.gyp-file NinjaWriter code. # 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 # All paths as written into the .ninja files are relative to the build
# of the tree. Call these paths "ninja paths". We set up the ninja # directory. Call these paths "ninja paths".
# 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 # We translate between these two notions of paths with two helper
# functions: # functions:
@ -131,24 +110,59 @@ def MaybeQuoteShellArgument(arg):
# to the input file name as well as the output target name. # to the input file name as well as the output target name.
class NinjaWriter: 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 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.base_dir = base_dir
self.build_dir = build_dir
self.ninja = ninja_syntax.Writer(output_file) 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): def GypPathToNinja(self, path):
"""Translate a gyp path to a ninja path. """Translate a gyp path to a ninja path.
See the above discourse on path conversions.""" See the above discourse on path conversions."""
if path.startswith('$'): if path.startswith('$!'):
# If the path contains a reference to a ninja variable, we know return self.ExpandSpecial(path)
# it's already relative to the source root. assert '$' not in path, path
return path return os.path.normpath(os.path.join(self.build_to_base, path))
return os.path.normpath(os.path.join(self.base_dir, path))
def GypPathToUniqueOutput(self, path, qualified=False): def GypPathToUniqueOutput(self, path, qualified=True):
"""Translate a gyp path to a ninja path for writing output. """Translate a gyp path to a ninja path for writing output.
If qualified is True, qualify the resulting filename with the name If qualified is True, qualify the resulting filename with the name
@ -157,30 +171,28 @@ class NinjaWriter:
See the above discourse on path conversions.""" See the above discourse on path conversions."""
# It may seem strange to discard components of the path, but we are just path = self.ExpandSpecial(path)
# attempting to produce a known-unique output filename; we don't want to assert not path.startswith('$'), path
# 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: # Translate the path following this scheme:
# Input: foo/bar.gyp, target targ, references baz/out.o # Input: foo/bar.gyp, target targ, references baz/out.o
# Output: $b/obj/foo/baz/targ.out.o (if qualified) # Output: obj/foo/baz/targ.out.o (if qualified)
# $b/obj/foo/baz/out.o (otherwise) # obj/foo/baz/out.o (otherwise)
# (and obj.host instead of obj for cross-compiles)
# #
# Why this scheme and not some other one? # Why this scheme and not some other one?
# 1) for a given input, you can compute all derived outputs by matching # 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 '..'. # its path, even if the input is brought via a gyp file with '..'.
# 2) simple files like libraries and stamps have a simple filename. # 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) path_dir, path_basename = os.path.split(path)
if qualified: if qualified:
path_basename = self.name + '.' + path_basename 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)) path_basename))
def StampPath(self, name): def StampPath(self, name):
@ -188,7 +200,7 @@ class NinjaWriter:
Stamp files are used to collapse a dependency on a bunch of files Stamp files are used to collapse a dependency on a bunch of files
into a single file.""" into a single file."""
return self.GypPathToUniqueOutput(name + '.stamp', qualified=True) return self.GypPathToUniqueOutput(name + '.stamp')
def WriteSpec(self, spec, config): def WriteSpec(self, spec, config):
"""The main entry point for NinjaWriter: write the build rules for a spec. """The main entry point for NinjaWriter: write the build rules for a spec.
@ -201,6 +213,7 @@ class NinjaWriter:
return None return None
self.name = spec['target_name'] self.name = spec['target_name']
self.toolset = spec['toolset']
# Compute predepends for all rules. # Compute predepends for all rules.
# prebuild is the dependencies this target depends on before # prebuild is the dependencies this target depends on before
@ -230,13 +243,20 @@ class NinjaWriter:
link_deps = self.WriteSources(config, sources, link_deps = self.WriteSources(config, sources,
sources_predepends or prebuild) sources_predepends or prebuild)
# Some actions/rules output 'sources' that are already object files. # 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 # The final output of our target depends on the last output of the
# above steps. # above steps.
output = None
final_deps = link_deps or sources_predepends or prebuild final_deps = link_deps or sources_predepends or prebuild
if final_deps: 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): def WriteActionsRulesCopies(self, spec, extra_sources, prebuild):
"""Write out the Actions, Rules, and Copies steps. Return any outputs """Write out the Actions, Rules, and Copies steps. Return any outputs
@ -258,15 +278,28 @@ class NinjaWriter:
return outputs 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): def WriteActions(self, actions, extra_sources, prebuild):
all_outputs = [] all_outputs = []
for action in actions: for action in actions:
# First write out a rule for the action. # First write out a rule for the action.
name = action['action_name'] name = action['action_name']
if 'message' in action: description = self.GenerateDescription('ACTION',
description = 'ACTION ' + action['message'] action.get('message', None),
else: name)
description = 'ACTION %s: %s' % (self.name, action['action_name'])
rule_name = self.WriteNewNinjaRule(name, action['action'], description) rule_name = self.WriteNewNinjaRule(name, action['action'], description)
inputs = [self.GypPathToNinja(i) for i in action['inputs']] inputs = [self.GypPathToNinja(i) for i in action['inputs']]
@ -289,10 +322,9 @@ class NinjaWriter:
# First write out a rule for the rule action. # First write out a rule for the rule action.
name = rule['rule_name'] name = rule['rule_name']
args = rule['action'] args = rule['action']
if 'message' in rule: description = self.GenerateDescription('RULE',
description = 'RULE ' + rule['message'] rule.get('message', None),
else: '%s $source' % name)
description = 'RULE %s: %s $source' % (self.name, name)
rule_name = self.WriteNewNinjaRule(name, args, description) rule_name = self.WriteNewNinjaRule(name, args, description)
# TODO: if the command references the outputs directly, we should # TODO: if the command references the outputs directly, we should
@ -312,18 +344,25 @@ class NinjaWriter:
for source in rule.get('rule_sources', []): for source in rule.get('rule_sources', []):
basename = os.path.basename(source) basename = os.path.basename(source)
root, ext = os.path.splitext(basename) root, ext = os.path.splitext(basename)
source = self.GypPathToNinja(source)
# Gather the list of outputs, expanding $vars if possible.
outputs = [] outputs = []
for output in rule['outputs']: for output in rule['outputs']:
outputs.append(output.replace('$root', root)) outputs.append(output.replace('$root', root))
if int(rule.get('process_outputs_as_sources', False)):
extra_sources += outputs
extra_bindings = [] extra_bindings = []
for var in needed_variables: for var in needed_variables:
if var == 'root': if var == 'root':
extra_bindings.append(('root', root)) extra_bindings.append(('root', root))
elif var == 'source': 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': elif var == 'ext':
extra_bindings.append(('ext', ext)) extra_bindings.append(('ext', ext))
elif var == 'name': elif var == 'name':
@ -332,14 +371,12 @@ class NinjaWriter:
assert var == None, repr(var) assert var == None, repr(var)
inputs = map(self.GypPathToNinja, rule.get('inputs', [])) 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, implicit=inputs,
order_only=prebuild, order_only=prebuild,
variables=extra_bindings) variables=extra_bindings)
if int(rule.get('process_outputs_as_sources', False)):
extra_sources += outputs
all_outputs.extend(outputs) all_outputs.extend(outputs)
return all_outputs return all_outputs
@ -360,6 +397,10 @@ class NinjaWriter:
def WriteSources(self, config, sources, predepends): def WriteSources(self, config, sources, predepends):
"""Write build rules to compile all of |sources|.""" """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', self.WriteVariableList('defines',
['-D' + MaybeQuoteShellArgument(ninja_syntax.escape(d)) ['-D' + MaybeQuoteShellArgument(ninja_syntax.escape(d))
for d in config.get('defines', [])]) for d in config.get('defines', [])])
@ -382,7 +423,7 @@ class NinjaWriter:
# TODO: should we assert here on unexpected extensions? # TODO: should we assert here on unexpected extensions?
continue continue
input = self.GypPathToNinja(source) input = self.GypPathToNinja(source)
output = self.GypPathToUniqueOutput(filename + '.o', qualified=True) output = self.GypPathToUniqueOutput(filename + '.o')
self.ninja.build(output, command, input, self.ninja.build(output, command, input,
order_only=predepends) order_only=predepends)
outputs.append(output) outputs.append(output)
@ -390,6 +431,14 @@ class NinjaWriter:
return outputs return outputs
def WriteTarget(self, spec, config, final_deps): 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 = self.ComputeOutput(spec)
output_uses_linker = spec['type'] in ('executable', 'loadable_module', output_uses_linker = spec['type'] in ('executable', 'loadable_module',
@ -412,7 +461,7 @@ class NinjaWriter:
else: else:
# TODO: Chrome-specific HACK. Chrome runs this lastchange rule on # TODO: Chrome-specific HACK. Chrome runs this lastchange rule on
# every build, but we don't want to rebuild when it runs. # 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) implicit_deps.add(input)
final_deps.extend(list(extra_deps)) final_deps.extend(list(extra_deps))
command_map = { command_map = {
@ -426,9 +475,11 @@ class NinjaWriter:
if output_uses_linker: if output_uses_linker:
self.WriteVariableList('ldflags', self.WriteVariableList('ldflags',
gyp.common.uniquer(config.get('ldflags', []))) gyp.common.uniquer(map(self.ExpandSpecial,
config.get('ldflags', []))))
self.WriteVariableList('libs', self.WriteVariableList('libs',
gyp.common.uniquer(spec.get('libraries', []))) gyp.common.uniquer(map(self.ExpandSpecial,
spec.get('libraries', []))))
extra_bindings = [] extra_bindings = []
if command == 'solink': if command == 'solink':
@ -438,11 +489,6 @@ class NinjaWriter:
implicit=list(implicit_deps), implicit=list(implicit_deps),
variables=extra_bindings) 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 return output
def ComputeOutputFileName(self, spec): def ComputeOutputFileName(self, spec):
@ -495,17 +541,20 @@ class NinjaWriter:
if 'product_dir' in spec: if 'product_dir' in spec:
path = os.path.join(spec['product_dir'], filename) path = os.path.join(spec['product_dir'], filename)
return path return self.ExpandSpecial(path)
# Executables and loadable modules go into the output root, # Executables and loadable modules go into the output root,
# libraries go into shared library dir, and everything else # libraries go into shared library dir, and everything else
# goes into the normal place. # goes into the normal place.
if spec['type'] in ('executable', 'loadable_module'): if spec['type'] in ('executable', 'loadable_module'):
return os.path.join('$b', filename) return filename
elif spec['type'] == 'shared_library': 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: else:
return self.GypPathToUniqueOutput(filename) return self.GypPathToUniqueOutput(filename, qualified=False)
def WriteVariableList(self, var, values): def WriteVariableList(self, var, values):
if values is None: if values is None:
@ -520,20 +569,19 @@ class NinjaWriter:
# TODO: we shouldn't need to qualify names; we do it because # TODO: we shouldn't need to qualify names; we do it because
# currently the ninja rule namespace is global, but it really # currently the ninja rule namespace is global, but it really
# should be scoped to the subninja. # 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[:] args = args[:]
if self.base_dir:
# gyp dictates that commands are run from the base directory. # gyp dictates that commands are run from the base directory.
# cd into the directory before running, and adjust all paths in # cd into the directory before running, and adjust paths in
# the arguments point to the proper locations. # the arguments to point to the proper locations.
cd = 'cd %s; ' % self.base_dir cd = 'cd %s; ' % self.build_to_base
cdup = '../' * len(self.base_dir.split('/')) args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args]
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) command = cd + gyp.common.EncodePOSIXShellList(args)
self.ninja.rule(rule_name, command, description) self.ninja.rule(rule_name, command, description)
@ -575,13 +623,53 @@ def GenerateOutput(target_list, target_dicts, data, params):
# e.g. "out/Debug" # e.g. "out/Debug"
builddir = os.path.join(generator_flags.get('output_dir', 'out'), config_name) builddir = os.path.join(generator_flags.get('output_dir', 'out'), config_name)
master_ninja = OpenOutput(os.path.join(options.toplevel_dir, builddir, master_ninja = ninja_syntax.Writer(
'build.ninja')) OpenOutput(os.path.join(options.toplevel_dir, builddir, 'build.ninja')),
master_ninja.write(NINJA_BASE % { width=120)
'builddir': builddir,
'cc': os.environ.get('CC', 'gcc'), # TODO: compute cc/cxx/ld/etc. by command-line arguments and system tests.
'cxx': os.environ.get('CXX', 'g++'), 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() all_targets = set()
for build_file in params['build_files']: for build_file in params['build_files']:
@ -589,25 +677,29 @@ def GenerateOutput(target_list, target_dicts, data, params):
all_targets.add(target) all_targets.add(target)
all_outputs = set() all_outputs = set()
subninjas = set()
target_outputs = {} target_outputs = {}
for qualified_target in target_list: for qualified_target in target_list:
# qualified_target is like: third_party/icu/icu.gyp:icui18n#target # 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 # TODO: what is options.depth and how is it different than
# options.toplevel_dir? # options.toplevel_dir?
build_file = gyp.common.RelativePath(build_file, options.depth) build_file = gyp.common.RelativePath(build_file, options.depth)
base_path = os.path.dirname(build_file) 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] spec = target_dicts[qualified_target]
config = spec['configurations'][config_name] 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, OpenOutput(os.path.join(options.toplevel_dir,
builddir,
output_file))) output_file)))
subninjas.add(output_file) master_ninja.subninja(output_file)
output = writer.WriteSpec(spec, config) output = writer.WriteSpec(spec, config)
if output: if output:
@ -617,10 +709,5 @@ def GenerateOutput(target_list, target_dicts, data, params):
if qualified_target in all_targets: if qualified_target in all_targets:
all_outputs.add(output) all_outputs.add(output)
for ninja in subninjas:
print >>master_ninja, 'subninja', ninja
if all_outputs: if all_outputs:
print >>master_ninja, 'build all: phony ||' + ' '.join(all_outputs) master_ninja.build('all', 'phony', list(all_outputs))
master_ninja.close()

43
tools/gyp/pylib/gyp/input.py

@ -1525,26 +1525,39 @@ def AdjustStaticLibraryDependencies(flat_list, targets, dependency_nodes,
target_dict['dependencies_original'] = target_dict.get( target_dict['dependencies_original'] = target_dict.get(
'dependencies', [])[:] '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 index = 0
while index < len(target_dict['dependencies']): while index < len(dependencies):
dependency = target_dict['dependencies'][index] dependency = dependencies[index]
dependency_dict = targets[dependency] dependency_dict = targets[dependency]
if dependency_dict['type'] == 'static_library' and \
(not 'hard_dependency' in dependency_dict or \ # Remove every non-hard static library dependency and remove every
not dependency_dict['hard_dependency']): # non-static library dependency that isn't a direct dependency.
# A static library should not depend on another static library unless if (dependency_dict['type'] == 'static_library' and \
# the dependency relationship is "hard," which should only be done not dependency_dict.get('hard_dependency', False)) or \
# when a dependent relies on some side effect other than just the (dependency_dict['type'] != 'static_library' and \
# build product, like a rule or action output. Take the dependency not dependency in target_dict['dependencies']):
# out of the list, and don't increment index because the next # Take the dependency out of the list, and don't increment index
# dependency to analyze will shift into the index formerly occupied # because the next dependency to analyze will shift into the index
# by the one being removed. # formerly occupied by the one being removed.
del target_dict['dependencies'][index] del dependencies[index]
else: else:
index = index + 1 index = index + 1
# If the dependencies list is empty, it's not needed, so unhook it. # Update the dependencies. If the dependencies list is empty, it's not
if len(target_dict['dependencies']) == 0: # needed, so unhook it.
if len(dependencies) > 0:
target_dict['dependencies'] = dependencies
else:
del target_dict['dependencies'] del target_dict['dependencies']
elif target_type in linkable_types: elif target_type in linkable_types:

6
tools/gyp/pylib/gyp/ninja_syntax.py

@ -58,6 +58,12 @@ class Writer(object):
return outputs 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): def _line(self, text, indent=0):
"""Write 'text' word-wrapped at self.width characters.""" """Write 'text' word-wrapped at self.width characters."""
leading_space = ' ' * indent leading_space = ' ' * indent

Loading…
Cancel
Save