| # Copyright 2017 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 implements starboard's abstract toolchain for MSVC UWP.""" |
| |
| from difflib import get_close_matches |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| _COBALT_SRC = os.path.abspath(os.path.join(*([__file__] + 5 * [os.pardir]))) |
| sys.path.append(os.path.join(_COBALT_SRC, 'starboard', 'tools')) |
| |
| import gyp.MSVSVersion # pylint: disable=g-import-not-at-top |
| # This tool_chain is starboard/build/toolchain.py |
| from starboard.tools.toolchain_deprecated import CompilerSettings |
| from starboard.tools.toolchain_deprecated import PrecompiledHeader |
| from starboard.tools.toolchain_deprecated import Toolchain |
| |
| 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 MSVCPrecompiledHeader(PrecompiledHeader): |
| """MSVC implementation of PrecompiledHeader interface.""" |
| |
| def __init__(self, **kwargs): |
| self.settings = kwargs['settings'] |
| self.config = kwargs['config'] |
| gyp_path_to_ninja = kwargs['gyp_path_to_ninja'] |
| gyp_path_to_unique_output = kwargs['gyp_path_to_unique_output'] |
| obj_ext = kwargs['obj_ext'] |
| pch_source = self.settings.msvs_precompiled_source[self.config] |
| self.pch_source = gyp_path_to_ninja(pch_source) |
| filename, _ = os.path.splitext(pch_source) |
| self.output_obj = gyp_path_to_unique_output(filename + obj_ext).lower() |
| |
| def GetFlagsModifications(self, input_flags, output, implicit, command, |
| cflags_c, cflags_cc, expand_special): |
| """Get the modified cflags and implicit dependencies.""" |
| if self.settings.UsesComponentExtensions(self.config): |
| # No-op if component extensions are used. |
| return [], output, [] |
| |
| if input_flags == 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 |
| |
| def GetInclude(self): |
| pass |
| |
| def GetPchBuildCommands(self): |
| """Not used on Windows as there are no additional build steps required |
| (instead, existing steps are modified in GetFlagsModifications below).""" |
| return [] |
| |
| 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, output): |
| """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 compatability |
| with make.py on Mac, and xcode_emulation.py.""" |
| 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 [] |
| |
| |
| 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 _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 _CallMap(map, element): |
| e = map(element) |
| if e is None: |
| raise Exception('Invalid element %s' % element) |
| return e |
| |
| |
| 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 = [_CallMap(map, elem) for elem in element] |
| else: |
| element = _CallMap(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 |
| |
| |
| class MsvsSettings(CompilerSettings): |
| """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) |
| # Try to find an installation location for the Windows DDK by checking |
| # the WDK_DIR environment variable, may be None. |
| self.wdk_dir = os.environ.get('WDK_DIR') |
| |
| 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), |
| ] |
| validators = { |
| 'msvs_settings': self._SettingsValidator, |
| } |
| 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()) |
| if field in validators: |
| validators[field](configname) |
| |
| self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.']) |
| |
| def GetVSMacroEnv(self, base_to_build=None, config=None): |
| """Get a dict of variables mapping internal VS macro names to their gyp |
| equivalents.""" |
| vs_path = self.vs_version.Path() |
| # Absolute paths in cygwin the path will start with /cygdrive/c/ |
| # The MS compiler tools need |
| # TODO: this is getting generated from the vs install path |
| # need to pass this in, or fix earlier in generation |
| if sys.platform == 'cygwin': |
| vs_path = cygpath.to_nt(vs_path) |
| |
| target_platform = 'Win32' if self.GetArch(config) == 'x86' else 'x64' |
| |
| replacements = { |
| '$(VSInstallDir)': vs_path, |
| '$(VCInstallDir)': os.path.join(vs_path, 'VC') + '\\', |
| '$(OutDir)\\': base_to_build + '\\' if base_to_build else '', |
| '$(IntDir)': '$!INTERMEDIATE_DIR', |
| '$(InputPath)': '${source}', |
| '$(InputName)': '${root}', |
| '$(ProjectName)': self.spec['target_name'], |
| '$(PlatformName)': target_platform, |
| '$(ProjectDir)\\': '', |
| } |
| |
| replacements['$(WDK_DIR)'] = self.wdk_dir if self.wdk_dir else '' |
| 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 ToolchainImpl().ExpandEnvVars(s, env) |
| |
| def ProcessLibraries(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 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.""" |
| # On Cobalt, we're not using any suffix on config names like Win_Debug_x64. |
| # Cobalt on Windows is x64 only. |
| 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 _SettingsValidator(self, configname): |
| """Validate msvs_settings.""" |
| valid_fields = [ |
| 'VCCLCompilerTool', |
| 'VCLinkerTool', |
| 'VCLibrarianTool', |
| 'VCMIDLTool', |
| 'VCResourceCompilerTool', |
| 'VCManifestTool', |
| ] |
| for field in self.msvs_settings[configname]: |
| if field not in valid_fields: |
| message = ('Invalid msvs_settings field: "%s", ' |
| 'config: "%s"' % (field, configname)) |
| close_match = get_close_matches(field, valid_fields, 1) |
| if close_match: |
| message += '\nDid you mean %s?' % tuple(close_match) |
| raise Exception(message) |
| |
| 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 ProcessIncludeDirs(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 GetDefines(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 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: 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): |
| """Gets the explicitly overridden pdb name for a target or returns None |
| if it's not overridden.""" |
| config = self._TargetConfig(config) |
| output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config) |
| if output_file: |
| output_file = expand_special( |
| self.ConvertVSMacros(output_file, config=config)) |
| return output_file |
| |
| 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') |
| cl('InlineFunctionExpansion', prefix='/Ob') |
| cl('OmitFramePointers', map={'false': '-', 'true': ''}, prefix='/Oy') |
| cl('FavorSizeOrSpeed', map={'1': 't', '2': 's'}, prefix='/O') |
| cl('WholeProgramOptimization', map={'true': '/GL'}) |
| cl('WarningLevel', prefix='/W') |
| cl('WarnAsError', map={'false': '', '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', 'false': '/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('EnablePREfast', map={'true': '/analyze'}) |
| cl('AdditionalOptions', prefix='') |
| cflags.extend([ |
| '/FI' + f |
| for f in self._Setting( |
| ('VCCLCompilerTool', 'ForcedIncludeFiles'), config, default=[]) |
| ]) |
| cflags.extend([ |
| '/Zc:' + f |
| for f in self._Setting( |
| ('VCCLCompilerTool', 'Conformance'), config, default=[]) |
| ]) |
| |
| # 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 GetCflagsObjectiveC(self): |
| pass |
| |
| def GetCflagsObjectiveCC(self): |
| pass |
| |
| 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 UsesComponentExtensions(self, config): |
| return self._Setting( |
| ('VCCLCompilerTool', 'ComponentExtensions'), config, default=[]) |
| |
| 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) |
| ccflags = [] |
| cl = self._GetWrapper( |
| self, self.msvs_settings[config], 'VCCLCompilerTool', append=ccflags) |
| cl('ComponentExtensions', map={'true': '/ZW'}) |
| |
| if self.UsesComponentExtensions(config): |
| # Disable PCH for libs compiled with /ZW, even if it was requested. |
| # Causes a fatal compiler error. |
| return ['/TP'] + ccflags |
| else: |
| return ['/TP'] + self._GetPchFlags(config, '.cc') + ccflags |
| |
| def _GetAdditionalLibraryDirectories(self, root, config, gyp_path_to_ninja): |
| """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_path_to_ninja(self.ConvertVSMacros(p, config=config))) |
| for p in libpaths |
| ] |
| return ['/LIBPATH:"' + p + '"' for p in libpaths] |
| |
| def GetLibFlags(self, config, gyp_path_to_ninja): |
| """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_path_to_ninja)) |
| lib('AdditionalOptions') |
| return libflags |
| |
| def _GetDefFileAsLdflags(self, spec, ldflags, gyp_path_to_ninja): |
| """.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', 'executable'): |
| def_files = [s for s in spec.get('sources', []) if s.endswith('.def')] |
| if len(def_files) == 1: |
| ldflags.append('/DEF:"%s"' % gyp_path_to_ninja(def_files[0])) |
| elif len(def_files) > 1: |
| raise Exception('Multiple .def files') |
| |
| def GetLdFlags(self, config, **kwargs): |
| """Returns the flags that need to be added to link commands, and the |
| manifest files.""" |
| |
| gyp_path_to_ninja = kwargs['gyp_path_to_ninja'] |
| expand_special = kwargs['expand_special'] |
| manifest_name = kwargs['manifest_name'] |
| is_executable = kwargs['is_executable'] |
| |
| config = self._TargetConfig(config) |
| ldflags = [] |
| ld = self._GetWrapper( |
| self, self.msvs_settings[config], 'VCLinkerTool', append=ldflags) |
| self._GetDefFileAsLdflags(self.spec, ldflags, gyp_path_to_ninja) |
| ld('GenerateDebugInformation', map={'true': '/DEBUG'}) |
| ld('TargetMachine', map={'1': 'X86', '17': 'X64'}, prefix='/MACHINE:') |
| ldflags.extend( |
| self._GetAdditionalLibraryDirectories('VCLinkerTool', config, |
| gyp_path_to_ninja)) |
| ld('DelayLoadDLLs', prefix='/DELAYLOAD:') |
| out = self.GetOutputName(config, expand_special) |
| if out: |
| ldflags.append('/OUT:' + out) |
| pdb = self.GetPDBName(config, expand_special) |
| if pdb: |
| ldflags.append('/PDB:' + pdb) |
| 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:') |
| ld('ResourceOnlyDLL', map={'true': '/NOENTRY'}) |
| ld('EntryPointSymbol', prefix='/ENTRY:') |
| ld('Profile', map={'true': '/PROFILE'}) |
| # TODO: This should sort of be somewhere else (not really a flag). |
| ld('AdditionalDependencies', prefix='') |
| |
| # 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_file = self._GetLdManifestFlags( |
| config, manifest_name, is_executable and not have_def_file) |
| ldflags.extend(manifest_flags) |
| manifest_files = self._GetAdditionalManifestFiles(config, gyp_path_to_ninja) |
| manifest_files.append(intermediate_manifest_file) |
| |
| return ldflags, manifest_files |
| |
| def _GetLdManifestFlags(self, config, name, allow_isolation): |
| """Returns the set of flags that need to be added to the link to generate |
| a default manifest, as well as the name of the generated file.""" |
| output_name = name + '.intermediate.manifest' |
| # Manifests are off for UWP. If needed by another target, |
| # please find a way to configure them per target. |
| flags = [ |
| '/MANIFEST:NO', |
| ] |
| return flags, output_name |
| |
| def _GetAdditionalManifestFiles(self, config, gyp_path_to_ninja): |
| """Gets additional manifest files that are added to the default one |
| generated by the linker.""" |
| files = self._Setting( |
| ('VCManifestTool', 'AdditionalManifestFiles'), config, default=[]) |
| if (self._Setting(('VCManifestTool', 'EmbedManifest'), config, |
| default='') == 'true'): |
| print 'gyp/msvs_emulation.py: "EmbedManifest: true" not yet supported.' |
| if isinstance(files, str): |
| files = files.split(';') |
| return [ |
| os.path.normpath( |
| gyp_path_to_ninja(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 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 HasExplicitIdlRules(self, spec): |
| """Determine if there's an explicit rule for idl files. When there isn't we |
| need to generate implicit rules to build MIDL .idl files.""" |
| return self._HasExplicitRuleForExtension(spec, 'idl') |
| |
| 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) |
| |
| # TODO: remove references to xb1 below. |
| if config.startswith('xb1'): |
| tlb = '' |
| header = midl('HeaderFileName', default='${root}.h') |
| dlldata = '' |
| iid = '' |
| proxy = '' |
| outdir = midl('OutputDirectory', default='') |
| else: |
| 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='') |
| if config.startswith('xb1'): |
| output = [header] |
| else: |
| output = [header, dlldata, iid, proxy] |
| variables = [('tlb', tlb), ('h', header), ('dlldata', dlldata), |
| ('iid', iid), ('proxy', proxy)] |
| if config.startswith('xb1'): |
| metadata_dir = '"%s%s"' % ('C:\\Program Files (x86)\\Windows Kits\\10\\', |
| 'UnionMetadata') |
| flags = [ |
| '/env', 'x64', '/W1', '/char', 'signed', '/enum_class', |
| '/metadata_dir', metadata_dir, '/notlb', '/winrt' |
| ] |
| else: |
| # TODO: Are there configuration settings to set these flags? |
| flags = ['/char', 'signed', '/env', 'win32', '/Oicf'] |
| return outdir, output, variables, flags |
| |
| |
| 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 = ( |
| 'durangoxdk', |
| 'goma_.*', # TODO: This is ugly, but needed for goma. |
| 'include', |
| 'lib', |
| 'libpath', |
| 'path', |
| 'pathext', |
| 'systemroot', |
| 'temp', |
| 'tmp', |
| 'xedk', |
| 'cell_.*', |
| 'sn_.*', |
| 'sce_.*',) |
| 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) + ';' + 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 |
| |
| |
| class MSVCUWPToolchain(Toolchain): |
| |
| def __init__(self): |
| self.compiler_settings = None |
| |
| def _escape(self, string): |
| """Escape a string such that it can be embedded into a Ninja file without |
| further interpretation.""" |
| assert '\n' not in string, 'Ninja syntax does not allow newlines' |
| # We only have one special metacharacter: '$'. |
| return string.replace('$', '$$') |
| |
| def Define(self, d): |
| # cl.exe replaces literal # characters with = in preprocesor definitions for |
| # some reason. Octal-encode to work around that. |
| d = d.replace('#', '\\%03o' % ord('#')) |
| return '/D' + self.QuoteForRspFile(self._escape(d)) |
| |
| def EncodeRspFileList(self, args): |
| """Process a list of arguments using QuoteForRspFile.""" |
| # 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( |
| self.QuoteForRspFile(arg) for arg in args[1:]) |
| |
| def ExpandEnvVars(self, 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 ExpandRuleVariables(self, path, root, dirname, source, ext, name): |
| path = self.compiler_settings.ConvertVSMacros(path, config=self.config_name) |
| path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root) |
| path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'], |
| dirname) |
| path = path.replace(generator_default_variables['RULE_INPUT_PATH'], source) |
| path = path.replace(generator_default_variables['RULE_INPUT_EXT'], ext) |
| path = path.replace(generator_default_variables['RULE_INPUT_NAME'], name) |
| return path |
| |
| def GenerateEnvironmentFiles(self, toplevel_build_dir, generator_flags, |
| 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. 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. |
| """ |
| arch = 'x64' |
| |
| # Get the dos environment via set: |
| # Use cmd /c to execute under native windows command |
| args = 'set' |
| |
| popen = subprocess.Popen( |
| args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| variables, _ = popen.communicate() |
| env = _ExtractImportantEnvironment(variables) |
| env_block = _FormatAsEnvironmentBlock(env) |
| f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb') |
| f.write(env_block) |
| f.close() |
| |
| def GetCompilerSettings(self): |
| return self.compiler_settings |
| |
| def GetPrecompiledHeader(self, **kwargs): |
| return MSVCPrecompiledHeader(**kwargs) |
| |
| def InitCompilerSettings(self, spec, **kwargs): |
| self.compiler_settings = MsvsSettings(spec, kwargs['generator_flags']) |
| |
| def SetAdditionalGypVariables(self, default_variables, **kwargs): |
| """Calculate additional variables for use in the build (called by gyp).""" |
| default_variables.setdefault('OS', 'win') |
| default_variables['EXECUTABLE_SUFFIX'] = '.exe' |
| default_variables['STATIC_LIB_PREFIX'] = '' |
| default_variables['STATIC_LIB_SUFFIX'] = '.lib' |
| default_variables['SHARED_LIB_PREFIX'] = '' |
| default_variables['SHARED_LIB_SUFFIX'] = '.dll' |
| generator_flags = {} |
| |
| # Copy additional generator configuration data from VS, which is shared |
| # by the Windows Ninja generator. |
| import gyp.generator.msvs as msvs_generator |
| generator_additional_non_configuration_keys = getattr( |
| msvs_generator, 'generator_additional_non_configuration_keys', []) |
| generator_additional_path_sections = getattr( |
| msvs_generator, 'generator_additional_path_sections', []) |
| |
| # 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 |
| return |
| |
| def VerifyMissingSources(self, sources, **kwargs): |
| """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. |
| """ |
| build_dir = kwargs['build_dir'] |
| generator_flags = kwargs['generator_flags'] |
| gyp_path_to_ninja = kwargs['gyp_path_to_ninja'] |
| 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_path_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)) |
| |
| def QuoteForRspFile(self, 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. |
| windows_quoter_regex = re.compile(r'(\\*)"') |
| 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 ToolchainImpl(): |
| return MSVCUWPToolchain() |