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