bootstrap: Add winget fallback (#32836)

When `choco` is not available, we can install the same packages from
winget. winget is an official package manager from Microsoft, which is
available by default on Windows 11.

Note: `winget` also has non-interactive installation options, but
accepting license agreements should still be the responsibility of the
user, so we don't add any such option for now.

---

- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix: Bootstrap on windows without `choco` available.

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-05-27 17:51:46 +08:00 committed by GitHub
parent 86b3b16b4c
commit 270dcf879d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,6 +13,7 @@ import tempfile
from typing import Optional
import urllib
import zipfile
import shutil
from servo import util
@ -28,12 +29,51 @@ GSTREAMER_URL = f"{DEPS_URL}/gstreamer-1.0-msvc-x86_64-1.22.8.msi"
GSTREAMER_DEVEL_URL = f"{DEPS_URL}/gstreamer-1.0-devel-msvc-x86_64-1.22.8.msi"
DEPENDENCIES_DIR = os.path.join(util.get_target_dir(), "dependencies")
WINGET_DEPENDENCIES = ["Kitware.CMake", "LLVM.LLVM", "Ninja-build.Ninja", "WiXToolset.WiXToolset"]
def get_dependency_dir(package):
"""Get the directory that a given Windows dependency should extract to."""
return os.path.join(DEPENDENCIES_DIR, package, DEPENDENCIES[package])
def _winget_import(force: bool = False):
try:
# We install tools like LLVM / CMake, so we probably don't want to force-upgrade
# a user installed version without good reason.
cmd = ["winget", "install", "--interactive"]
if force:
cmd.append("--force")
else:
cmd.append("--no-upgrade")
cmd.extend(WINGET_DEPENDENCIES)
# The output will be printed to the terminal that `./mach bootstrap` is running in.
subprocess.run(cmd, encoding="utf-8")
except subprocess.CalledProcessError as e:
print("Could not run winget. Follow manual build setup instructions.")
raise e
def _choco_install(force: bool = False):
try:
choco_config = os.path.join(util.SERVO_ROOT, "support", "windows", "chocolatey.config")
# This is the format that PowerShell wants arguments passed to it.
cmd_exe_args = f"'/K','choco','install','-y', '\"{choco_config}\"'"
if force:
cmd_exe_args += ",'-f'"
print(cmd_exe_args)
subprocess.check_output(
["powershell", "Start-Process", "-Wait", "-verb", "runAs", "cmd.exe", "-ArgumentList", f"@({cmd_exe_args})"]
).decode("utf-8")
except subprocess.CalledProcessError as e:
print("Could not run chocolatey. Follow manual build setup instructions.")
raise e
class Windows(Base):
def __init__(self, triple: str):
super().__init__(triple)
@ -61,31 +101,11 @@ class Windows(Base):
def _platform_bootstrap(self, force: bool) -> bool:
installed_something = self.passive_bootstrap()
try:
choco_config = os.path.join(util.SERVO_ROOT, "support", "windows", "chocolatey.config")
# This is the format that PowerShell wants arguments passed to it.
cmd_exe_args = f"'/K','choco','install','-y', '\"{choco_config}\"'"
if force:
cmd_exe_args += ",'-f'"
print(cmd_exe_args)
subprocess.check_output(
[
"powershell",
"Start-Process",
"-Wait",
"-verb",
"runAs",
"cmd.exe",
"-ArgumentList",
f"@({cmd_exe_args})",
]
).decode("utf-8")
except subprocess.CalledProcessError as e:
print("Could not run chocolatey. Follow manual build setup instructions.")
raise e
# If `winget` works well in practice, we could switch the default in the future.
if shutil.which("choco") is not None:
_choco_install(force)
else:
_winget_import()
target = BuildTarget.from_triple(None)
installed_something |= self._platform_bootstrap_gstreamer(target, force)