• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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