Windows bootstrap support

This commit is contained in:
Martin Robinson 2023-06-23 11:07:18 +02:00 committed by Martin Robinson
parent 35ab311635
commit 633d9b0eb9
10 changed files with 69 additions and 74 deletions

View file

@ -43,9 +43,12 @@ manually, try the [manual build setup][manual-build].
- 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/) then follow the onscreen instructions.
- Install [chocolatey](https://chocolatey.org/) - Install [chocolatey](https://chocolatey.org/)
- Run `choco install support\windows\chocolatey.config` - Run `mach bootstrap`
*This will install CMake, Git, LLVM, Ninja, NuGet, Python and the Visual Studio 2019 Build Tools.* - *This will install CMake, Git, Ninja, NuGet, Python and the Visual Studio 2019 Build Tools
- Run `mach bootstrap-gstreamer` via choco in an Administrator console. It can take quite a while.*
- *If you already have Visual Studio 2019 installed, this may not install all necessary components.
Please follow the Visual Studio 2019 installation instructions in the [manual setup][manual-build].*
- Run `refreshenv`
See also [Windows Troubleshooting Tips][windows-tips]. See also [Windows Troubleshooting Tips][windows-tips].

View file

@ -241,13 +241,8 @@ def bootstrap_command_only(topdir):
import servo.platform import servo.platform
import servo.util import servo.util
# We are not set up yet, so we always use the default cache directory
# for the initial bootstrap.
# TODO(mrobinson): Why not just run the bootstrap command in this case?
try: try:
servo.platform.get().bootstrap( servo.platform.get().bootstrap('-f' in sys.argv or '--force' in sys.argv)
servo.util.get_default_cache_dir(topdir), '-f' in sys.argv)
except NotImplementedError as exception: except NotImplementedError as exception:
print(exception) print(exception)
return 1 return 1

View file

@ -46,7 +46,7 @@ class MachCommands(CommandBase):
# ./mach bootstrap calls mach_bootstrap.bootstrap_command_only so that # ./mach bootstrap calls mach_bootstrap.bootstrap_command_only so that
# it can install dependencies without needing mach's dependencies # it can install dependencies without needing mach's dependencies
try: try:
servo.platform.get().bootstrap(self.context.sharedir, force) servo.platform.get().bootstrap(force)
except NotImplementedError as exception: except NotImplementedError as exception:
print(exception) print(exception)
return 1 return 1
@ -60,7 +60,7 @@ class MachCommands(CommandBase):
help='Boostrap without confirmation') help='Boostrap without confirmation')
def bootstrap_gstreamer(self, force=False): def bootstrap_gstreamer(self, force=False):
try: try:
servo.platform.get().bootstrap_gstreamer(self.context.sharedir, force) servo.platform.get().bootstrap_gstreamer(force)
except NotImplementedError as exception: except NotImplementedError as exception:
print(exception) print(exception)
return 1 return 1

View file

@ -178,11 +178,11 @@ class MachCommands(CommandBase):
# Override any existing GStreamer installation with the vendored libraries. # Override any existing GStreamer installation with the vendored libraries.
env["GSTREAMER_1_0_ROOT_" + arch['gst']] = path.join( env["GSTREAMER_1_0_ROOT_" + arch['gst']] = path.join(
self.msvc_package_dir("gstreamer-uwp"), arch['gst_root'] servo.platform.windows.get_dependency_dir("gstreamer-uwp"), arch['gst_root']
) )
env["PKG_CONFIG_PATH"] = path.join( env["PKG_CONFIG_PATH"] = path.join(
self.msvc_package_dir("gstreamer-uwp"), arch['gst_root'], servo.platform.windows.get_dependency_dir("gstreamer-uwp"),
"lib", "pkgconfig" arch['gst_root'], "lib", "pkgconfig"
) )
if 'windows' in host: if 'windows' in host:

View file

@ -476,8 +476,7 @@ class CommandBase(object):
return self.get_executable(destination_folder) return self.get_executable(destination_folder)
def msvc_package_dir(self, package): def msvc_package_dir(self, package):
return path.join(self.context.sharedir, "msvc-dependencies", package, return servo.platform.windows.get_dependency_dir(package)
servo.platform.windows.DEPENDENCIES[package])
def vs_dirs(self): def vs_dirs(self):
assert 'windows' in servo.platform.host_triple() assert 'windows' in servo.platform.host_triple()
@ -508,11 +507,7 @@ class CommandBase(object):
extra_path = [] extra_path = []
effective_target = self.cross_compile_target or servo.platform.host_triple() effective_target = self.cross_compile_target or servo.platform.host_triple()
if "msvc" in effective_target: if "msvc" in effective_target:
extra_path += [path.join(self.msvc_package_dir("cmake"), "bin")]
extra_path += [path.join(self.msvc_package_dir("llvm"), "bin")] extra_path += [path.join(self.msvc_package_dir("llvm"), "bin")]
extra_path += [path.join(self.msvc_package_dir("ninja"), "bin")]
extra_path += [self.msvc_package_dir("nuget")]
env.setdefault("CC", "clang-cl.exe") env.setdefault("CC", "clang-cl.exe")
env.setdefault("CXX", "clang-cl.exe") env.setdefault("CXX", "clang-cl.exe")
if self.is_uwp_build: if self.is_uwp_build:
@ -911,10 +906,7 @@ class CommandBase(object):
if self.context.bootstrapped: if self.context.bootstrapped:
return return
# Always check if all needed MSVC dependencies are installed servo.platform.get().passive_bootstrap()
target_platform = self.cross_compile_target or servo.platform.host_triple()
if "msvc" in target_platform:
Registrar.dispatch("bootstrap", context=self.context)
if self.config["tools"]["use-rustup"]: if self.config["tools"]["use-rustup"]:
self.ensure_rustup_version() self.ensure_rustup_version()

View file

@ -75,7 +75,7 @@ class Base:
def executable_suffix(self): def executable_suffix(self):
return "" return ""
def _platform_bootstrap(self, _cache_dir: str, _force: bool) -> bool: def _platform_bootstrap(self, _force: bool) -> bool:
raise NotImplementedError("Bootstrap installation detection not yet available.") raise NotImplementedError("Bootstrap installation detection not yet available.")
def _platform_bootstrap_gstreamer(self, _force: bool) -> bool: def _platform_bootstrap_gstreamer(self, _force: bool) -> bool:
@ -97,11 +97,17 @@ class Base:
== 0 == 0
) )
def bootstrap(self, cache_dir: str, force: bool): def bootstrap(self, force: bool):
if not self._platform_bootstrap(cache_dir, force): if not self._platform_bootstrap(force):
print("Dependencies were already installed!") print("Dependencies were already installed!")
def bootstrap_gstreamer(self, _cache_dir: str, force: bool): def passive_bootstrap(self) -> bool:
"""A bootstrap method that is called without explicitly invoking `./mach bootstrap`
but that is executed in the process of other `./mach` commands. This should be
as fast as possible."""
return False
def bootstrap_gstreamer(self, force: bool):
if not self._platform_bootstrap_gstreamer(force): if not self._platform_bootstrap_gstreamer(force):
root = self.gstreamer_root(None) root = self.gstreamer_root(None)
if root: if root:

View file

@ -94,7 +94,7 @@ class Linux(Base):
return (distrib, version) return (distrib, version)
def _platform_bootstrap(self, _cache_dir: str, force: bool) -> bool: def _platform_bootstrap(self, force: bool) -> bool:
if self.distro.lower() == 'nixos': if self.distro.lower() == 'nixos':
print('NixOS does not need bootstrap, it will automatically enter a nix-shell') print('NixOS does not need bootstrap, it will automatically enter a nix-shell')
print('Just run ./mach build') print('Just run ./mach build')

View file

@ -54,7 +54,7 @@ class MacOS(Base):
return False return False
return True return True
def _platform_bootstrap(self, _cache_dir: str, force: bool) -> bool: def _platform_bootstrap(self, _force: bool) -> bool:
installed_something = False installed_something = False
try: try:
brewfile = os.path.join(util.SERVO_ROOT, "etc", "homebrew", "Brewfile") brewfile = os.path.join(util.SERVO_ROOT, "etc", "homebrew", "Brewfile")

View file

@ -8,25 +8,19 @@
# except according to those terms. # except according to those terms.
import os import os
import shutil
import subprocess import subprocess
import tempfile import tempfile
from typing import Optional from typing import Optional
import urllib import urllib
import zipfile import zipfile
from distutils.version import LooseVersion
import six
from .. import util from .. import util
from .base import Base from .base import Base
DEPS_URL = "https://github.com/servo/servo-build-deps/releases/download/msvc-deps/" DEPS_URL = "https://github.com/servo/servo-build-deps/releases/download/msvc-deps/"
DEPENDENCIES = { DEPENDENCIES = {
"cmake": "3.14.3",
"llvm": "15.0.5", "llvm": "15.0.5",
"moztools": "3.2", "moztools": "3.2",
"ninja": "1.7.1",
"nuget": "08-08-2019",
"openssl": "111.3.0+1.1.1c-vs2017-2019-09-18", "openssl": "111.3.0+1.1.1c-vs2017-2019-09-18",
"gstreamer-uwp": "1.16.0.5", "gstreamer-uwp": "1.16.0.5",
"openxr-loader-uwp": "1.0", "openxr-loader-uwp": "1.0",
@ -38,6 +32,11 @@ GSTREAMER_DEVEL_URL = f"{URL_BASE}/gstreamer-1.0-devel-msvc-x86_64-1.16.0.msi"
DEPENDENCIES_DIR = os.path.join(util.get_target_dir(), "dependencies") DEPENDENCIES_DIR = os.path.join(util.get_target_dir(), "dependencies")
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])
class Windows(Base): class Windows(Base):
def __init__(self, triple: str): def __init__(self, triple: str):
super().__init__(triple) super().__init__(triple)
@ -49,64 +48,65 @@ class Windows(Base):
def library_path_variable_name(self): def library_path_variable_name(self):
return "LIB" return "LIB"
@staticmethod
def cmake_already_installed(required_version: str) -> bool:
cmake_path = shutil.which("cmake")
if not cmake_path:
return False
output = subprocess.check_output([cmake_path, "--version"])
cmake_version_output = six.ensure_str(output).splitlines()[0]
installed_version = cmake_version_output.replace("cmake version ", "")
return LooseVersion(installed_version) >= LooseVersion(required_version)
@classmethod @classmethod
def prepare_file(cls, deps_dir: str, zip_path: str, full_spec: str): def download_and_extract_dependency(cls, zip_path: str, full_spec: str):
if not os.path.isfile(zip_path): if not os.path.isfile(zip_path):
zip_url = "{}{}.zip".format(DEPS_URL, urllib.parse.quote(full_spec)) zip_url = f"{DEPS_URL}{urllib.parse.quote(full_spec)}.zip"
util.download_file(full_spec, zip_url, zip_path) util.download_file(full_spec, zip_url, zip_path)
print("Extracting {}...".format(full_spec), end="") zip_dir = os.path.dirname(zip_path)
print(f"Extracting {full_spec} to {zip_dir}...", end="")
try: try:
util.extract(zip_path, deps_dir) util.extract(zip_path, zip_dir)
except zipfile.BadZipfile: except zipfile.BadZipfile:
print("\nError: %s.zip is not a valid zip file, redownload..." % full_spec) print(f"\nError: {full_spec}.zip is not a valid zip file, redownload...")
os.remove(zip_path) os.remove(zip_path)
cls.prepare_file(deps_dir, zip_path, full_spec) cls.download_and_extract_dependency(zip_path, full_spec)
else: else:
print("done") print("done")
def _platform_bootstrap(self, cache_dir: str, _force: bool = False) -> bool: def _platform_bootstrap(self, force: bool = False) -> bool:
deps_dir = os.path.join(cache_dir, "msvc-dependencies") installed_something = self.passive_bootstrap()
def get_package_dir(package, version) -> str: try:
return os.path.join(deps_dir, package, version) choco_config = os.path.join(util.SERVO_ROOT, "support", "windows", "chocolatey.config")
to_install = {} # This is the format that PowerShell wants arguments passed to it.
for package, version in DEPENDENCIES.items(): cmd_exe_args = f"'/K','choco','install','-y','{choco_config}'"
# Don't install CMake if it already exists in PATH if force:
if package == "cmake" and self.cmake_already_installed(version): cmd_exe_args += ",'-f'"
continue
if not os.path.isdir(get_package_dir(package, version)): print(cmd_exe_args)
to_install[package] = version 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
return installed_something
def passive_bootstrap(self) -> bool:
"""A bootstrap method that is called without explicitly invoking `./mach bootstrap`
but that is executed in the process of other `./mach` commands. This should be
as fast as possible."""
to_install = [package for package in DEPENDENCIES if
not os.path.isdir(get_dependency_dir(package))]
if not to_install: if not to_install:
return False return False
print("Installing missing MSVC dependencies...") print("Installing missing MSVC dependencies...")
for package, version in to_install.items(): for package in to_install:
full_spec = "{}-{}".format(package, version) full_spec = "{}-{}".format(package, DEPENDENCIES[package])
package_dir = get_package_dir(package, version) package_dir = get_dependency_dir(package)
parent_dir = os.path.dirname(package_dir) parent_dir = os.path.dirname(package_dir)
if not os.path.isdir(parent_dir): if not os.path.isdir(parent_dir):
os.makedirs(parent_dir) os.makedirs(parent_dir)
self.prepare_file(deps_dir, package_dir + ".zip", full_spec) self.download_and_extract_dependency(package_dir + ".zip", full_spec)
os.rename(os.path.join(parent_dir, full_spec), package_dir)
extracted_path = os.path.join(deps_dir, full_spec)
os.rename(extracted_path, package_dir)
return True return True

View file

@ -1,8 +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" /> <package id="cmake" version="3.26.4" installArguments="ADD_CMAKE_TO_PATH=System"/>
<package id="git"/> <package id="git"/>
<package id="llvm"/>
<package id="ninja"/> <package id="ninja"/>
<package id="nuget.commandline" version="6.6.0" /> <package id="nuget.commandline" version="6.6.0" />
<package id="python"/> <package id="python"/>