From 7399a79923d859d03212ebaf2a0ee30838eafcf5 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Wed, 11 Nov 2026 21:51:02 -0800 Subject: [PATCH 1/2] nvx-rs: delete the bundled feature and its build script The bundled feature staged a pinned NVX release at build time: its build script downloaded the package that artifacts.json pinned, or copied the release that NVX_BUNDLE_DIR named, for the Linux package that NVX_BUNDLE_HYPERVISOR chose, unless NVX_BUNDLE=skip. It exported the staged directory to the library as NVX_BUNDLED_ARTIFACTS_DIR, and to the build scripts of dependents as DEP_NVX_ARTIFACTS_DIR, through links = "nvx". Nothing in NVX uses it, and #1076 releases the crate without it, so the feature, the build script, links = "nvx", artifacts.json and the four variables go. Artifacts::bundled, which returned the staged release, goes with the feature, and Artifacts::discover loses its fourth source, the staged directory: a discovery that finds no source suggests the variables and a release beside the executable, but no longer the feature. The public API snapshots lose Artifacts::bundled on each platform. NVX_OPENVMM, NVX_KERNEL, NVX_INITRD, NVX_ARTIFACTS_DIR, the release beside the executable, NVX_AUTO_DOWNLOAD and NVX_RS keep their meaning, and contract::manifest_mismatches, which the build script also called, stays for discovery and downloads, with the time ABI's and the restore packet's fields. contract.rs no longer has to build alone, since no build script includes it any more. The tooling stops building, checking or listing the feature and its files: check-nvx-rs drops its bundling step and NVX_BUNDLE=skip, and so does the mutants workflow; the crate release no longer requires the build script or artifacts.json in the package, and builds the package once, with every feature, rather than once for each Linux package that it bundled; and the inventories of the guest binaries' sources and of the nvx-rs that nvx.py builds, which fail on a listed file that is missing, no longer list nvx-rs/build.rs. The README, the docs and the case matrix follow, where CI's bundling step retires. The bundle's tests retire: the_bundle_is_a_compatible_release, the crate release's test_windows_builds_its_one_bundle, and test_nvx_rs_bundles_a_release_in_the_current_layout, which checked the bundling step. the_bundle_is_the_last_resort becomes discovery_fails_when_no_source_is_present, which keeps its check of the error and checks that the error no longer suggests the feature. The other discovery tests stop passing a bundle source, the release test expects one build, and the guest-binary and provenance fixtures no longer hold build.rs or artifacts.json; one case of an input that the guest identity omits names nvx-rs/README.md in place of the deleted artifacts.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/check-nvx-rs/action.yml | 42 --- .github/workflows/nvx-rs-mutants.yml | 2 - doc/acceptance-case-matrix.md | 8 +- doc/ci.md | 12 +- doc/contribute.md | 2 +- doc/distribution.md | 9 +- doc/project-structure.md | 8 +- doc/usage.md | 4 +- nvx-rs/Cargo.toml | 15 - nvx-rs/README.md | 74 ++--- nvx-rs/artifacts.json | 18 -- nvx-rs/build.rs | 356 ------------------------ nvx-rs/public-api/linux.txt | 2 - nvx-rs/public-api/windows.txt | 2 - nvx-rs/src/contract.rs | 3 - nvx-rs/src/host/artifacts/mod.rs | 42 +-- nvx-rs/src/host/artifacts/tests.rs | 57 +--- nvx-rs/src/host/cli/download.rs | 6 +- nvx-rs/src/lib.rs | 3 - scripts/nvx_tools/build_constants.py | 1 - scripts/nvx_tools/crate_release.py | 75 ++--- scripts/nvx_tools/nvx_rs.py | 5 +- scripts/test_crate_release.py | 31 +-- scripts/test_guest_binaries.py | 21 +- scripts/test_nvx_tools.py | 35 +-- 25 files changed, 88 insertions(+), 745 deletions(-) delete mode 100644 nvx-rs/artifacts.json delete mode 100644 nvx-rs/build.rs diff --git a/.github/actions/check-nvx-rs/action.yml b/.github/actions/check-nvx-rs/action.yml index 9ec2984f8..80fda3062 100644 --- a/.github/actions/check-nvx-rs/action.yml +++ b/.github/actions/check-nvx-rs/action.yml @@ -40,9 +40,6 @@ runs: - name: Lint shell: bash working-directory: nvx-rs - env: - # --all-features enables `bundled`; stage nothing instead of downloading a release. - NVX_BUNDLE: skip run: | set -euo pipefail cargo "+${{ steps.toolchains.outputs.toolchain }}" clippy --locked --all-targets --all-features \ @@ -64,9 +61,6 @@ runs: - name: Test shell: bash working-directory: nvx-rs - env: - # --all-features enables `bundled`; stage nothing instead of downloading a release. - NVX_BUNDLE: skip run: | set -euo pipefail cargo "+${{ steps.toolchains.outputs.toolchain }}" test --locked --all-features @@ -75,40 +69,10 @@ runs: cargo "+${{ steps.toolchains.outputs.toolchain }}" build --locked --lib --no-default-features cargo "+${{ steps.toolchains.outputs.toolchain }}" test --locked --lib --no-default-features - # Stages a synthetic release in the current layout, with nvx-rs and its - # provenance, which the bundled-artifacts test then requires to find. - - name: Check artifact bundling - shell: bash - working-directory: nvx-rs - run: | - set -euo pipefail - bundle=$(mktemp -d) - executable=openvmm - cli=nvx-rs - bundle_dir="${bundle}" - if [[ "${RUNNER_OS}" == Windows ]]; then - executable=openvmm.exe - cli=nvx-rs.exe - bundle_dir=$(cygpath -w "${bundle}") - fi - mkdir -p "${bundle}/bin" "${bundle}/guest" "${bundle}/provenance" - printf openvmm > "${bundle}/bin/${executable}" - printf nvx-rs > "${bundle}/bin/${cli}" - printf '{}' > "${bundle}/provenance/nvx-rs.provenance.json" - printf kernel > "${bundle}/guest/vmlinux" - printf initramfs > "${bundle}/guest/initramfs.cpio.gz" - cp ../SOURCE-MANIFEST.json "${bundle}/" - (cd "${bundle}" && sha256sum "bin/${executable}" "bin/${cli}" guest/vmlinux \ - guest/initramfs.cpio.gz provenance/nvx-rs.provenance.json \ - SOURCE-MANIFEST.json > SHA256SUMS) - NVX_BUNDLE_DIR="${bundle_dir}" cargo "+${{ steps.toolchains.outputs.toolchain }}" test --locked --lib \ - --features bundled artifacts - - name: Build documentation shell: bash working-directory: nvx-rs env: - NVX_BUNDLE: skip RUSTDOCFLAGS: -D warnings run: cargo "+${{ steps.toolchains.outputs.toolchain }}" doc --locked --no-deps --all-features @@ -124,9 +88,6 @@ runs: - name: Check the minimum supported Rust version shell: bash working-directory: nvx-rs - env: - # --all-features enables `bundled`; stage nothing instead of downloading a release. - NVX_BUNDLE: skip run: >- cargo "+${{ steps.toolchains.outputs.msrv }}" check --locked --all-targets --all-features @@ -135,9 +96,6 @@ runs: if: runner.os == 'Linux' shell: bash working-directory: nvx-rs - env: - # --all-features enables `bundled`; stage nothing instead of downloading a release. - NVX_BUNDLE: skip run: | set -euo pipefail rustup target add --toolchain "${{ steps.toolchains.outputs.toolchain }}" aarch64-apple-darwin diff --git a/.github/workflows/nvx-rs-mutants.yml b/.github/workflows/nvx-rs-mutants.yml index 506da1e96..6ebec91c1 100644 --- a/.github/workflows/nvx-rs-mutants.yml +++ b/.github/workflows/nvx-rs-mutants.yml @@ -145,8 +145,6 @@ jobs: shell: bash working-directory: nvx-rs env: - # --all-features enables `bundled`; stage nothing instead of downloading a release. - NVX_BUNDLE: skip FILES: ${{ needs.plan.outputs.files }} TESTS: ${{ needs.plan.outputs.tests }} TARGETS: ${{ needs.plan.outputs.targets }} diff --git a/doc/acceptance-case-matrix.md b/doc/acceptance-case-matrix.md index 4503cb534..559def069 100644 --- a/doc/acceptance-case-matrix.md +++ b/doc/acceptance-case-matrix.md @@ -33,7 +33,7 @@ its own in turn. | [The adapter's scenarios](#the-adapters-scenarios) | 157 | 0 | 113 | 44 | | [The adapter's other tests](#the-adapters-other-tests) | 169 | 0 | 9 | 160 | | [MicroVM scenarios](#microvm-scenarios) | 54 | 50 | 3 | 1 | -| [CI](#ci) | 69 | 43 | 3 | 23 | +| [CI](#ci) | 69 | 42 | 3 | 24 | | [Docs](#docs) | 36 | 25 | 3 | 8 | ## The adapter's scenarios @@ -424,7 +424,7 @@ their own. The tests that a step runs have rows of their own where they concern | `.github/actions/check-nvx-rs` | Check formatting | kept | | | `.github/actions/check-nvx-rs` | Lint | kept | Builds `nvx` with each feature set, warnings denied: all features, `host` (the default), no default features, and `serde`, `cli` and `testing` alone, which checks `serde` alone since this matrix | | `.github/actions/check-nvx-rs` | Test | kept | Runs `nvx-rs/tests/security_checklist.rs` too | -| `.github/actions/check-nvx-rs` | Check artifact bundling | kept | | +| `.github/actions/check-nvx-rs` | Check artifact bundling | retired | The `bundled` feature, which staged a release at build time, leaves NVX with its build script and `artifacts.json` (SL6.6a, #1122) | | `.github/actions/check-nvx-rs` | Build documentation | kept | | | `.github/actions/check-nvx-rs` | Check the public API | kept | | | `.github/actions/check-nvx-rs` | Check the minimum supported Rust version | kept | | @@ -443,8 +443,8 @@ their own. The tests that a step runs have rows of their own where they concern | `.github/workflows/ci.yml` | openvmm-ci: Check OpenVMM's CI of the pinned revision | kept | | | `.github/workflows/ci.yml` | performance-gate: Gate on performance regressions | kept | | | `.github/workflows/ci.yml` | required-status-check: Verify required jobs | kept | SL5.2 removes the adapter's job from what it requires | -| `.github/workflows/ci.yml` | crate-package: Package, check, and build the nvx crate | kept | | -| `.github/workflows/ci.yml` | crate-release: Check the nvx package | kept | | +| `.github/workflows/ci.yml` | crate-package: Package, check, and build the nvx crate | kept | Since SL6.6a, the package holds no build script or `artifacts.json`, and builds once, with every feature | +| `.github/workflows/ci.yml` | crate-release: Check the nvx package | kept | Since SL6.6a, the package holds no build script or `artifacts.json` | | `.github/workflows/ci.yml` | nvx-microvm-adapter-whp: the runtime adapter's job on WHP | retired | The adapter's scenarios, whose fates are above | | `.github/workflows/run-nvx-microvm-tests.yml` | Run NVX microVM tests on Linux and on Windows | kept | The default suite's scenarios, by shard and on the debug kernel | | `.github/workflows/run-nvx-microvm-tests.yml` | Run public managed sandbox acceptance on Linux and on Windows | kept | Runs `sandbox-gpt-layer` and `vm-lifecycle` too, since SL5.1b: see [Cold boots](#cold-boots); and `snapshot-core` and `snapshot-tiers`, since SL5.1c: see [Snapshots](#snapshots) | diff --git a/doc/ci.md b/doc/ci.md index 7d803e9b6..34e4db8a8 100644 --- a/doc/ci.md +++ b/doc/ci.md @@ -525,7 +525,7 @@ 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 +`rust-toolchain.toml`; the crate's manifest, lockfile, 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 and the `tests` @@ -2120,11 +2120,8 @@ the commit's files in the version alone. - Every file is the commit's byte for byte, which rules out CRLF line endings, but for the rewritten version and the files that Cargo generates, `Cargo.toml` and `.cargo_vcs_info.json`, which must hold no CR either. -- It holds the license, the readme, the build script, and `artifacts.json`. -- Its files build with every feature, as a dependent's build does. `bundled` - downloads the Linux KVM and MSHV packages of the release that - `artifacts.json` pins, which must match their digests and declare the - crate's control contract. +- It holds the license and the readme. +- Its files build with every feature, as a dependent's build does. The job outputs the version and the package's SHA-256. Its build runs the build scripts and procedural macros of the crate's dependencies, so it holds @@ -2302,9 +2299,6 @@ otherwise use; the musl check of the guest binaries checks `guest` alone. Clippy's checks of all targets compile the crate's examples, among them the compile-only `host-workflows` and, in the musl check, `guest-workflows`, so that the documented workflows keep compiling against the public API. -It also validates bundled artifact staging with a fixture package. The action -sets `NVX_BUNDLE=skip` for `--all-features` builds, so that `bundled` stages -nothing instead of downloading the pinned release. On Linux, the `nvx-rs` job also checks the public API of `nvx` against its snapshots, [`nvx-rs/public-api/linux.txt`](../nvx-rs/public-api/linux.txt) and diff --git a/doc/contribute.md b/doc/contribute.md index 12e41c47b..9bb2023cd 100644 --- a/doc/contribute.md +++ b/doc/contribute.md @@ -89,7 +89,7 @@ the Python, shell, and link checks. | `scripts/setup/setup-windows-whp.ps1` | PowerShell syntax | [check-quality](../.github/actions/check-quality/action.yml) | | Markdown files and `doc/` paths | Every relative link and image in a tracked Markdown file resolves to a tracked file, and every anchor to a heading slug that GitHub generates, ignoring code. Every `doc/` path that a tracked file mentions resolves too, except the test fixtures that `DOC_PATH_FIXTURES` lists. | [check-quality](../.github/actions/check-quality/action.yml), [check_markdown_links.py](../scripts/check_markdown_links.py) | | Tests | Every test lives in a test file, and production files hold only the test hooks that the allowlist allows; see [Test layout](#test-layout) | [check-quality](../.github/actions/check-quality/action.yml), [check_test_layout.py](../scripts/check_test_layout.py) | -| `nvx-rs/` | rustfmt, Clippy and rustdoc with warnings denied, Clippy also for the `serde`, `cli` and `testing` features alone, tests including a bundled-release fixture, the minimum supported Rust version, a macOS check, the public API against its snapshots in `nvx-rs/public-api/`, and, through `nvx.py`, the musl lint, tests, build, digest manifest, and static-ELF check of the guest binaries; see [Rust crates](ci.md#rust-crates) | [check-nvx-rs](../.github/actions/check-nvx-rs/action.yml), [public_api.py](../scripts/public_api.py) | +| `nvx-rs/` | rustfmt, Clippy and rustdoc with warnings denied, Clippy also for the `serde`, `cli` and `testing` features alone, tests, the minimum supported Rust version, a macOS check, the public API against its snapshots in `nvx-rs/public-api/`, and, through `nvx.py`, the musl lint, tests, build, digest manifest, and static-ELF check of the guest binaries; see [Rust crates](ci.md#rust-crates) | [check-nvx-rs](../.github/actions/check-nvx-rs/action.yml), [public_api.py](../scripts/public_api.py) | | NVX CLI | Compiles `scripts/`, runs every `scripts/test_*.py` module, and checks command help, on Linux and Windows, and compares the copies of OpenVMM's protocol vectors in `nvx-rs/tests/vectors/openvmm/`, and of the identities of its built-in CPU profiles in `nvx-rs/src/host/cli/cpu_generations.json`, with the submodule's files | [validate-nvx](../.github/actions/validate-nvx/action.yml), [ci.yml](../.github/workflows/ci.yml) | check-quality names each shell script that it checks, and a CLI test fails diff --git a/doc/distribution.md b/doc/distribution.md index eb70029fb..154837de1 100644 --- a/doc/distribution.md +++ b/doc/distribution.md @@ -162,13 +162,7 @@ change reaches crates.io within a day of its merge. The version that `nvx-rs/Cargo.toml` declares is the lowest that CI publishes. A pull request changes it to start another series, such as -`0.2.0-alpha.1`. Only `nvx`'s version changes in `nvx-rs/Cargo.lock`. The -`bundled` feature of the published crate downloads the release that -[`nvx-rs/artifacts.json`](../nvx-rs/artifacts.json) pins and fails unless that -release declares the crate's microVM, time, restore-packet, control-session, -and control contracts, which CI checks before it publishes. When a contract has -changed, publication stays blocked until a pull request repins a compatible release; -see [Bundled artifacts](../nvx-rs/README.md#bundled-artifacts). +`0.2.0-alpha.1`. Only `nvx`'s version changes in `nvx-rs/Cargo.lock`. When CI cannot publish, as when the newest version on crates.io records no commit that `dev` holds, a pull request declares the next version as above, @@ -179,7 +173,6 @@ and once it merges, a crate owner publishes that version from a fresh clone of git clone --branch dev https://github.com/microsoft/nvx.git nvx-crate cd nvx-crate/nvx-rs git ls-files --eol . | grep -E 'w/(crlf|mixed)' # prints nothing -cargo check --locked --features bundled # stages the pinned release cargo publish --locked --dry-run cargo publish --locked ``` diff --git a/doc/project-structure.md b/doc/project-structure.md index d63adc7bd..661638709 100644 --- a/doc/project-structure.md +++ b/doc/project-structure.md @@ -108,9 +108,7 @@ nvx/ | | `-- bin/ nvx-rs CLI, nvx-fake-openvmm test double, and the guest binaries | |-- examples/ The external-launch example, and the documented host and guest workflows, which only compile | |-- tests/ Fake-OpenVMM behavior, process and VM spawn, CLI scenario, NVXS session, guest-binary, and hypervisor public-API VM lifecycle tests, the security checklist, sandbox configurations of every format, the cases of the export and root boot tokens, and the conformance vectors of the wire formats -| |-- public-api/ The crate's public API on Linux and on Windows, which CI checks -| |-- build.rs Stages the bundled artifacts (feature `bundled`) -| `-- artifacts.json Release package pinned for the bundled artifacts +| `-- public-api/ The crate's public API on Linux and on Windows, which CI checks |-- openvmm/ OpenVMM Git submodule |-- scripts/ Build, run, benchmark, and release tooling | |-- nvx_tools/ Python implementation behind the NVX CLI @@ -292,8 +290,8 @@ for other kernel-specific details. ### `nvx-rs/` The Rust crate `nvx` holds the NVX mechanics: the -authenticated control protocol of the guest agent, artifact discovery and -bundled-release staging, OpenVMM's command line, the lowering of network and +authenticated control protocol of the guest agent, artifact discovery, +OpenVMM's command line, the lowering of network and filesystem policies, the compiler of the egress policy files that `nvx-rs run` and `nvx-rs sandbox` accept, process and platform primitives for Linux and Windows hosts, the creation, spawn, and stop of VMs, the sandbox launch profile diff --git a/doc/usage.md b/doc/usage.md index d371f42a7..37758d1d6 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -60,7 +60,7 @@ their `nvx-rs` tests. The provenance of a checkout's `nvx-rs`, `build/nvx-rs.provenance.json`, binds the SHA-256 of its executable to a digest of the `nvx-rs` sources that built -it, the crate's manifest, lock file, build script, and `src/` tree and +it, the crate's manifest, lock file, and `src/` tree and `rust-toolchain.toml`, and to the Rust release, target, and Cargo features of the build. `nvx.py` records it when it builds `nvx-rs` for the scenarios, and [`record-nvx-rs-provenance`](#record-nvx-rs-provenance) records it for a build @@ -681,7 +681,7 @@ 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 that their -build reads. Those are the crate's manifest, lockfile, and build script, its +build reads. Those are the crate's manifest and lockfile, 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 diff --git a/nvx-rs/Cargo.toml b/nvx-rs/Cargo.toml index 0bdbe396e..967491642 100644 --- a/nvx-rs/Cargo.toml +++ b/nvx-rs/Cargo.toml @@ -10,11 +10,7 @@ readme = "README.md" keywords = ["sandbox", "microvm", "openvmm", "virtualization"] categories = ["virtualization"] publish = ["crates-io"] -# Lets the build scripts of dependents read the staged bundle as `DEP_NVX_ARTIFACTS_DIR`. -links = "nvx" -# docs.rs builds without network access, so its documentation leaves out `bundled`, whose build -# script downloads a release. [package.metadata.docs.rs] features = ["cli", "testing", "guest"] targets = ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] @@ -35,10 +31,6 @@ host = [ "dep:nix", "dep:windows-sys", ] -# Stages the OpenVMM executable, guest kernel, and control initramfs at build time; see -# `Artifacts::bundled`. The build script downloads the release pinned in `artifacts.json`, or -# copies the release directory named by `NVX_BUNDLE_DIR`. -bundled = ["host", "dep:sha2"] # NVX's own `nvx-fake-openvmm` binary, a fake OpenVMM for tests. It adds no API. testing = ["dep:getrandom", "dep:serde", "dep:serde_json", "dep:nix", "dep:windows-sys"] # NVX's own `nvx-rs` binary, the command-line interface that runs NVX's microVMs and sandboxes, @@ -55,7 +47,6 @@ crossterm = { version = "0.29", default-features = false, features = ["windows"] getrandom = { version = "0.4", optional = true } serde = { version = "1.0.200", features = ["derive"], optional = true } serde_json = { version = "1.0.120", optional = true } -# The same release as the build script's: `host` enables both. sha2 = { version = "0.10", optional = true } [target.'cfg(windows)'.dependencies] @@ -94,12 +85,6 @@ nix = { version = "0.31.3", default-features = false, optional = true, features "user", ] } -[build-dependencies] -# Only the `bundled` build script reads JSON and computes digests; `host`, which `bundled` -# enables, enables both here too. -serde_json = { version = "1.0.120", optional = true } -sha2 = { version = "0.10", optional = true } - [dev-dependencies] # The property tests of the protocol's codecs, which need none of the forking and timeouts of its # default features. diff --git a/nvx-rs/README.md b/nvx-rs/README.md index 9c8437b89..80a0dc8c3 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -67,9 +67,7 @@ nvx-rs/ │ and root boot tokens, and the template manifests that OpenVMM wrote, in │ fixtures/, and the conformance vectors of the wire formats and of a template's │ key, in vectors/ -├── public-api/ the crate's public API on Linux and on Windows, which CI checks -├── build.rs bundled-release staging -└── artifacts.json the NVX release that the `bundled` feature stages +└── public-api/ the crate's public API on Linux and on Windows, which CI checks ``` The [module diagrams](../doc/modules/README.md) show the crate's modules, the @@ -927,11 +925,10 @@ host namespace policy or skips the identity assertions. ### `nvx::host::artifacts` `Artifacts` (also `nvx::Artifacts`): the OpenVMM executable, guest kernel, and -Alpine initramfs, from a release, a repository checkout, the environment, a -directory next to the executable, or the bundle. Artifacts located in a release -or checkout keep its `ArtifactDir`, and `Artifacts::runtime_identity` reads -their `RuntimeIdentity` from its install metadata; artifacts named by path have -none. +Alpine initramfs, from a release, a repository checkout, the environment, or a +directory next to the executable. Artifacts located in a release or checkout +keep its `ArtifactDir`, and `Artifacts::runtime_identity` reads their +`RuntimeIdentity` from its install metadata; artifacts named by path have none. `ArtifactDir` and `Guest`: every artifact of the guests that `nvx.py` runs, the OpenVMM executable, the kernel, the Alpine, Ubuntu, and Azure Linux initramfs @@ -2051,19 +2048,18 @@ installs all of them or, if a step fails, none, and never installs `nvx-rs`, which the checkout builds. A directory where the release has a file fails the installation, which deletes no directory. An extracted release root is refused. -It runs host tools, as the [bundled artifacts](#bundled-artifacts) build script -does: `git` reads the history, from the root alone; `curl` queries the GitHub -REST API and downloads the package, speaking HTTPS alone and following no -redirect itself, so that the token of `GH_TOKEN` or `GITHUB_TOKEN`, which it -reads on its standard input, reaches only the API; and `tar` lists the -package's members, whose names must be relative and canonical below one root, -and extracts it into a private directory below `build/`. Every extracted file -must be a regular file that `SHA256SUMS` lists with its digest. On Windows, -`curl` and `tar` are those of `%SystemRoot%\System32`, whose `tar` reads the -ZIP package of `windows-whp`. `download` reads the status and the redirect of -each response from the headers that `curl` dumps, rather than from its -`--write-out`, with which the `curl` 8.8.0 of some Windows releases fails every -HTTPS request. +It runs host tools: `git` reads the history, from the root alone; `curl` +queries the GitHub REST API and downloads the package, speaking HTTPS alone +and following no redirect itself, so that the token of `GH_TOKEN` or +`GITHUB_TOKEN`, which it reads on its standard input, reaches only the API; and +`tar` lists the package's members, whose names must be relative and canonical +below one root, and extracts it into a private directory below `build/`. Every +extracted file must be a regular file that `SHA256SUMS` lists with its digest. +On Windows, `curl` and `tar` are those of `%SystemRoot%\System32`, whose `tar` +reads the ZIP package of `windows-whp`. `download` reads the status and the +redirect of each response from the headers that `curl` dumps, rather than from +its `--write-out`, with which the `curl` 8.8.0 of some Windows releases fails +every HTTPS request. `run`, `sandbox run`, and `sandbox start` download the release themselves, before they launch, when an artifact that they run is missing from a root that @@ -2532,7 +2528,6 @@ caller owner. | Feature | Contents | | --- | --- | | `host` (default) | The `host` module. It enables `serde`, and `sha2`, which names the children of a sandbox's aggregate share and hashes distro layers in their explicit verification | -| `bundled` | Stages a release at build time; see [Bundled artifacts](#bundled-artifacts) | | `testing` | NVX's own `nvx-fake-openvmm` binary, a fake OpenVMM for tests; see [Test hooks](#test-hooks). It adds no API | | `cli` | NVX's own `nvx-rs` binary; it adds no API, and enables `host`. See [The nvx-rs CLI](#the-nvx-rs-cli) | | `serde` | The serde forms of `Hypervisor` and `net::Ipv4Cidr` | @@ -2685,31 +2680,6 @@ usage errors, which no library error has. | `template-tombstoned` | `Unavailable` | A tombstone quarantines the template's generation, which takes no new clones, while the clones that lease it keep their leases | | `sandbox-leased` | `Other` | The sandbox holds a lease of a template's generation, which allows no other lease, and one launch of its clone, until a release ends it once its VM has exited | -## Bundled artifacts - -With the `bundled` feature, the build script stages the OpenVMM executable, -guest kernel, and Alpine initramfs of a release, with its -`SOURCE-MANIFEST.json` and `SHA256SUMS`, in its output directory, and -`Artifacts::bundled` returns them. `Artifacts::discover` uses the bundle when no -other source is present. - -| Build variable | Effect | -| --- | --- | -| `NVX_BUNDLE_DIR` | Stages the extracted release in this directory, for offline builds or local artifacts | -| `NVX_BUNDLE_HYPERVISOR` | `kvm` (default) or `mshv`: the Linux package to download | -| `NVX_BUNDLE=skip` | Stages nothing, as when the feature is disabled; CI sets it for `--all-features` builds | - -Without `NVX_BUNDLE_DIR`, the build script downloads the release package that -[`artifacts.json`](artifacts.json) pins with `curl` and extracts it with `tar`; -Windows packages are ZIP archives, which need bsdtar (Windows' own `tar`) or -`unzip`. Every staged file must match the source's `SHA256SUMS`, a download -must match its pinned digest, and the release must declare the microVM, time, -restore-packet, control-session, and control contracts of `nvx::contract`. The -build script extracts and verifies only the files that the bundle holds, and -leaves out the rest of the release, such as `bin/nvx-rs[.exe]`. The build -scripts of dependents receive the staged directory as `DEP_NVX_ARTIFACTS_DIR` -and can ship it next to their executables as `nvx/`. - ## Guest binaries The `guest` feature adds the programs that run in NVX's Linux guests: the @@ -2790,7 +2760,7 @@ 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/`, +`Cargo.toml`, `Cargo.lock`, 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 and the `tests` @@ -2808,7 +2778,7 @@ select. ```bash cd nvx-rs -NVX_BUNDLE=skip cargo test --all-features # unit, conformance-vector, property, and fake-OpenVMM tests +cargo test --all-features # unit, conformance-vector, property, and fake-OpenVMM tests ``` A module's unit tests belong in its `tests` module, out of line, in the @@ -2897,7 +2867,7 @@ The rustfmt, Clippy with warnings denied for all features, for the library with and without default features, and for the `serde`, `cli` and `testing` features alone, the tests, a build and the tests of the library -without default features, a synthetic-release `bundled` test, rustdoc with +without default features, rustdoc with warnings denied, the minimum supported Rust version (1.89), and a macOS check. On Linux, it also lints, builds, and tests the guest binaries through `nvx.py`, and checks the crate's public API against its snapshots, @@ -2921,7 +2891,7 @@ namespace. It mounts a temporary tmpfs and needs root, but no hypervisor; run it as root with: ```bash -NVX_BUNDLE=skip cargo test --locked --all-features --lib -- \ +cargo test --locked --all-features --lib -- \ --ignored --exact guest::root::tests::a_thread_with_private_tables_finds_its_mount_points_and_options ``` @@ -2934,7 +2904,7 @@ its own, and check the mounts that stay and a mapping through descriptors opened before the switch. They need root and Linux 6.8 or later; run them as root with: ```bash -NVX_BUNDLE=skip cargo test --locked --all-features --lib -- \ +cargo test --locked --all-features --lib -- \ --ignored guest::root::tests::privileged ``` diff --git a/nvx-rs/artifacts.json b/nvx-rs/artifacts.json deleted file mode 100644 index fc9ebb9d5..000000000 --- a/nvx-rs/artifacts.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "repository": "microsoft/nvx", - "tag": "v0.1.0-dev.de5bfab30695", - "assets": { - "linux-kvm": { - "name": "nvx-0.1.0-linux-kvm.tar.gz", - "sha256": "8c631ed9bfd1de42d83c244c081b8779e8a71293caa7c11d402c86ed1c4f193c" - }, - "linux-mshv": { - "name": "nvx-0.1.0-linux-mshv.tar.gz", - "sha256": "2f28221644d9e3a361d9fa62194483c258facb450b4c5e5c11534652687a3eba" - }, - "windows-whp": { - "name": "nvx-0.1.0-windows-whp.zip", - "sha256": "931557b781da32c6417dc4b33f6fc4473733d58c141659fa2bd82f97309af896" - } - } -} diff --git a/nvx-rs/build.rs b/nvx-rs/build.rs deleted file mode 100644 index 1d81bb83a..000000000 --- a/nvx-rs/build.rs +++ /dev/null @@ -1,356 +0,0 @@ -//! Stages the OpenVMM executable, guest kernel, and Alpine initramfs when the `bundled` feature -//! is enabled. -//! -//! The staged directory has the release layout (`bin/`, `guest/`, `SOURCE-MANIFEST.json`, and -//! `SHA256SUMS`). The library reads it through `NVX_BUNDLED_ARTIFACTS_DIR`, and dependent build -//! scripts read it as `DEP_NVX_ARTIFACTS_DIR` to ship it next to their executables. -//! -//! Environment variables: -//! -//! - `NVX_BUNDLE_DIR`: an extracted NVX release to stage instead of downloading the release -//! pinned in `artifacts.json`. Use it for offline builds or to bundle locally built artifacts. -//! - `NVX_BUNDLE_HYPERVISOR`: `kvm` (default) or `mshv`; selects the Linux release package. -//! - `NVX_BUNDLE=skip`: stages nothing, as when the feature is disabled. -//! -//! Every staged file must match the source's `SHA256SUMS`, and a downloaded archive must match -//! the digest pinned in `artifacts.json`. - -fn main() { - println!("cargo::rerun-if-changed=build.rs"); - #[cfg(feature = "bundled")] - bundle::run(); -} - -#[cfg(feature = "bundled")] -#[allow(dead_code, unreachable_pub)] -#[path = "src/contract.rs"] -mod contract; - -#[cfg(feature = "bundled")] -/// Stages and verifies the release bundle selected for the build. -mod bundle { - use std::collections::HashMap; - use std::env; - use std::fs::{self, File}; - use std::io::{BufReader, Read}; - use std::path::{Path, PathBuf}; - use std::process::Command; - - use serde_json::Value; - use sha2::{Digest, Sha256}; - - /// Environment variable whose presence suppresses bundle staging. - const SKIP: &str = "NVX_BUNDLE"; - /// Environment variable that selects an existing extracted release. - const SOURCE_DIR: &str = "NVX_BUNDLE_DIR"; - /// Environment variable that selects the KVM or MSHV Linux package. - const HYPERVISOR: &str = "NVX_BUNDLE_HYPERVISOR"; - /// Name of the release's source and compatibility manifest. - const MANIFEST: &str = "SOURCE-MANIFEST.json"; - /// Name of the release's SHA-256 checksum list. - const CHECKSUMS: &str = "SHA256SUMS"; - /// Guest artifacts copied into every bundle. - const GUEST: [&str; 2] = ["guest/vmlinux", "guest/initramfs.cpio.gz"]; - - /// Resolves the requested release, verifies it, and exposes the staged path to Cargo. - pub(crate) fn run() { - println!("cargo::rerun-if-changed=artifacts.json"); - for name in [SKIP, SOURCE_DIR, HYPERVISOR] { - println!("cargo::rerun-if-env-changed={name}"); - } - if env::var_os(SKIP).is_some_and(|value| value == "skip") { - return; - } - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let executable = match target_os.as_str() { - "windows" => "bin/openvmm.exe", - "linux" => "bin/openvmm", - // The OpenVMM backend supports Linux and Windows hosts only. - _ => return, - }; - let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set")); - let source = match env::var_os(SOURCE_DIR) { - Some(directory) => { - let directory = PathBuf::from(directory); - println!( - "cargo::rerun-if-changed={}", - directory.join(CHECKSUMS).display() - ); - directory - } - None => download(&out_dir, &target_os), - }; - let staged = out_dir.join("nvx"); - stage(&source, &staged, executable); - println!( - "cargo::rustc-env=NVX_BUNDLED_ARTIFACTS_DIR={}", - staged.display() - ); - println!("cargo::metadata=artifacts_dir={}", staged.display()); - } - - /// Verifies the release at `source` and copies the files the backend needs to `staged`. - fn stage(source: &Path, staged: &Path, executable: &str) { - let checksums = read_checksums(&source.join(CHECKSUMS)); - let manifest: Value = serde_json::from_str( - &fs::read_to_string(source.join(MANIFEST)) - .unwrap_or_else(|error| fail(format!("cannot read {MANIFEST}: {error}"))), - ) - .unwrap_or_else(|error| fail(format!("{MANIFEST} is not valid JSON: {error}"))); - let mismatches = crate::contract::manifest_mismatches(&manifest); - if !mismatches.is_empty() { - fail(format!( - "{} does not declare the compatible OpenVMM contracts ({})", - source.join(MANIFEST).display(), - mismatches.join("; ") - )); - } - let mut files = vec![executable, MANIFEST]; - files.extend(GUEST); - if staged.exists() { - fs::remove_dir_all(staged).unwrap_or_else(|error| { - fail(format!("cannot clear {}: {error}", staged.display())) - }); - } - let mut sums = String::new(); - for name in files { - let expected = checksums.get(name).unwrap_or_else(|| { - fail(format!( - "{} does not list {name}", - source.join(CHECKSUMS).display() - )) - }); - let from = source.join(name); - let actual = sha256(&from); - if &actual != expected { - fail(format!( - "SHA-256 mismatch for {}: expected {expected}, found {actual}", - from.display() - )); - } - let to = staged.join(name); - fs::create_dir_all(to.parent().expect("staged files have a parent")).unwrap_or_else( - |error| fail(format!("cannot create {}: {error}", staged.display())), - ); - fs::copy(&from, &to).unwrap_or_else(|error| { - fail(format!( - "cannot copy {} to {}: {error}", - from.display(), - to.display() - )) - }); - sums.push_str(&format!("{actual} {name}\n")); - } - fs::write(staged.join(CHECKSUMS), sums) - .unwrap_or_else(|error| fail(format!("cannot write {CHECKSUMS}: {error}"))); - } - - /// Downloads and extracts the release package pinned in `artifacts.json`. - fn download(out_dir: &Path, target_os: &str) -> PathBuf { - let pins_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("artifacts.json"); - let pins: Value = serde_json::from_str( - &fs::read_to_string(&pins_path) - .unwrap_or_else(|error| fail(format!("cannot read artifacts.json: {error}"))), - ) - .unwrap_or_else(|error| fail(format!("artifacts.json is not valid JSON: {error}"))); - let platform = if target_os == "windows" { - "windows-whp".to_owned() - } else { - let hypervisor = env::var(HYPERVISOR).unwrap_or_else(|_| "kvm".to_owned()); - if hypervisor != "kvm" && hypervisor != "mshv" { - fail(format!( - "{HYPERVISOR} must be kvm or mshv, not {hypervisor:?}" - )); - } - format!("linux-{hypervisor}") - }; - let text = |value: &Value, field: &str| -> String { - value - .get(field) - .and_then(Value::as_str) - .unwrap_or_else(|| fail(format!("artifacts.json lacks {field}"))) - .to_owned() - }; - let asset = pins - .get("assets") - .and_then(|assets| assets.get(&platform)) - .unwrap_or_else(|| fail(format!("artifacts.json pins no {platform} package"))); - let (repository, tag) = (text(&pins, "repository"), text(&pins, "tag")); - let (name, digest) = (text(asset, "name"), text(asset, "sha256")); - let root = name - .strip_suffix(".zip") - .or_else(|| name.strip_suffix(".tar.gz")) - .unwrap_or_else(|| fail(format!("unsupported release package {name}"))); - - let downloads = out_dir.join("download"); - fs::create_dir_all(&downloads).unwrap_or_else(|error| { - fail(format!("cannot create {}: {error}", downloads.display())) - }); - let archive = downloads.join(&name); - if !archive.is_file() || sha256(&archive) != digest { - let partial = downloads.join(format!("{name}.part")); - let url = format!("https://github.com/{repository}/releases/download/{tag}/{name}"); - println!("cargo::warning=nvx: downloading {url}"); - execute( - Command::new("curl") - .args(["--fail", "--silent", "--show-error", "--location"]) - .args(["--retry", "5", "--retry-delay", "5", "--retry-all-errors"]) - .arg("--output") - .arg(&partial) - .arg(&url), - ); - let actual = sha256(&partial); - if actual != digest { - let _ = fs::remove_file(&partial); - fail(format!( - "SHA-256 mismatch for {url}: expected {digest}, found {actual}" - )); - } - fs::rename(&partial, &archive) - .unwrap_or_else(|error| fail(format!("cannot store {name}: {error}"))); - } - - let extracted = downloads.join("extract"); - if extracted.exists() { - fs::remove_dir_all(&extracted).unwrap_or_else(|error| { - fail(format!("cannot clear {}: {error}", extracted.display())) - }); - } - fs::create_dir_all(&extracted).unwrap_or_else(|error| { - fail(format!("cannot create {}: {error}", extracted.display())) - }); - let executable = if target_os == "windows" { - "bin/openvmm.exe" - } else { - "bin/openvmm" - }; - let mut members = vec![CHECKSUMS, MANIFEST, executable]; - members.extend(GUEST); - let members: Vec = members - .iter() - .map(|member| format!("{root}/{member}")) - .collect(); - execute(&mut extraction( - name.ends_with(".zip"), - &archive, - &extracted, - &members, - )); - extracted.join(root) - } - - /// Returns a command that extracts `members` of a release package into `directory` with a - /// host tool that reads the package's format. - fn extraction(zip: bool, archive: &Path, directory: &Path, members: &[String]) -> Command { - let tar_command = if zip { - // GNU tar, the usual `tar` on Linux, cannot read ZIP archives, but bsdtar can. - [tar(), PathBuf::from("bsdtar")] - .into_iter() - .find(|candidate| is_bsdtar(candidate)) - } else { - Some(tar()) - }; - let mut command; - match tar_command { - Some(tar) => { - command = Command::new(tar); - command.arg("-xf").arg(archive).arg("-C").arg(directory); - command.args(members); - } - None if succeeds(Command::new("unzip").arg("-v")) => { - command = Command::new("unzip"); - command.arg("-q").arg(archive).args(members); - command.arg("-d").arg(directory); - } - None => fail(format!( - "cannot extract {}: reading ZIP archives requires bsdtar or unzip on the build \ - host; install one, or set {SOURCE_DIR} to an extracted release", - archive.display() - )), - } - command - } - - /// Returns the host's `tar`. - fn tar() -> PathBuf { - // Windows ships bsdtar, which reads ZIP archives; GNU tar from Git for Windows does not. - if let Some(system_root) = env::var_os("SystemRoot") { - let system_tar = Path::new(&system_root).join("System32").join("tar.exe"); - if system_tar.is_file() { - return system_tar; - } - } - PathBuf::from("tar") - } - - /// Returns whether `tar` is libarchive's bsdtar, which also reads ZIP archives. - fn is_bsdtar(tar: &Path) -> bool { - Command::new(tar) - .arg("--version") - .output() - .is_ok_and(|output| output.status.success() && output.stdout.starts_with(b"bsdtar")) - } - - /// Runs `command` and reports whether it exited successfully. - fn succeeds(command: &mut Command) -> bool { - command.output().is_ok_and(|output| output.status.success()) - } - - /// Runs `command`, or terminates the build with its stderr on failure. - fn execute(command: &mut Command) { - let output = command - .output() - .unwrap_or_else(|error| fail(format!("cannot run {command:?}: {error}"))); - if !output.status.success() { - fail(format!( - "{command:?} failed with {}: {}", - output.status, - String::from_utf8_lossy(&output.stderr).trim() - )); - } - } - - /// Parses the checksum file at `path` into release path-to-digest entries. - fn read_checksums(path: &Path) -> HashMap { - let text = fs::read_to_string(path) - .unwrap_or_else(|error| fail(format!("cannot read {}: {error}", path.display()))); - text.lines() - .filter(|line| !line.trim().is_empty()) - .map(|line| { - let (digest, name) = line - .split_once(char::is_whitespace) - .unwrap_or_else(|| fail(format!("malformed line in {}", path.display()))); - let name = name.trim_start().trim_start_matches('*'); - (name.to_owned(), digest.to_ascii_lowercase()) - }) - .collect() - } - - /// Returns the lowercase SHA-256 digest of the file at `path`. - fn sha256(path: &Path) -> String { - let file = File::open(path) - .unwrap_or_else(|error| fail(format!("cannot open {}: {error}", path.display()))); - let mut reader = BufReader::with_capacity(1 << 20, file); - let mut hasher = Sha256::new(); - let mut buffer = vec![0; 1 << 20]; - loop { - let read = reader - .read(&mut buffer) - .unwrap_or_else(|error| fail(format!("cannot read {}: {error}", path.display()))); - if read == 0 { - break; - } - hasher.update(&buffer[..read]); - } - hasher - .finalize() - .iter() - .map(|byte| format!("{byte:02x}")) - .collect() - } - - /// Stops the build with a bundle-specific `message`. - fn fail(message: String) -> ! { - panic!("nvx bundled artifacts: {message}"); - } -} diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index ca8c7f54f..98eff7faa 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -6,7 +6,6 @@ const nvx::Artifacts::ENV_INITRD const nvx::Artifacts::ENV_KERNEL const nvx::Artifacts::ENV_OPENVMM method nvx::Artifacts::artifact_dir -method nvx::Artifacts::bundled method nvx::Artifacts::discover method nvx::Artifacts::from_dir method nvx::Artifacts::from_release_dir @@ -1642,7 +1641,6 @@ const nvx::host::artifacts::Artifacts::ENV_INITRD const nvx::host::artifacts::Artifacts::ENV_KERNEL const nvx::host::artifacts::Artifacts::ENV_OPENVMM method nvx::host::artifacts::Artifacts::artifact_dir -method nvx::host::artifacts::Artifacts::bundled method nvx::host::artifacts::Artifacts::discover method nvx::host::artifacts::Artifacts::from_dir method nvx::host::artifacts::Artifacts::from_release_dir diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt index 5af2af9b2..00f6f4132 100644 --- a/nvx-rs/public-api/windows.txt +++ b/nvx-rs/public-api/windows.txt @@ -6,7 +6,6 @@ const nvx::Artifacts::ENV_INITRD const nvx::Artifacts::ENV_KERNEL const nvx::Artifacts::ENV_OPENVMM method nvx::Artifacts::artifact_dir -method nvx::Artifacts::bundled method nvx::Artifacts::discover method nvx::Artifacts::from_dir method nvx::Artifacts::from_release_dir @@ -370,7 +369,6 @@ const nvx::host::artifacts::Artifacts::ENV_INITRD const nvx::host::artifacts::Artifacts::ENV_KERNEL const nvx::host::artifacts::Artifacts::ENV_OPENVMM method nvx::host::artifacts::Artifacts::artifact_dir -method nvx::host::artifacts::Artifacts::bundled method nvx::host::artifacts::Artifacts::discover method nvx::host::artifacts::Artifacts::from_dir method nvx::host::artifacts::Artifacts::from_release_dir diff --git a/nvx-rs/src/contract.rs b/nvx-rs/src/contract.rs index 33304cfb9..b0beaaaa1 100644 --- a/nvx-rs/src/contract.rs +++ b/nvx-rs/src/contract.rs @@ -46,9 +46,6 @@ //! also holds the version of the activation contract, //! [`activation::VERSION`](crate::activation::VERSION), through which each clone receives the //! settings of its own launch. -//! -//! The build script includes this file to check bundled artifacts, so it must not depend on the -//! rest of the crate. /// OpenVMM microVM ABI version this crate drives. /// diff --git a/nvx-rs/src/host/artifacts/mod.rs b/nvx-rs/src/host/artifacts/mod.rs index 224503a25..ecb757169 100644 --- a/nvx-rs/src/host/artifacts/mod.rs +++ b/nvx-rs/src/host/artifacts/mod.rs @@ -1,9 +1,8 @@ //! Locating the OpenVMM executable and the NVX guest artifacts. //! //! [`Artifacts`] names the three files that a VM runs. They come from an extracted NVX release -//! archive, an NVX repository checkout, explicit paths, or a bundle that the build script staged -//! with the `bundled` feature. [`Artifacts::discover`] tries these sources in a fixed order so -//! consumers such as MXC need no NVX-specific configuration. +//! archive, an NVX repository checkout, or explicit paths. [`Artifacts::discover`] tries these +//! sources in a fixed order so consumers such as MXC need no NVX-specific configuration. //! //! [`ArtifactDir`] names every artifact of the guests that `nvx.py` runs, which [`Guest`] //! describes, in a repository checkout or an extracted release: @@ -51,10 +50,6 @@ pub(crate) use self::require::require_existing; /// Name of the release manifest at the root of a release archive or repository checkout. pub const MANIFEST_NAME: &str = "SOURCE-MANIFEST.json"; -/// Directory staged by the build script when the `bundled` feature is enabled. -#[cfg(feature = "bundled")] -const BUNDLED_DIR: Option<&str> = option_env!("NVX_BUNDLED_ARTIFACTS_DIR"); - /// The OpenVMM executable and the NVX guest kernel and initramfs. /// /// Every path is used as given. Resolve relative paths with [`absolute`] before a VM runs them, @@ -203,22 +198,6 @@ impl Artifacts { Ok(Self::located(directory)) } - /// Returns the artifacts staged by the build script, if the crate was built with the - /// `bundled` feature for Linux or Windows and the staged directory still exists. - /// - /// The staged directory lives in Cargo's build output, so it suits development and tests on - /// the build host. Deployments copy it next to their executable instead; see - /// [`Artifacts::discover`]. - pub fn bundled() -> Result> { - #[cfg(feature = "bundled")] - if let Some(directory) = BUNDLED_DIR.map(Path::new) - && directory.is_dir() - { - return Self::from_release_dir(directory).map(Some); - } - Ok(None) - } - /// Locates the artifacts on this host. /// /// Sources are tried in this order, and the first one present wins: @@ -227,10 +206,7 @@ impl Artifacts { /// [`ENV_INITRD`](Self::ENV_INITRD) variables, which must be set together. /// 2. The [`ENV_DIR`](Self::ENV_DIR) variable, naming a release or repository directory. /// 3. The [`BESIDE_EXE_DIR`](Self::BESIDE_EXE_DIR) directory next to the current executable, - /// in the release layout. Consumers ship the bundle there; with the `bundled` feature the - /// build script exports its staged directory to dependent build scripts as - /// `DEP_NVX_ARTIFACTS_DIR` for this purpose. - /// 4. The directory staged by the build script ([`Artifacts::bundled`]). + /// in the release layout. Consumers ship a release there. /// /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) when no source is /// present or the chosen source is unusable. It does not check that the files exist. @@ -238,7 +214,7 @@ impl Artifacts { let exe_dir = env::current_exe() .ok() .and_then(|exe| exe.parent().map(Path::to_path_buf)); - discover_with(|name| env::var_os(name), exe_dir.as_deref(), Self::bundled) + discover_with(|name| env::var_os(name), exe_dir.as_deref()) } /// Environment variable naming the `openvmm` executable. @@ -254,11 +230,11 @@ impl Artifacts { pub const BESIDE_EXE_DIR: &'static str = "nvx"; } -/// Implements artifact discovery with injectable environment and bundle sources for tests. +/// Implements artifact discovery with an injectable environment and executable directory for +/// tests. fn discover_with( variable: impl Fn(&str) -> Option, exe_dir: Option<&Path>, - bundled: impl FnOnce() -> Result>, ) -> Result { let explicit = [ Artifacts::ENV_OPENVMM, @@ -288,15 +264,11 @@ fn discover_with( { return Artifacts::from_release_dir(directory); } - if let Some(artifacts) = bundled()? { - return Ok(artifacts); - } Err(Error::new( ErrorCode::ArtifactMissing, format!( "cannot locate the OpenVMM executable and NVX guest artifacts: set {} or {}, {}, and \ - {}, place an NVX release in a {:?} directory next to the executable, or build the \ - nvx crate with the bundled feature", + {}, or place an NVX release in a {:?} directory next to the executable", Artifacts::ENV_DIR, Artifacts::ENV_OPENVMM, Artifacts::ENV_KERNEL, diff --git a/nvx-rs/src/host/artifacts/tests.rs b/nvx-rs/src/host/artifacts/tests.rs index 65671aa15..33c1489e5 100644 --- a/nvx-rs/src/host/artifacts/tests.rs +++ b/nvx-rs/src/host/artifacts/tests.rs @@ -33,10 +33,6 @@ fn checkout(root: &Path) { fs::write(root.join(MANIFEST_NAME), COMPATIBLE_MANIFEST).unwrap(); } -fn no_bundle() -> Result> { - Ok(None) -} - fn variables(pairs: &[(&str, &Path)]) -> impl Fn(&str) -> Option { let map: HashMap = pairs .iter() @@ -99,12 +95,8 @@ fn repository_layout_is_detected_beside_the_guest_sources() { fn the_directory_variable_accepts_a_repository_checkout() { let directory = tempfile::tempdir().unwrap(); checkout(directory.path()); - let artifacts = discover_with( - variables(&[(Artifacts::ENV_DIR, directory.path())]), - None, - no_bundle, - ) - .unwrap(); + let artifacts = + discover_with(variables(&[(Artifacts::ENV_DIR, directory.path())]), None).unwrap(); let build = std::path::absolute(directory.path()).unwrap().join("build"); assert_eq!(artifacts.kernel, build.join("vmlinux")); } @@ -135,17 +127,11 @@ fn explicit_variables_win_and_must_be_complete() { (Artifacts::ENV_DIR, directory.path()), ]), None, - no_bundle, ) .unwrap(); assert_eq!(artifacts, Artifacts::new(openvmm, kernel, initrd)); - let error = discover_with( - variables(&[(Artifacts::ENV_OPENVMM, openvmm)]), - None, - no_bundle, - ) - .unwrap_err(); + let error = discover_with(variables(&[(Artifacts::ENV_OPENVMM, openvmm)]), None).unwrap_err(); assert_eq!(error.kind(), ErrorKind::Unavailable); } @@ -159,7 +145,6 @@ fn the_directory_variable_precedes_the_executable_directory() { let artifacts = discover_with( variables(&[(Artifacts::ENV_DIR, variable_dir.path())]), Some(exe_dir.path()), - no_bundle, ) .unwrap(); assert!( @@ -168,7 +153,7 @@ fn the_directory_variable_precedes_the_executable_directory() { .starts_with(std::path::absolute(variable_dir.path()).unwrap()) ); - let artifacts = discover_with(variables(&[]), Some(exe_dir.path()), no_bundle).unwrap(); + let artifacts = discover_with(variables(&[]), Some(exe_dir.path())).unwrap(); assert!( artifacts.kernel.starts_with( std::path::absolute(exe_dir.path().join(Artifacts::BESIDE_EXE_DIR)).unwrap() @@ -177,22 +162,13 @@ fn the_directory_variable_precedes_the_executable_directory() { } #[test] -fn the_bundle_is_the_last_resort() { +fn discovery_fails_when_no_source_is_present() { let exe_dir = tempfile::tempdir().unwrap(); - let bundle = Artifacts::new("bundled-openvmm", "bundled-kernel", "bundled-initrd"); - let expected = bundle.clone(); - let artifacts = - discover_with(variables(&[]), Some(exe_dir.path()), || Ok(Some(bundle))).unwrap(); - assert_eq!(artifacts, expected); - - let error = discover_with(variables(&[]), Some(exe_dir.path()), no_bundle).unwrap_err(); + let error = discover_with(variables(&[]), Some(exe_dir.path())).unwrap_err(); assert_eq!(error.kind(), ErrorKind::Unavailable); assert!(error.message().contains(Artifacts::ENV_DIR)); - assert!( - error - .message() - .contains("build the nvx crate with the bundled feature") - ); + // No build-time bundle remains to suggest. + assert!(!error.message().contains("bundled")); } #[test] @@ -753,20 +729,3 @@ fn located_artifacts_have_the_identity_of_their_directory() { ) ); } - -#[cfg(feature = "bundled")] -#[test] -fn the_bundle_is_a_compatible_release() { - let bundled = Artifacts::bundled().unwrap(); - // A build that was asked to stage a release must have staged it. - let requested = env::var_os("NVX_BUNDLE_DIR").is_some() - && env::var_os("NVX_BUNDLE").is_none_or(|value| value != "skip"); - if requested && (cfg!(target_os = "linux") || cfg!(windows)) { - assert!(bundled.is_some(), "NVX_BUNDLE_DIR was not staged"); - } - if let Some(artifacts) = bundled { - for path in [&artifacts.openvmm, &artifacts.kernel, &artifacts.initrd] { - assert!(path.is_file(), "{} is missing", path.display()); - } - } -} diff --git a/nvx-rs/src/host/cli/download.rs b/nvx-rs/src/host/cli/download.rs index 07eb1d47d..b588b2443 100644 --- a/nvx-rs/src/host/cli/download.rs +++ b/nvx-rs/src/host/cli/download.rs @@ -26,9 +26,9 @@ //! ([`ensure_under`]), so that it never reads half of an installation, and no download replaces //! the artifacts while it reads them and launches OpenVMM on them ([`Hold`]). //! -//! The download runs host tools, as the `bundled` build script does: `git`, which [`git`] runs; -//! `curl`, which [`github`] runs; and `tar`, which [`archive`] runs. On Windows, `curl` and `tar` -//! are those of `%SystemRoot%\System32`, whose `tar` reads the ZIP package of `windows-whp`. +//! The download runs host tools: `git`, which [`git`] runs; `curl`, which [`github`] runs; and +//! `tar`, which [`archive`] runs. On Windows, `curl` and `tar` are those of +//! `%SystemRoot%\System32`, whose `tar` reads the ZIP package of `windows-whp`. mod archive; mod git; diff --git a/nvx-rs/src/lib.rs b/nvx-rs/src/lib.rs index 411320790..dd1366e10 100644 --- a/nvx-rs/src/lib.rs +++ b/nvx-rs/src/lib.rs @@ -125,9 +125,6 @@ //! # Features //! //! - `host` (default): the [`host`] module. It enables `serde`. -//! - `bundled`: stages the OpenVMM executable, guest kernel, and control initramfs at build time; -//! see `Artifacts::bundled`. The build script downloads the release that `artifacts.json` pins, -//! or copies the release directory that `NVX_BUNDLE_DIR` names. //! - `testing`: NVX's own `nvx-fake-openvmm` binary, a fake OpenVMM that serves the control //! console without running a VM. It adds no API. //! - `cli`: NVX's own `nvx-rs` binary, the command-line interface that runs NVX's microVMs and diff --git a/scripts/nvx_tools/build_constants.py b/scripts/nvx_tools/build_constants.py index 685248afc..98b51133b 100644 --- a/scripts/nvx_tools/build_constants.py +++ b/scripts/nvx_tools/build_constants.py @@ -137,7 +137,6 @@ class GuestRustBuildConstants: SOURCE_FILES: Final = ( Path("nvx-rs") / "Cargo.toml", Path("nvx-rs") / "Cargo.lock", - Path("nvx-rs") / "build.rs", Path("rust-toolchain.toml"), Path("scripts") / "nvx_tools" / "build_constants.py", Path("scripts") / "nvx_tools" / "guest_binaries.py", diff --git a/scripts/nvx_tools/crate_release.py b/scripts/nvx_tools/crate_release.py index 8deaa8cd6..7007b5b8f 100644 --- a/scripts/nvx_tools/crate_release.py +++ b/scripts/nvx_tools/crate_release.py @@ -9,12 +9,11 @@ version differs from the declared one. Before the upload, the package must hold the commit's files byte for byte, but -for the version, and must build with every feature, ``bundled`` included, -which downloads and verifies the release that ``artifacts.json`` pins. The -package job checks and builds the package without the right to publish it, so -that no build script or procedural macro can publish either. The publish job -builds nothing: it packs the same bytes again and uploads them, and then -crates.io must serve those very bytes. +for the version, and must build with every feature. The package job checks +and builds the package without the right to publish it, so that no build +script or procedural macro can publish either. The publish job builds +nothing: it packs the same bytes again and uploads them, and then crates.io +must serve those very bytes. """ from __future__ import annotations @@ -24,7 +23,6 @@ import os import re import shutil -import sys import tarfile import time import urllib.error @@ -72,13 +70,8 @@ "Cargo.lock", "LICENSE", "README.md", - "build.rs", - "artifacts.json", "src/lib.rs", ) -# Environment variables that would make `bundled` stage something else than -# the release that artifacts.json pins. -BUNDLE_VARIABLES = ("NVX_BUNDLE", "NVX_BUNDLE_DIR", "NVX_BUNDLE_HYPERVISOR") # The state that the package step hands to the publish step. STATE_FILE = "state.json" # The most problems that a failed package check lists. @@ -720,23 +713,13 @@ def cargo_package( return package -def bundle_hypervisors(platform: str = sys.platform) -> tuple[str | None, ...]: - """Return the values of NVX_BUNDLE_HYPERVISOR with which to build the - package: Linux has a KVM and an MSHV package, and Windows one.""" - return ("kvm", "mshv") if platform.startswith("linux") else (None,) - - def build_package( files: Mapping[str, bytes], root: str, work_dir: Path, environment: Mapping[str, str], - *, - platform: str = sys.platform, ) -> None: - """Build the package's files with every feature, as a dependent would. - `bundled` downloads the release that the package's artifacts.json pins, - which must match its digest and declare the crate's control contract.""" + """Build the package's files with every feature, as a dependent would.""" source = work_dir / "build" / root if source.exists(): shutil.rmtree(source) @@ -744,29 +727,20 @@ def build_package( destination = source.joinpath(*PurePosixPath(relative).parts) destination.parent.mkdir(parents=True, exist_ok=True) destination.write_bytes(data) - base = { - key: value for key, value in environment.items() if key not in BUNDLE_VARIABLES - } - for hypervisor in bundle_hypervisors(platform): - build_environment = dict(base) - description = "with every feature" - if hypervisor is not None: - build_environment["NVX_BUNDLE_HYPERVISOR"] = hypervisor - description += f", bundling the {hypervisor} release" - print(f">> building {root} {description}") - run_checked( - [ - "cargo", - "build", - "--locked", - "--all-features", - "--manifest-path", - source / "Cargo.toml", - "--target-dir", - work_dir / "build-target", - ], - env=build_environment, - ) + print(f">> building {root} with every feature") + run_checked( + [ + "cargo", + "build", + "--locked", + "--all-features", + "--manifest-path", + source / "Cargo.toml", + "--target-dir", + work_dir / "build-target", + ], + env=environment, + ) @dataclass(frozen=True) @@ -974,7 +948,6 @@ def package_release( *, registry: Registry | None = None, environment: Mapping[str, str] | None = None, - platform: str = sys.platform, ) -> Plan: """The package job: decide whether the checkout's commit needs a new version, and if so, pack the crate with that version, check the package, @@ -1006,13 +979,7 @@ def package_release( f"{checkout.commit}" ) package, files = checkout.pack(plan.version, work_dir, environment) - build_package( - files, - f"{manifest.name}-{plan.version}", - work_dir, - environment, - platform=platform, - ) + build_package(files, f"{manifest.name}-{plan.version}", work_dir, environment) sha256 = sha256_file(package) for name, value in ( ("publish", "true"), diff --git a/scripts/nvx_tools/nvx_rs.py b/scripts/nvx_tools/nvx_rs.py index 83eff6caf..970893c70 100644 --- a/scripts/nvx_tools/nvx_rs.py +++ b/scripts/nvx_tools/nvx_rs.py @@ -108,14 +108,13 @@ def release_target() -> str: def source_files(root: Path | None = None) -> tuple[Path, ...]: """Return the files that determine the nvx-rs executable of the checkout at `root`, this one by default, in path order: the crate's manifest, lock - file, build script, and sources, and rust-toolchain.toml, which pins the - Rust release that builds it.""" + file, and sources, and rust-toolchain.toml, which pins the Rust release + that builds it.""" root = DIRECTORY.parent if root is None else root crate = root / DIRECTORY.name files = [ crate / "Cargo.toml", crate / "Cargo.lock", - crate / "build.rs", root / "rust-toolchain.toml", ] sources = crate / "src" diff --git a/scripts/test_crate_release.py b/scripts/test_crate_release.py index 304a2810b..1ec76eb81 100644 --- a/scripts/test_crate_release.py +++ b/scripts/test_crate_release.py @@ -72,8 +72,6 @@ "Cargo.lock": LOCKFILE, "LICENSE": "MIT\n", "README.md": "# demo\n", - "build.rs": "fn main() {}\n", - "artifacts.json": "{}\n", "src/lib.rs": "//! Demo.\n", "src/main.rs": "fn main() {}\n", } @@ -914,19 +912,15 @@ def setUp(self) -> None: "GITHUB_OUTPUT": str(self.output), "GITHUB_STEP_SUMMARY": str(self.summary), "PATH": os.environ.get("PATH", ""), - "NVX_BUNDLE": "skip", - "NVX_BUNDLE_DIR": str(root / "bundle"), - "NVX_BUNDLE_HYPERVISOR": "mshv", } - def package(self, platform: str = "linux") -> crate_release.Plan: + def package(self) -> crate_release.Plan: with patch.object(crate_release, "run_checked", self.cargo): return crate_release.package_release( self.checkout.crate_dir, self.work_dir, registry=self.crates_io.registry(), environment=self.environment, - platform=platform, ) def package_job(self) -> tuple[str, str]: @@ -990,24 +984,14 @@ def test_the_first_version_is_the_declared_one(self): ) self.assertEqual(self.checkout.status(), "") self.assertFalse((self.work_dir / crate_release.STATE_FILE).exists()) - self.assertEqual(self.cargo.subcommands(), ["package", "build", "build"]) + self.assertEqual(self.cargo.subcommands(), ["package", "build"]) self.assertNotIn("--allow-dirty", self.cargo.commands[0]) self.assertIn("--no-verify", self.cargo.commands[0]) self.assertIn("--locked", self.cargo.commands[0]) built = self.work_dir / "build" / f"demo-{DECLARED}" - for command, environment, hypervisor in zip( - self.cargo.commands[1:], - self.cargo.environments[1:], - ("kvm", "mshv"), - strict=True, - ): - with self.subTest(hypervisor=hypervisor): - self.assertIn("--all-features", command) - self.assertIn("--locked", command) - self.assertIn(str(built / "Cargo.toml"), command) - self.assertEqual(environment["NVX_BUNDLE_HYPERVISOR"], hypervisor) - self.assertNotIn("NVX_BUNDLE", environment) - self.assertNotIn("NVX_BUNDLE_DIR", environment) + self.assertIn("--all-features", self.cargo.commands[1]) + self.assertIn("--locked", self.cargo.commands[1]) + self.assertIn(str(built / "Cargo.toml"), self.cargo.commands[1]) self.assertEqual( (built / "src" / "lib.rs").read_bytes(), CRATE_FILES["src/lib.rs"].encode(), @@ -1019,11 +1003,6 @@ def test_the_first_version_is_the_declared_one(self): self.summary.read_text(encoding="utf-8"), ) - def test_windows_builds_its_one_bundle(self): - self.package(platform="win32") - self.assertEqual(self.cargo.subcommands(), ["package", "build"]) - self.assertNotIn("NVX_BUNDLE_HYPERVISOR", self.cargo.environments[1]) - def test_a_changed_crate_publishes_the_next_number(self): self.crates_io.release(self.checkout) base = self.checkout.head[:12] diff --git a/scripts/test_guest_binaries.py b/scripts/test_guest_binaries.py index 687cf41d9..97c756d5d 100644 --- a/scripts/test_guest_binaries.py +++ b/scripts/test_guest_binaries.py @@ -60,10 +60,7 @@ def escape(path: Path) -> str: @contextlib.contextmanager def _fake_guest_build( - sources: Sequence[Path] = ( - BuildConstants.REPO_ROOT / "nvx-rs" / "build.rs", - BuildConstants.REPO_ROOT / "nvx-rs" / "src" / "lib.rs", - ), + sources: Sequence[Path] = (BuildConstants.REPO_ROOT / "nvx-rs" / "src" / "lib.rs",), ) -> Generator[MagicMock]: """Stand for Cargo, which writes each binary's name as its contents. @@ -120,7 +117,6 @@ def test_source_identity_covers_the_guest_build_inputs_only(self): self.assertIn("rust-toolchain.toml", relative) self.assertIn("nvx-rs/Cargo.toml", relative) self.assertIn("nvx-rs/Cargo.lock", relative) - self.assertIn("nvx-rs/build.rs", relative) self.assertIn("scripts/nvx_tools/build_constants.py", relative) self.assertIn("scripts/nvx_tools/guest_binaries.py", relative) # The guest build compiles every source file of the crate but the host @@ -155,7 +151,6 @@ def test_source_identity_covers_the_guest_build_inputs_only(self): self.assertIn(path, relative) for path in ( "nvx-rs/README.md", - "nvx-rs/artifacts.json", "nvx-rs/tests/guest_tools.rs", "nvx-rs/src/host/mod.rs", "nvx-rs/src/testing/mod.rs", @@ -192,7 +187,6 @@ def test_source_identity_changes_only_with_the_guest_build_inputs(self): ) others = ( "nvx-rs/README.md", - "nvx-rs/artifacts.json", "nvx-rs/tests/guest_tools.rs", "nvx-rs/src/host/mod.rs", "nvx-rs/src/host/cli/args.rs", @@ -268,7 +262,6 @@ def test_source_inventory_preserves_the_callers_root_spelling(self): for path in ( crate / "Cargo.toml", crate / "Cargo.lock", - crate / "build.rs", source / "lib.rs", tools / "build_constants.py", tools / "guest_binaries.py", @@ -298,7 +291,6 @@ def test_source_identity_follows_a_content_preserving_module_move(self): for path in ( crate / "Cargo.toml", crate / "Cargo.lock", - crate / "build.rs", crate / "src" / "lib.rs", module / "encode.rs", tools / "build_constants.py", @@ -339,7 +331,6 @@ def test_build_uses_fresh_remapped_target_and_writes_manifest(self): files = { crate / "Cargo.toml": '[package]\nname = "fixture"\n', crate / "Cargo.lock": "# lock\n", - crate / "build.rs": "fn main() {}\n", source / "lib.rs": "pub fn fixture() {}\n", recipe_constants: "PROFILE = 'guest'\n", recipe_builder: "def build(): pass\n", @@ -383,11 +374,7 @@ def run_cargo( # A file that the build generates in its own target directory # derives from the inputs that the dep-info lists. generated = destination / "build" / "nvx-0" / "out" / "generated.rs" - _write_dep_info( - destination, - names, - (crate / "build.rs", source / "lib.rs", generated), - ) + _write_dep_info(destination, names, (source / "lib.rs", generated)) cargo_home = root / "cargo-home" with ( @@ -498,8 +485,8 @@ def test_build_refuses_inputs_that_the_source_identity_omits(self): for case, dependency, message in ( ( "file in the checkout", - crate / "artifacts.json", - "source identity omits.*: nvx-rs/artifacts.json$", + crate / "README.md", + "source identity omits.*: nvx-rs/README.md$", ), ( "host module", diff --git a/scripts/test_nvx_tools.py b/scripts/test_nvx_tools.py index 7e87cd565..30d3ae8a4 100644 --- a/scripts/test_nvx_tools.py +++ b/scripts/test_nvx_tools.py @@ -2029,15 +2029,13 @@ def test_source_digest_covers_the_build_inputs_in_any_line_endings(self): for name in ( "nvx-rs/Cargo.toml", "nvx-rs/Cargo.lock", - "nvx-rs/build.rs", "nvx-rs/src/bin/nvx-rs.rs", "rust-toolchain.toml", ): self.assertIn(name, relative) - # Tests, documentation, and the bundled release pin build no part of - # the executable. + # Tests and documentation build no part of the executable. for name in relative: - self.assertNotRegex(name, r"^nvx-rs/(tests/|README\.md$|artifacts\.json$)") + self.assertNotRegex(name, r"^nvx-rs/(tests/|README\.md$)") self.assertEqual(nvx_rs.source_sha256(), nvx_rs.source_sha256()) with tempfile.TemporaryDirectory() as temporary: @@ -2046,7 +2044,6 @@ def test_source_digest_covers_the_build_inputs_in_any_line_endings(self): for name in ( "nvx-rs/Cargo.toml", "nvx-rs/Cargo.lock", - "nvx-rs/build.rs", "nvx-rs/src/lib.rs", "nvx-rs/src/bin/nvx-rs.rs", "rust-toolchain.toml", @@ -6521,31 +6518,6 @@ def test_crate_checks_run_on_every_os(self): self.assertIn("uses: ./.github/actions/check-nvx-rs", definition) self.assertEqual(ci.REQUIRED_CI_RESULT_ENVIRONMENTS["nvx-rs"], "NVX_RS_RESULT") - def test_nvx_rs_bundles_a_release_in_the_current_layout(self): - # The bundled feature stages what a release holds besides nvx-rs, so - # its synthetic release ships nvx-rs and its provenance, as releases - # do, and the pinned older release, which doesn't, stages as well. - action = ( - BuildConstants.REPO_ROOT - / ".github" - / "actions" - / "check-nvx-rs" - / "action.yml" - ).read_text(encoding="utf-8") - script = _composite_action_script(action, "Check artifact bundling") - for member in ( - "bin/${executable}", - "bin/${cli}", - "guest/vmlinux", - "guest/initramfs.cpio.gz", - f"provenance/{nvx_rs.PROVENANCE_NAME}", - ): - with self.subTest(member=member): - self.assertIn(f'"${{bundle}}/{member}"', script) - self.assertIn(member, script.split("sha256sum", 1)[1]) - self.assertIn("cli=nvx-rs.exe", script) - self.assertIn("--features bundled artifacts", script) - def test_nvx_rs_builds_and_checks_every_guest_binary_for_musl(self): crate = BuildConstants.REPO_ROOT / "nvx-rs" action = ( @@ -8153,12 +8125,10 @@ def test_only_guest_binary_inputs_rebuild_the_guest_images(self): "nvx-rs/src/protocol/nvxs/tests/host.rs", "nvx-rs/src/guest/managed_agent/tests/loopback.rs", "nvx-rs/tests/guest_tools.rs", - "nvx-rs/artifacts.json", ) guest_side = ( "nvx-rs/Cargo.toml", "nvx-rs/Cargo.lock", - "nvx-rs/build.rs", "nvx-rs/src/lib.rs", "nvx-rs/src/protocol/nvxs/host.rs", "nvx-rs/src/guest/time/checks.rs", @@ -17470,7 +17440,6 @@ def test_device_io_provenance_uses_shared_rust_source_digest(self): sources = { crate / "Cargo.toml": "[package]\nname = 'fixture'\n", crate / "Cargo.lock": "# lock\n", - crate / "build.rs": "fn main() {}\n", crate / "src" / "lib.rs": "pub fn fixture() {}\n", checkout / "rust-toolchain.toml": ( f'[toolchain]\nchannel = "{GuestRustBuildConstants.TOOLCHAIN}"\n' From 58681a95949185cf9511fb656bbcf90d45c85c0d Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Wed, 11 Nov 2026 21:51:12 -0800 Subject: [PATCH 2/2] scripts: list the public API with every feature, bundled being gone public_api.py documented the crate with every feature but bundled, whose build script downloaded a release, and default, which only names others. With bundled deleted, only default remains to leave out, so the listing names no feature that no longer exists, and its docs and doc/ci.md say that it documents every feature. The arguments that it passes Cargo, and so the snapshots, stay the same. test_lists_feature_gated_items_as_any_other drops bundled from the features that it gives feature_arguments, which still enables each of the others by name, without default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- doc/ci.md | 2 +- scripts/public_api.py | 25 +++++++++++-------------- scripts/test_public_api.py | 5 ++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/doc/ci.md b/doc/ci.md index 34e4db8a8..91fac0736 100644 --- a/doc/ci.md +++ b/doc/ci.md @@ -2304,7 +2304,7 @@ On Linux, the `nvx-rs` job also checks the public API of `nvx` against its snapshots, [`nvx-rs/public-api/linux.txt`](../nvx-rs/public-api/linux.txt) and [`windows.txt`](../nvx-rs/public-api/windows.txt), with [`public_api.py`](../scripts/public_api.py). The script builds the library's -rustdoc JSON with every feature but `bundled`, on the pinned Rust release, for +rustdoc JSON with every feature, on the pinned Rust release, for `x86_64-unknown-linux-musl`, the guests' target, and for `x86_64-pc-windows-msvc`, after it adds both targets to that release with rustup. rustdoc needs only a target's standard library, not its linker, so the diff --git a/scripts/public_api.py b/scripts/public_api.py index 56fe595ef..e6230581b 100644 --- a/scripts/public_api.py +++ b/scripts/public_api.py @@ -12,14 +12,13 @@ Without `--update`, the script prints how the API differs from each snapshot, and fails if it does. -The script builds the library's rustdoc JSON with every feature but `bundled`, -whose build script downloads a release, for x86_64-unknown-linux-musl, the -guests' target, and for x86_64-pc-windows-msvc, on the Rust release that -rust-toolchain.toml pins. It first adds both targets to that release with -rustup: rustdoc needs only a target's standard library, not its linker, so any -host lists both platforms, and lists them alike. The JSON output is unstable, -so the script enables it with RUSTC_BOOTSTRAP=1, and reads only the format -version of the pinned release. +The script builds the library's rustdoc JSON with every feature, for +x86_64-unknown-linux-musl, the guests' target, and for x86_64-pc-windows-msvc, +on the Rust release that rust-toolchain.toml pins. It first adds both targets +to that release with rustup: rustdoc needs only a target's standard library, +not its linker, so any host lists both platforms, and lists them alike. The +JSON output is unstable, so the script enables it with RUSTC_BOOTSTRAP=1, and +reads only the format version of the pinned release. It walks the public tree from the crate root, following re-exports, and lists the modules, structs, enums, unions, traits and their items, functions, @@ -64,9 +63,8 @@ "windows": OpenVMMBuildConstants.WINDOWS_RUST_TARGET, } -# `default` names other features, and the build script of `bundled` downloads -# a release. -EXCLUDED_FEATURES = frozenset({"bundled", "default"}) +# `default` names other features. +EXCLUDED_FEATURES = frozenset({"default"}) # How rustdoc's JSON gives the attribute `#[doc(hidden)]`. HIDDEN = {"other": "#[doc(hidden)]"} @@ -230,9 +228,8 @@ def api_lines(document: Any) -> list[str]: def feature_arguments(features: Mapping[str, object]) -> list[str]: - """Returns Cargo's arguments that enable every feature but `bundled`: no - default feature, which could enable `bundled`, and each other one by - name.""" + """Returns Cargo's arguments that enable every feature: no default + feature, which only names others, and each other one by name.""" return [ "--no-default-features", "--features", diff --git a/scripts/test_public_api.py b/scripts/test_public_api.py index 8b661329b..8229773b9 100644 --- a/scripts/test_public_api.py +++ b/scripts/test_public_api.py @@ -266,14 +266,13 @@ def test_lists_feature_gated_items_as_any_other(self) -> None: public_api.api_lines(crate.document(root)), ["mod demo", "mod demo::cli", "fn demo::cli::main"], ) - # The listing documents every feature but `bundled`, which no default - # feature enables either. + # The listing documents every feature, each by name rather than + # through `default`. self.assertEqual( public_api.feature_arguments( { "default": ["host"], "host": ["serde"], - "bundled": ["host"], "testing": [], "cli": ["host"], "serde": [],