From fc67588fc03df037947c5e53c1fa0cf711ab7fe8 Mon Sep 17 00:00:00 2001 From: Douglas Raillard <douglas.raillard@arm.com> Date: Sun, 6 Oct 2024 12:28:21 +0100 Subject: [PATCH 1/2] lisa._kmod: Use rust spec as part of Alpine chroot cache key FIX Ensure we cache Alpine chroots based on the required Rust install spec. --- lisa/_kmod.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 471f1bce6..6c57988d8 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -514,7 +514,7 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl _subprocess_log(cmd, logger=logger, level=logging.DEBUG) def populate(key, path, init_cache=True): - version, alpine_arch, packages = key + version, alpine_arch, packages, rust_spec = key path = path.resolve() # Packages have already been installed, so we can speed things up a @@ -551,7 +551,7 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl if packages: run_cmd(['apk', 'add', *sorted(set(packages))]) - def install_rust(): + def install_rust(rust_spec): if rust_spec: _install_rust( rust_spec=rust_spec, @@ -559,7 +559,7 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl ) install_packages(packages) - install_rust() + install_rust(rust_spec) abi = abi or LISA_HOST_ABI use_qemu = abi != LISA_HOST_ABI @@ -595,6 +595,7 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl version, alpine_arch, sorted(set(packages or [])), + rust_spec, ) cache_path = dir_cache.get_entry(key) with _overlay_folders([cache_path], backend=overlay_backend) as path: @@ -2502,8 +2503,8 @@ class KmodSrc(Loggable): populate=populate, ) key = sorted(rust_spec.items()) - cache_path = dir_cache.get_entry(key) - return cache_path + rust_home = dir_cache.get_entry(key) + return rust_home @contextlib.contextmanager def cmd_cm(): -- GitLab From 4d8bd85d7fa0269d8fc23372607840d958621b8e Mon Sep 17 00:00:00 2001 From: Douglas Raillard <douglas.raillard@arm.com> Date: Sun, 6 Oct 2024 12:52:57 +0100 Subject: [PATCH 2/2] lisa._kmod: Dissociate Alpine chroot install and Rust install FEATURE Dissociate the creation of the chroot in the DirCache with the installation of the Rust toolchain. This allows re-using an existing chroot that was setup without Rust (e.g. to prepare the kernel tree) and then simply add a Rust toolchain on top in a separate DirCache bind-mounted in the alpine chroot. This way, we recycle the existing chroot, rather than having to recreate it. --- lisa/_kmod.py | 117 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 6c57988d8..a3252ad20 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -501,10 +501,15 @@ def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, ov def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overlay_backend='overlayfs', rust_spec=None): logger = logging.getLogger(f'{__name__}.alpine_chroot') + def in_chroot(chroot_path, path): + path = Path(path) + assert path.is_absolute() + path = path.relative_to('/') + return chroot_path / path + def mount_binds(chroot, bind_paths, mount=True): for src, dst in bind_paths.items(): - dst = Path(dst).resolve() - dst = (chroot / dst.relative_to('/')).resolve() + dst = in_chroot(chroot, dst) # This will be unmounted by the destroy script if mount: dst.mkdir(parents=True, exist_ok=True) @@ -513,13 +518,28 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl cmd = ['umount', '-nl', '--', dst] _subprocess_log(cmd, logger=logger, level=logging.DEBUG) - def populate(key, path, init_cache=True): - version, alpine_arch, packages, rust_spec = key + def run_cmd_host(cmd): + _subprocess_log(cmd, logger=logger, level=logging.DEBUG) + + def run_cmd_chroot(chroot_path, cmd): + cmd = _make_build_chroot_cmd(chroot_path, cmd) + run_cmd_host(cmd) + + def populate(key, path, stage='init-chroot'): + version, alpine_arch, packages = key path = path.resolve() + run_cmd = lambda cmd: run_cmd_chroot(path, cmd) + + def install_packages(packages): + if packages: + run_cmd(['apk', 'add', *sorted(set(packages))]) + + def enable_network(): + shutil.copy('/etc/resolv.conf', in_chroot(path, Path('/etc/resolv.conf'))) # Packages have already been installed, so we can speed things up a # bit - if init_cache: + if stage == 'init-chroot': version = list(map(str, version)) minor = '.'.join(version[:2]) version = '.'.join(version) @@ -538,28 +558,25 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl with tarfile.open(tar_path, 'r') as f: _tar_extractall(f, path=path) - else: - packages = [] - - shutil.copy('/etc/resolv.conf', path / 'etc' / 'resolv.conf') - - def run_cmd(cmd): - cmd = _make_build_chroot_cmd(path, cmd) - _subprocess_log(cmd, logger=logger, level=logging.DEBUG) - - def install_packages(packages): - if packages: - run_cmd(['apk', 'add', *sorted(set(packages))]) - - def install_rust(rust_spec): + enable_network() + install_packages(packages) + elif stage == 'after-bind-mounts': + enable_network() if rust_spec: - _install_rust( - rust_spec=rust_spec, - run_cmd=run_cmd, - ) + # FIXME: this marker file is messy, we should probably make DirCache evolve to handle that staged used case better. + marker_file = in_chroot(path, rust_spec['cargo_home']) / 'not_installed' + if marker_file.exists(): + _install_rust( + rust_spec=rust_spec, + run_cmd=run_cmd, + ) + # Delete the marker file once we have successfully + # finished, so that we don't accidentally mark an + # interrupted install as completed + marker_file.unlink() + else: + raise ValueError(f'Unknown stage: {stage}') - install_packages(packages) - install_rust(rust_spec) abi = abi or LISA_HOST_ABI use_qemu = abi != LISA_HOST_ABI @@ -578,33 +595,55 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl if not binfmt_path.exists(): raise ValueError(f'Alpine chroot is setup for {qemu_arch} architecture but QEMU userspace emulation is not installed on the host (missing {binfmt_path})') - alpine_arch = _abi_to_alpine_arch(abi) dir_cache = DirCache( category='alpine_chroot', populate=populate, ) - - bind_paths = { - '/proc': '/proc', - '/sys': '/sys', - '/dev': '/dev', - **(bind_paths or {}), - } - + alpine_arch =_abi_to_alpine_arch(abi) key = ( version, alpine_arch, sorted(set(packages or [])), - rust_spec, ) cache_path = dir_cache.get_entry(key) + + bind_paths = bind_paths or {} + + def pre_populate_rust(key, path): + # As a first step, we get the cache location so we can bind mount it + # and then we will populate it once the chroot is setup. + for sub in ('rustup', 'cargo'): + (path / sub).mkdir(parents=True) + + (path / 'cargo' / 'not_installed').touch() + + rust_dir_cache = DirCache( + category='rust_for_alpine_chroot', + populate=pre_populate_rust, + ) + if rust_spec: + rust_key = (alpine_arch, rust_spec) + rust_home = rust_dir_cache.get_entry(rust_key) + bind_paths.update({ + rust_home / 'rustup': rust_spec['rustup_home'], + rust_home / 'cargo': rust_spec['cargo_home'], + }) + + # Bind a host path (key) to a path inside the chroot (value). Values have + # to be absolute paths. + bind_paths = { + '/proc': '/proc', + '/sys': '/sys', + '/dev': '/dev', + **bind_paths, + } with _overlay_folders([cache_path], backend=overlay_backend) as path: # We need to "repopulate" the overlay in order to get a working # system with /etc/resolv.conf etc try: mount_binds(path, bind_paths) - populate(key, path, init_cache=False) + populate(key, path, stage='after-bind-mounts') yield path except ContextManagerExit: mount_binds(path, bind_paths, mount=False) @@ -798,7 +837,7 @@ def _overlay_folders(lowers, backend, upper=None, copy_filter=None): elif backend == 'copy': action = do_copy else: - raise ValueError(f'Unknwon overlay backend "{backend}"') + raise ValueError(f'Unknown overlay backend "{backend}"') with dirs_cm() as dirs: with action(dirs) as mnt: @@ -1376,7 +1415,7 @@ class _KernelBuildEnv(Loggable, SerializeViaConstructor): for runners in cmds ] else: - raise ValueError('Unknwon build-env kind: {build_env}') + raise ValueError('Unknown build-env kind: {build_env}') try: config_path = os.environ['KCONFIG_CONFIG'] @@ -2530,7 +2569,7 @@ class KmodSrc(Loggable): yield (mod_path, cmd) else: - raise ValueError('Unknwon build-env kind: {build_env}') + raise ValueError('Unknown build-env kind: {build_env}') env = _KernelBuildEnv._make_toolchain_env_from_conf(build_conf) with cmd_cm() as (mod_path, cmd): -- GitLab