Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
MSVSVersion.py600 linesDownload Raw Back to gyp
1# Copyright (c) 2013 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"""Handle version information related to Visual Stuio."""
6
7import errno
8import glob
9import os
10import re
11import subprocess
12import sys
13
14
15def JoinPath(*args):
16    return os.path.normpath(os.path.join(*args))
17
18
19class VisualStudioVersion:
20    """Information regarding a version of Visual Studio."""
21
22    def __init__(
23        self,
24        short_name,
25        description,
26        solution_version,
27        project_version,
28        flat_sln,
29        uses_vcxproj,
30        path,
31        sdk_based,
32        default_toolset=None,
33        compatible_sdks=None,
34    ):
35        self.short_name = short_name
36        self.description = description
37        self.solution_version = solution_version
38        self.project_version = project_version
39        self.flat_sln = flat_sln
40        self.uses_vcxproj = uses_vcxproj
41        self.path = path
42        self.sdk_based = sdk_based
43        self.default_toolset = default_toolset
44        compatible_sdks = compatible_sdks or []
45        compatible_sdks.sort(key=lambda v: float(v.replace("v", "")), reverse=True)
46        self.compatible_sdks = compatible_sdks
47
48    def ShortName(self):
49        return self.short_name
50
51    def Description(self):
52        """Get the full description of the version."""
53        return self.description
54
55    def SolutionVersion(self):
56        """Get the version number of the sln files."""
57        return self.solution_version
58
59    def ProjectVersion(self):
60        """Get the version number of the vcproj or vcxproj files."""
61        return self.project_version
62
63    def FlatSolution(self):
64        return self.flat_sln
65
66    def UsesVcxproj(self):
67        """Returns true if this version uses a vcxproj file."""
68        return self.uses_vcxproj
69
70    def ProjectExtension(self):
71        """Returns the file extension for the project."""
72        return (self.uses_vcxproj and ".vcxproj") or ".vcproj"
73
74    def Path(self):
75        """Returns the path to Visual Studio installation."""
76        return self.path
77
78    def ToolPath(self, tool):
79        """Returns the path to a given compiler tool."""
80        return os.path.normpath(os.path.join(self.path, "VC/bin", tool))
81
82    def DefaultToolset(self):
83        """Returns the msbuild toolset version that will be used in the absence
84        of a user override."""
85        return self.default_toolset
86
87    def _SetupScriptInternal(self, target_arch):
88        """Returns a command (with arguments) to be used to set up the
89        environment."""
90        assert target_arch in ("x86", "x64"), "target_arch not supported"
91        # If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the
92        # depot_tools build tools and should run SetEnv.Cmd to set up the
93        # environment. The check for WindowsSDKDir alone is not sufficient because
94        # this is set by running vcvarsall.bat.
95        sdk_dir = os.environ.get("WindowsSDKDir", "")
96        setup_path = JoinPath(sdk_dir, "Bin", "SetEnv.Cmd")
97        if self.sdk_based and sdk_dir and os.path.exists(setup_path):
98            return [setup_path, "/" + target_arch]
99
100        is_host_arch_x64 = (
101            os.environ.get("PROCESSOR_ARCHITECTURE") == "AMD64"
102            or os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64"
103        )
104
105        # For VS2017 (and newer) it's fairly easy
106        if self.short_name >= "2017":
107            script_path = JoinPath(
108                self.path, "VC", "Auxiliary", "Build", "vcvarsall.bat"
109            )
110
111            # Always use a native executable, cross-compiling if necessary.
112            host_arch = "amd64" if is_host_arch_x64 else "x86"
113            msvc_target_arch = "amd64" if target_arch == "x64" else "x86"
114            arg = host_arch
115            if host_arch != msvc_target_arch:
116                arg += "_" + msvc_target_arch
117
118            return [script_path, arg]
119
120        # We try to find the best version of the env setup batch.
121        vcvarsall = JoinPath(self.path, "VC", "vcvarsall.bat")
122        if target_arch == "x86":
123            if (
124                self.short_name >= "2013"
125                and self.short_name[-1] != "e"
126                and is_host_arch_x64
127            ):
128                # VS2013 and later, non-Express have a x64-x86 cross that we want
129                # to prefer.
130                return [vcvarsall, "amd64_x86"]
131            else:
132                # Otherwise, the standard x86 compiler. We don't use VC/vcvarsall.bat
133                # for x86 because vcvarsall calls vcvars32, which it can only find if
134                # VS??COMNTOOLS is set, which isn't guaranteed.
135                return [JoinPath(self.path, "Common7", "Tools", "vsvars32.bat")]
136        elif target_arch == "x64":
137            arg = "x86_amd64"
138            # Use the 64-on-64 compiler if we're not using an express edition and
139            # we're running on a 64bit OS.
140            if self.short_name[-1] != "e" and is_host_arch_x64:
141                arg = "amd64"
142            return [vcvarsall, arg]
143
144    def SetupScript(self, target_arch):
145        script_data = self._SetupScriptInternal(target_arch)
146        script_path = script_data[0]
147        if not os.path.exists(script_path):
148            raise Exception(
149                "%s is missing - make sure VC++ tools are installed." % script_path
150            )
151        return script_data
152
153
154def _RegistryQueryBase(sysdir, key, value):
155    """Use reg.exe to read a particular key.
156
157    While ideally we might use the win32 module, we would like gyp to be
158    python neutral, so for instance cygwin python lacks this module.
159
160    Arguments:
161      sysdir: The system subdirectory to attempt to launch reg.exe from.
162      key: The registry key to read from.
163      value: The particular value to read.
164    Return:
165      stdout from reg.exe, or None for failure.
166    """
167    # Skip if not on Windows or Python Win32 setup issue
168    if sys.platform not in ("win32", "cygwin"):
169        return None
170    # Setup params to pass to and attempt to launch reg.exe
171    cmd = [os.path.join(os.environ.get("WINDIR", ""), sysdir, "reg.exe"), "query", key]
172    if value:
173        cmd.extend(["/v", value])
174    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
175    # Obtain the stdout from reg.exe, reading to the end so p.returncode is valid
176    # Note that the error text may be in [1] in some cases
177    text = p.communicate()[0].decode("utf-8")
178    # Check return code from reg.exe; officially 0==success and 1==error
179    if p.returncode:
180        return None
181    return text
182
183
184def _RegistryQuery(key, value=None):
185    r"""Use reg.exe to read a particular key through _RegistryQueryBase.
186
187    First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
188    that fails, it falls back to System32.  Sysnative is available on Vista and
189    up and available on Windows Server 2003 and XP through KB patch 942589. Note
190    that Sysnative will always fail if using 64-bit python due to it being a
191    virtual directory and System32 will work correctly in the first place.
192
193    KB 942589 - http://support.microsoft.com/kb/942589/en-us.
194
195    Arguments:
196      key: The registry key.
197      value: The particular registry value to read (optional).
198    Return:
199      stdout from reg.exe, or None for failure.
200    """
201    text = None
202    try:
203        text = _RegistryQueryBase("Sysnative", key, value)
204    except OSError as e:
205        if e.errno == errno.ENOENT:
206            text = _RegistryQueryBase("System32", key, value)
207        else:
208            raise
209    return text
210
211
212def _RegistryGetValueUsingWinReg(key, value):
213    """Use the _winreg module to obtain the value of a registry key.
214
215    Args:
216      key: The registry key.
217      value: The particular registry value to read.
218    Return:
219      contents of the registry key's value, or None on failure.  Throws
220      ImportError if winreg is unavailable.
221    """
222    from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx  # noqa: PLC0415
223
224    try:
225        root, subkey = key.split("\\", 1)
226        assert root == "HKLM"  # Only need HKLM for now.
227        with OpenKey(HKEY_LOCAL_MACHINE, subkey) as hkey:
228            return QueryValueEx(hkey, value)[0]
229    except OSError:
230        return None
231
232
233def _RegistryGetValue(key, value):
234    """Use _winreg or reg.exe to obtain the value of a registry key.
235
236    Using _winreg is preferable because it solves an issue on some corporate
237    environments where access to reg.exe is locked down. However, we still need
238    to fallback to reg.exe for the case where the _winreg module is not available
239    (for example in cygwin python).
240
241    Args:
242      key: The registry key.
243      value: The particular registry value to read.
244    Return:
245      contents of the registry key's value, or None on failure.
246    """
247    try:
248        return _RegistryGetValueUsingWinReg(key, value)
249    except ImportError:
250        pass
251
252    # Fallback to reg.exe if we fail to import _winreg.
253    text = _RegistryQuery(key, value)
254    if not text:
255        return None
256    # Extract value.
257    match = re.search(r"REG_\w+\s+([^\r]+)\r\n", text)
258    if not match:
259        return None
260    return match.group(1)
261
262
263def _CreateVersion(name, path, sdk_based=False):
264    """Sets up MSVS project generation.
265
266    Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
267    autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
268    passed in that doesn't match a value in versions python will throw a error.
269    """
270    if path:
271        path = os.path.normpath(path)
272    versions = {
273        "2026": VisualStudioVersion(
274            "2026",
275            "Visual Studio 2026",
276            solution_version="12.00",
277            project_version="18.0",
278            flat_sln=False,
279            uses_vcxproj=True,
280            path=path,
281            sdk_based=sdk_based,
282            default_toolset="v145",
283            compatible_sdks=["v8.1", "v10.0"],
284        ),
285        "2022": VisualStudioVersion(
286            "2022",
287            "Visual Studio 2022",
288            solution_version="12.00",
289            project_version="17.0",
290            flat_sln=False,
291            uses_vcxproj=True,
292            path=path,
293            sdk_based=sdk_based,
294            default_toolset="v143",
295            compatible_sdks=["v8.1", "v10.0"],
296        ),
297        "2019": VisualStudioVersion(
298            "2019",
299            "Visual Studio 2019",
300            solution_version="12.00",
301            project_version="16.0",
302            flat_sln=False,
303            uses_vcxproj=True,
304            path=path,
305            sdk_based=sdk_based,
306            default_toolset="v142",
307            compatible_sdks=["v8.1", "v10.0"],
308        ),
309        "2017": VisualStudioVersion(
310            "2017",
311            "Visual Studio 2017",
312            solution_version="12.00",
313            project_version="15.0",
314            flat_sln=False,
315            uses_vcxproj=True,
316            path=path,
317            sdk_based=sdk_based,
318            default_toolset="v141",
319            compatible_sdks=["v8.1", "v10.0"],
320        ),
321        "2015": VisualStudioVersion(
322            "2015",
323            "Visual Studio 2015",
324            solution_version="12.00",
325            project_version="14.0",
326            flat_sln=False,
327            uses_vcxproj=True,
328            path=path,
329            sdk_based=sdk_based,
330            default_toolset="v140",
331        ),
332        "2013": VisualStudioVersion(
333            "2013",
334            "Visual Studio 2013",
335            solution_version="13.00",
336            project_version="12.0",
337            flat_sln=False,
338            uses_vcxproj=True,
339            path=path,
340            sdk_based=sdk_based,
341            default_toolset="v120",
342        ),
343        "2013e": VisualStudioVersion(
344            "2013e",
345            "Visual Studio 2013",
346            solution_version="13.00",
347            project_version="12.0",
348            flat_sln=True,
349            uses_vcxproj=True,
350            path=path,
351            sdk_based=sdk_based,
352            default_toolset="v120",
353        ),
354        "2012": VisualStudioVersion(
355            "2012",
356            "Visual Studio 2012",
357            solution_version="12.00",
358            project_version="4.0",
359            flat_sln=False,
360            uses_vcxproj=True,
361            path=path,
362            sdk_based=sdk_based,
363            default_toolset="v110",
364        ),
365        "2012e": VisualStudioVersion(
366            "2012e",
367            "Visual Studio 2012",
368            solution_version="12.00",
369            project_version="4.0",
370            flat_sln=True,
371            uses_vcxproj=True,
372            path=path,
373            sdk_based=sdk_based,
374            default_toolset="v110",
375        ),
376        "2010": VisualStudioVersion(
377            "2010",
378            "Visual Studio 2010",
379            solution_version="11.00",
380            project_version="4.0",
381            flat_sln=False,
382            uses_vcxproj=True,
383            path=path,
384            sdk_based=sdk_based,
385        ),
386        "2010e": VisualStudioVersion(
387            "2010e",
388            "Visual C++ Express 2010",
389            solution_version="11.00",
390            project_version="4.0",
391            flat_sln=True,
392            uses_vcxproj=True,
393            path=path,
394            sdk_based=sdk_based,
395        ),
396        "2008": VisualStudioVersion(
397            "2008",
398            "Visual Studio 2008",
399            solution_version="10.00",
400            project_version="9.00",
401            flat_sln=False,
402            uses_vcxproj=False,
403            path=path,
404            sdk_based=sdk_based,
405        ),
406        "2008e": VisualStudioVersion(
407            "2008e",
408            "Visual Studio 2008",
409            solution_version="10.00",
410            project_version="9.00",
411            flat_sln=True,
412            uses_vcxproj=False,
413            path=path,
414            sdk_based=sdk_based,
415        ),
416        "2005": VisualStudioVersion(
417            "2005",
418            "Visual Studio 2005",
419            solution_version="9.00",
420            project_version="8.00",
421            flat_sln=False,
422            uses_vcxproj=False,
423            path=path,
424            sdk_based=sdk_based,
425        ),
426        "2005e": VisualStudioVersion(
427            "2005e",
428            "Visual Studio 2005",
429            solution_version="9.00",
430            project_version="8.00",
431            flat_sln=True,
432            uses_vcxproj=False,
433            path=path,
434            sdk_based=sdk_based,
435        ),
436    }
437    return versions[str(name)]
438
439
440def _ConvertToCygpath(path):
441    """Convert to cygwin path if we are using cygwin."""
442    if sys.platform == "cygwin":
443        p = subprocess.Popen(["cygpath", path], stdout=subprocess.PIPE)
444        path = p.communicate()[0].decode("utf-8").strip()
445    return path
446
447
448def _DetectVisualStudioVersions(versions_to_check, force_express):
449    """Collect the list of installed visual studio versions.
450
451    Returns:
452      A list of visual studio versions installed in descending order of
453      usage preference.
454      Base this on the registry and a quick check if devenv.exe exists.
455      Possibilities are:
456        2005(e) - Visual Studio 2005 (8)
457        2008(e) - Visual Studio 2008 (9)
458        2010(e) - Visual Studio 2010 (10)
459        2012(e) - Visual Studio 2012 (11)
460        2013(e) - Visual Studio 2013 (12)
461        2015    - Visual Studio 2015 (14)
462        2017    - Visual Studio 2017 (15)
463        2019    - Visual Studio 2019 (16)
464        2022    - Visual Studio 2022 (17)
465      Where (e) is e for express editions of MSVS and blank otherwise.
466    """
467    version_to_year = {
468        "8.0": "2005",
469        "9.0": "2008",
470        "10.0": "2010",
471        "11.0": "2012",
472        "12.0": "2013",
473        "14.0": "2015",
474        "15.0": "2017",
475        "16.0": "2019",
476        "17.0": "2022",
477        "18.0": "2026",
478    }
479    versions = []
480    for version in versions_to_check:
481        # Old method of searching for which VS version is installed
482        # We don't use the 2010-encouraged-way because we also want to get the
483        # path to the binaries, which it doesn't offer.
484        keys = [
485            r"HKLM\Software\Microsoft\VisualStudio\%s" % version,
486            r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s" % version,
487            r"HKLM\Software\Microsoft\VCExpress\%s" % version,
488            r"HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s" % version,
489        ]
490        for index in range(len(keys)):
491            path = _RegistryGetValue(keys[index], "InstallDir")
492            if not path:
493                continue
494            path = _ConvertToCygpath(path)
495            # Check for full.
496            full_path = os.path.join(path, "devenv.exe")
497            express_path = os.path.join(path, "*express.exe")
498            if not force_express and os.path.exists(full_path):
499                # Add this one.
500                versions.append(
501                    _CreateVersion(
502                        version_to_year[version], os.path.join(path, "..", "..")
503                    )
504                )
505            # Check for express.
506            elif glob.glob(express_path):
507                # Add this one.
508                versions.append(
509                    _CreateVersion(
510                        version_to_year[version] + "e", os.path.join(path, "..", "..")
511                    )
512                )
513
514        # The old method above does not work when only SDK is installed.
515        keys = [
516            r"HKLM\Software\Microsoft\VisualStudio\SxS\VC7",
517            r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7",
518            r"HKLM\Software\Microsoft\VisualStudio\SxS\VS7",
519            r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VS7",
520        ]
521        for index in range(len(keys)):
522            path = _RegistryGetValue(keys[index], version)
523            if not path:
524                continue
525            path = _ConvertToCygpath(path)
526            if version == "15.0":
527                if os.path.exists(path):
528                    versions.append(_CreateVersion("2017", path))
529            elif version != "14.0":  # There is no Express edition for 2015.
530                versions.append(
531                    _CreateVersion(
532                        version_to_year[version] + "e",
533                        os.path.join(path, ".."),
534                        sdk_based=True,
535                    )
536                )
537
538    return versions
539
540
541def SelectVisualStudioVersion(version="auto", allow_fallback=True):
542    """Select which version of Visual Studio projects to generate.
543
544    Arguments:
545      version: Hook to allow caller to force a particular version (vs auto).
546    Returns:
547      An object representing a visual studio project format version.
548    """
549    # In auto mode, check environment variable for override.
550    if version == "auto":
551        version = os.environ.get("GYP_MSVS_VERSION", "auto")
552    version_map = {
553        "auto": (
554            "18.0",
555            "17.0",
556            "16.0",
557            "15.0",
558            "14.0",
559            "12.0",
560            "10.0",
561            "9.0",
562            "8.0",
563            "11.0",
564        ),
565        "2005": ("8.0",),
566        "2005e": ("8.0",),
567        "2008": ("9.0",),
568        "2008e": ("9.0",),
569        "2010": ("10.0",),
570        "2010e": ("10.0",),
571        "2012": ("11.0",),
572        "2012e": ("11.0",),
573        "2013": ("12.0",),
574        "2013e": ("12.0",),
575        "2015": ("14.0",),
576        "2017": ("15.0",),
577        "2019": ("16.0",),
578        "2022": ("17.0",),
579        "2026": ("18.0",),
580    }
581    if override_path := os.environ.get("GYP_MSVS_OVERRIDE_PATH"):
582        msvs_version = os.environ.get("GYP_MSVS_VERSION")
583        if not msvs_version:
584            raise ValueError(
585                "GYP_MSVS_OVERRIDE_PATH requires GYP_MSVS_VERSION to be "
586                "set to a particular version (e.g. 2010e)."
587            )
588        return _CreateVersion(msvs_version, override_path, sdk_based=True)
589    version = str(version)
590    versions = _DetectVisualStudioVersions(version_map[version], "e" in version)
591    if not versions:
592        if not allow_fallback:
593            raise ValueError("Could not locate Visual Studio installation.")
594        if version == "auto":
595            # Default to 2005 if we couldn't find anything
596            return _CreateVersion("2005", None)
597        else:
598            return _CreateVersion(version, None)
599    return versions[0]
600 
codekingpro/portable-devtools · Team Ai