build: Add support for Visual Studio 2022 and VC143 DLLs (#31148)

* build: Add support for Visual Studio 2022 and VC143 DLLs

This change adds supports fot Visual Studio 2022 and the VC143 (current)
version of the Visual Studio CRT. In addition, it reworks the way that
Visual Studio is found, returning all installations in a generator,
separately finding it via vswhere.exe, searching paths, and via
environment variables.

All of these installations are searched for the DLLs with highest
priority given to the highest version of MS Visual Studio installed. The
hope is that this makes the process more robust and properly handles
having multiple versions installed, but only one with the correct
runtime DLLs.

* Update based on review comments

Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>

---------

Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2024-01-23 12:04:01 +01:00 committed by GitHub
parent 45af1198aa
commit dc2df7b027
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 165 additions and 135 deletions

View file

@ -46,7 +46,7 @@ env:
jobs: jobs:
build: build:
name: Windows Build name: Windows Build
runs-on: windows-2019 runs-on: windows-2022
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
if: github.event_name != 'pull_request_target' if: github.event_name != 'pull_request_target'

View file

@ -41,14 +41,21 @@ manually, try the [manual build setup][manual-build].
### Windows ### Windows
- Download and run [`rustup-init.exe`](https://win.rustup.rs/) then follow the onscreen instructions. - Download and run [`rustup-init.exe`](https://win.rustup.rs/)
- Make sure to select *Quick install via the Visual Studio Community
installer* or otherwise install Visual Studio 2022.
- In the *Visual Studio Installer* ensure the following components are installed for Visual Studio 2022:
- **Windows 10 SDK (10.0.19041.0)** (`Microsoft.VisualStudio.Component.Windows10SDK.19041`)
- **MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)** (`Microsoft.VisualStudio.Component.VC.Tools.x86.x64`)
- **C++ ATL for latest v143 build tools (x86 & x64)** (`Microsoft.VisualStudio.Component.VC.ATL`)
- **C++ MFC for latest v143 build tools (x86 & x64)** (`Microsoft.VisualStudio.Component.VC.ATLMFC`)
- Install [chocolatey](https://chocolatey.org/) - Install [chocolatey](https://chocolatey.org/)
- Install [Python 3.11](https://apps.microsoft.com/detail/9NRWMJP3717K?hl=en-US&gl=US) - Install [Python 3.11](https://www.python.org/downloads/windows/)
- Run `mach bootstrap` - Run `mach bootstrap`
- *This will install CMake, Git, Ninja, and the Visual Studio 2019 Build Tools - *This will install CMake, Git, Ninja, via choco in an
via choco in an Administrator console. It can take quite a while.* + *This will install CMake, Git, and Ninja via choco in an
- *If you already have Visual Studio 2019 installed, this may not install all necessary components. Administrator console. Allow the scripts to run and once
Please follow the Visual Studio 2019 installation instructions in the [manual setup][manual-build].* the operation finishes, close the new console.*
- Run `refreshenv` - Run `refreshenv`
See also [Windows Troubleshooting Tips][windows-tips]. See also [Windows Troubleshooting Tips][windows-tips].

View file

@ -18,7 +18,7 @@ import sys
import urllib import urllib
from time import time from time import time
from typing import Dict from typing import Dict, Optional
import zipfile import zipfile
import notifypy import notifypy
@ -32,10 +32,10 @@ from mach.registrar import Registrar
import servo.platform import servo.platform
import servo.util import servo.util
import servo.visual_studio
from servo.command_base import BuildType, CommandBase, call, check_call from servo.command_base import BuildType, CommandBase, call, check_call
from servo.gstreamer import windows_dlls, windows_plugins, macos_plugins from servo.gstreamer import windows_dlls, windows_plugins, macos_plugins
from python.servo.visual_studio import find_msvc_redist_dirs
@CommandProvider @CommandProvider
@ -438,29 +438,36 @@ def package_gstreamer_dlls(servo_exe_dir: str, target: str):
return not missing return not missing
def package_msvc_dlls(servo_exe_dir, target): def package_msvc_dlls(servo_exe_dir: str, target: str):
msvc_deps = [ def copy_file(dll_path: Optional[str]) -> bool:
"msvcp140.dll", if not dll_path or not os.path.exists(dll_path):
"vcruntime140.dll", print(f"WARNING: Could not find DLL at {dll_path}", file=sys.stderr)
] return False
if "aarch64" not in target != "aarch64": servo_dir_dll = path.join(servo_exe_dir, os.path.basename(dll_path))
msvc_deps += ["api-ms-win-crt-runtime-l1-1-0.dll"] # Avoid permission denied error when overwriting DLLs.
if os.path.isfile(servo_dir_dll):
os.chmod(servo_dir_dll, stat.S_IWUSR)
print(f" • Copying {dll_path}")
shutil.copy(dll_path, servo_exe_dir)
return True
missing = [] vs_platform = {
redist_dirs = find_msvc_redist_dirs(target) "x86_64": "x64",
for msvc_dll in msvc_deps: "i686": "x86",
for dll_dir in redist_dirs: "aarch64": "arm64",
dll = path.join(dll_dir, msvc_dll) }[target.split('-')[0]]
servo_dir_dll = path.join(servo_exe_dir, msvc_dll)
if os.path.isfile(dll):
if os.path.isfile(servo_dir_dll):
# avoid permission denied error when overwrite dll in servo build directory
os.chmod(servo_dir_dll, stat.S_IWUSR)
shutil.copy(dll, servo_exe_dir)
break
else:
missing += [msvc_dll]
for msvc_dll in missing: for msvc_redist_dir in servo.visual_studio.find_msvc_redist_dirs(vs_platform):
print(f"Could not find DLL dependency: {msvc_dll}") if copy_file(os.path.join(msvc_redist_dir, "msvcp140.dll")) and \
return not missing copy_file(os.path.join(msvc_redist_dir, "vcruntime140.dll")):
break
# Different SDKs install the file into different directory structures within the
# Windows SDK installation directory, so use a glob to search for a path like
# "**\x64\api-ms-win-crt-runtime-l1-1-0.dll".
windows_sdk_dir = servo.visual_studio.find_windows_sdk_installation_path()
dll_name = "api-ms-win-crt-runtime-l1-1-0.dll"
file_to_copy = next(pathlib.Path(windows_sdk_dir).rglob(os.path.join("**", vs_platform, dll_name)))
copy_file(file_to_copy)
return True

View file

@ -12,80 +12,144 @@ import json
import os import os
import subprocess import subprocess
import sys import sys
from typing import List from typing import Generator, List, Optional
import servo.platform 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(kw_only=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class VisualStudioInstallation: class VisualStudioInstallation:
version_number: str version_number: str
installation_path: str installation_path: str
vc_install_path: str vc_install_path: str
def __lt__(self, other):
return self.version_number < other.version_number
def find_highest_msvc_version_ext():
def find_vswhere():
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 """Try to find the MSVC installation with the `vswhere.exe` tool. The results
are sorted with newer versions first.""" are sorted with newer versions first."""
def vswhere(args):
program_files = (os.environ.get('PROGRAMFILES(X86)') vswhere = find_vswhere()
or os.environ.get('PROGRAMFILES')) if not vswhere:
return
output = subprocess.check_output([
vswhere,
'-format', 'json',
'-products', '*',
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-requires', 'Microsoft.VisualStudio.Component.Windows10SDK'
]).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: if not program_files:
return [] continue
vswhere = os.path.join(program_files, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe') for (version, version_number) in COMPATIBLE_MSVC_VERSIONS.items():
if not os.path.exists(vswhere): for edition in ["Enterprise", "Professional", "Community", "BuildTools"]:
return [] installation_path = os.path.join(program_files, "Microsoft Visual Studio", version, edition)
output = subprocess.check_output([vswhere, '-format', 'json'] + args).decode(errors='ignore') if os.path.exists(installation_path):
return json.loads(output) yield VisualStudioInstallation(
version_number=version_number,
for install in vswhere(['-products', '*', installation_path=installation_path,
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', vc_install_path=os.path.join(installation_path, "VC")
'-requires', 'Microsoft.VisualStudio.Component.Windows10SDK']): )
version = install['installationVersion'].split('.')[0] + '.0'
yield (install['installationPath'], version)
def find_highest_msvc_version(): def find_compatible_msvc_with_environment_variables() -> Optional[VisualStudioInstallation]:
prog_files = os.environ.get("ProgramFiles(x86)") installation_path = os.environ.get('VSINSTALLDIR')
# TODO(mrobinson): Add support for Visual Studio 2022.
vs_versions = {
"2019": "16.0",
}
for (version, version_number) in vs_versions.items():
for edition in ["Enterprise", "Professional", "Community", "BuildTools"]:
vsinstalldir = os.path.join(prog_files, "Microsoft Visual Studio", version, edition)
if os.path.exists(vsinstalldir):
return (vsinstalldir, version_number)
versions = sorted(find_highest_msvc_version_ext(), key=lambda tup: float(tup[1]))
if not versions:
print("Can't find a Visual Studio installation. "
"Please set the VSINSTALLDIR and VisualStudioVersion environment variables")
sys.exit(1)
return versions[0]
def find_msvc() -> VisualStudioInstallation:
vsinstalldir = os.environ.get('VSINSTALLDIR')
version_number = os.environ.get('VisualStudioVersion') version_number = os.environ.get('VisualStudioVersion')
if not vsinstalldir or not version_number: if not installation_path or not version_number:
(vsinstalldir, version_number) = find_highest_msvc_version() return None
vc_install_path = os.environ.get("VCINSTALLDIR", os.path.join(installation_path, "VC"))
vc_install_path = os.environ.get("VCINSTALLDIR", os.path.join(vsinstalldir, "VC")) if not os.path.exists(installation_path) or not os.path.exists(vc_install_path):
if not os.path.exists(vc_install_path): return None
print(f"Can't find Visual C++ {version_number} installation at {vc_install_path}")
sys.exit(1)
return VisualStudioInstallation( return VisualStudioInstallation(
version_number=version_number, version_number=version_number,
installation_path=vsinstalldir, installation_path=installation_path,
vc_install_path=vc_install_path, vc_install_path=vc_install_path,
) )
def find_windows_sdk_installation_path(vs_platform: str) -> str: 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]:
path = os.path.join(redist_path, path)
if os.path.isdir(path):
yield path
else:
tried.append(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. """Try to find the Windows SDK installation path using the Windows registry.
Raises an Exception if the path cannot be found in the registry.""" Raises an Exception if the path cannot be found in the registry."""
@ -98,52 +162,6 @@ def find_windows_sdk_installation_path(vs_platform: str) -> str:
key_path = r'SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' key_path = r'SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0'
try: try:
with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key_path) as key: with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
path = str(winreg.QueryValueEx(key, "InstallationFolder")[0]) return str(winreg.QueryValueEx(key, "InstallationFolder")[0])
return os.path.join(path, "Redist", "ucrt", "DLLs", vs_platform)
except FileNotFoundError: except FileNotFoundError:
raise Exception(f"Couldn't find Windows SDK installation path in registry at path ({key_path})") raise Exception(f"Couldn't find Windows SDK installation path in registry at path ({key_path})")
def find_msvc_redist_dirs(target: str) -> List[str]:
assert 'windows' in servo.platform.host_triple()
installation = find_msvc()
msvc_redist_dir = None
vs_platforms = {
"x86_64": "x64",
"i686": "x86",
"aarch64": "arm64",
}
target_arch = target.split('-')[0]
vs_platform = vs_platforms[target_arch]
redist_dir = os.path.join(installation.vc_install_path, "Redist", "MSVC")
if not os.path.isdir(redist_dir):
raise Exception(f"Couldn't locate MSVC redistributable directory {redist_dir}")
for p in os.listdir(redist_dir)[::-1]:
redist_path = os.path.join(redist_dir, p)
for v in ["VC141", "VC142", "VC150", "VC160"]:
# there are two possible paths
# `x64\Microsoft.VC*.CRT` or `onecore\x64\Microsoft.VC*.CRT`
redist1 = os.path.join(redist_path, vs_platform, "Microsoft.{}.CRT".format(v))
redist2 = os.path.join(redist_path, "onecore", vs_platform, "Microsoft.{}.CRT".format(v))
if os.path.isdir(redist1):
msvc_redist_dir = redist1
break
elif os.path.isdir(redist2):
msvc_redist_dir = redist2
break
if msvc_redist_dir:
break
if not msvc_redist_dir:
print("Couldn't locate MSVC redistributable directory")
sys.exit(1)
redist_dirs = [
msvc_redist_dir,
find_windows_sdk_installation_path(vs_platform)
]
return redist_dirs

View file

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="cmake" version="3.26.4" installArguments="ADD_CMAKE_TO_PATH=System"/> <package id="cmake" version="3.26.4" installArguments="ADD_CMAKE_TO_PATH=System"/>
<package id="git"/>
<package id="llvm"/> <package id="llvm"/>
<package id="wixtoolset" version="3.11.2"/> <package id="wixtoolset" version="3.11.2"/>
<package id="ninja"/> <package id="ninja"/>
<package id="visualstudio2019buildtools" packageParameters="--add Microsoft.VisualStudio.Component.Roslyn.Compiler --add Microsoft.Component.MSBuild --add Microsoft.VisualStudio.Component.CoreBuildTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.Windows10SDK.20348 --add Microsoft.VisualStudio.Component.VC.CoreBuildTools --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.VC.Redist.14.Latest --add Microsoft.VisualStudio.Component.VC.ATL --add Microsoft.VisualStudio.Component.VC.ATLMFC --add Microsoft.VisualStudio.Component.TextTemplating --add Microsoft.VisualStudio.Component.VC.CoreIde --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core --add Microsoft.VisualStudio.Workload.VCTools" />
</packages> </packages>