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