Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 1 | # Copyright (c) 2012 Google Inc. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """ |
| 6 | This module helps emulate Visual Studio 2008 behavior on top of other |
| 7 | build systems, primarily ninja. |
| 8 | """ |
| 9 | |
| 10 | import os |
| 11 | import re |
| 12 | import subprocess |
| 13 | import sys |
| 14 | |
| 15 | from gyp.common import OrderedSet |
| 16 | import gyp.MSVSUtil |
| 17 | import gyp.MSVSVersion |
| 18 | |
| 19 | |
| 20 | windows_quoter_regex = re.compile(r'(\\*)"') |
| 21 | |
| 22 | |
| 23 | def QuoteForRspFile(arg): |
| 24 | """Quote a command line argument so that it appears as one argument when |
| 25 | processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for |
| 26 | Windows programs).""" |
| 27 | # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment |
| 28 | # threads. This is actually the quoting rules for CommandLineToArgvW, not |
| 29 | # for the shell, because the shell doesn't do anything in Windows. This |
| 30 | # works more or less because most programs (including the compiler, etc.) |
| 31 | # use that function to handle command line arguments. |
| 32 | |
| 33 | # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes |
| 34 | # preceding it, and results in n backslashes + the quote. So we substitute |
| 35 | # in 2* what we match, +1 more, plus the quote. |
| 36 | arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) |
| 37 | |
| 38 | # %'s also need to be doubled otherwise they're interpreted as batch |
| 39 | # positional arguments. Also make sure to escape the % so that they're |
| 40 | # passed literally through escaping so they can be singled to just the |
| 41 | # original %. Otherwise, trying to pass the literal representation that |
| 42 | # looks like an environment variable to the shell (e.g. %PATH%) would fail. |
| 43 | arg = arg.replace('%', '%%') |
| 44 | |
| 45 | # These commands are used in rsp files, so no escaping for the shell (via ^) |
| 46 | # is necessary. |
| 47 | |
| 48 | # Finally, wrap the whole thing in quotes so that the above quote rule |
| 49 | # applies and whitespace isn't a word break. |
| 50 | return '"' + arg + '"' |
| 51 | |
| 52 | |
| 53 | def EncodeRspFileList(args): |
| 54 | """Process a list of arguments using QuoteCmdExeArgument.""" |
| 55 | # Note that the first argument is assumed to be the command. Don't add |
| 56 | # quotes around it because then built-ins like 'echo', etc. won't work. |
| 57 | # Take care to normpath only the path in the case of 'call ../x.bat' because |
| 58 | # otherwise the whole thing is incorrectly interpreted as a path and not |
| 59 | # normalized correctly. |
| 60 | if not args: return '' |
| 61 | if args[0].startswith('call '): |
| 62 | call, program = args[0].split(' ', 1) |
| 63 | program = call + ' ' + os.path.normpath(program) |
| 64 | else: |
| 65 | program = os.path.normpath(args[0]) |
| 66 | return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:]) |
| 67 | |
| 68 | |
| 69 | def _GenericRetrieve(root, default, path): |
| 70 | """Given a list of dictionary keys |path| and a tree of dicts |root|, find |
| 71 | value at path, or return |default| if any of the path doesn't exist.""" |
| 72 | if not root: |
| 73 | return default |
| 74 | if not path: |
| 75 | return root |
| 76 | return _GenericRetrieve(root.get(path[0]), default, path[1:]) |
| 77 | |
| 78 | |
| 79 | def _AddPrefix(element, prefix): |
| 80 | """Add |prefix| to |element| or each subelement if element is iterable.""" |
| 81 | if element is None: |
| 82 | return element |
| 83 | # Note, not Iterable because we don't want to handle strings like that. |
| 84 | if isinstance(element, list) or isinstance(element, tuple): |
| 85 | return [prefix + e for e in element] |
| 86 | else: |
| 87 | return prefix + element |
| 88 | |
| 89 | |
| 90 | def _DoRemapping(element, map): |
| 91 | """If |element| then remap it through |map|. If |element| is iterable then |
| 92 | each item will be remapped. Any elements not found will be removed.""" |
| 93 | if map is not None and element is not None: |
| 94 | if not callable(map): |
| 95 | map = map.get # Assume it's a dict, otherwise a callable to do the remap. |
| 96 | if isinstance(element, list) or isinstance(element, tuple): |
| 97 | element = filter(None, [map(elem) for elem in element]) |
| 98 | else: |
| 99 | element = map(element) |
| 100 | return element |
| 101 | |
| 102 | |
| 103 | def _AppendOrReturn(append, element): |
| 104 | """If |append| is None, simply return |element|. If |append| is not None, |
| 105 | then add |element| to it, adding each item in |element| if it's a list or |
| 106 | tuple.""" |
| 107 | if append is not None and element is not None: |
| 108 | if isinstance(element, list) or isinstance(element, tuple): |
| 109 | append.extend(element) |
| 110 | else: |
| 111 | append.append(element) |
| 112 | else: |
| 113 | return element |
| 114 | |
| 115 | |
| 116 | def _FindDirectXInstallation(): |
| 117 | """Try to find an installation location for the DirectX SDK. Check for the |
| 118 | standard environment variable, and if that doesn't exist, try to find |
| 119 | via the registry. May return None if not found in either location.""" |
| 120 | # Return previously calculated value, if there is one |
| 121 | if hasattr(_FindDirectXInstallation, 'dxsdk_dir'): |
| 122 | return _FindDirectXInstallation.dxsdk_dir |
| 123 | |
| 124 | dxsdk_dir = os.environ.get('DXSDK_DIR') |
| 125 | if not dxsdk_dir: |
| 126 | # Setup params to pass to and attempt to launch reg.exe. |
| 127 | cmd = ['reg.exe', 'query', r'HKLM\Software\Microsoft\DirectX', '/s'] |
| 128 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 129 | for line in p.communicate()[0].splitlines(): |
| 130 | if 'InstallPath' in line: |
| 131 | dxsdk_dir = line.split(' ')[3] + "\\" |
| 132 | |
| 133 | # Cache return value |
| 134 | _FindDirectXInstallation.dxsdk_dir = dxsdk_dir |
| 135 | return dxsdk_dir |
| 136 | |
| 137 | |
| 138 | def GetGlobalVSMacroEnv(vs_version): |
| 139 | """Get a dict of variables mapping internal VS macro names to their gyp |
| 140 | equivalents. Returns all variables that are independent of the target.""" |
| 141 | env = {} |
| 142 | # '$(VSInstallDir)' and '$(VCInstallDir)' are available when and only when |
| 143 | # Visual Studio is actually installed. |
| 144 | if vs_version.Path(): |
| 145 | env['$(VSInstallDir)'] = vs_version.Path() |
| 146 | env['$(VCInstallDir)'] = os.path.join(vs_version.Path(), 'VC') + '\\' |
| 147 | # Chromium uses DXSDK_DIR in include/lib paths, but it may or may not be |
| 148 | # set. This happens when the SDK is sync'd via src-internal, rather than |
| 149 | # by typical end-user installation of the SDK. If it's not set, we don't |
| 150 | # want to leave the unexpanded variable in the path, so simply strip it. |
| 151 | dxsdk_dir = _FindDirectXInstallation() |
| 152 | env['$(DXSDK_DIR)'] = dxsdk_dir if dxsdk_dir else '' |
| 153 | # Try to find an installation location for the Windows DDK by checking |
| 154 | # the WDK_DIR environment variable, may be None. |
| 155 | env['$(WDK_DIR)'] = os.environ.get('WDK_DIR', '') |
| 156 | return env |
| 157 | |
| 158 | def ExtractSharedMSVSSystemIncludes(configs, generator_flags): |
| 159 | """Finds msvs_system_include_dirs that are common to all targets, removes |
| 160 | them from all targets, and returns an OrderedSet containing them.""" |
| 161 | all_system_includes = OrderedSet( |
| 162 | configs[0].get('msvs_system_include_dirs', [])) |
| 163 | for config in configs[1:]: |
| 164 | system_includes = config.get('msvs_system_include_dirs', []) |
| 165 | all_system_includes = all_system_includes & OrderedSet(system_includes) |
| 166 | if not all_system_includes: |
| 167 | return None |
| 168 | # Expand macros in all_system_includes. |
| 169 | env = GetGlobalVSMacroEnv(GetVSVersion(generator_flags)) |
| 170 | expanded_system_includes = OrderedSet([ExpandMacros(include, env) |
| 171 | for include in all_system_includes]) |
| 172 | if any(['$' in include for include in expanded_system_includes]): |
| 173 | # Some path relies on target-specific variables, bail. |
| 174 | return None |
| 175 | |
| 176 | # Remove system includes shared by all targets from the targets. |
| 177 | for config in configs: |
| 178 | includes = config.get('msvs_system_include_dirs', []) |
| 179 | if includes: # Don't insert a msvs_system_include_dirs key if not needed. |
| 180 | # This must check the unexpanded includes list: |
| 181 | new_includes = [i for i in includes if i not in all_system_includes] |
| 182 | config['msvs_system_include_dirs'] = new_includes |
| 183 | return expanded_system_includes |
| 184 | |
| 185 | |
| 186 | class MsvsSettings(object): |
| 187 | """A class that understands the gyp 'msvs_...' values (especially the |
| 188 | msvs_settings field). They largely correpond to the VS2008 IDE DOM. This |
| 189 | class helps map those settings to command line options.""" |
| 190 | |
| 191 | def __init__(self, spec, generator_flags): |
| 192 | self.spec = spec |
| 193 | self.vs_version = GetVSVersion(generator_flags) |
| 194 | |
| 195 | supported_fields = [ |
| 196 | ('msvs_configuration_attributes', dict), |
| 197 | ('msvs_settings', dict), |
| 198 | ('msvs_system_include_dirs', list), |
| 199 | ('msvs_disabled_warnings', list), |
| 200 | ('msvs_precompiled_header', str), |
| 201 | ('msvs_precompiled_source', str), |
| 202 | ('msvs_configuration_platform', str), |
| 203 | ('msvs_target_platform', str), |
| 204 | ] |
| 205 | configs = spec['configurations'] |
| 206 | for field, default in supported_fields: |
| 207 | setattr(self, field, {}) |
| 208 | for configname, config in configs.iteritems(): |
| 209 | getattr(self, field)[configname] = config.get(field, default()) |
| 210 | |
| 211 | self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.']) |
| 212 | |
| 213 | unsupported_fields = [ |
| 214 | 'msvs_prebuild', |
| 215 | 'msvs_postbuild', |
| 216 | ] |
| 217 | unsupported = [] |
| 218 | for field in unsupported_fields: |
| 219 | for config in configs.values(): |
| 220 | if field in config: |
| 221 | unsupported += ["%s not supported (target %s)." % |
| 222 | (field, spec['target_name'])] |
| 223 | if unsupported: |
| 224 | raise Exception('\n'.join(unsupported)) |
| 225 | |
| 226 | def GetExtension(self): |
| 227 | """Returns the extension for the target, with no leading dot. |
| 228 | |
| 229 | Uses 'product_extension' if specified, otherwise uses MSVS defaults based on |
| 230 | the target type. |
| 231 | """ |
| 232 | ext = self.spec.get('product_extension', None) |
| 233 | if ext: |
| 234 | return ext |
| 235 | return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec['type'], '') |
| 236 | |
| 237 | def GetVSMacroEnv(self, base_to_build=None, config=None): |
| 238 | """Get a dict of variables mapping internal VS macro names to their gyp |
| 239 | equivalents.""" |
| 240 | target_platform = 'Win32' if self.GetArch(config) == 'x86' else 'x64' |
| 241 | target_name = self.spec.get('product_prefix', '') + \ |
| 242 | self.spec.get('product_name', self.spec['target_name']) |
| 243 | target_dir = base_to_build + '\\' if base_to_build else '' |
| 244 | target_ext = '.' + self.GetExtension() |
| 245 | target_file_name = target_name + target_ext |
| 246 | |
| 247 | replacements = { |
| 248 | '$(InputName)': '${root}', |
| 249 | '$(InputPath)': '${source}', |
| 250 | '$(IntDir)': '$!INTERMEDIATE_DIR', |
| 251 | '$(OutDir)\\': target_dir, |
| 252 | '$(PlatformName)': target_platform, |
| 253 | '$(ProjectDir)\\': '', |
| 254 | '$(ProjectName)': self.spec['target_name'], |
| 255 | '$(TargetDir)\\': target_dir, |
| 256 | '$(TargetExt)': target_ext, |
| 257 | '$(TargetFileName)': target_file_name, |
| 258 | '$(TargetName)': target_name, |
| 259 | '$(TargetPath)': os.path.join(target_dir, target_file_name), |
| 260 | } |
| 261 | replacements.update(GetGlobalVSMacroEnv(self.vs_version)) |
| 262 | return replacements |
| 263 | |
| 264 | def ConvertVSMacros(self, s, base_to_build=None, config=None): |
| 265 | """Convert from VS macro names to something equivalent.""" |
| 266 | env = self.GetVSMacroEnv(base_to_build, config=config) |
| 267 | return ExpandMacros(s, env) |
| 268 | |
| 269 | def AdjustLibraries(self, libraries): |
| 270 | """Strip -l from library if it's specified with that.""" |
| 271 | libs = [lib[2:] if lib.startswith('-l') else lib for lib in libraries] |
| 272 | return [lib + '.lib' if not lib.endswith('.lib') else lib for lib in libs] |
| 273 | |
| 274 | def _GetAndMunge(self, field, path, default, prefix, append, map): |
| 275 | """Retrieve a value from |field| at |path| or return |default|. If |
| 276 | |append| is specified, and the item is found, it will be appended to that |
| 277 | object instead of returned. If |map| is specified, results will be |
| 278 | remapped through |map| before being returned or appended.""" |
| 279 | result = _GenericRetrieve(field, default, path) |
| 280 | result = _DoRemapping(result, map) |
| 281 | result = _AddPrefix(result, prefix) |
| 282 | return _AppendOrReturn(append, result) |
| 283 | |
| 284 | class _GetWrapper(object): |
| 285 | def __init__(self, parent, field, base_path, append=None): |
| 286 | self.parent = parent |
| 287 | self.field = field |
| 288 | self.base_path = [base_path] |
| 289 | self.append = append |
| 290 | def __call__(self, name, map=None, prefix='', default=None): |
| 291 | return self.parent._GetAndMunge(self.field, self.base_path + [name], |
| 292 | default=default, prefix=prefix, append=self.append, map=map) |
| 293 | |
| 294 | def GetArch(self, config): |
| 295 | """Get architecture based on msvs_configuration_platform and |
| 296 | msvs_target_platform. Returns either 'x86' or 'x64'.""" |
| 297 | configuration_platform = self.msvs_configuration_platform.get(config, '') |
| 298 | platform = self.msvs_target_platform.get(config, '') |
| 299 | if not platform: # If no specific override, use the configuration's. |
| 300 | platform = configuration_platform |
| 301 | # Map from platform to architecture. |
| 302 | return {'Win32': 'x86', 'x64': 'x64'}.get(platform, 'x86') |
| 303 | |
| 304 | def _TargetConfig(self, config): |
| 305 | """Returns the target-specific configuration.""" |
| 306 | # There's two levels of architecture/platform specification in VS. The |
| 307 | # first level is globally for the configuration (this is what we consider |
| 308 | # "the" config at the gyp level, which will be something like 'Debug' or |
| 309 | # 'Release_x64'), and a second target-specific configuration, which is an |
| 310 | # override for the global one. |config| is remapped here to take into |
| 311 | # account the local target-specific overrides to the global configuration. |
| 312 | arch = self.GetArch(config) |
| 313 | if arch == 'x64' and not config.endswith('_x64'): |
| 314 | config += '_x64' |
| 315 | if arch == 'x86' and config.endswith('_x64'): |
| 316 | config = config.rsplit('_', 1)[0] |
| 317 | return config |
| 318 | |
| 319 | def _Setting(self, path, config, |
| 320 | default=None, prefix='', append=None, map=None): |
| 321 | """_GetAndMunge for msvs_settings.""" |
| 322 | return self._GetAndMunge( |
| 323 | self.msvs_settings[config], path, default, prefix, append, map) |
| 324 | |
| 325 | def _ConfigAttrib(self, path, config, |
| 326 | default=None, prefix='', append=None, map=None): |
| 327 | """_GetAndMunge for msvs_configuration_attributes.""" |
| 328 | return self._GetAndMunge( |
| 329 | self.msvs_configuration_attributes[config], |
| 330 | path, default, prefix, append, map) |
| 331 | |
| 332 | def AdjustIncludeDirs(self, include_dirs, config): |
| 333 | """Updates include_dirs to expand VS specific paths, and adds the system |
| 334 | include dirs used for platform SDK and similar.""" |
| 335 | config = self._TargetConfig(config) |
| 336 | includes = include_dirs + self.msvs_system_include_dirs[config] |
| 337 | includes.extend(self._Setting( |
| 338 | ('VCCLCompilerTool', 'AdditionalIncludeDirectories'), config, default=[])) |
| 339 | return [self.ConvertVSMacros(p, config=config) for p in includes] |
| 340 | |
| 341 | def AdjustMidlIncludeDirs(self, midl_include_dirs, config): |
| 342 | """Updates midl_include_dirs to expand VS specific paths, and adds the |
| 343 | system include dirs used for platform SDK and similar.""" |
| 344 | config = self._TargetConfig(config) |
| 345 | includes = midl_include_dirs + self.msvs_system_include_dirs[config] |
| 346 | includes.extend(self._Setting( |
| 347 | ('VCMIDLTool', 'AdditionalIncludeDirectories'), config, default=[])) |
| 348 | return [self.ConvertVSMacros(p, config=config) for p in includes] |
| 349 | |
| 350 | def GetComputedDefines(self, config): |
| 351 | """Returns the set of defines that are injected to the defines list based |
| 352 | on other VS settings.""" |
| 353 | config = self._TargetConfig(config) |
| 354 | defines = [] |
| 355 | if self._ConfigAttrib(['CharacterSet'], config) == '1': |
| 356 | defines.extend(('_UNICODE', 'UNICODE')) |
| 357 | if self._ConfigAttrib(['CharacterSet'], config) == '2': |
| 358 | defines.append('_MBCS') |
| 359 | defines.extend(self._Setting( |
| 360 | ('VCCLCompilerTool', 'PreprocessorDefinitions'), config, default=[])) |
| 361 | return defines |
| 362 | |
| 363 | def GetCompilerPdbName(self, config, expand_special): |
| 364 | """Get the pdb file name that should be used for compiler invocations, or |
| 365 | None if there's no explicit name specified.""" |
| 366 | config = self._TargetConfig(config) |
| 367 | pdbname = self._Setting( |
| 368 | ('VCCLCompilerTool', 'ProgramDataBaseFileName'), config) |
| 369 | if pdbname: |
| 370 | pdbname = expand_special(self.ConvertVSMacros(pdbname)) |
| 371 | return pdbname |
| 372 | |
| 373 | def GetMapFileName(self, config, expand_special): |
| 374 | """Gets the explicitly overriden map file name for a target or returns None |
| 375 | if it's not set.""" |
| 376 | config = self._TargetConfig(config) |
| 377 | map_file = self._Setting(('VCLinkerTool', 'MapFileName'), config) |
| 378 | if map_file: |
| 379 | map_file = expand_special(self.ConvertVSMacros(map_file, config=config)) |
| 380 | return map_file |
| 381 | |
| 382 | def GetOutputName(self, config, expand_special): |
| 383 | """Gets the explicitly overridden output name for a target or returns None |
| 384 | if it's not overridden.""" |
| 385 | config = self._TargetConfig(config) |
| 386 | type = self.spec['type'] |
| 387 | root = 'VCLibrarianTool' if type == 'static_library' else 'VCLinkerTool' |
| 388 | # TODO(scottmg): Handle OutputDirectory without OutputFile. |
| 389 | output_file = self._Setting((root, 'OutputFile'), config) |
| 390 | if output_file: |
| 391 | output_file = expand_special(self.ConvertVSMacros( |
| 392 | output_file, config=config)) |
| 393 | return output_file |
| 394 | |
| 395 | def GetPDBName(self, config, expand_special, default): |
| 396 | """Gets the explicitly overridden pdb name for a target or returns |
| 397 | default if it's not overridden, or if no pdb will be generated.""" |
| 398 | config = self._TargetConfig(config) |
| 399 | output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config) |
| 400 | generate_debug_info = self._Setting( |
| 401 | ('VCLinkerTool', 'GenerateDebugInformation'), config) |
| 402 | if generate_debug_info == 'true': |
| 403 | if output_file: |
| 404 | return expand_special(self.ConvertVSMacros(output_file, config=config)) |
| 405 | else: |
| 406 | return default |
| 407 | else: |
| 408 | return None |
| 409 | |
| 410 | def GetNoImportLibrary(self, config): |
| 411 | """If NoImportLibrary: true, ninja will not expect the output to include |
| 412 | an import library.""" |
| 413 | config = self._TargetConfig(config) |
| 414 | noimplib = self._Setting(('NoImportLibrary',), config) |
| 415 | return noimplib == 'true' |
| 416 | |
| 417 | def GetAsmflags(self, config): |
| 418 | """Returns the flags that need to be added to ml invocations.""" |
| 419 | config = self._TargetConfig(config) |
| 420 | asmflags = [] |
| 421 | safeseh = self._Setting(('MASM', 'UseSafeExceptionHandlers'), config) |
| 422 | if safeseh == 'true': |
| 423 | asmflags.append('/safeseh') |
| 424 | return asmflags |
| 425 | |
| 426 | def GetCflags(self, config): |
| 427 | """Returns the flags that need to be added to .c and .cc compilations.""" |
| 428 | config = self._TargetConfig(config) |
| 429 | cflags = [] |
| 430 | cflags.extend(['/wd' + w for w in self.msvs_disabled_warnings[config]]) |
| 431 | cl = self._GetWrapper(self, self.msvs_settings[config], |
| 432 | 'VCCLCompilerTool', append=cflags) |
| 433 | cl('Optimization', |
| 434 | map={'0': 'd', '1': '1', '2': '2', '3': 'x'}, prefix='/O', default='2') |
| 435 | cl('InlineFunctionExpansion', prefix='/Ob') |
| 436 | cl('DisableSpecificWarnings', prefix='/wd') |
| 437 | cl('StringPooling', map={'true': '/GF'}) |
| 438 | cl('EnableFiberSafeOptimizations', map={'true': '/GT'}) |
| 439 | cl('OmitFramePointers', map={'false': '-', 'true': ''}, prefix='/Oy') |
| 440 | cl('EnableIntrinsicFunctions', map={'false': '-', 'true': ''}, prefix='/Oi') |
| 441 | cl('FavorSizeOrSpeed', map={'1': 't', '2': 's'}, prefix='/O') |
| 442 | cl('FloatingPointModel', |
| 443 | map={'0': 'precise', '1': 'strict', '2': 'fast'}, prefix='/fp:', |
| 444 | default='0') |
| 445 | cl('CompileAsManaged', map={'false': '', 'true': '/clr'}) |
| 446 | cl('WholeProgramOptimization', map={'true': '/GL'}) |
| 447 | cl('WarningLevel', prefix='/W') |
| 448 | cl('WarnAsError', map={'true': '/WX'}) |
| 449 | cl('CallingConvention', |
| 450 | map={'0': 'd', '1': 'r', '2': 'z', '3': 'v'}, prefix='/G') |
| 451 | cl('DebugInformationFormat', |
| 452 | map={'1': '7', '3': 'i', '4': 'I'}, prefix='/Z') |
| 453 | cl('RuntimeTypeInfo', map={'true': '/GR', 'false': '/GR-'}) |
| 454 | cl('EnableFunctionLevelLinking', map={'true': '/Gy', 'false': '/Gy-'}) |
| 455 | cl('MinimalRebuild', map={'true': '/Gm'}) |
| 456 | cl('BufferSecurityCheck', map={'true': '/GS', 'false': '/GS-'}) |
| 457 | cl('BasicRuntimeChecks', map={'1': 's', '2': 'u', '3': '1'}, prefix='/RTC') |
| 458 | cl('RuntimeLibrary', |
| 459 | map={'0': 'T', '1': 'Td', '2': 'D', '3': 'Dd'}, prefix='/M') |
| 460 | cl('ExceptionHandling', map={'1': 'sc','2': 'a'}, prefix='/EH') |
| 461 | cl('DefaultCharIsUnsigned', map={'true': '/J'}) |
| 462 | cl('TreatWChar_tAsBuiltInType', |
| 463 | map={'false': '-', 'true': ''}, prefix='/Zc:wchar_t') |
| 464 | cl('EnablePREfast', map={'true': '/analyze'}) |
| 465 | cl('AdditionalOptions', prefix='') |
| 466 | cl('EnableEnhancedInstructionSet', |
| 467 | map={'1': 'SSE', '2': 'SSE2', '3': 'AVX', '4': 'IA32', '5': 'AVX2'}, |
| 468 | prefix='/arch:') |
| 469 | cflags.extend(['/FI' + f for f in self._Setting( |
| 470 | ('VCCLCompilerTool', 'ForcedIncludeFiles'), config, default=[])]) |
| 471 | if self.vs_version.short_name in ('2013', '2013e', '2015'): |
| 472 | # New flag required in 2013 to maintain previous PDB behavior. |
| 473 | cflags.append('/FS') |
| 474 | # ninja handles parallelism by itself, don't have the compiler do it too. |
| 475 | cflags = filter(lambda x: not x.startswith('/MP'), cflags) |
| 476 | return cflags |
| 477 | |
| 478 | def _GetPchFlags(self, config, extension): |
| 479 | """Get the flags to be added to the cflags for precompiled header support. |
| 480 | """ |
| 481 | config = self._TargetConfig(config) |
| 482 | # The PCH is only built once by a particular source file. Usage of PCH must |
| 483 | # only be for the same language (i.e. C vs. C++), so only include the pch |
| 484 | # flags when the language matches. |
| 485 | if self.msvs_precompiled_header[config]: |
| 486 | source_ext = os.path.splitext(self.msvs_precompiled_source[config])[1] |
| 487 | if _LanguageMatchesForPch(source_ext, extension): |
| 488 | pch = self.msvs_precompiled_header[config] |
| 489 | pchbase = os.path.split(pch)[1] |
| 490 | return ['/Yu' + pch, '/FI' + pch, '/Fp${pchprefix}.' + pchbase + '.pch'] |
| 491 | return [] |
| 492 | |
| 493 | def GetCflagsC(self, config): |
| 494 | """Returns the flags that need to be added to .c compilations.""" |
| 495 | config = self._TargetConfig(config) |
| 496 | return self._GetPchFlags(config, '.c') |
| 497 | |
| 498 | def GetCflagsCC(self, config): |
| 499 | """Returns the flags that need to be added to .cc compilations.""" |
| 500 | config = self._TargetConfig(config) |
| 501 | return ['/TP'] + self._GetPchFlags(config, '.cc') |
| 502 | |
| 503 | def _GetAdditionalLibraryDirectories(self, root, config, gyp_to_build_path): |
| 504 | """Get and normalize the list of paths in AdditionalLibraryDirectories |
| 505 | setting.""" |
| 506 | config = self._TargetConfig(config) |
| 507 | libpaths = self._Setting((root, 'AdditionalLibraryDirectories'), |
| 508 | config, default=[]) |
| 509 | libpaths = [os.path.normpath( |
| 510 | gyp_to_build_path(self.ConvertVSMacros(p, config=config))) |
| 511 | for p in libpaths] |
| 512 | return ['/LIBPATH:"' + p + '"' for p in libpaths] |
| 513 | |
| 514 | def GetLibFlags(self, config, gyp_to_build_path): |
| 515 | """Returns the flags that need to be added to lib commands.""" |
| 516 | config = self._TargetConfig(config) |
| 517 | libflags = [] |
| 518 | lib = self._GetWrapper(self, self.msvs_settings[config], |
| 519 | 'VCLibrarianTool', append=libflags) |
| 520 | libflags.extend(self._GetAdditionalLibraryDirectories( |
| 521 | 'VCLibrarianTool', config, gyp_to_build_path)) |
| 522 | lib('LinkTimeCodeGeneration', map={'true': '/LTCG'}) |
| 523 | lib('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'}, |
| 524 | prefix='/MACHINE:') |
| 525 | lib('AdditionalOptions') |
| 526 | return libflags |
| 527 | |
| 528 | def GetDefFile(self, gyp_to_build_path): |
| 529 | """Returns the .def file from sources, if any. Otherwise returns None.""" |
| 530 | spec = self.spec |
| 531 | if spec['type'] in ('shared_library', 'loadable_module', 'executable'): |
| 532 | def_files = [s for s in spec.get('sources', []) if s.endswith('.def')] |
| 533 | if len(def_files) == 1: |
| 534 | return gyp_to_build_path(def_files[0]) |
| 535 | elif len(def_files) > 1: |
| 536 | raise Exception("Multiple .def files") |
| 537 | return None |
| 538 | |
| 539 | def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path): |
| 540 | """.def files get implicitly converted to a ModuleDefinitionFile for the |
| 541 | linker in the VS generator. Emulate that behaviour here.""" |
| 542 | def_file = self.GetDefFile(gyp_to_build_path) |
| 543 | if def_file: |
| 544 | ldflags.append('/DEF:"%s"' % def_file) |
| 545 | |
| 546 | def GetPGDName(self, config, expand_special): |
| 547 | """Gets the explicitly overridden pgd name for a target or returns None |
| 548 | if it's not overridden.""" |
| 549 | config = self._TargetConfig(config) |
| 550 | output_file = self._Setting( |
| 551 | ('VCLinkerTool', 'ProfileGuidedDatabase'), config) |
| 552 | if output_file: |
| 553 | output_file = expand_special(self.ConvertVSMacros( |
| 554 | output_file, config=config)) |
| 555 | return output_file |
| 556 | |
| 557 | def GetLdflags(self, config, gyp_to_build_path, expand_special, |
| 558 | manifest_base_name, output_name, is_executable, build_dir): |
| 559 | """Returns the flags that need to be added to link commands, and the |
| 560 | manifest files.""" |
| 561 | config = self._TargetConfig(config) |
| 562 | ldflags = [] |
| 563 | ld = self._GetWrapper(self, self.msvs_settings[config], |
| 564 | 'VCLinkerTool', append=ldflags) |
| 565 | self._GetDefFileAsLdflags(ldflags, gyp_to_build_path) |
| 566 | ld('GenerateDebugInformation', map={'true': '/DEBUG'}) |
| 567 | ld('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'}, |
| 568 | prefix='/MACHINE:') |
| 569 | ldflags.extend(self._GetAdditionalLibraryDirectories( |
| 570 | 'VCLinkerTool', config, gyp_to_build_path)) |
| 571 | ld('DelayLoadDLLs', prefix='/DELAYLOAD:') |
| 572 | ld('TreatLinkerWarningAsErrors', prefix='/WX', |
| 573 | map={'true': '', 'false': ':NO'}) |
| 574 | out = self.GetOutputName(config, expand_special) |
| 575 | if out: |
| 576 | ldflags.append('/OUT:' + out) |
| 577 | pdb = self.GetPDBName(config, expand_special, output_name + '.pdb') |
| 578 | if pdb: |
| 579 | ldflags.append('/PDB:' + pdb) |
| 580 | pgd = self.GetPGDName(config, expand_special) |
| 581 | if pgd: |
| 582 | ldflags.append('/PGD:' + pgd) |
| 583 | map_file = self.GetMapFileName(config, expand_special) |
| 584 | ld('GenerateMapFile', map={'true': '/MAP:' + map_file if map_file |
| 585 | else '/MAP'}) |
| 586 | ld('MapExports', map={'true': '/MAPINFO:EXPORTS'}) |
| 587 | ld('AdditionalOptions', prefix='') |
| 588 | |
| 589 | minimum_required_version = self._Setting( |
| 590 | ('VCLinkerTool', 'MinimumRequiredVersion'), config, default='') |
| 591 | if minimum_required_version: |
| 592 | minimum_required_version = ',' + minimum_required_version |
| 593 | ld('SubSystem', |
| 594 | map={'1': 'CONSOLE%s' % minimum_required_version, |
| 595 | '2': 'WINDOWS%s' % minimum_required_version}, |
| 596 | prefix='/SUBSYSTEM:') |
| 597 | |
| 598 | stack_reserve_size = self._Setting( |
| 599 | ('VCLinkerTool', 'StackReserveSize'), config, default='') |
| 600 | if stack_reserve_size: |
| 601 | stack_commit_size = self._Setting( |
| 602 | ('VCLinkerTool', 'StackCommitSize'), config, default='') |
| 603 | if stack_commit_size: |
| 604 | stack_commit_size = ',' + stack_commit_size |
| 605 | ldflags.append('/STACK:%s%s' % (stack_reserve_size, stack_commit_size)) |
| 606 | |
| 607 | ld('TerminalServerAware', map={'1': ':NO', '2': ''}, prefix='/TSAWARE') |
| 608 | ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL') |
| 609 | ld('BaseAddress', prefix='/BASE:') |
| 610 | ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED') |
| 611 | ld('RandomizedBaseAddress', |
| 612 | map={'1': ':NO', '2': ''}, prefix='/DYNAMICBASE') |
| 613 | ld('DataExecutionPrevention', |
| 614 | map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT') |
| 615 | ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:') |
| 616 | ld('ForceSymbolReferences', prefix='/INCLUDE:') |
| 617 | ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:') |
| 618 | ld('LinkTimeCodeGeneration', |
| 619 | map={'1': '', '2': ':PGINSTRUMENT', '3': ':PGOPTIMIZE', |
| 620 | '4': ':PGUPDATE'}, |
| 621 | prefix='/LTCG') |
| 622 | ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:') |
| 623 | ld('ResourceOnlyDLL', map={'true': '/NOENTRY'}) |
| 624 | ld('EntryPointSymbol', prefix='/ENTRY:') |
| 625 | ld('Profile', map={'true': '/PROFILE'}) |
| 626 | ld('LargeAddressAware', |
| 627 | map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE') |
| 628 | # TODO(scottmg): This should sort of be somewhere else (not really a flag). |
| 629 | ld('AdditionalDependencies', prefix='') |
| 630 | |
| 631 | if self.GetArch(config) == 'x86': |
| 632 | safeseh_default = 'true' |
| 633 | else: |
| 634 | safeseh_default = None |
| 635 | ld('ImageHasSafeExceptionHandlers', |
| 636 | map={'false': ':NO', 'true': ''}, prefix='/SAFESEH', |
| 637 | default=safeseh_default) |
| 638 | |
| 639 | # If the base address is not specifically controlled, DYNAMICBASE should |
| 640 | # be on by default. |
| 641 | base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED', |
| 642 | ldflags) |
| 643 | if not base_flags: |
| 644 | ldflags.append('/DYNAMICBASE') |
| 645 | |
| 646 | # If the NXCOMPAT flag has not been specified, default to on. Despite the |
| 647 | # documentation that says this only defaults to on when the subsystem is |
| 648 | # Vista or greater (which applies to the linker), the IDE defaults it on |
| 649 | # unless it's explicitly off. |
| 650 | if not filter(lambda x: 'NXCOMPAT' in x, ldflags): |
| 651 | ldflags.append('/NXCOMPAT') |
| 652 | |
| 653 | have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags) |
| 654 | manifest_flags, intermediate_manifest, manifest_files = \ |
| 655 | self._GetLdManifestFlags(config, manifest_base_name, gyp_to_build_path, |
| 656 | is_executable and not have_def_file, build_dir) |
| 657 | ldflags.extend(manifest_flags) |
| 658 | return ldflags, intermediate_manifest, manifest_files |
| 659 | |
| 660 | def _GetLdManifestFlags(self, config, name, gyp_to_build_path, |
| 661 | allow_isolation, build_dir): |
| 662 | """Returns a 3-tuple: |
| 663 | - the set of flags that need to be added to the link to generate |
| 664 | a default manifest |
| 665 | - the intermediate manifest that the linker will generate that should be |
| 666 | used to assert it doesn't add anything to the merged one. |
| 667 | - the list of all the manifest files to be merged by the manifest tool and |
| 668 | included into the link.""" |
| 669 | generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'), |
| 670 | config, |
| 671 | default='true') |
| 672 | if generate_manifest != 'true': |
| 673 | # This means not only that the linker should not generate the intermediate |
| 674 | # manifest but also that the manifest tool should do nothing even when |
| 675 | # additional manifests are specified. |
| 676 | return ['/MANIFEST:NO'], [], [] |
| 677 | |
| 678 | output_name = name + '.intermediate.manifest' |
| 679 | flags = [ |
| 680 | '/MANIFEST', |
| 681 | '/ManifestFile:' + output_name, |
| 682 | ] |
| 683 | |
| 684 | # Instead of using the MANIFESTUAC flags, we generate a .manifest to |
| 685 | # include into the list of manifests. This allows us to avoid the need to |
| 686 | # do two passes during linking. The /MANIFEST flag and /ManifestFile are |
| 687 | # still used, and the intermediate manifest is used to assert that the |
| 688 | # final manifest we get from merging all the additional manifest files |
| 689 | # (plus the one we generate here) isn't modified by merging the |
| 690 | # intermediate into it. |
| 691 | |
| 692 | # Always NO, because we generate a manifest file that has what we want. |
| 693 | flags.append('/MANIFESTUAC:NO') |
| 694 | |
| 695 | config = self._TargetConfig(config) |
| 696 | enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config, |
| 697 | default='true') |
| 698 | manifest_files = [] |
| 699 | generated_manifest_outer = \ |
| 700 | "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" \ |
| 701 | "<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>%s" \ |
| 702 | "</assembly>" |
| 703 | if enable_uac == 'true': |
| 704 | execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'), |
| 705 | config, default='0') |
| 706 | execution_level_map = { |
| 707 | '0': 'asInvoker', |
| 708 | '1': 'highestAvailable', |
| 709 | '2': 'requireAdministrator' |
| 710 | } |
| 711 | |
| 712 | ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config, |
| 713 | default='false') |
| 714 | |
| 715 | inner = ''' |
| 716 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> |
| 717 | <security> |
| 718 | <requestedPrivileges> |
| 719 | <requestedExecutionLevel level='%s' uiAccess='%s' /> |
| 720 | </requestedPrivileges> |
| 721 | </security> |
| 722 | </trustInfo>''' % (execution_level_map[execution_level], ui_access) |
| 723 | else: |
| 724 | inner = '' |
| 725 | |
| 726 | generated_manifest_contents = generated_manifest_outer % inner |
| 727 | generated_name = name + '.generated.manifest' |
| 728 | # Need to join with the build_dir here as we're writing it during |
| 729 | # generation time, but we return the un-joined version because the build |
| 730 | # will occur in that directory. We only write the file if the contents |
| 731 | # have changed so that simply regenerating the project files doesn't |
| 732 | # cause a relink. |
| 733 | build_dir_generated_name = os.path.join(build_dir, generated_name) |
| 734 | gyp.common.EnsureDirExists(build_dir_generated_name) |
| 735 | f = gyp.common.WriteOnDiff(build_dir_generated_name) |
| 736 | f.write(generated_manifest_contents) |
| 737 | f.close() |
| 738 | manifest_files = [generated_name] |
| 739 | |
| 740 | if allow_isolation: |
| 741 | flags.append('/ALLOWISOLATION') |
| 742 | |
| 743 | manifest_files += self._GetAdditionalManifestFiles(config, |
| 744 | gyp_to_build_path) |
| 745 | return flags, output_name, manifest_files |
| 746 | |
| 747 | def _GetAdditionalManifestFiles(self, config, gyp_to_build_path): |
| 748 | """Gets additional manifest files that are added to the default one |
| 749 | generated by the linker.""" |
| 750 | files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config, |
| 751 | default=[]) |
| 752 | if isinstance(files, str): |
| 753 | files = files.split(';') |
| 754 | return [os.path.normpath( |
| 755 | gyp_to_build_path(self.ConvertVSMacros(f, config=config))) |
| 756 | for f in files] |
| 757 | |
| 758 | def IsUseLibraryDependencyInputs(self, config): |
| 759 | """Returns whether the target should be linked via Use Library Dependency |
| 760 | Inputs (using component .objs of a given .lib).""" |
| 761 | config = self._TargetConfig(config) |
| 762 | uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config) |
| 763 | return uldi == 'true' |
| 764 | |
| 765 | def IsEmbedManifest(self, config): |
| 766 | """Returns whether manifest should be linked into binary.""" |
| 767 | config = self._TargetConfig(config) |
| 768 | embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config, |
| 769 | default='true') |
| 770 | return embed == 'true' |
| 771 | |
| 772 | def IsLinkIncremental(self, config): |
| 773 | """Returns whether the target should be linked incrementally.""" |
| 774 | config = self._TargetConfig(config) |
| 775 | link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config) |
| 776 | return link_inc != '1' |
| 777 | |
| 778 | def GetRcflags(self, config, gyp_to_ninja_path): |
| 779 | """Returns the flags that need to be added to invocations of the resource |
| 780 | compiler.""" |
| 781 | config = self._TargetConfig(config) |
| 782 | rcflags = [] |
| 783 | rc = self._GetWrapper(self, self.msvs_settings[config], |
| 784 | 'VCResourceCompilerTool', append=rcflags) |
| 785 | rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I') |
| 786 | rcflags.append('/I' + gyp_to_ninja_path('.')) |
| 787 | rc('PreprocessorDefinitions', prefix='/d') |
| 788 | # /l arg must be in hex without leading '0x' |
| 789 | rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:]) |
| 790 | return rcflags |
| 791 | |
| 792 | def BuildCygwinBashCommandLine(self, args, path_to_base): |
| 793 | """Build a command line that runs args via cygwin bash. We assume that all |
| 794 | incoming paths are in Windows normpath'd form, so they need to be |
| 795 | converted to posix style for the part of the command line that's passed to |
| 796 | bash. We also have to do some Visual Studio macro emulation here because |
| 797 | various rules use magic VS names for things. Also note that rules that |
| 798 | contain ninja variables cannot be fixed here (for example ${source}), so |
| 799 | the outer generator needs to make sure that the paths that are written out |
| 800 | are in posix style, if the command line will be used here.""" |
| 801 | cygwin_dir = os.path.normpath( |
| 802 | os.path.join(path_to_base, self.msvs_cygwin_dirs[0])) |
| 803 | cd = ('cd %s' % path_to_base).replace('\\', '/') |
| 804 | args = [a.replace('\\', '/').replace('"', '\\"') for a in args] |
| 805 | args = ["'%s'" % a.replace("'", "'\\''") for a in args] |
| 806 | bash_cmd = ' '.join(args) |
| 807 | cmd = ( |
| 808 | 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir + |
| 809 | 'bash -c "%s ; %s"' % (cd, bash_cmd)) |
| 810 | return cmd |
| 811 | |
| 812 | def IsRuleRunUnderCygwin(self, rule): |
| 813 | """Determine if an action should be run under cygwin. If the variable is |
| 814 | unset, or set to 1 we use cygwin.""" |
| 815 | return int(rule.get('msvs_cygwin_shell', |
| 816 | self.spec.get('msvs_cygwin_shell', 1))) != 0 |
| 817 | |
| 818 | def _HasExplicitRuleForExtension(self, spec, extension): |
| 819 | """Determine if there's an explicit rule for a particular extension.""" |
| 820 | for rule in spec.get('rules', []): |
| 821 | if rule['extension'] == extension: |
| 822 | return True |
| 823 | return False |
| 824 | |
| 825 | def _HasExplicitIdlActions(self, spec): |
| 826 | """Determine if an action should not run midl for .idl files.""" |
| 827 | return any([action.get('explicit_idl_action', 0) |
| 828 | for action in spec.get('actions', [])]) |
| 829 | |
| 830 | def HasExplicitIdlRulesOrActions(self, spec): |
| 831 | """Determine if there's an explicit rule or action for idl files. When |
| 832 | there isn't we need to generate implicit rules to build MIDL .idl files.""" |
| 833 | return (self._HasExplicitRuleForExtension(spec, 'idl') or |
| 834 | self._HasExplicitIdlActions(spec)) |
| 835 | |
| 836 | def HasExplicitAsmRules(self, spec): |
| 837 | """Determine if there's an explicit rule for asm files. When there isn't we |
| 838 | need to generate implicit rules to assemble .asm files.""" |
| 839 | return self._HasExplicitRuleForExtension(spec, 'asm') |
| 840 | |
| 841 | def GetIdlBuildData(self, source, config): |
| 842 | """Determine the implicit outputs for an idl file. Returns output |
| 843 | directory, outputs, and variables and flags that are required.""" |
| 844 | config = self._TargetConfig(config) |
| 845 | midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool') |
| 846 | def midl(name, default=None): |
| 847 | return self.ConvertVSMacros(midl_get(name, default=default), |
| 848 | config=config) |
| 849 | tlb = midl('TypeLibraryName', default='${root}.tlb') |
| 850 | header = midl('HeaderFileName', default='${root}.h') |
| 851 | dlldata = midl('DLLDataFileName', default='dlldata.c') |
| 852 | iid = midl('InterfaceIdentifierFileName', default='${root}_i.c') |
| 853 | proxy = midl('ProxyFileName', default='${root}_p.c') |
| 854 | # Note that .tlb is not included in the outputs as it is not always |
| 855 | # generated depending on the content of the input idl file. |
| 856 | outdir = midl('OutputDirectory', default='') |
| 857 | output = [header, dlldata, iid, proxy] |
| 858 | variables = [('tlb', tlb), |
| 859 | ('h', header), |
| 860 | ('dlldata', dlldata), |
| 861 | ('iid', iid), |
| 862 | ('proxy', proxy)] |
| 863 | # TODO(scottmg): Are there configuration settings to set these flags? |
| 864 | target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64' |
| 865 | flags = ['/char', 'signed', '/env', target_platform, '/Oicf'] |
| 866 | return outdir, output, variables, flags |
| 867 | |
| 868 | |
| 869 | def _LanguageMatchesForPch(source_ext, pch_source_ext): |
| 870 | c_exts = ('.c',) |
| 871 | cc_exts = ('.cc', '.cxx', '.cpp') |
| 872 | return ((source_ext in c_exts and pch_source_ext in c_exts) or |
| 873 | (source_ext in cc_exts and pch_source_ext in cc_exts)) |
| 874 | |
| 875 | |
| 876 | class PrecompiledHeader(object): |
| 877 | """Helper to generate dependencies and build rules to handle generation of |
| 878 | precompiled headers. Interface matches the GCH handler in xcode_emulation.py. |
| 879 | """ |
| 880 | def __init__( |
| 881 | self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext): |
| 882 | self.settings = settings |
| 883 | self.config = config |
| 884 | pch_source = self.settings.msvs_precompiled_source[self.config] |
| 885 | self.pch_source = gyp_to_build_path(pch_source) |
| 886 | filename, _ = os.path.splitext(pch_source) |
| 887 | self.output_obj = gyp_to_unique_output(filename + obj_ext).lower() |
| 888 | |
| 889 | def _PchHeader(self): |
| 890 | """Get the header that will appear in an #include line for all source |
| 891 | files.""" |
| 892 | return self.settings.msvs_precompiled_header[self.config] |
| 893 | |
| 894 | def GetObjDependencies(self, sources, objs, arch): |
| 895 | """Given a list of sources files and the corresponding object files, |
| 896 | returns a list of the pch files that should be depended upon. The |
| 897 | additional wrapping in the return value is for interface compatibility |
| 898 | with make.py on Mac, and xcode_emulation.py.""" |
| 899 | assert arch is None |
| 900 | if not self._PchHeader(): |
| 901 | return [] |
| 902 | pch_ext = os.path.splitext(self.pch_source)[1] |
| 903 | for source in sources: |
| 904 | if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext): |
| 905 | return [(None, None, self.output_obj)] |
| 906 | return [] |
| 907 | |
| 908 | def GetPchBuildCommands(self, arch): |
| 909 | """Not used on Windows as there are no additional build steps required |
| 910 | (instead, existing steps are modified in GetFlagsModifications below).""" |
| 911 | return [] |
| 912 | |
| 913 | def GetFlagsModifications(self, input, output, implicit, command, |
| 914 | cflags_c, cflags_cc, expand_special): |
| 915 | """Get the modified cflags and implicit dependencies that should be used |
| 916 | for the pch compilation step.""" |
| 917 | if input == self.pch_source: |
| 918 | pch_output = ['/Yc' + self._PchHeader()] |
| 919 | if command == 'cxx': |
| 920 | return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))], |
| 921 | self.output_obj, []) |
| 922 | elif command == 'cc': |
| 923 | return ([('cflags_c', map(expand_special, cflags_c + pch_output))], |
| 924 | self.output_obj, []) |
| 925 | return [], output, implicit |
| 926 | |
| 927 | |
| 928 | vs_version = None |
| 929 | def GetVSVersion(generator_flags): |
| 930 | global vs_version |
| 931 | if not vs_version: |
| 932 | vs_version = gyp.MSVSVersion.SelectVisualStudioVersion( |
| 933 | generator_flags.get('msvs_version', 'auto'), |
| 934 | allow_fallback=False) |
| 935 | return vs_version |
| 936 | |
| 937 | def _GetVsvarsSetupArgs(generator_flags, arch): |
| 938 | vs = GetVSVersion(generator_flags) |
| 939 | return vs.SetupScript() |
| 940 | |
| 941 | def ExpandMacros(string, expansions): |
| 942 | """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv |
| 943 | for the canonical way to retrieve a suitable dict.""" |
| 944 | if '$' in string: |
| 945 | for old, new in expansions.iteritems(): |
| 946 | assert '$(' not in new, new |
| 947 | string = string.replace(old, new) |
| 948 | return string |
| 949 | |
| 950 | def _ExtractImportantEnvironment(output_of_set): |
| 951 | """Extracts environment variables required for the toolchain to run from |
| 952 | a textual dump output by the cmd.exe 'set' command.""" |
| 953 | envvars_to_save = ( |
| 954 | 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma. |
| 955 | 'include', |
| 956 | 'lib', |
| 957 | 'libpath', |
| 958 | 'path', |
| 959 | 'pathext', |
| 960 | 'systemroot', |
| 961 | 'temp', |
| 962 | 'tmp', |
| 963 | ) |
| 964 | env = {} |
| 965 | # This occasionally happens and leads to misleading SYSTEMROOT error messages |
| 966 | # if not caught here. |
| 967 | if output_of_set.count('=') == 0: |
| 968 | raise Exception('Invalid output_of_set. Value is:\n%s' % output_of_set) |
| 969 | for line in output_of_set.splitlines(): |
| 970 | for envvar in envvars_to_save: |
| 971 | if re.match(envvar + '=', line.lower()): |
| 972 | var, setting = line.split('=', 1) |
| 973 | if envvar == 'path': |
| 974 | # Our own rules (for running gyp-win-tool) and other actions in |
| 975 | # Chromium rely on python being in the path. Add the path to this |
| 976 | # python here so that if it's not in the path when ninja is run |
| 977 | # later, python will still be found. |
| 978 | setting = os.path.dirname(sys.executable) + os.pathsep + setting |
| 979 | env[var.upper()] = setting |
| 980 | break |
| 981 | for required in ('SYSTEMROOT', 'TEMP', 'TMP'): |
| 982 | if required not in env: |
| 983 | raise Exception('Environment variable "%s" ' |
| 984 | 'required to be set to valid path' % required) |
| 985 | return env |
| 986 | |
| 987 | def _FormatAsEnvironmentBlock(envvar_dict): |
| 988 | """Format as an 'environment block' directly suitable for CreateProcess. |
| 989 | Briefly this is a list of key=value\0, terminated by an additional \0. See |
| 990 | CreateProcess documentation for more details.""" |
| 991 | block = '' |
| 992 | nul = '\0' |
| 993 | for key, value in envvar_dict.iteritems(): |
| 994 | block += key + '=' + value + nul |
| 995 | block += nul |
| 996 | return block |
| 997 | |
| 998 | def _ExtractCLPath(output_of_where): |
| 999 | """Gets the path to cl.exe based on the output of calling the environment |
| 1000 | setup batch file, followed by the equivalent of `where`.""" |
| 1001 | # Take the first line, as that's the first found in the PATH. |
| 1002 | for line in output_of_where.strip().splitlines(): |
| 1003 | if line.startswith('LOC:'): |
| 1004 | return line[len('LOC:'):].strip() |
| 1005 | |
| 1006 | def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, |
| 1007 | system_includes, open_out): |
| 1008 | """It's not sufficient to have the absolute path to the compiler, linker, |
| 1009 | etc. on Windows, as those tools rely on .dlls being in the PATH. We also |
| 1010 | need to support both x86 and x64 compilers within the same build (to support |
| 1011 | msvs_target_platform hackery). Different architectures require a different |
| 1012 | compiler binary, and different supporting environment variables (INCLUDE, |
| 1013 | LIB, LIBPATH). So, we extract the environment here, wrap all invocations |
| 1014 | of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which |
| 1015 | sets up the environment, and then we do not prefix the compiler with |
| 1016 | an absolute path, instead preferring something like "cl.exe" in the rule |
| 1017 | which will then run whichever the environment setup has put in the path. |
| 1018 | When the following procedure to generate environment files does not |
| 1019 | meet your requirement (e.g. for custom toolchains), you can pass |
| 1020 | "-G ninja_use_custom_environment_files" to the gyp to suppress file |
| 1021 | generation and use custom environment files prepared by yourself.""" |
| 1022 | archs = ('x86', 'x64') |
| 1023 | if generator_flags.get('ninja_use_custom_environment_files', 0): |
| 1024 | cl_paths = {} |
| 1025 | for arch in archs: |
| 1026 | cl_paths[arch] = 'cl.exe' |
| 1027 | return cl_paths |
| 1028 | vs = GetVSVersion(generator_flags) |
| 1029 | cl_paths = {} |
| 1030 | for arch in archs: |
| 1031 | # Extract environment variables for subprocesses. |
| 1032 | args = vs.SetupScript(arch) |
| 1033 | args.extend(('&&', 'set')) |
| 1034 | popen = subprocess.Popen( |
| 1035 | args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| 1036 | variables, _ = popen.communicate() |
| 1037 | if popen.returncode != 0: |
| 1038 | raise Exception('"%s" failed with error %d' % (args, popen.returncode)) |
| 1039 | env = _ExtractImportantEnvironment(variables) |
| 1040 | |
| 1041 | # Inject system includes from gyp files into INCLUDE. |
| 1042 | if system_includes: |
| 1043 | system_includes = system_includes | OrderedSet( |
| 1044 | env.get('INCLUDE', '').split(';')) |
| 1045 | env['INCLUDE'] = ';'.join(system_includes) |
| 1046 | |
| 1047 | env_block = _FormatAsEnvironmentBlock(env) |
| 1048 | f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb') |
| 1049 | f.write(env_block) |
| 1050 | f.close() |
| 1051 | |
| 1052 | # Find cl.exe location for this architecture. |
| 1053 | args = vs.SetupScript(arch) |
| 1054 | args.extend(('&&', |
| 1055 | 'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i')) |
| 1056 | popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE) |
| 1057 | output, _ = popen.communicate() |
| 1058 | cl_paths[arch] = _ExtractCLPath(output) |
| 1059 | return cl_paths |
| 1060 | |
| 1061 | def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja): |
| 1062 | """Emulate behavior of msvs_error_on_missing_sources present in the msvs |
| 1063 | generator: Check that all regular source files, i.e. not created at run time, |
| 1064 | exist on disk. Missing files cause needless recompilation when building via |
| 1065 | VS, and we want this check to match for people/bots that build using ninja, |
| 1066 | so they're not surprised when the VS build fails.""" |
| 1067 | if int(generator_flags.get('msvs_error_on_missing_sources', 0)): |
| 1068 | no_specials = filter(lambda x: '$' not in x, sources) |
| 1069 | relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials] |
| 1070 | missing = filter(lambda x: not os.path.exists(x), relative) |
| 1071 | if missing: |
| 1072 | # They'll look like out\Release\..\..\stuff\things.cc, so normalize the |
| 1073 | # path for a slightly less crazy looking output. |
| 1074 | cleaned_up = [os.path.normpath(x) for x in missing] |
| 1075 | raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up)) |
| 1076 | |
| 1077 | # Sets some values in default_variables, which are required for many |
| 1078 | # generators, run on Windows. |
| 1079 | def CalculateCommonVariables(default_variables, params): |
| 1080 | generator_flags = params.get('generator_flags', {}) |
| 1081 | |
| 1082 | # Set a variable so conditions can be based on msvs_version. |
| 1083 | msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags) |
| 1084 | default_variables['MSVS_VERSION'] = msvs_version.ShortName() |
| 1085 | |
| 1086 | # To determine processor word size on Windows, in addition to checking |
| 1087 | # PROCESSOR_ARCHITECTURE (which reflects the word size of the current |
| 1088 | # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which |
| 1089 | # contains the actual word size of the system when running thru WOW64). |
| 1090 | if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or |
| 1091 | '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')): |
| 1092 | default_variables['MSVS_OS_BITS'] = 64 |
| 1093 | else: |
| 1094 | default_variables['MSVS_OS_BITS'] = 32 |