| Index: pylib/gyp/msvs_emulation.py
|
| diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py
|
| index 7d10b94132af5c77cf2f4c370fbe8ef8ad81a39e..b60b19987094c6a1a90f942cd782f19dd57688e7 100644
|
| --- a/pylib/gyp/msvs_emulation.py
|
| +++ b/pylib/gyp/msvs_emulation.py
|
| @@ -7,7 +7,10 @@ This module helps emulate Visual Studio 2008 behavior on top of other
|
| build systems, primarily ninja.
|
| """
|
|
|
| +import gyp.MSVSVersion
|
| +import os
|
| import re
|
| +import sys
|
|
|
| windows_quoter_regex = re.compile(r'(\\*)"')
|
|
|
| @@ -20,27 +23,371 @@ def QuoteCmdExeArgument(arg):
|
| # 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.
|
| - #
|
| + tmp = arg
|
| +
|
| + # If the string ends in a \, then it will be interpreted as an escaper for
|
| + # the trailing ", so we need to pre-escape that.
|
| + if tmp[-1] == '\\':
|
| + tmp = tmp + '\\'
|
| +
|
| # 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.
|
| - tmp = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg)
|
| + tmp = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', tmp)
|
|
|
| # Now, we need to escape some things that are actually for the shell.
|
| # ^-escape various characters that are otherwise interpreted by the shell.
|
| - tmp = re.sub(r'([&|^])', r'^\1', tmp)
|
| + # Note, removed ^ escape, these args go in .rsp files now. Blech. :(
|
| + #tmp = re.sub(r'([&|^])', r'^\1', tmp)
|
|
|
| # %'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.
|
| - tmp = tmp.replace('%', '^%^%')
|
| + # Note, removed ^ escape, these args go in .rsp files now. Blech :(
|
| + tmp = tmp.replace('%', '%%')
|
|
|
| # Finally, wrap the whole thing in quotes so that the above quote rule
|
| # applies and whitespace isn't a word break.
|
| return '"' + tmp + '"'
|
|
|
| -def EncodeCmdExeList(args):
|
| - """Process a list of arguments using QuoteCmdExeArgument."""
|
| - return ' '.join(QuoteCmdExeArgument(arg) for arg in args)
|
| +
|
| +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 isinstance(element, list) or isinstance(element, tuple):
|
| + element = filter(None, [map.get(elem) for elem in element])
|
| + else:
|
| + element = map.get(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
|
| +
|
| +
|
| +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):
|
| + self.spec = spec
|
| +
|
| + supported_fields = [
|
| + ('msvs_configuration_attributes', dict),
|
| + ('msvs_settings', dict),
|
| + ('msvs_system_include_dirs', list),
|
| + ('msvs_disabled_warnings', list),
|
| + ('msvs_cygwin_dirs', list),
|
| + ]
|
| + 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())
|
| +
|
| + def ConvertVSMacros(self, s):
|
| + """Convert from VS macro names to something equivalent."""
|
| + if '$' in s:
|
| + replacements = {
|
| + '$(VSInstallDir)': os.environ.get('VSInstallDir') + '\\',
|
| + '$(VCInstallDir)': os.environ.get('VCInstallDir') + '\\',
|
| + '$(DXSDK_DIR)': os.environ.get('DXSDK_DIR') + '\\',
|
| + '$(OutDir)\\': '',
|
| + }
|
| + for old, new in replacements.iteritems():
|
| + s = s.replace(old, new)
|
| + return s
|
| +
|
| + def EncodeCmdExeList(self, args):
|
| + """Process a list of arguments using QuoteCmdExeArgument."""
|
| + expanded = [self.ConvertVSMacros(arg) for arg in args]
|
| + return ' '.join(QuoteCmdExeArgument(arg) for arg in expanded)
|
| +
|
| + def AdjustLibraries(self, libraries):
|
| + """Strip -l from library if it's specified with that."""
|
| + return [lib[2:] if lib.startswith('-l') else lib for lib in libraries]
|
| +
|
| + 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 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."""
|
| + includes = include_dirs + self.msvs_system_include_dirs[config]
|
| + return [self.ConvertVSMacros(p) 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."""
|
| + 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 GetCflags(self, config):
|
| + """Returns the flags that need to be added to .c and .cc compilations."""
|
| + 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', '2':'s'}, prefix='$!/O')
|
| + cl('InlineFunctionExpansion', prefix='$!/Ob')
|
| + cl('OmitFramePointers', map={'false':'-', 'true':''}, prefix='$!/Oy')
|
| + cl('FavorSizeOrSpeed', map={'1':'s', '2':'t'}, prefix='$!/O')
|
| + cl('WholeProgramOptimization', map={'true':'$!/GL'})
|
| + cl('WarningLevel', prefix='$!/W')
|
| + cl('WarnAsError', map={'true':'$!/WX'})
|
| + 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('AdditionalOptions', prefix='$!')
|
| + return cflags
|
| +
|
| + def GetCflagsC(self, config):
|
| + """Returns the flags that need to be added to .c compilations."""
|
| + return []
|
| +
|
| + def GetCflagsCC(self, config):
|
| + """Returns the flags that need to be added to .cc compilations."""
|
| + return ['$!/TP']
|
| +
|
| + def _GetDefFileAsLdflags(self, spec, 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 = ''
|
| + if spec['type'] in ('shared_library', 'loadable_module'):
|
| + def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
|
| + if len(def_files) == 1:
|
| + ldflags.append('$!/DEF:"%s"' % gyp_to_build_path(def_files[0]))
|
| + elif len(def_files) > 1:
|
| + raise Exception("Multiple .def files")
|
| +
|
| + def GetLibFlags(self, config, spec):
|
| + libflags = []
|
| + lib = self._GetWrapper(self, self.msvs_settings[config],
|
| + 'VCLibrarianTool', append=libflags)
|
| + libpaths = self.Setting(('VCLibrarianTool', 'AdditionalLibraryDirectories'),
|
| + config, default=[])
|
| + libpaths = [os.path.normpath(self.ConvertVSMacros(p)) for p in libpaths]
|
| + libflags.extend(['$!/LIBPATH:"' + p + '"' for p in libpaths])
|
| + lib('AdditionalOptions', prefix='$!')
|
| + return libflags
|
| +
|
| + def GetLdflags(self, config, spec, product_dir, gyp_to_build_path):
|
| + """Returns the flags that need to be added to link and lib commands."""
|
| + ldflags = []
|
| + ld = self._GetWrapper(self, self.msvs_settings[config],
|
| + 'VCLinkerTool', append=ldflags)
|
| + self._GetDefFileAsLdflags(spec, ldflags, gyp_to_build_path)
|
| + ld('GenerateDebugInformation', map={'true':'$!/DEBUG'})
|
| + ld('TargetMachine', map={'1':'X86', '17':'X64'}, prefix='$!/MACHINE:')
|
| + libpaths = self.Setting(('VCLinkerTool', 'AdditionalLibraryDirectories'),
|
| + config, default=[])
|
| + libpaths = [os.path.normpath(self.ConvertVSMacros(p)) for p in libpaths]
|
| + ldflags.extend(['$!/LIBPATH:"' + p + '"' for p in libpaths])
|
| + ld('DelayLoadDLLs', prefix='$!/DELAYLOAD:')
|
| + ld('AdditionalOptions', prefix='$!')
|
| + ld('SubSystem', map={'1':'CONSOLE', '2':'WINDOWS'}, prefix='$!/SUBSYSTEM:')
|
| + ld('LinkIncremental', map={'1':':NO', '2':''}, prefix='$!/INCREMENTAL')
|
| + 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('EnableCOMDATFolding', map={'1':'NOICF', '2':'ICF'}, prefix='$!/OPT:')
|
| + ld('LinkTimeCodeGeneration', map={'1':'$!/LTCG'})
|
| + ld('IgnoreDefaultLibraryNames', prefix='$!/NODEFAULTLIB:')
|
| + # TODO(scottmg): This should sort of be somewhere else (not really a flag).
|
| + ld('AdditionalDependencies', prefix='$!')
|
| + # TODO(scottmg): These too.
|
| + ldflags.extend(('kernel32.lib', 'user32.lib', 'gdi32.lib', 'winspool.lib',
|
| + 'comdlg32.lib', 'advapi32.lib', 'shell32.lib', 'ole32.lib',
|
| + 'oleaut32.lib', 'uuid.lib', 'odbc32.lib', 'odbccp32.lib',
|
| + 'DelayImp.lib'))
|
| + return ldflags
|
| +
|
| + def GetOutputName(self, config, product_dir):
|
| + ld = self._GetWrapper(self, self.msvs_settings[config], 'VCLinkerTool')
|
| + return ld('OutputFile')
|
| +
|
| + def GetIdlRule(self, source, config,
|
| + gyp_to_build_path, gyp_to_unique_output, expand_rule_variables):
|
| + """Determine the implicit outputs for an idl file. Returns both the inputs
|
| + and the outputs for the build rule."""
|
| + midl = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
|
| + tlb = midl('TypeLibraryName')
|
| + header = midl('HeaderFileName')
|
| + dlldata = midl('DLLDataFileName')
|
| + iid = midl('InterfaceIdentifierFileName')
|
| + proxy = midl('ProxyFileName')
|
| + # Note that .tlb is not included in the outputs as it is not always
|
| + # generated depending on the content of the input idl file.
|
| + output = [header, dlldata, iid, proxy]
|
| + input = gyp_to_build_path(source)
|
| + outdir = gyp_to_build_path(midl('OutputDirectory'))
|
| + filename = os.path.splitext(os.path.split(source)[1])[0]
|
| + def fix_path(path, rel=None):
|
| + path = os.path.join(outdir, path)
|
| + path = expand_rule_variables(path, '?', '?', '?', '?', filename)
|
| + if rel:
|
| + path = os.path.relpath(path, rel)
|
| + return path
|
| + output = [fix_path(o) for o in output]
|
| + # TODO(scottmg): flags: /char signed /env win32, /Oicf
|
| + variables = [('name', filename),
|
| + ('tlb', fix_path(tlb, outdir)),
|
| + ('h', fix_path(header, outdir)),
|
| + ('dlldata', fix_path(dlldata, outdir)),
|
| + ('iid', fix_path(iid, outdir)),
|
| + ('proxy', fix_path(proxy, outdir)),
|
| + ('outdir', outdir)]
|
| + return input, output, variables
|
| +
|
| + def HandleIdlFiles(self, ninja, spec, sources, config_name,
|
| + gyp_to_build_path, gyp_to_unique_output, expand_rule_variables):
|
| + """Visual Studio has implicit .idl build rules for MIDL files, but other
|
| + projects (e.g., WebKit) use .idl for a different import format with custom
|
| + build rules. So, only do the implicit rules if they're not overriden by
|
| + explicit rules."""
|
| + native_idl = True
|
| + for rule in spec.get('rules', []):
|
| + if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
|
| + native_idl = False
|
| + if native_idl:
|
| + for source in sources:
|
| + filename, ext = os.path.splitext(source)
|
| + if ext == '.idl':
|
| + input, output, vars = self.GetIdlRule(
|
| + source,
|
| + config_name,
|
| + gyp_to_build_path,
|
| + gyp_to_unique_output,
|
| + expand_rule_variables)
|
| + ninja.build(output, 'idl', input, variables=vars)
|
| +
|
| +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'))
|
| + return vs_version
|
| +
|
| +def _GetBinaryPath(generator_flags, tool):
|
| + vs = GetVSVersion(generator_flags)
|
| + return ('"' +
|
| + os.path.normpath(os.path.join(vs.Path(), "VC/bin", tool)) +
|
| + '"')
|
| +
|
| +def GetCLPath(generator_flags):
|
| + return _GetBinaryPath(generator_flags, 'cl.exe')
|
| +
|
| +def GetLinkPath(generator_flags):
|
| + return _GetBinaryPath(generator_flags, 'link.exe')
|
| +
|
| +def GetMidlPath(generator_flags):
|
| + return _GetBinaryPath(generator_flags, 'midl.exe')
|
| +
|
| +def IsRuleRunUnderCygwin(spec):
|
| + """Determine if an action should be run under cygwin. If the variable is
|
| + unset, or set to 1 we use cygwin."""
|
| + if not spec:
|
| + return False
|
| + return int(spec.get('msvs_cygwin_shell', 1)) != 0
|
| +
|
| +def BuildCygwinBashCommandLine(settings, rule, args, path_to_base, config):
|
| + """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."""
|
| + assert IsRuleRunUnderCygwin(rule)
|
| + cygwin_dir = os.path.normpath(
|
| + os.path.join(path_to_base, settings.msvs_cygwin_dirs[config][0]))
|
| + cd = ('cd %s' % path_to_base).replace('\\', '/')
|
| + args = [settings.ConvertVSMacros(a).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
|
|
|