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