Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/actions/build-guest-artifacts/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ runs:
AZURELINUX_VERSION: ${{ steps.guest-versions.outputs.azurelinux-version }}
KERNEL_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'kernel/config-microvm', 'kernel/patches/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py') }}
DEBUG_KERNEL_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'kernel/config-microvm', 'kernel/config-microvm-debug', 'kernel/patches/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py') }}
ALPINE_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/alpine/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }}
UBUNTU_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/ubuntu/**', 'ubuntu/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py', 'scripts/nvx_tools/ubuntu.py') }}
AZURELINUX_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'azurelinux/**', 'nvx-rs/**', '!nvx-rs/target/**', 'rust-toolchain.toml', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/azurelinux.py', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }}
ALPINE_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/alpine/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }}
UBUNTU_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'guest/ubuntu/**', 'ubuntu/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py', 'scripts/nvx_tools/ubuntu.py') }}
AZURELINUX_INPUT_HASH: ${{ hashFiles('SOURCE-MANIFEST.json', 'guest/common/**', 'azurelinux/**', 'docker/Dockerfile', '.dockerignore', 'scripts/nvx_tools/azurelinux.py', 'scripts/nvx_tools/build.py', 'scripts/nvx_tools/build_config.py', 'scripts/nvx_tools/build_constants.py', 'scripts/nvx_tools/common.py', 'scripts/nvx_tools/elf.py', 'scripts/nvx_tools/guest_binaries.py', 'scripts/nvx_tools/guests.py') }}
run: |
set -euo pipefail
# hashFiles hashes contents only, so the kernel keys also name each patch
# path and its order, and the guest image keys the path-aware digest of
# the Rust guest sources that their package manifests record.
# the Rust guest sources that their package manifests record. Those are
# the files that the guest binaries' build reads, the only nvx-rs files
# that the image keys cover.
patch_identity=$(python3 scripts/nvx.py kernel-patch-identity)
rust_source_identity=$(python3 scripts/nvx.py guest-binary-source-identity)
{
Expand Down
43 changes: 31 additions & 12 deletions doc/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,25 @@ process exits, so delayed final output chunks cannot create a false failure.

Shared guest artifacts are built with Docker on a GitHub-hosted Ubuntu runner.
The kernel, Alpine initramfs, Ubuntu initramfs, and Ubuntu EROFS layer use
separate cache keys. Every guest key includes `nvx-rs/**` except its `target`
directory, `rust-toolchain.toml`, the shared binary-build modules, the
Dockerfile, and `.dockerignore`; the Rust builder image itself is pinned by
digest. Because `hashFiles` hashes contents only, every guest key also ends
with the path-aware Rust source digest that the guest package manifests
record, which `nvx.py guest-binary-source-identity` prints. Ubuntu keys also
include the Canonical archive pin, supplemental
separate cache keys. Every guest image key includes the shared binary-build
modules, the Dockerfile, and `.dockerignore`; the Rust builder image itself is
pinned by digest. Every guest image key also ends with the path-aware digest
of the Rust guest binaries' inputs that the guest package manifests record,
which `nvx.py guest-binary-source-identity` prints, because the images carry
only those binaries of `nvx-rs` and `hashFiles` hashes contents only. The
inputs are the files that the guest binaries' build reads:
`rust-toolchain.toml`; the crate's manifest, lockfile, build script, and Cargo
configuration; every file under `nvx-rs/src/` but the host side, which is the
`host` and `testing` modules and the `nvx-rs` and `nvx-fake-openvmm` binaries,
and the test modules, which are the `tests.rs` files, the `tests` directories,
and the managed agent's `loopback` harness; and the two Python modules that
define the build. `GuestRustBuildConstants` in
[`build_constants.py`](../scripts/nvx_tools/build_constants.py) selects them,
and every guest build fails when Cargo's dep-info for the nine executables
lists another file, such as a host module that guest code starts to compile.
A change to the host side or a test of `nvx-rs` therefore rebuilds no guest
image, and a change to any file that a guest binary compiles rebuilds them
all. Ubuntu keys also include the Canonical archive pin, supplemental
package lock, common guest sources, shared download and guest descriptor
modules, and converter implementation. Artifact upload
retains the Alpine filenames and adds the distinct Ubuntu filenames. Each
Expand Down Expand Up @@ -510,12 +522,16 @@ participate in the decision:
runner setup scripts, `ci.yml`, the actions that their job uses, `nvx.py`,
and the modules that `ci.py` imports. The VMM tests also boot the kernel and
Alpine initramfs, so they add every input of those artifacts' cache keys and
the `build-guest-artifacts` action.
the `build-guest-artifacts` action. Of `nvx-rs`, those are only the inputs
of the guest binaries, which `GuestRustBuildConstants.SOURCE_PATTERN`
matches, so a change to its host side or tests runs the NVX microVM tests
but not the VMM tests.

A push or pull request that changes neither OpenVMM suite's inputs, such as
one that changes only the microVM tests, the benchmarks, or the Ubuntu or
Azure Linux guests, therefore skips the two jobs that occupy the WHP runners
longest. The nightly schedule runs at 05:17 UTC, and a manual workflow
one that changes only the microVM tests, the benchmarks, the Ubuntu or Azure
Linux guests, or the host side of `nvx-rs`, therefore skips the two jobs that
occupy the WHP runners longest. The nightly schedule runs at 05:17 UTC, and a
manual workflow
dispatch starts the same full run. Both select the full matrix before the
detector examines a comparison base. Each full run has a separate concurrency
key, so it neither cancels nor queues behind a `dev` push. Full runs build,
Expand All @@ -537,7 +553,10 @@ missing output would skip its jobs.
expects every scheduled job to succeed and every other job to be skipped.
Tests in `scripts/test_nvx_tools.py` derive each suite's inputs from the
actions that its jobs use, the modules that `ci.py` imports, and the guest
cache keys, and fail when a pattern misses one.
cache keys, including the files of the guest binaries' source identity, and
fail when a pattern misses one. They also check that no other file of
`nvx-rs` selects the VMM tests or changes a guest image key, while every one
selects the NVX microVM tests.

## Host qualification

Expand Down
32 changes: 24 additions & 8 deletions doc/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,21 @@ python3 scripts/nvx.py guest-binary-source-identity [--root PATH]
```

Prints the source digest that guest package manifests record for the Rust guest
binaries: a digest of the relative path and content of every file under
`nvx-rs/src/`, the crate's manifest, lockfile, and build script,
`rust-toolchain.toml`, and the `build_constants.py` and `guest_binaries.py`
build modules. The guest build appends it to the Alpine, Ubuntu, and Azure
Linux cache keys, because `hashFiles` hashes contents only: moving a module
into its own `mod.rs` changes the key even when no bytes change.
binaries: a digest of the relative path and content of every file that their
build reads. Those are the crate's manifest, lockfile, and build script, its
Cargo configuration under `nvx-rs/.cargo/`, every file under `nvx-rs/src/`
except the host side and the test modules, `rust-toolchain.toml`, and the
`build_constants.py` and `guest_binaries.py` build modules. The host side is
the `host` and `testing` modules and the `nvx-rs` and `nvx-fake-openvmm`
binaries; the test modules are the `tests.rs` files, the `tests` directories,
and the managed agent's `loopback` harness. `GuestRustBuildConstants` in
`scripts/nvx_tools/build_constants.py` defines the set, and
[`build-guest-binaries`](#build-guest-binaries) fails when Cargo's dep-info
lists a file outside it. The Alpine, Ubuntu, and Azure Linux cache keys end
with the digest and hash no other file of `nvx-rs`, so a change to the
crate's host side or tests rebuilds no guest image. Because the digest covers
paths, and `hashFiles` hashes contents only, moving a module into its own
`mod.rs` changes the keys even when no bytes change.

### `classify-ci-changes`

Expand Down Expand Up @@ -234,8 +243,15 @@ The Linux-only build command checks the exact `rust-toolchain.toml` pin and
builds the nine Rust guest executables for `x86_64-unknown-linux-musl` with one
`cargo build` of the `guest` profile, a fresh target directory, Cargo's locked
mode, incremental compilation disabled, inherited Cargo profile overrides
removed, remapped source paths, and no linker build IDs. It runs the static-ELF
check and writes `manifest.json` with the source and executable SHA-256 digests.
removed, remapped source paths, and no linker build IDs. It then fails unless
the source identity, which
[`guest-binary-source-identity`](#guest-binary-source-identity) prints and the
guest image cache keys end with, covers every file that Cargo's dep-info lists
for the executables: each source file that rustc read, including the targets of
`include_str!` and `include_bytes!`, and each path that the build script
watches. Files that the build generates in its own target directory are exempt.
It runs the static-ELF check and writes `manifest.json` with the source and
executable SHA-256 digests.
It updates the output of an earlier build in place, adding `nvx-managed-agent`,
`nvx-ingress`, `nvx-time`, and `nvx-time-probe` when that build's manifest does
not record them and removing the retired `nvx-port-io` executable when that
Expand Down
15 changes: 15 additions & 0 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,21 @@ Native guest builds, the digest-pinned Docker Rust stage, and CI all call this
routine. Docker builds the set once and installs identical bytes in all three
guest images; Ubuntu determinism checks rebuild from fresh targets.

Of this crate, the guest images depend only on the files that this build reads:
`Cargo.toml`, `Cargo.lock`, `build.rs`, any Cargo configuration in `.cargo/`,
and every file under `src/` but the host side and the test modules. The host
side is `src/host/`, `src/testing/`, and the `nvx-rs` and `nvx-fake-openvmm`
binaries; the test modules are the `tests.rs` files, the `tests` directories,
and the managed agent's `loopback` harness. CI keys the guest images on those
files and runs the OpenVMM VMM tests, which boot the Alpine image, only when
one of them changes, while every change to the crate runs the NVX microVM
tests; see [Continuous integration](../doc/ci.md#change-classification). Keep
host-only code under `src/host/` and test-only modules in `tests.rs` files or
`tests` directories. The build fails when Cargo's dep-info lists another file,
which `GuestRustBuildConstants` in
[`build_constants.py`](../scripts/nvx_tools/build_constants.py) then has to
select.

## Testing

```bash
Expand Down
29 changes: 27 additions & 2 deletions scripts/nvx_tools/build_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import re
from pathlib import Path
from typing import Final

Expand Down Expand Up @@ -158,7 +159,7 @@ class GuestRustBuildConstants:

DIRECTORY: Final = BuildConstants.REPO_ROOT / "nvx-rs"
# The inputs of every guest executable, relative to a checkout's root: these
# files and every file under SOURCE_DIRECTORY outside a `target` tree.
# files, and every file under SOURCE_DIRECTORIES that SOURCE_PATTERN matches.
SOURCE_FILES: Final = (
Path("nvx-rs") / "Cargo.toml",
Path("nvx-rs") / "Cargo.lock",
Expand All @@ -168,13 +169,20 @@ class GuestRustBuildConstants:
Path("scripts") / "nvx_tools" / "guest_binaries.py",
)
SOURCE_DIRECTORY: Final = Path("nvx-rs") / "src"
# The Cargo configuration that a build in the crate reads, and the sources.
SOURCE_DIRECTORIES: Final = (Path("nvx-rs") / ".cargo", SOURCE_DIRECTORY)
# Test-only modules that no guest build compiles, besides `tests.rs` files and
# `tests` directories.
TEST_MODULES: Final = (
SOURCE_DIRECTORY / "guest" / "managed_agent" / "loopback.rs",
)
TOOLCHAIN_FILE: Final = BuildConstants.REPO_ROOT / "rust-toolchain.toml"
TOOLCHAIN: Final = OpenVMMBuildConstants.RUST_TOOLCHAIN
TARGET: Final = OpenVMMBuildConstants.MUSL_RUST_TARGET
PROFILE: Final = "guest"
FEATURE: Final = "guest"
BINARIES_VARIABLE: Final = "NVX_GUEST_BINARIES"
SOURCE_DIGEST_FORMAT: Final = 1
SOURCE_DIGEST_FORMAT: Final = 2
SOURCE_DIGEST_DOMAIN: Final = "nvx-rust-guest-binaries"
BUILD_MANIFEST_NAME: Final = "manifest.json"
BUILD_MANIFEST_FORMAT: Final = 1
Expand Down Expand Up @@ -203,6 +211,23 @@ class GuestRustBuildConstants:
"nvx-time",
"nvx-time-probe",
)
# Matches the path, relative to a checkout's root, of every input of the guest
# executables: SOURCE_FILES, the Cargo configuration, and every source file
# but the host side, that is, the `host` and `testing` modules and the
# binaries other than BINARIES, and the test modules. A guest build fails when
# Cargo's dep-info lists a file outside the set that it selects.
SOURCE_PATTERN: Final = re.compile(
"|".join(re.escape(path.as_posix()) for path in SOURCE_FILES)
+ r"|nvx-rs/\.cargo/.+"
+ r"|nvx-rs/src/bin/(?:"
+ "|".join(re.escape(binary) for binary in BINARIES)
+ r")\.rs"
+ r"|(?!(?:"
+ "|".join(re.escape(path.as_posix()) for path in TEST_MODULES)
+ r")$)"
+ r"nvx-rs/src/(?!bin/|(?:host|testing)(?:/|\.rs$))"
+ r"(?!(?:[^/]+/)*tests(?:/|\.rs$)).+"
)


class AlpineBuildConstants:
Expand Down
9 changes: 7 additions & 2 deletions scripts/nvx_tools/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from .build_constants import (
AlpineBuildConstants,
GuestRustBuildConstants,
KernelBuildConstants,
OpenVMMBuildConstants,
ZstdBuildConstants,
Expand Down Expand Up @@ -132,16 +133,20 @@
)
# The OpenVMM VMM tests also boot the NVX kernel and Alpine initramfs, so they
# add the inputs of those artifacts' cache keys and of the job that builds them.
# Of nvx-rs, the Alpine key covers only the files that the guest binaries' build
# reads, through their source identity, so a change to its host side or tests
# leaves the VMM tests out.
OPENVMM_VMM_TEST_INPUT_PATHS = re.compile(
r"^(openvmm($|/)|nvx-rs/|rust-toolchain\.toml$|scripts/setup/|"
r"^(openvmm($|/)|rust-toolchain\.toml$|scripts/setup/|"
r"\.github/workflows/ci\.yml$|"
r"\.github/actions/(build-guest-artifacts|checkout-openvmm|sccache|"
r"setup-curl|validate-runner)/|"
r"scripts/nvx\.py$|"
r"scripts/nvx_tools/"
r"(__init__|build|build_config|build_constants|ci|common|elf|"
r"guest_binaries|guests)\.py$|"
r"SOURCE-MANIFEST\.json$|docker/Dockerfile$|\.dockerignore$|kernel/|guest/(alpine|common)/)"
r"SOURCE-MANIFEST\.json$|docker/Dockerfile$|\.dockerignore$|kernel/|guest/(alpine|common)/|"
rf"(?:{GuestRustBuildConstants.SOURCE_PATTERN.pattern})$)"
)


Expand Down
Loading
Loading