1087 lines
47 KiB
Python
1087 lines
47 KiB
Python
# Copyright (c) 2012 Google Inc. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
This module helps emulate Visual Studio 2008 behavior on top of other
|
|
build systems, primarily ninja.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
from gyp.common import OrderedSet
|
|
import gyp.MSVSUtil
|
|
import gyp.MSVSVersion
|
|
|
|
|
|
windows_quoter_regex = re.compile(r'(\\*)"')
|
|
|
|
|
|
def QuoteForRspFile(arg):
|
|
"""Quote a command line argument so that it appears as one argument when
|
|
processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
|
|
Windows programs)."""
|
|
# See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment
|
|
# threads. This is actually the quoting rules for CommandLineToArgvW, not
|
|
# for the shell, because the shell doesn't do anything in Windows. This
|
|
# works more or less because most programs (including the compiler, etc.)
|
|
# use that function to handle command line arguments.
|
|
|
|
# For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
|
|
# preceding it, and results in n backslashes + the quote. So we substitute
|
|
# in 2* what we match, +1 more, plus the quote.
|
|
arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg)
|
|
|
|
# %'s also need to be doubled otherwise they're interpreted as batch
|
|
# positional arguments. Also make sure to escape the % so that they're
|
|
# passed literally through escaping so they can be singled to just the
|
|
# original %. Otherwise, trying to pass the literal representation that
|
|
# looks like an environment variable to the shell (e.g. %PATH%) would fail.
|
|
arg = arg.replace('%', '%%')
|
|
|
|
# These commands are used in rsp files, so no escaping for the shell (via ^)
|
|
# is necessary.
|
|
|
|
# Finally, wrap the whole thing in quotes so that the above quote rule
|
|
# applies and whitespace isn't a word break.
|
|
return '"' + arg + '"'
|
|
|
|
|
|
def EncodeRspFileList(args):
|
|
"""Process a list of arguments using QuoteCmdExeArgument."""
|
|
# Note that the first argument is assumed to be the command. Don't add
|
|
# quotes around it because then built-ins like 'echo', etc. won't work.
|
|
# Take care to normpath only the path in the case of 'call ../x.bat' because
|
|
# otherwise the whole thing is incorrectly interpreted as a path and not
|
|
# normalized correctly.
|
|
if not args: return ''
|
|
if args[0].startswith('call '):
|
|
call, program = args[0].split(' ', 1)
|
|
program = call + ' ' + os.path.normpath(program)
|
|
else:
|
|
program = os.path.normpath(args[0])
|
|
return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:])
|
|
|
|
|
|
def _GenericRetrieve(root, default, path):
|
|
"""Given a list of dictionary keys |path| and a tree of dicts |root|, find
|
|
value at path, or return |default| if any of the path doesn't exist."""
|
|
if not root:
|
|
return default
|
|
if not path:
|
|
return root
|
|
return _GenericRetrieve(root.get(path[0]), default, path[1:])
|
|
|
|
|
|
def _AddPrefix(element, prefix):
|
|
"""Add |prefix| to |element| or each subelement if element is iterable."""
|
|
if element is None:
|
|
return element
|
|
# Note, not Iterable because we don't want to handle strings like that.
|
|
if isinstance(element, list) or isinstance(element, tuple):
|
|
return [prefix + e for e in element]
|
|
else:
|
|
return prefix + element
|
|
|
|
|
|
def _DoRemapping(element, map):
|
|
"""If |element| then remap it through |map|. If |element| is iterable then
|
|
each item will be remapped. Any elements not found will be removed."""
|
|
if map is not None and element is not None:
|
|
if not callable(map):
|
|
map = map.get # Assume it's a dict, otherwise a callable to do the remap.
|
|
if isinstance(element, list) or isinstance(element, tuple):
|
|
element = filter(None, [map(elem) for elem in element])
|
|
else:
|
|
element = map(element)
|
|
return element
|
|
|
|
|
|
def _AppendOrReturn(append, element):
|
|
"""If |append| is None, simply return |element|. If |append| is not None,
|
|
then add |element| to it, adding each item in |element| if it's a list or
|
|
tuple."""
|
|
if append is not None and element is not None:
|
|
if isinstance(element, list) or isinstance(element, tuple):
|
|
append.extend(element)
|
|
else:
|
|
append.append(element)
|
|
else:
|
|
return element
|
|
|
|
|
|
def _FindDirectXInstallation():
|
|
"""Try to find an installation location for the DirectX SDK. Check for the
|
|
standard environment variable, and if that doesn't exist, try to find
|
|
via the registry. May return None if not found in either location."""
|
|
# Return previously calculated value, if there is one
|
|
if hasattr(_FindDirectXInstallation, 'dxsdk_dir'):
|
|
return _FindDirectXInstallation.dxsdk_dir
|
|
|
|
dxsdk_dir = os.environ.get('DXSDK_DIR')
|
|
if not dxsdk_dir:
|
|
# Setup params to pass to and attempt to launch reg.exe.
|
|
cmd = ['reg.exe', 'query', r'HKLM\Software\Microsoft\DirectX', '/s']
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
for line in p.communicate()[0].splitlines():
|
|
if 'InstallPath' in line:
|
|
dxsdk_dir = line.split(' ')[3] + "\\"
|
|
|
|
# Cache return value
|
|
_FindDirectXInstallation.dxsdk_dir = dxsdk_dir
|
|
return dxsdk_dir
|
|
|
|
|
|
def GetGlobalVSMacroEnv(vs_version):
|
|
"""Get a dict of variables mapping internal VS macro names to their gyp
|
|
equivalents. Returns all variables that are independent of the target."""
|
|
env = {}
|
|
# '$(VSInstallDir)' and '$(VCInstallDir)' are available when and only when
|
|
# Visual Studio is actually installed.
|
|
if vs_version.Path():
|
|
env['$(VSInstallDir)'] = vs_version.Path()
|
|
env['$(VCInstallDir)'] = os.path.join(vs_version.Path(), 'VC') + '\\'
|
|
# Chromium uses DXSDK_DIR in include/lib paths, but it may or may not be
|
|
# set. This happens when the SDK is sync'd via src-internal, rather than
|
|
# by typical end-user installation of the SDK. If it's not set, we don't
|
|
# want to leave the unexpanded variable in the path, so simply strip it.
|
|
dxsdk_dir = _FindDirectXInstallation()
|
|
env['$(DXSDK_DIR)'] = dxsdk_dir if dxsdk_dir else ''
|
|
# Try to find an installation location for the Windows DDK by checking
|
|
# the WDK_DIR environment variable, may be None.
|
|
env['$(WDK_DIR)'] = os.environ.get('WDK_DIR', '')
|
|
return env
|
|
|
|
def ExtractSharedMSVSSystemIncludes(configs, generator_flags):
|
|
"""Finds msvs_system_include_dirs that are common to all targets, removes
|
|
them from all targets, and returns an OrderedSet containing them."""
|
|
all_system_includes = OrderedSet(
|
|
configs[0].get('msvs_system_include_dirs', []))
|
|
for config in configs[1:]:
|
|
system_includes = config.get('msvs_system_include_dirs', [])
|
|
all_system_includes = all_system_includes & OrderedSet(system_includes)
|
|
if not all_system_includes:
|
|
return None
|
|
# Expand macros in all_system_includes.
|
|
env = GetGlobalVSMacroEnv(GetVSVersion(generator_flags))
|
|
expanded_system_includes = OrderedSet([ExpandMacros(include, env)
|
|
for include in all_system_includes])
|
|
if any(['$' in include for include in expanded_system_includes]):
|
|
# Some path relies on target-specific variables, bail.
|
|
return None
|
|
|
|
# Remove system includes shared by all targets from the targets.
|
|
for config in configs:
|
|
includes = config.get('msvs_system_include_dirs', [])
|
|
if includes: # Don't insert a msvs_system_include_dirs key if not needed.
|
|
# This must check the unexpanded includes list:
|
|
new_includes = [i for i in includes if i not in all_system_includes]
|
|
config['msvs_system_include_dirs'] = new_includes
|
|
return expanded_system_includes
|
|
|
|
|
|
class MsvsSettings(object):
|
|
"""A class that understands the gyp 'msvs_...' values (especially the
|
|
msvs_settings field). They largely correpond to the VS2008 IDE DOM. This
|
|
class helps map those settings to command line options."""
|
|
|
|
def __init__(self, spec, generator_flags):
|
|
self.spec = spec
|
|
self.vs_version = GetVSVersion(generator_flags)
|
|
|
|
supported_fields = [
|
|
('msvs_configuration_attributes', dict),
|
|
('msvs_settings', dict),
|
|
('msvs_system_include_dirs', list),
|
|
('msvs_disabled_warnings', list),
|
|
('msvs_precompiled_header', str),
|
|
('msvs_precompiled_source', str),
|
|
('msvs_configuration_platform', str),
|
|
('msvs_target_platform', str),
|
|
]
|
|
configs = spec['configurations']
|
|
for field, default in supported_fields:
|
|
setattr(self, field, {})
|
|
for configname, config in configs.iteritems():
|
|
getattr(self, field)[configname] = config.get(field, default())
|
|
|
|
self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])
|
|
|
|
unsupported_fields = [
|
|
'msvs_prebuild',
|
|
'msvs_postbuild',
|
|
]
|
|
unsupported = []
|
|
for field in unsupported_fields:
|
|
for config in configs.values():
|
|
if field in config:
|
|
unsupported += ["%s not supported (target %s)." %
|
|
(field, spec['target_name'])]
|
|
if unsupported:
|
|
raise Exception('\n'.join(unsupported))
|
|
|
|
def GetExtension(self):
|
|
"""Returns the extension for the target, with no leading dot.
|
|
|
|
Uses 'product_extension' if specified, otherwise uses MSVS defaults based on
|
|
the target type.
|
|
"""
|
|
ext = self.spec.get('product_extension', None)
|
|
if ext:
|
|
return ext
|
|
return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec['type'], '')
|
|
|
|
def GetVSMacroEnv(self, base_to_build=None, config=None):
|
|
"""Get a dict of variables mapping internal VS macro names to their gyp
|
|
equivalents."""
|
|
target_platform = 'Win32' if self.GetArch(config) == 'x86' else 'x64'
|
|
target_name = self.spec.get('product_prefix', '') + \
|
|
self.spec.get('product_name', self.spec['target_name'])
|
|
target_dir = base_to_build + '\\' if base_to_build else ''
|
|
target_ext = '.' + self.GetExtension()
|
|
target_file_name = target_name + target_ext
|
|
|
|
replacements = {
|
|
'$(InputName)': '${root}',
|
|
'$(InputPath)': '${source}',
|
|
'$(IntDir)': '$!INTERMEDIATE_DIR',
|
|
'$(OutDir)\\': target_dir,
|
|
'$(PlatformName)': target_platform,
|
|
'$(ProjectDir)\\': '',
|
|
'$(ProjectName)': self.spec['target_name'],
|
|
'$(TargetDir)\\': target_dir,
|
|
'$(TargetExt)': target_ext,
|
|
'$(TargetFileName)': target_file_name,
|
|
'$(TargetName)': target_name,
|
|
'$(TargetPath)': os.path.join(target_dir, target_file_name),
|
|
}
|
|
replacements.update(GetGlobalVSMacroEnv(self.vs_version))
|
|
return replacements
|
|
|
|
def ConvertVSMacros(self, s, base_to_build=None, config=None):
|
|
"""Convert from VS macro names to something equivalent."""
|
|
env = self.GetVSMacroEnv(base_to_build, config=config)
|
|
return ExpandMacros(s, env)
|
|
|
|
def AdjustLibraries(self, libraries):
|
|
"""Strip -l from library if it's specified with that."""
|
|
libs = [lib[2:] if lib.startswith('-l') else lib for lib in libraries]
|
|
return [lib + '.lib' if not lib.endswith('.lib') else lib for lib in libs]
|
|
|
|
def _GetAndMunge(self, field, path, default, prefix, append, map):
|
|
"""Retrieve a value from |field| at |path| or return |default|. If
|
|
|append| is specified, and the item is found, it will be appended to that
|
|
object instead of returned. If |map| is specified, results will be
|
|
remapped through |map| before being returned or appended."""
|
|
result = _GenericRetrieve(field, default, path)
|
|
result = _DoRemapping(result, map)
|
|
result = _AddPrefix(result, prefix)
|
|
return _AppendOrReturn(append, result)
|
|
|
|
class _GetWrapper(object):
|
|
def __init__(self, parent, field, base_path, append=None):
|
|
self.parent = parent
|
|
self.field = field
|
|
self.base_path = [base_path]
|
|
self.append = append
|
|
def __call__(self, name, map=None, prefix='', default=None):
|
|
return self.parent._GetAndMunge(self.field, self.base_path + [name],
|
|
default=default, prefix=prefix, append=self.append, map=map)
|
|
|
|
def GetArch(self, config):
|
|
"""Get architecture based on msvs_configuration_platform and
|
|
msvs_target_platform. Returns either 'x86' or 'x64'."""
|
|
configuration_platform = self.msvs_configuration_platform.get(config, '')
|
|
platform = self.msvs_target_platform.get(config, '')
|
|
if not platform: # If no specific override, use the configuration's.
|
|
platform = configuration_platform
|
|
# Map from platform to architecture.
|
|
return {'Win32': 'x86', 'x64': 'x64'}.get(platform, 'x86')
|
|
|
|
def _TargetConfig(self, config):
|
|
"""Returns the target-specific configuration."""
|
|
# There's two levels of architecture/platform specification in VS. The
|
|
# first level is globally for the configuration (this is what we consider
|
|
# "the" config at the gyp level, which will be something like 'Debug' or
|
|
# 'Release_x64'), and a second target-specific configuration, which is an
|
|
# override for the global one. |config| is remapped here to take into
|
|
# account the local target-specific overrides to the global configuration.
|
|
arch = self.GetArch(config)
|
|
if arch == 'x64' and not config.endswith('_x64'):
|
|
config += '_x64'
|
|
if arch == 'x86' and config.endswith('_x64'):
|
|
config = config.rsplit('_', 1)[0]
|
|
return config
|
|
|
|
def _Setting(self, path, config,
|
|
default=None, prefix='', append=None, map=None):
|
|
"""_GetAndMunge for msvs_settings."""
|
|
return self._GetAndMunge(
|
|
self.msvs_settings[config], path, default, prefix, append, map)
|
|
|
|
def _ConfigAttrib(self, path, config,
|
|
default=None, prefix='', append=None, map=None):
|
|
"""_GetAndMunge for msvs_configuration_attributes."""
|
|
return self._GetAndMunge(
|
|
self.msvs_configuration_attributes[config],
|
|
path, default, prefix, append, map)
|
|
|
|
def AdjustIncludeDirs(self, include_dirs, config):
|
|
"""Updates include_dirs to expand VS specific paths, and adds the system
|
|
include dirs used for platform SDK and similar."""
|
|
config = self._TargetConfig(config)
|
|
includes = include_dirs + self.msvs_system_include_dirs[config]
|
|
includes.extend(self._Setting(
|
|
('VCCLCompilerTool', 'AdditionalIncludeDirectories'), config, default=[]))
|
|
return [self.ConvertVSMacros(p, config=config) for p in includes]
|
|
|
|
def AdjustMidlIncludeDirs(self, midl_include_dirs, config):
|
|
"""Updates midl_include_dirs to expand VS specific paths, and adds the
|
|
system include dirs used for platform SDK and similar."""
|
|
config = self._TargetConfig(config)
|
|
includes = midl_include_dirs + self.msvs_system_include_dirs[config]
|
|
includes.extend(self._Setting(
|
|
('VCMIDLTool', 'AdditionalIncludeDirectories'), config, default=[]))
|
|
return [self.ConvertVSMacros(p, config=config) for p in includes]
|
|
|
|
def GetComputedDefines(self, config):
|
|
"""Returns the set of defines that are injected to the defines list based
|
|
on other VS settings."""
|
|
config = self._TargetConfig(config)
|
|
defines = []
|
|
if self._ConfigAttrib(['CharacterSet'], config) == '1':
|
|
defines.extend(('_UNICODE', 'UNICODE'))
|
|
if self._ConfigAttrib(['CharacterSet'], config) == '2':
|
|
defines.append('_MBCS')
|
|
defines.extend(self._Setting(
|
|
('VCCLCompilerTool', 'PreprocessorDefinitions'), config, default=[]))
|
|
return defines
|
|
|
|
def GetCompilerPdbName(self, config, expand_special):
|
|
"""Get the pdb file name that should be used for compiler invocations, or
|
|
None if there's no explicit name specified."""
|
|
config = self._TargetConfig(config)
|
|
pdbname = self._Setting(
|
|
('VCCLCompilerTool', 'ProgramDataBaseFileName'), config)
|
|
if pdbname:
|
|
pdbname = expand_special(self.ConvertVSMacros(pdbname))
|
|
return pdbname
|
|
|
|
def GetMapFileName(self, config, expand_special):
|
|
"""Gets the explicitly overriden map file name for a target or returns None
|
|
if it's not set."""
|
|
config = self._TargetConfig(config)
|
|
map_file = self._Setting(('VCLinkerTool', 'MapFileName'), config)
|
|
if map_file:
|
|
map_file = expand_special(self.ConvertVSMacros(map_file, config=config))
|
|
return map_file
|
|
|
|
def GetOutputName(self, config, expand_special):
|
|
"""Gets the explicitly overridden output name for a target or returns None
|
|
if it's not overridden."""
|
|
config = self._TargetConfig(config)
|
|
type = self.spec['type']
|
|
root = 'VCLibrarianTool' if type == 'static_library' else 'VCLinkerTool'
|
|
# TODO(scottmg): Handle OutputDirectory without OutputFile.
|
|
output_file = self._Setting((root, 'OutputFile'), config)
|
|
if output_file:
|
|
output_file = expand_special(self.ConvertVSMacros(
|
|
output_file, config=config))
|
|
return output_file
|
|
|
|
def GetPDBName(self, config, expand_special, default):
|
|
"""Gets the explicitly overridden pdb name for a target or returns
|
|
default if it's not overridden, or if no pdb will be generated."""
|
|
config = self._TargetConfig(config)
|
|
output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config)
|
|
generate_debug_info = self._Setting(
|
|
('VCLinkerTool', 'GenerateDebugInformation'), config)
|
|
if generate_debug_info == 'true':
|
|
if output_file:
|
|
return expand_special(self.ConvertVSMacros(output_file, config=config))
|
|
else:
|
|
return default
|
|
else:
|
|
return None
|
|
|
|
def GetNoImportLibrary(self, config):
|
|
"""If NoImportLibrary: true, ninja will not expect the output to include
|
|
an import library."""
|
|
config = self._TargetConfig(config)
|
|
noimplib = self._Setting(('NoImportLibrary',), config)
|
|
return noimplib == 'true'
|
|
|
|
def GetAsmflags(self, config):
|
|
"""Returns the flags that need to be added to ml invocations."""
|
|
config = self._TargetConfig(config)
|
|
asmflags = []
|
|
safeseh = self._Setting(('MASM', 'UseSafeExceptionHandlers'), config)
|
|
if safeseh == 'true':
|
|
asmflags.append('/safeseh')
|
|
return asmflags
|
|
|
|
def GetCflags(self, config):
|
|
"""Returns the flags that need to be added to .c and .cc compilations."""
|
|
config = self._TargetConfig(config)
|
|
cflags = []
|
|
cflags.extend(['/wd' + w for w in self.msvs_disabled_warnings[config]])
|
|
cl = self._GetWrapper(self, self.msvs_settings[config],
|
|
'VCCLCompilerTool', append=cflags)
|
|
cl('Optimization',
|
|
map={'0': 'd', '1': '1', '2': '2', '3': 'x'}, prefix='/O', default='2')
|
|
cl('InlineFunctionExpansion', prefix='/Ob')
|
|
cl('DisableSpecificWarnings', prefix='/wd')
|
|
cl('StringPooling', map={'true': '/GF'})
|
|
cl('EnableFiberSafeOptimizations', map={'true': '/GT'})
|
|
cl('OmitFramePointers', map={'false': '-', 'true': ''}, prefix='/Oy')
|
|
cl('EnableIntrinsicFunctions', map={'false': '-', 'true': ''}, prefix='/Oi')
|
|
cl('FavorSizeOrSpeed', map={'1': 't', '2': 's'}, prefix='/O')
|
|
cl('FloatingPointModel',
|
|
map={'0': 'precise', '1': 'strict', '2': 'fast'}, prefix='/fp:',
|
|
default='0')
|
|
cl('CompileAsManaged', map={'false': '', 'true': '/clr'})
|
|
cl('WholeProgramOptimization', map={'true': '/GL'})
|
|
cl('WarningLevel', prefix='/W')
|
|
cl('WarnAsError', map={'true': '/WX'})
|
|
cl('CallingConvention',
|
|
map={'0': 'd', '1': 'r', '2': 'z', '3': 'v'}, prefix='/G')
|
|
cl('DebugInformationFormat',
|
|
map={'1': '7', '3': 'i', '4': 'I'}, prefix='/Z')
|
|
cl('RuntimeTypeInfo', map={'true': '/GR', 'false': '/GR-'})
|
|
cl('EnableFunctionLevelLinking', map={'true': '/Gy', 'false': '/Gy-'})
|
|
cl('MinimalRebuild', map={'true': '/Gm'})
|
|
cl('BufferSecurityCheck', map={'true': '/GS', 'false': '/GS-'})
|
|
cl('BasicRuntimeChecks', map={'1': 's', '2': 'u', '3': '1'}, prefix='/RTC')
|
|
cl('RuntimeLibrary',
|
|
map={'0': 'T', '1': 'Td', '2': 'D', '3': 'Dd'}, prefix='/M')
|
|
cl('ExceptionHandling', map={'1': 'sc','2': 'a'}, prefix='/EH')
|
|
cl('DefaultCharIsUnsigned', map={'true': '/J'})
|
|
cl('TreatWChar_tAsBuiltInType',
|
|
map={'false': '-', 'true': ''}, prefix='/Zc:wchar_t')
|
|
cl('EnablePREfast', map={'true': '/analyze'})
|
|
cl('AdditionalOptions', prefix='')
|
|
cl('EnableEnhancedInstructionSet',
|
|
map={'1': 'SSE', '2': 'SSE2', '3': 'AVX', '4': 'IA32', '5': 'AVX2'},
|
|
prefix='/arch:')
|
|
cflags.extend(['/FI' + f for f in self._Setting(
|
|
('VCCLCompilerTool', 'ForcedIncludeFiles'), config, default=[])])
|
|
if self.vs_version.short_name in ('2013', '2013e', '2015'):
|
|
# New flag required in 2013 to maintain previous PDB behavior.
|
|
cflags.append('/FS')
|
|
# ninja handles parallelism by itself, don't have the compiler do it too.
|
|
cflags = filter(lambda x: not x.startswith('/MP'), cflags)
|
|
return cflags
|
|
|
|
def _GetPchFlags(self, config, extension):
|
|
"""Get the flags to be added to the cflags for precompiled header support.
|
|
"""
|
|
config = self._TargetConfig(config)
|
|
# The PCH is only built once by a particular source file. Usage of PCH must
|
|
# only be for the same language (i.e. C vs. C++), so only include the pch
|
|
# flags when the language matches.
|
|
if self.msvs_precompiled_header[config]:
|
|
source_ext = os.path.splitext(self.msvs_precompiled_source[config])[1]
|
|
if _LanguageMatchesForPch(source_ext, extension):
|
|
pch = os.path.split(self.msvs_precompiled_header[config])[1]
|
|
return ['/Yu' + pch, '/FI' + pch, '/Fp${pchprefix}.' + pch + '.pch']
|
|
return []
|
|
|
|
def GetCflagsC(self, config):
|
|
"""Returns the flags that need to be added to .c compilations."""
|
|
config = self._TargetConfig(config)
|
|
return self._GetPchFlags(config, '.c')
|
|
|
|
def GetCflagsCC(self, config):
|
|
"""Returns the flags that need to be added to .cc compilations."""
|
|
config = self._TargetConfig(config)
|
|
return ['/TP'] + self._GetPchFlags(config, '.cc')
|
|
|
|
def _GetAdditionalLibraryDirectories(self, root, config, gyp_to_build_path):
|
|
"""Get and normalize the list of paths in AdditionalLibraryDirectories
|
|
setting."""
|
|
config = self._TargetConfig(config)
|
|
libpaths = self._Setting((root, 'AdditionalLibraryDirectories'),
|
|
config, default=[])
|
|
libpaths = [os.path.normpath(
|
|
gyp_to_build_path(self.ConvertVSMacros(p, config=config)))
|
|
for p in libpaths]
|
|
return ['/LIBPATH:"' + p + '"' for p in libpaths]
|
|
|
|
def GetLibFlags(self, config, gyp_to_build_path):
|
|
"""Returns the flags that need to be added to lib commands."""
|
|
config = self._TargetConfig(config)
|
|
libflags = []
|
|
lib = self._GetWrapper(self, self.msvs_settings[config],
|
|
'VCLibrarianTool', append=libflags)
|
|
libflags.extend(self._GetAdditionalLibraryDirectories(
|
|
'VCLibrarianTool', config, gyp_to_build_path))
|
|
lib('LinkTimeCodeGeneration', map={'true': '/LTCG'})
|
|
lib('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'},
|
|
prefix='/MACHINE:')
|
|
lib('AdditionalOptions')
|
|
return libflags
|
|
|
|
def GetDefFile(self, gyp_to_build_path):
|
|
"""Returns the .def file from sources, if any. Otherwise returns None."""
|
|
spec = self.spec
|
|
if spec['type'] in ('shared_library', 'loadable_module', 'executable'):
|
|
def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
|
|
if len(def_files) == 1:
|
|
return gyp_to_build_path(def_files[0])
|
|
elif len(def_files) > 1:
|
|
raise Exception("Multiple .def files")
|
|
return None
|
|
|
|
def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path):
|
|
""".def files get implicitly converted to a ModuleDefinitionFile for the
|
|
linker in the VS generator. Emulate that behaviour here."""
|
|
def_file = self.GetDefFile(gyp_to_build_path)
|
|
if def_file:
|
|
ldflags.append('/DEF:"%s"' % def_file)
|
|
|
|
def GetPGDName(self, config, expand_special):
|
|
"""Gets the explicitly overridden pgd name for a target or returns None
|
|
if it's not overridden."""
|
|
config = self._TargetConfig(config)
|
|
output_file = self._Setting(
|
|
('VCLinkerTool', 'ProfileGuidedDatabase'), config)
|
|
if output_file:
|
|
output_file = expand_special(self.ConvertVSMacros(
|
|
output_file, config=config))
|
|
return output_file
|
|
|
|
def GetLdflags(self, config, gyp_to_build_path, expand_special,
|
|
manifest_base_name, output_name, is_executable, build_dir):
|
|
"""Returns the flags that need to be added to link commands, and the
|
|
manifest files."""
|
|
config = self._TargetConfig(config)
|
|
ldflags = []
|
|
ld = self._GetWrapper(self, self.msvs_settings[config],
|
|
'VCLinkerTool', append=ldflags)
|
|
self._GetDefFileAsLdflags(ldflags, gyp_to_build_path)
|
|
ld('GenerateDebugInformation', map={'true': '/DEBUG'})
|
|
ld('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'},
|
|
prefix='/MACHINE:')
|
|
ldflags.extend(self._GetAdditionalLibraryDirectories(
|
|
'VCLinkerTool', config, gyp_to_build_path))
|
|
ld('DelayLoadDLLs', prefix='/DELAYLOAD:')
|
|
ld('TreatLinkerWarningAsErrors', prefix='/WX',
|
|
map={'true': '', 'false': ':NO'})
|
|
out = self.GetOutputName(config, expand_special)
|
|
if out:
|
|
ldflags.append('/OUT:' + out)
|
|
pdb = self.GetPDBName(config, expand_special, output_name + '.pdb')
|
|
if pdb:
|
|
ldflags.append('/PDB:' + pdb)
|
|
pgd = self.GetPGDName(config, expand_special)
|
|
if pgd:
|
|
ldflags.append('/PGD:' + pgd)
|
|
map_file = self.GetMapFileName(config, expand_special)
|
|
ld('GenerateMapFile', map={'true': '/MAP:' + map_file if map_file
|
|
else '/MAP'})
|
|
ld('MapExports', map={'true': '/MAPINFO:EXPORTS'})
|
|
ld('AdditionalOptions', prefix='')
|
|
|
|
minimum_required_version = self._Setting(
|
|
('VCLinkerTool', 'MinimumRequiredVersion'), config, default='')
|
|
if minimum_required_version:
|
|
minimum_required_version = ',' + minimum_required_version
|
|
ld('SubSystem',
|
|
map={'1': 'CONSOLE%s' % minimum_required_version,
|
|
'2': 'WINDOWS%s' % minimum_required_version},
|
|
prefix='/SUBSYSTEM:')
|
|
|
|
stack_reserve_size = self._Setting(
|
|
('VCLinkerTool', 'StackReserveSize'), config, default='')
|
|
if stack_reserve_size:
|
|
stack_commit_size = self._Setting(
|
|
('VCLinkerTool', 'StackCommitSize'), config, default='')
|
|
if stack_commit_size:
|
|
stack_commit_size = ',' + stack_commit_size
|
|
ldflags.append('/STACK:%s%s' % (stack_reserve_size, stack_commit_size))
|
|
|
|
ld('TerminalServerAware', map={'1': ':NO', '2': ''}, prefix='/TSAWARE')
|
|
ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL')
|
|
ld('BaseAddress', prefix='/BASE:')
|
|
ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED')
|
|
ld('RandomizedBaseAddress',
|
|
map={'1': ':NO', '2': ''}, prefix='/DYNAMICBASE')
|
|
ld('DataExecutionPrevention',
|
|
map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT')
|
|
ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:')
|
|
ld('ForceSymbolReferences', prefix='/INCLUDE:')
|
|
ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:')
|
|
ld('LinkTimeCodeGeneration',
|
|
map={'1': '', '2': ':PGINSTRUMENT', '3': ':PGOPTIMIZE',
|
|
'4': ':PGUPDATE'},
|
|
prefix='/LTCG')
|
|
ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:')
|
|
ld('ResourceOnlyDLL', map={'true': '/NOENTRY'})
|
|
ld('EntryPointSymbol', prefix='/ENTRY:')
|
|
ld('Profile', map={'true': '/PROFILE'})
|
|
ld('LargeAddressAware',
|
|
map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE')
|
|
# TODO(scottmg): This should sort of be somewhere else (not really a flag).
|
|
ld('AdditionalDependencies', prefix='')
|
|
|
|
if self.GetArch(config) == 'x86':
|
|
safeseh_default = 'true'
|
|
else:
|
|
safeseh_default = None
|
|
ld('ImageHasSafeExceptionHandlers',
|
|
map={'false': ':NO', 'true': ''}, prefix='/SAFESEH',
|
|
default=safeseh_default)
|
|
|
|
# If the base address is not specifically controlled, DYNAMICBASE should
|
|
# be on by default.
|
|
base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED',
|
|
ldflags)
|
|
if not base_flags:
|
|
ldflags.append('/DYNAMICBASE')
|
|
|
|
# If the NXCOMPAT flag has not been specified, default to on. Despite the
|
|
# documentation that says this only defaults to on when the subsystem is
|
|
# Vista or greater (which applies to the linker), the IDE defaults it on
|
|
# unless it's explicitly off.
|
|
if not filter(lambda x: 'NXCOMPAT' in x, ldflags):
|
|
ldflags.append('/NXCOMPAT')
|
|
|
|
have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
|
|
manifest_flags, intermediate_manifest, manifest_files = \
|
|
self._GetLdManifestFlags(config, manifest_base_name, gyp_to_build_path,
|
|
is_executable and not have_def_file, build_dir)
|
|
ldflags.extend(manifest_flags)
|
|
return ldflags, intermediate_manifest, manifest_files
|
|
|
|
def _GetLdManifestFlags(self, config, name, gyp_to_build_path,
|
|
allow_isolation, build_dir):
|
|
"""Returns a 3-tuple:
|
|
- the set of flags that need to be added to the link to generate
|
|
a default manifest
|
|
- the intermediate manifest that the linker will generate that should be
|
|
used to assert it doesn't add anything to the merged one.
|
|
- the list of all the manifest files to be merged by the manifest tool and
|
|
included into the link."""
|
|
generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'),
|
|
config,
|
|
default='true')
|
|
if generate_manifest != 'true':
|
|
# This means not only that the linker should not generate the intermediate
|
|
# manifest but also that the manifest tool should do nothing even when
|
|
# additional manifests are specified.
|
|
return ['/MANIFEST:NO'], [], []
|
|
|
|
output_name = name + '.intermediate.manifest'
|
|
flags = [
|
|
'/MANIFEST',
|
|
'/ManifestFile:' + output_name,
|
|
]
|
|
|
|
# Instead of using the MANIFESTUAC flags, we generate a .manifest to
|
|
# include into the list of manifests. This allows us to avoid the need to
|
|
# do two passes during linking. The /MANIFEST flag and /ManifestFile are
|
|
# still used, and the intermediate manifest is used to assert that the
|
|
# final manifest we get from merging all the additional manifest files
|
|
# (plus the one we generate here) isn't modified by merging the
|
|
# intermediate into it.
|
|
|
|
# Always NO, because we generate a manifest file that has what we want.
|
|
flags.append('/MANIFESTUAC:NO')
|
|
|
|
config = self._TargetConfig(config)
|
|
enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config,
|
|
default='true')
|
|
manifest_files = []
|
|
generated_manifest_outer = \
|
|
"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" \
|
|
"<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>%s" \
|
|
"</assembly>"
|
|
if enable_uac == 'true':
|
|
execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'),
|
|
config, default='0')
|
|
execution_level_map = {
|
|
'0': 'asInvoker',
|
|
'1': 'highestAvailable',
|
|
'2': 'requireAdministrator'
|
|
}
|
|
|
|
ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config,
|
|
default='false')
|
|
|
|
inner = '''
|
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
<security>
|
|
<requestedPrivileges>
|
|
<requestedExecutionLevel level='%s' uiAccess='%s' />
|
|
</requestedPrivileges>
|
|
</security>
|
|
</trustInfo>''' % (execution_level_map[execution_level], ui_access)
|
|
else:
|
|
inner = ''
|
|
|
|
generated_manifest_contents = generated_manifest_outer % inner
|
|
generated_name = name + '.generated.manifest'
|
|
# Need to join with the build_dir here as we're writing it during
|
|
# generation time, but we return the un-joined version because the build
|
|
# will occur in that directory. We only write the file if the contents
|
|
# have changed so that simply regenerating the project files doesn't
|
|
# cause a relink.
|
|
build_dir_generated_name = os.path.join(build_dir, generated_name)
|
|
gyp.common.EnsureDirExists(build_dir_generated_name)
|
|
f = gyp.common.WriteOnDiff(build_dir_generated_name)
|
|
f.write(generated_manifest_contents)
|
|
f.close()
|
|
manifest_files = [generated_name]
|
|
|
|
if allow_isolation:
|
|
flags.append('/ALLOWISOLATION')
|
|
|
|
manifest_files += self._GetAdditionalManifestFiles(config,
|
|
gyp_to_build_path)
|
|
return flags, output_name, manifest_files
|
|
|
|
def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
|
|
"""Gets additional manifest files that are added to the default one
|
|
generated by the linker."""
|
|
files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config,
|
|
default=[])
|
|
if isinstance(files, str):
|
|
files = files.split(';')
|
|
return [os.path.normpath(
|
|
gyp_to_build_path(self.ConvertVSMacros(f, config=config)))
|
|
for f in files]
|
|
|
|
def IsUseLibraryDependencyInputs(self, config):
|
|
"""Returns whether the target should be linked via Use Library Dependency
|
|
Inputs (using component .objs of a given .lib)."""
|
|
config = self._TargetConfig(config)
|
|
uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config)
|
|
return uldi == 'true'
|
|
|
|
def IsEmbedManifest(self, config):
|
|
"""Returns whether manifest should be linked into binary."""
|
|
config = self._TargetConfig(config)
|
|
embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config,
|
|
default='true')
|
|
return embed == 'true'
|
|
|
|
def IsLinkIncremental(self, config):
|
|
"""Returns whether the target should be linked incrementally."""
|
|
config = self._TargetConfig(config)
|
|
link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config)
|
|
return link_inc != '1'
|
|
|
|
def GetRcflags(self, config, gyp_to_ninja_path):
|
|
"""Returns the flags that need to be added to invocations of the resource
|
|
compiler."""
|
|
config = self._TargetConfig(config)
|
|
rcflags = []
|
|
rc = self._GetWrapper(self, self.msvs_settings[config],
|
|
'VCResourceCompilerTool', append=rcflags)
|
|
rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I')
|
|
rcflags.append('/I' + gyp_to_ninja_path('.'))
|
|
rc('PreprocessorDefinitions', prefix='/d')
|
|
# /l arg must be in hex without leading '0x'
|
|
rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:])
|
|
return rcflags
|
|
|
|
def BuildCygwinBashCommandLine(self, args, path_to_base):
|
|
"""Build a command line that runs args via cygwin bash. We assume that all
|
|
incoming paths are in Windows normpath'd form, so they need to be
|
|
converted to posix style for the part of the command line that's passed to
|
|
bash. We also have to do some Visual Studio macro emulation here because
|
|
various rules use magic VS names for things. Also note that rules that
|
|
contain ninja variables cannot be fixed here (for example ${source}), so
|
|
the outer generator needs to make sure that the paths that are written out
|
|
are in posix style, if the command line will be used here."""
|
|
cygwin_dir = os.path.normpath(
|
|
os.path.join(path_to_base, self.msvs_cygwin_dirs[0]))
|
|
cd = ('cd %s' % path_to_base).replace('\\', '/')
|
|
args = [a.replace('\\', '/').replace('"', '\\"') for a in args]
|
|
args = ["'%s'" % a.replace("'", "'\\''") for a in args]
|
|
bash_cmd = ' '.join(args)
|
|
cmd = (
|
|
'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
|
|
'bash -c "%s ; %s"' % (cd, bash_cmd))
|
|
return cmd
|
|
|
|
def IsRuleRunUnderCygwin(self, rule):
|
|
"""Determine if an action should be run under cygwin. If the variable is
|
|
unset, or set to 1 we use cygwin."""
|
|
return int(rule.get('msvs_cygwin_shell',
|
|
self.spec.get('msvs_cygwin_shell', 1))) != 0
|
|
|
|
def _HasExplicitRuleForExtension(self, spec, extension):
|
|
"""Determine if there's an explicit rule for a particular extension."""
|
|
for rule in spec.get('rules', []):
|
|
if rule['extension'] == extension:
|
|
return True
|
|
return False
|
|
|
|
def _HasExplicitIdlActions(self, spec):
|
|
"""Determine if an action should not run midl for .idl files."""
|
|
return any([action.get('explicit_idl_action', 0)
|
|
for action in spec.get('actions', [])])
|
|
|
|
def HasExplicitIdlRulesOrActions(self, spec):
|
|
"""Determine if there's an explicit rule or action for idl files. When
|
|
there isn't we need to generate implicit rules to build MIDL .idl files."""
|
|
return (self._HasExplicitRuleForExtension(spec, 'idl') or
|
|
self._HasExplicitIdlActions(spec))
|
|
|
|
def HasExplicitAsmRules(self, spec):
|
|
"""Determine if there's an explicit rule for asm files. When there isn't we
|
|
need to generate implicit rules to assemble .asm files."""
|
|
return self._HasExplicitRuleForExtension(spec, 'asm')
|
|
|
|
def GetIdlBuildData(self, source, config):
|
|
"""Determine the implicit outputs for an idl file. Returns output
|
|
directory, outputs, and variables and flags that are required."""
|
|
config = self._TargetConfig(config)
|
|
midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
|
|
def midl(name, default=None):
|
|
return self.ConvertVSMacros(midl_get(name, default=default),
|
|
config=config)
|
|
tlb = midl('TypeLibraryName', default='${root}.tlb')
|
|
header = midl('HeaderFileName', default='${root}.h')
|
|
dlldata = midl('DLLDataFileName', default='dlldata.c')
|
|
iid = midl('InterfaceIdentifierFileName', default='${root}_i.c')
|
|
proxy = midl('ProxyFileName', default='${root}_p.c')
|
|
# Note that .tlb is not included in the outputs as it is not always
|
|
# generated depending on the content of the input idl file.
|
|
outdir = midl('OutputDirectory', default='')
|
|
output = [header, dlldata, iid, proxy]
|
|
variables = [('tlb', tlb),
|
|
('h', header),
|
|
('dlldata', dlldata),
|
|
('iid', iid),
|
|
('proxy', proxy)]
|
|
# TODO(scottmg): Are there configuration settings to set these flags?
|
|
target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64'
|
|
flags = ['/char', 'signed', '/env', target_platform, '/Oicf']
|
|
return outdir, output, variables, flags
|
|
|
|
|
|
def _LanguageMatchesForPch(source_ext, pch_source_ext):
|
|
c_exts = ('.c',)
|
|
cc_exts = ('.cc', '.cxx', '.cpp')
|
|
return ((source_ext in c_exts and pch_source_ext in c_exts) or
|
|
(source_ext in cc_exts and pch_source_ext in cc_exts))
|
|
|
|
|
|
class PrecompiledHeader(object):
|
|
"""Helper to generate dependencies and build rules to handle generation of
|
|
precompiled headers. Interface matches the GCH handler in xcode_emulation.py.
|
|
"""
|
|
def __init__(
|
|
self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext):
|
|
self.settings = settings
|
|
self.config = config
|
|
pch_source = self.settings.msvs_precompiled_source[self.config]
|
|
self.pch_source = gyp_to_build_path(pch_source)
|
|
filename, _ = os.path.splitext(pch_source)
|
|
self.output_obj = gyp_to_unique_output(filename + obj_ext).lower()
|
|
|
|
def _PchHeader(self):
|
|
"""Get the header that will appear in an #include line for all source
|
|
files."""
|
|
return os.path.split(self.settings.msvs_precompiled_header[self.config])[1]
|
|
|
|
def GetObjDependencies(self, sources, objs, arch):
|
|
"""Given a list of sources files and the corresponding object files,
|
|
returns a list of the pch files that should be depended upon. The
|
|
additional wrapping in the return value is for interface compatibility
|
|
with make.py on Mac, and xcode_emulation.py."""
|
|
assert arch is None
|
|
if not self._PchHeader():
|
|
return []
|
|
pch_ext = os.path.splitext(self.pch_source)[1]
|
|
for source in sources:
|
|
if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
|
|
return [(None, None, self.output_obj)]
|
|
return []
|
|
|
|
def GetPchBuildCommands(self, arch):
|
|
"""Not used on Windows as there are no additional build steps required
|
|
(instead, existing steps are modified in GetFlagsModifications below)."""
|
|
return []
|
|
|
|
def GetFlagsModifications(self, input, output, implicit, command,
|
|
cflags_c, cflags_cc, expand_special):
|
|
"""Get the modified cflags and implicit dependencies that should be used
|
|
for the pch compilation step."""
|
|
if input == self.pch_source:
|
|
pch_output = ['/Yc' + self._PchHeader()]
|
|
if command == 'cxx':
|
|
return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))],
|
|
self.output_obj, [])
|
|
elif command == 'cc':
|
|
return ([('cflags_c', map(expand_special, cflags_c + pch_output))],
|
|
self.output_obj, [])
|
|
return [], output, implicit
|
|
|
|
|
|
vs_version = None
|
|
def GetVSVersion(generator_flags):
|
|
global vs_version
|
|
if not vs_version:
|
|
vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
|
|
generator_flags.get('msvs_version', 'auto'),
|
|
allow_fallback=False)
|
|
return vs_version
|
|
|
|
def _GetVsvarsSetupArgs(generator_flags, arch):
|
|
vs = GetVSVersion(generator_flags)
|
|
return vs.SetupScript()
|
|
|
|
def ExpandMacros(string, expansions):
|
|
"""Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
|
|
for the canonical way to retrieve a suitable dict."""
|
|
if '$' in string:
|
|
for old, new in expansions.iteritems():
|
|
assert '$(' not in new, new
|
|
string = string.replace(old, new)
|
|
return string
|
|
|
|
def _ExtractImportantEnvironment(output_of_set):
|
|
"""Extracts environment variables required for the toolchain to run from
|
|
a textual dump output by the cmd.exe 'set' command."""
|
|
envvars_to_save = (
|
|
'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
|
|
'include',
|
|
'lib',
|
|
'libpath',
|
|
'path',
|
|
'pathext',
|
|
'systemroot',
|
|
'temp',
|
|
'tmp',
|
|
)
|
|
env = {}
|
|
for line in output_of_set.splitlines():
|
|
for envvar in envvars_to_save:
|
|
if re.match(envvar + '=', line.lower()):
|
|
var, setting = line.split('=', 1)
|
|
if envvar == 'path':
|
|
# Our own rules (for running gyp-win-tool) and other actions in
|
|
# Chromium rely on python being in the path. Add the path to this
|
|
# python here so that if it's not in the path when ninja is run
|
|
# later, python will still be found.
|
|
setting = os.path.dirname(sys.executable) + os.pathsep + setting
|
|
env[var.upper()] = setting
|
|
break
|
|
for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
|
|
if required not in env:
|
|
raise Exception('Environment variable "%s" '
|
|
'required to be set to valid path' % required)
|
|
return env
|
|
|
|
def _FormatAsEnvironmentBlock(envvar_dict):
|
|
"""Format as an 'environment block' directly suitable for CreateProcess.
|
|
Briefly this is a list of key=value\0, terminated by an additional \0. See
|
|
CreateProcess documentation for more details."""
|
|
block = ''
|
|
nul = '\0'
|
|
for key, value in envvar_dict.iteritems():
|
|
block += key + '=' + value + nul
|
|
block += nul
|
|
return block
|
|
|
|
def _ExtractCLPath(output_of_where):
|
|
"""Gets the path to cl.exe based on the output of calling the environment
|
|
setup batch file, followed by the equivalent of `where`."""
|
|
# Take the first line, as that's the first found in the PATH.
|
|
for line in output_of_where.strip().splitlines():
|
|
if line.startswith('LOC:'):
|
|
return line[len('LOC:'):].strip()
|
|
|
|
def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags,
|
|
system_includes, open_out):
|
|
"""It's not sufficient to have the absolute path to the compiler, linker,
|
|
etc. on Windows, as those tools rely on .dlls being in the PATH. We also
|
|
need to support both x86 and x64 compilers within the same build (to support
|
|
msvs_target_platform hackery). Different architectures require a different
|
|
compiler binary, and different supporting environment variables (INCLUDE,
|
|
LIB, LIBPATH). So, we extract the environment here, wrap all invocations
|
|
of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
|
|
sets up the environment, and then we do not prefix the compiler with
|
|
an absolute path, instead preferring something like "cl.exe" in the rule
|
|
which will then run whichever the environment setup has put in the path.
|
|
When the following procedure to generate environment files does not
|
|
meet your requirement (e.g. for custom toolchains), you can pass
|
|
"-G ninja_use_custom_environment_files" to the gyp to suppress file
|
|
generation and use custom environment files prepared by yourself."""
|
|
archs = ('x86', 'x64')
|
|
if generator_flags.get('ninja_use_custom_environment_files', 0):
|
|
cl_paths = {}
|
|
for arch in archs:
|
|
cl_paths[arch] = 'cl.exe'
|
|
return cl_paths
|
|
vs = GetVSVersion(generator_flags)
|
|
cl_paths = {}
|
|
for arch in archs:
|
|
# Extract environment variables for subprocesses.
|
|
args = vs.SetupScript(arch)
|
|
args.extend(('&&', 'set'))
|
|
popen = subprocess.Popen(
|
|
args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
variables, _ = popen.communicate()
|
|
env = _ExtractImportantEnvironment(variables)
|
|
|
|
# Inject system includes from gyp files into INCLUDE.
|
|
if system_includes:
|
|
system_includes = system_includes | OrderedSet(
|
|
env.get('INCLUDE', '').split(';'))
|
|
env['INCLUDE'] = ';'.join(system_includes)
|
|
|
|
env_block = _FormatAsEnvironmentBlock(env)
|
|
f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
|
|
f.write(env_block)
|
|
f.close()
|
|
|
|
# Find cl.exe location for this architecture.
|
|
args = vs.SetupScript(arch)
|
|
args.extend(('&&',
|
|
'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
|
|
popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
|
|
output, _ = popen.communicate()
|
|
cl_paths[arch] = _ExtractCLPath(output)
|
|
return cl_paths
|
|
|
|
def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja):
|
|
"""Emulate behavior of msvs_error_on_missing_sources present in the msvs
|
|
generator: Check that all regular source files, i.e. not created at run time,
|
|
exist on disk. Missing files cause needless recompilation when building via
|
|
VS, and we want this check to match for people/bots that build using ninja,
|
|
so they're not surprised when the VS build fails."""
|
|
if int(generator_flags.get('msvs_error_on_missing_sources', 0)):
|
|
no_specials = filter(lambda x: '$' not in x, sources)
|
|
relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials]
|
|
missing = filter(lambda x: not os.path.exists(x), relative)
|
|
if missing:
|
|
# They'll look like out\Release\..\..\stuff\things.cc, so normalize the
|
|
# path for a slightly less crazy looking output.
|
|
cleaned_up = [os.path.normpath(x) for x in missing]
|
|
raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up))
|
|
|
|
# Sets some values in default_variables, which are required for many
|
|
# generators, run on Windows.
|
|
def CalculateCommonVariables(default_variables, params):
|
|
generator_flags = params.get('generator_flags', {})
|
|
|
|
# Set a variable so conditions can be based on msvs_version.
|
|
msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
|
|
default_variables['MSVS_VERSION'] = msvs_version.ShortName()
|
|
|
|
# To determine processor word size on Windows, in addition to checking
|
|
# PROCESSOR_ARCHITECTURE (which reflects the word size of the current
|
|
# process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
|
|
# contains the actual word size of the system when running thru WOW64).
|
|
if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
|
|
'64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
|
|
default_variables['MSVS_OS_BITS'] = 64
|
|
else:
|
|
default_variables['MSVS_OS_BITS'] = 32
|