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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 26 additions & 55 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,10 @@ jobs:
- name: Install protoc-gen-elixir
run: mix escript.install hex protobuf 0.17.0 --force

# The `:guest_agent_build` Mix compiler builds the static musl guest agent
# (native/guest-agent) into priv/ on every `mix compile`; the host-arch
# build is a hard requirement, so the runner's default toolchain needs the
# musl std. Cross arches are best-effort and stay skipped here.
- name: Install musl target for the guest-agent build
run: rustup target add x86_64-unknown-linux-musl

- name: Cache guest-agent cargo build
- name: Cache native cargo build
uses: Swatinem/rust-cache@v2
with:
workspaces: native/guest-agent
workspaces: native

- name: Compile (warnings as errors)
# --force matches the `mix check` alias: re-surfaces warnings even on a
Expand Down Expand Up @@ -173,54 +166,37 @@ jobs:
- name: ShellCheck
run: git ls-files -z '*.sh' | xargs -0 -r shellcheck

# The guest agent is otherwise only built by the `:guest_agent_build` Mix
# compiler, where cargo warnings scroll past without failing the build.
guest-agent:
name: Rust (guest-agent)
runs-on: ubuntu-latest
defaults:
run:
working-directory: native/guest-agent
steps:
- uses: actions/checkout@v7

# build.rs compiles agent.proto with prost-build, which shells out to protoc.
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- uses: Swatinem/rust-cache@v2
with:
workspaces: native/guest-agent

- name: rustfmt
run: cargo fmt --check

- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings

# One job for the whole native/ workspace: guest-agent, hyperrs-elfsum,
# suidhelper and xtask. The guest agent and xtask are otherwise only built by
# Mix compilers, where cargo warnings scroll past without failing the build.
rust:
name: Rust (suidhelper)
name: Rust (native workspace)
runs-on: ubuntu-latest
defaults:
run:
working-directory: native/suidhelper
working-directory: native
steps:
- uses: actions/checkout@v7

# rustup reads native/suidhelper/rust-toolchain.toml on the first cargo
# call and installs the pinned nightly toolchain, components, and targets.
# rustup reads native/rust-toolchain.toml on the first cargo call and
# installs the pinned nightly toolchain, components, and targets.
- name: Show toolchain
run: rustup show

# guest-agent's build.rs compiles agent.proto with prost-build, which
# shells out to protoc.
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- uses: Swatinem/rust-cache@v2
with:
workspaces: native/suidhelper
workspaces: native

- name: rustfmt
run: cargo fmt --check
run: cargo fmt --all --check

- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

- name: Install cargo-llvm-cov and nextest
uses: taiki-e/install-action@v2
Expand All @@ -231,7 +207,7 @@ jobs:
# `nextest` runs the suite while llvm-cov collects coverage AND nextest
# writes target/nextest/ci/junit.xml (see .config/nextest.toml). One run,
# two artifacts: lcov.info for coverage, junit.xml for Test Analytics.
run: cargo llvm-cov nextest --profile ci --all-features --lcov --output-path lcov.info
run: cargo llvm-cov nextest --workspace --profile ci --all-features --lcov --output-path lcov.info

# !cancelled() uploads on pass OR fail (see elixir job). The action runs at
# repo root, so the path is fully qualified -- working-directory only
Expand All @@ -242,25 +218,25 @@ jobs:
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: native/suidhelper/target/nextest/ci/junit.xml
files: native/target/nextest/ci/junit.xml
flags: rust
report_type: test_results

# Repo-relative path: upload-artifact is an action, so the rust job's
# `working-directory: native/suidhelper` default does NOT apply.
# `working-directory: native` default does NOT apply.
- name: Upload Rust test results artifact
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: rust-test-results
path: native/suidhelper/target/nextest/ci/junit.xml
path: native/target/nextest/ci/junit.xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
# working-directory only applies to `run:` steps, not actions.
files: native/suidhelper/lcov.info
files: native/lcov.info
flags: rust
fail_ci_if_error: true

Expand All @@ -273,14 +249,14 @@ jobs:
steps:
- uses: actions/checkout@v7

# rustup reads native/suidhelper/rust-toolchain.toml and installs the pinned
# rustup reads native/rust-toolchain.toml and installs the pinned
# nightly on the first cargo call.
- name: Show toolchain
run: rustup show

- uses: Swatinem/rust-cache@v2
with:
workspaces: native/suidhelper
workspaces: native

- name: Install cargo-llvm-cov and nextest
uses: taiki-e/install-action@v2
Expand Down Expand Up @@ -401,15 +377,10 @@ jobs:
- name: Install protoc-gen-elixir
run: mix escript.install hex protobuf 0.17.0 --force

- name: Install musl target for the guest-agent build
run: rustup target add x86_64-unknown-linux-musl

- name: Cache rust builds
uses: Swatinem/rust-cache@v2
with:
workspaces: |
native/guest-agent
native/suidhelper
workspaces: native

- name: Compile (warnings as errors)
run: mix compile --warnings-as-errors
Expand Down Expand Up @@ -528,7 +499,7 @@ jobs:
-o suidhelper.profdata
"$TOOLS/llvm-cov" export --format=lcov \
--instr-profile=suidhelper.profdata \
target/hyper-suidhelper \
../target/hyper-suidhelper \
"$GITHUB_WORKSPACE/native/suidhelper/src" \
> lcov-e2e.info
sed -i "s|^SF:$GITHUB_WORKSPACE/|SF:|" lcov-e2e.info
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ jobs:
# manifest. `0,/re/` rewrites only the first match, which for each
# Cargo.toml is its own `[package] version` (xtask is intentionally
# left at 0.0.0 — it is publish = false).
for m in native/suidhelper/Cargo.toml native/suidhelper/meta/Cargo.toml native/guest-agent/Cargo.toml; do
for m in native/suidhelper/Cargo.toml native/hyperrs-elfsum/Cargo.toml native/guest-agent/Cargo.toml; do
sed -i -E "0,/^version = \"[^\"]*\"/s//version = \"${version}\"/" "$m"
done
sed -i -E "0,/version: \"[^\"]*\"/s//version: \"${version}\"/" mix.exs

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add native/suidhelper/Cargo.toml native/suidhelper/meta/Cargo.toml native/guest-agent/Cargo.toml mix.exs
git add native/suidhelper/Cargo.toml native/hyperrs-elfsum/Cargo.toml native/guest-agent/Cargo.toml mix.exs

# Idempotent: a re-run after the bump already landed has nothing to commit.
if git diff --cached --quiet; then
Expand Down Expand Up @@ -80,14 +80,14 @@ jobs:
with:
ref: ${{ needs.prepare-release.outputs.sha }}

# rustup reads native/suidhelper/rust-toolchain.toml on the first cargo
# rustup reads native/rust-toolchain.toml on the first cargo
# call and installs the pinned nightly + both musl targets.
- name: Show toolchain
run: rustup show

- uses: Swatinem/rust-cache@v2
with:
workspaces: native/suidhelper
workspaces: native

- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
Expand All @@ -97,7 +97,7 @@ jobs:
v="${{ needs.prepare-release.outputs.version }}"
out="hyper-suidhelper-${v}-${{ matrix.target }}"
mkdir -p dist
cp "target/${{ matrix.target }}/release/hyper-suidhelper" "dist/${out}"
cp "../target/${{ matrix.target }}/release/hyper-suidhelper" "dist/${out}"

- uses: actions/upload-artifact@v7
with:
Expand Down
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ mix test --only integration --only external
# live E2E: boots real Firecracker VMs. Needs a provisioned
# KVM host (docs/cookbook/install.md) + passwordless sudo.
# CI runs this in ci.yml's `integration` job on ubuntu-latest.
cargo nextest run # Rust suite (run inside native/suidhelper/)
cargo nextest run --workspace --all-features
# Rust suite (run inside native/; one workspace for every crate)
```

Pure tests under `test/unit` and `test/controls` need neither Postgres nor
Expand All @@ -47,6 +48,10 @@ run in CI's `integration` job, which provisions the GitHub-hosted runner via
`lib/hyper/redist/` — **pure cores**: units algebra, strict number parsing,
EWMA/rate controls, `/proc` parsers, hashing.
No processes, no I/O. These are where property tests pay off most.
- `native/` — one Cargo workspace (shared `Cargo.lock`, `target/`,
`rust-toolchain.toml`, release profiles) for every Rust crate below.
- `native/hyperrs-elfsum/` — ELF self-checksum slot and stamping, shared by the
suidhelper and its xtask.
- `native/suidhelper/` — the privileged Rust helper. Source in `src/`, tests
inline in `src/` or in `tests/` (see Rust rules below).
- Generated, do not hand-edit: `lib/hyper/firecracker/api/{operations,schemas}`
Expand Down Expand Up @@ -168,7 +173,7 @@ If a case needs its own assertions, it is a separate test, not a row.
(`tests/` subdirectories are not auto-discovered) — copy an existing
`[[test]]` block when adding one.
- `proptest` is the dev-dependency; use `proptest!{ #[test] fn ... }` with
`prop_assert!`. CI runs `cargo nextest run --profile ci` (one retry to damp
`prop_assert!`. CI runs `cargo nextest run --workspace --profile ci` (one retry to damp
flakes — do not write tests that *rely* on the retry).

## Other conventions
Expand Down
4 changes: 2 additions & 2 deletions lib/mix/tasks/suidhelper.install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule Mix.Tasks.Suidhelper.Install do

This is the privileged counterpart to `mix suidhelper.stamp`, which stamps
only. `cargo` and the helper's toolchain (see
`native/suidhelper/rust-toolchain.toml`) must be installed.
`native/rust-toolchain.toml`) must be installed.
"""

use Mix.Task
Expand All @@ -31,7 +31,7 @@ defmodule Mix.Tasks.Suidhelper.Install do
# dependency: there the crate lives under deps/hypervm/native/suidhelper, not
# under the consumer project's root.
@helper_dir Path.expand("../../../native/suidhelper", __DIR__)
@source Path.join(@helper_dir, "target/hyper-suidhelper")
@source Path.expand("../target/hyper-suidhelper", @helper_dir)
# Must match `Hyper.Cfg.Tools.suidhelper/0`'s default path and the xtask's
# `INSTALL_PATH`: a `PATH` location the unprivileged node can exec.
@install_path "/usr/local/bin/hyper-suidhelper"
Expand Down
25 changes: 14 additions & 11 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,14 @@ defmodule Hyper.MixProject do
files: ~w(
lib priv/firecracker priv/repo priv/vmlinux proto config mix.exs
README.md LICENSE
native/Cargo.toml native/Cargo.lock native/rust-toolchain.toml native/.config
native/guest-agent/src native/guest-agent/tests native/guest-agent/build.rs
native/guest-agent/Cargo.toml native/guest-agent/Cargo.lock
native/guest-agent/.cargo
native/suidhelper/src native/suidhelper/tests native/suidhelper/meta
native/guest-agent/Cargo.toml native/guest-agent/.cargo
native/hyperrs-elfsum/src native/hyperrs-elfsum/Cargo.toml
native/suidhelper/src native/suidhelper/tests
native/suidhelper/xtask native/suidhelper/build.rs
native/suidhelper/Cargo.toml native/suidhelper/Cargo.lock
native/suidhelper/rust-toolchain.toml native/suidhelper/README.md
native/suidhelper/.cargo native/suidhelper/.config
native/suidhelper/Cargo.toml native/suidhelper/README.md
native/suidhelper/.cargo
),
# `lib` is included wholesale, so on a dev box the gitignored generated
# outputs (bindings, expected.ex) exist on disk and would leak into the
Expand Down Expand Up @@ -501,7 +501,7 @@ defmodule Mix.Tasks.Compile.SuidhelperStamp do
use Mix.Task.Compiler

@helper_dir "native/suidhelper"
@binary "native/suidhelper/target/hyper-suidhelper"
@binary "native/target/hyper-suidhelper"
@out "lib/hyper/suid_helper/expected.ex"

@impl Mix.Task.Compiler
Expand All @@ -522,7 +522,7 @@ defmodule Mix.Tasks.Compile.SuidhelperStamp do
`cargo xtask stamp` failed (exit #{code}) building the suidhelper:

#{output}
Ensure `cargo`, the helper's toolchain and its musl target (see #{@helper_dir}/rust-toolchain.toml)
Ensure `cargo`, the helper's toolchain and its musl target (see native/rust-toolchain.toml)
are installed.
""")
end
Expand Down Expand Up @@ -619,7 +619,7 @@ defmodule Mix.Tasks.Compile.GuestAgentBuild do
end

defp build!(arch, triple, required: required?) do
case System.cmd("cargo", ["build", "--release", "--target", triple],
case System.cmd("cargo", ["build", "--profile", "release-guest", "--target", triple],
cd: @agent_dir,
stderr_to_stdout: true
) do
Expand All @@ -644,7 +644,7 @@ defmodule Mix.Tasks.Compile.GuestAgentBuild do
end

defp install(arch, triple) do
src = Path.join(@agent_dir, "target/#{triple}/release/hyper-guest-agent")
src = Path.join("native/target", "#{triple}/release-guest/hyper-guest-agent")
dest = Path.join(@out_dir, "hyper-guest-agent-#{arch}")
File.cp!(src, dest)
File.chmod!(dest, 0o755)
Expand All @@ -665,7 +665,10 @@ defmodule Mix.Tasks.Compile.GuestAgentBuild do
end

defp installed_targets do
case System.cmd("rustup", ["target", "list", "--installed"], stderr_to_stdout: true) do
case System.cmd("rustup", ["target", "list", "--installed"],
cd: @agent_dir,
stderr_to_stdout: true
) do
{out, 0} -> out |> String.split("\n", trim: true) |> Enum.map(&String.trim/1)
# No rustup / unknown: build only the host arch (never attempt a cross build).
_ -> []
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions native/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
/lcov.info
*.profraw
Loading
Loading