Files
servo/python/servo/visual_studio.py
Jerens Lensun a4fdbe8be3 mach: add type annotation in function for 'python/servo' folder (#38592)
This part of function strictly typed in python by adding ANN rule in
ruff, similiar to #38531.

Testing: `./mach test-tidy`
Fixes: Not related to any issues

---------

Signed-off-by: Jerens Lensun <jerensslensun@gmail.com>
2025-08-15 10:37:24 +00:00

182 lines
7.1 KiB
Python

# Copyright 2024 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
from __future__ import annotations
import dataclasses
import json
import os
import subprocess
import sys
from typing import Optional
from collections.abc import Generator
COMPATIBLE_MSVC_VERSIONS = {
"2019": "16.0",
"2022": "17.0",
}
MSVC_REDIST_VERSIONS = ["VC141", "VC142", "VC143", "VC150", "VC160"]
PROGRAM_FILES = os.environ.get("PROGRAMFILES", "C:\\Program Files")
PROGRAM_FILES_X86 = os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)")
@dataclasses.dataclass(frozen=True, kw_only=True)
class VisualStudioInstallation:
version_number: str
installation_path: str
vc_install_path: str
def __lt__(self, other: VisualStudioInstallation) -> bool:
return self.version_number < other.version_number
def find_vswhere() -> str | None:
for path in [PROGRAM_FILES, PROGRAM_FILES_X86]:
if not path:
continue
vswhere = os.path.join(path, "Microsoft Visual Studio", "Installer", "vswhere.exe")
if os.path.exists(vswhere):
return vswhere
return None
def find_compatible_msvc_with_vswhere() -> Generator[VisualStudioInstallation, None, None]:
"""Try to find the MSVC installation with the `vswhere.exe` tool. The results
are sorted with newer versions first."""
vswhere = find_vswhere()
if not vswhere:
return
output = subprocess.check_output(
[
vswhere,
"-format",
"json",
"-products",
"*",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-requires",
"Microsoft.VisualStudio.Component.Windows*SDK.*",
"-utf8",
]
).decode(errors="ignore")
for install in json.loads(output):
installed_version = f"{install['installationVersion'].split('.')[0]}.0"
if installed_version not in COMPATIBLE_MSVC_VERSIONS.values():
continue
installation_path = install["installationPath"]
yield VisualStudioInstallation(
version_number=installed_version,
installation_path=installation_path,
vc_install_path=os.path.join(installation_path, "VC"),
)
def find_compatible_msvc_with_path() -> Generator[VisualStudioInstallation, None, None]:
for program_files in [PROGRAM_FILES, PROGRAM_FILES_X86]:
if not program_files:
continue
for version, version_number in COMPATIBLE_MSVC_VERSIONS.items():
for edition in ["Enterprise", "Professional", "Community", "BuildTools"]:
installation_path = os.path.join(program_files, "Microsoft Visual Studio", version, edition)
if os.path.exists(installation_path):
yield VisualStudioInstallation(
version_number=version_number,
installation_path=installation_path,
vc_install_path=os.path.join(installation_path, "VC"),
)
def find_compatible_msvc_with_environment_variables() -> Optional[VisualStudioInstallation]:
installation_path = os.environ.get("VSINSTALLDIR")
version_number = os.environ.get("VisualStudioVersion")
if not installation_path or not version_number:
return None
vc_install_path = os.environ.get("VCINSTALLDIR", os.path.join(installation_path, "VC"))
if not os.path.exists(installation_path) or not os.path.exists(vc_install_path):
return None
return VisualStudioInstallation(
version_number=version_number,
installation_path=installation_path,
vc_install_path=vc_install_path,
)
def find_msvc_installations() -> list[VisualStudioInstallation]:
# First try to find Visual Studio via `vswhere.exe` and in well-known paths.
installations = list(find_compatible_msvc_with_vswhere())
installations.extend(find_compatible_msvc_with_path())
if installations:
return sorted(set(installations), reverse=True)
# Fall back to using the environment variables, which could theoretically
# point to a version of Visual Studio that is unsupported.
installation = find_compatible_msvc_with_environment_variables()
if installation:
return [installation]
raise Exception(
"Can't find a Visual Studio installation. "
"Please set the VSINSTALLDIR and VisualStudioVersion environment variables"
)
def find_msvc_redist_dirs(vs_platform: str) -> Generator[str, None, None]:
installations = sorted(set(list(find_msvc_installations())), reverse=True)
tried = []
for installation in installations:
redist_dir = os.path.join(installation.vc_install_path, "Redist", "MSVC")
if not os.path.isdir(redist_dir):
tried.append(redist_dir)
continue
for subdirectory in os.listdir(redist_dir)[::-1]:
redist_path = os.path.join(redist_dir, subdirectory)
for redist_version in MSVC_REDIST_VERSIONS:
# there are two possible paths
# `x64\Microsoft.VC*.CRT` or `onecore\x64\Microsoft.VC*.CRT`
path1 = os.path.join(vs_platform, "Microsoft.{}.CRT".format(redist_version))
path2 = os.path.join("onecore", vs_platform, "Microsoft.{}.CRT".format(redist_version))
for path in [path1, path2]:
full_path = os.path.join(redist_path, path)
if os.path.isdir(full_path):
yield full_path
else:
tried.append(full_path)
print("Couldn't locate MSVC redistributable directory. Tried:", file=sys.stderr)
for path in tried:
print(f" * {path}", file=sys.stderr)
raise Exception("Can't find a MSVC redistributatable directory.")
def find_windows_sdk_installation_path() -> str:
"""Try to find the Windows SDK installation path using the Windows registry.
Raises an Exception if the path cannot be found in the registry."""
# This module must be imported here, because other platforms also
# load this file and the module is platform-specific.
import winreg
# This is based on the advice from
# https://stackoverflow.com/questions/35119223/how-to-programmatically-detect-and-locate-the-windows-10-sdk
key_path = r"SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0"
try:
if sys.platform == "win32":
with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
return str(winreg.QueryValueEx(key, "InstallationFolder")[0])
else:
raise EnvironmentError("Couldn't locate HKEY_LOCAL_MACHINE because it's only available on Windows system")
except FileNotFoundError:
raise Exception(f"Couldn't find Windows SDK installation path in registry at path ({key_path})")