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
63 changes: 53 additions & 10 deletions .github/workflows/prbuild.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
---
name: PR build
on:
pull_request_target:
pull_request:
paths-ignore:
- ".github/workflows/**"
branches:
- "main"

# This workflow does not push or consume any secrets, so `pull_request`
# (not `pull_request_target`) is appropriate: fork PRs still build but
# their GITHUB_TOKEN is read-only and secrets aren't exposed, which
# matters because the smoketest job runs a script from the PR tree.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
# PR-scoped local image tag used by the smoketest job after hydrating
# from the GHA buildx cache written by the build job.
CI_IMAGE: eve-rust-ci:pr-${{ github.event.pull_request.number }}

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
fail-fast: false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Starting Report
run: |
Expand All @@ -29,17 +38,51 @@ jobs:
free -m
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
# Validates the Dockerfile builds for both host arches, and populates
# the GHA buildx cache that the smoketest job rehydrates from. The
# tag is purely local — nothing pushes or loads this image.
- uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: false
tags: |
${{ github.event.pull_request.head.repo.full_name }}:latest
tags: eve-rust-ci:multiarch
cache-to: type=gha,mode=max
cache-from: type=gha

smoketest:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
# Pull the amd64 image from the GHA cache written by the build job
# and load it into the local docker daemon so `docker run` in run.sh
# can see it. All layers are cache hits — this step is effectively
# just the --load export.
- name: Hydrate eve-rust image from build cache
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: .
platforms: linux/amd64
load: true
tags: ${{ env.CI_IMAGE }}
cache-from: type=gha
# Regression test: cross-compile the smoketest crate for each supported
# musl target, assert ELF arch, then run each binary under qemu-user.
# Catches silent cross-compile breakage of the kind we hit enabling
# riscv64 (wrong-arch libc picked up from host /usr/lib, tier-3 target
# spec missing crt-static-default, etc).
- name: Run smoketest
env:
RUST_IMAGE: ${{ env.CI_IMAGE }}
run: ./test/smoketest/run.sh
3 changes: 3 additions & 0 deletions test/smoketest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target/
/Cargo.lock
/.cargo/
9 changes: 9 additions & 0 deletions test/smoketest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "eve-rust-smoketest"
version = "0.1.0"
edition = "2021"
publish = false

[[bin]]
name = "smoketest"
path = "src/main.rs"
15 changes: 15 additions & 0 deletions test/smoketest/cargo-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Shape matches what real consumers (e.g. EVE's pkg/vector) use: profile
# settings plus per-cfg rustflags that merge with the base image's
# [target.<triple>] entries in /usr/local/cargo/config.toml.

[profile.release]
opt-level = "z"
lto = "fat"
codegen-units = 1
strip = "symbols"

[target.'cfg(target_env = "musl")']
rustflags = [
"-C", "embed-bitcode=yes",
"-A", "dead_code",
]
98 changes: 98 additions & 0 deletions test/smoketest/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/bin/bash
# Regression test for the eve-rust toolchain image:
# - cross-compile a tiny crate for all three supported musl targets using
# $RUST_IMAGE as the toolchain
# - assert each resulting ELF has the expected machine type
# - execute each binary under qemu-user (via binfmt_misc) to prove it
# actually runs, not just links
#
# Used both locally and in the eve-rust CI pipeline. For non-amd64 execution
# the caller is responsible for registering qemu handlers
# (docker/setup-qemu-action in CI, `docker run --privileged tonistiigi/binfmt
# --install all` locally).

set -euo pipefail

RUST_IMAGE="${RUST_IMAGE:-lfedge/eve-rust:latest}"
cd "$(dirname "$0")"

declare -A TARGETS=(
[amd64]=x86_64-unknown-linux-musl
[arm64]=aarch64-unknown-linux-musl
[riscv64]=riscv64gc-unknown-linux-musl
)

declare -A EXPECTED_MACHINE=(
[amd64]="Advanced Micro Devices X86-64"
[arm64]="AArch64"
[riscv64]="RISC-V"
)

# Build all three targets in one invocation of the rust container, reusing
# the target/ cache across them. We `docker run` directly (no buildx) so a
# just-loaded local image like `eve-rust-ci:pr` is visible without pushing
# to a registry.
echo "=== Cross-compiling smoketest for all 3 targets ==="
docker run --rm \
-v "$(pwd):/app" \
-w /app \
"${RUST_IMAGE}" \
sh -c '
set -eu
mkdir -p .cargo
cp cargo-config.toml .cargo/config.toml
for t in x86_64-unknown-linux-musl aarch64-unknown-linux-musl riscv64gc-unknown-linux-musl; do
echo "--- compile: $t ---"
CARGO_BUILD_TARGET=$t cargo build --release
done
'

fail=0
for arch in amd64 arm64 riscv64; do
target="${TARGETS[$arch]}"
expected_machine="${EXPECTED_MACHINE[$arch]}"
binary="target/${target}/release/smoketest"

echo
echo "================ $target ($arch) ================"

if [ ! -f "$binary" ]; then
echo "FAIL: $binary was not produced"
fail=1
continue
fi

# readelf is provided by binutils (preinstalled on ubuntu-latest); fall
# back to running it inside the rust image if missing.
if command -v readelf >/dev/null 2>&1; then
elf_hdr=$(readelf -h "$binary")
else
elf_hdr=$(docker run --rm -v "$(pwd):/app" -w /app "${RUST_IMAGE}" llvm-readelf -h "$binary")
fi
echo "$elf_hdr" | grep -E "Class|Data|Machine|Type"

actual_machine=$(echo "$elf_hdr" | awk -F: '/Machine:/ {sub(/^ +/, "", $2); print $2}')
if [ "$actual_machine" != "$expected_machine" ]; then
echo "FAIL: $arch has ELF Machine=\"${actual_machine}\", expected \"${expected_machine}\""
fail=1
continue
fi

# Execute the binary inside a matching-arch alpine container. Docker plus
# binfmt_misc routes through qemu-user for non-host arches.
if docker run --rm --platform="linux/${arch}" -v "$(pwd):/app" -w /app alpine:3.22 "./${binary}"; then
echo "PASS: $arch"
else
rc=$?
echo "FAIL: $arch (exit $rc)"
fail=1
fi
done

echo
if [ "$fail" -eq 0 ]; then
echo "All three targets linked, are the correct arch, and ran under qemu-user."
else
echo "One or more targets failed." >&2
exit 1
fi
12 changes: 12 additions & 0 deletions test/smoketest/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::collections::HashMap;

fn main() {
let mut m: HashMap<&str, u32> = HashMap::new();
m.insert("riscv64", 64);
m.insert("musl", 1);
for (k, v) in &m {
println!("{k} = {v}");
}
assert_eq!(m.values().sum::<u32>(), 65);
println!("ok");
}
Loading