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..91fac0736 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,15 +2299,12 @@ 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 [`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/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/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_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' 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": [],