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 |