name: Build binaries on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: permissions: contents: read env: CARGO_TERM_COLOR: always # Measure before stripping; package stripped copies separately. CARGO_PROFILE_RELEASE_STRIP: none jobs: build: name: Build (${{ matrix.target }}) runs-on: ubuntu-latest timeout-minutes: 120 env: TARGET: ${{ matrix.target }} defaults: run: shell: bash strategy: fail-fast: false max-parallel: 2 matrix: target: - x86_64-musl - i686-musl - arm-musleabi - arm-musleabihf - armv7-musleabi - armv7-musleabihf - aarch64-musl - mips-musl - mipsel-musl - mips64-muslabi64 - mips64el-muslabi64 - riscv64gc-musl steps: - name: Checkout uses: builtin:checkout - name: Configure Rust directories # Cross remote does not traverse hidden directories under /root. run: | echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" echo "$RUNNER_TEMP/cargo/bin" >> "$GITHUB_PATH" - name: Install standard Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 with: toolchain: ${{ startsWith(matrix.target, 'mips') && 'nightly' || 'stable' }} cache: false build-warnings: warn - name: Install nightly toolchain and standard library sources run: | rustup toolchain install nightly \ --profile minimal \ --component rust-src - name: Install cross and binary tools run: | sudo apt-get update sudo apt-get install -y --no-install-recommends binutils llvm cargo install --git https://github.com/cross-rs/cross --locked cross echo "RUST_TARGET=${TARGET%-*}-unknown-linux-${TARGET#*-}" >> "$GITHUB_ENV" - name: Detect statistics feature run: | if grep -Eq '^[[:space:]]*stats[[:space:]]*=' crates/irtt-app/Cargo.toml; then echo "HAS_STATS_FEATURE=true" >> "$GITHUB_ENV" else echo "HAS_STATS_FEATURE=false" >> "$GITHUB_ENV" fi - name: Build, verify, package, and compare env: # MIPS build-std needs cross GCC's CRT objects. RUSTFLAGS: ${{ startsWith(matrix.target, 'mips') && '-C target-feature=+crt-static -C link-self-contained=no' || '-C target-feature=+crt-static' }} CROSS_REMOTE: "true" CROSS_REMOTE_COPY_FULL_TARGET_DIR: ${{ startsWith(matrix.target, 'mips') && 'true' || 'false' }} run: | set -euo pipefail declare -A raw_size declare -A stripped_size declare -A compressed_size mkdir -p dist # MIPS requires nightly build-std via Cross.toml. # Every other standard build uses stable. standard_toolchain=stable if [[ "$TARGET" == mips* ]]; then standard_toolchain=nightly fi build_variant() { local variant="$1" local toolchain="$2" local features="$3" shift 3 local binary="target/${RUST_TARGET}/release/irtt-client" local directory="dist/${variant}" local artifact="dist/irtt-client-${TARGET}-${variant}.tar.gz" echo "::group::Build ${variant} (${toolchain}, ${features})" cross "+${toolchain}" build \ --release \ --locked \ --target "$RUST_TARGET" \ -p irtt-rs \ --bin irtt-client \ --no-default-features \ --features "$features" \ "$@" # Reject dynamically linked executables. readelf --program-headers "$binary" > /tmp/program-headers.txt readelf --dynamic "$binary" > /tmp/dynamic-section.txt if grep -q INTERP /tmp/program-headers.txt || grep -q NEEDED /tmp/dynamic-section.txt; then echo "Binary is not statically linked: ${variant}" >&2 exit 1 fi mkdir -p "$directory" cp "$binary" "$directory/irtt-client" raw_size[$variant]=$(stat -c %s "$binary") llvm-strip --strip-all "$directory/irtt-client" stripped_size[$variant]=$( stat -c %s "$directory/irtt-client" ) # Each archive contains an executable named irtt-client. tar -czf "$artifact" -C "$directory" irtt-client compressed_size[$variant]=$(stat -c %s "$artifact") echo "::endgroup::" } human() { numfmt --to=iec-i --suffix=B --format='%.1f' "$1" } # Negative means smaller than the reference binary. delta() { awk -v base="$1" -v current="$2" 'BEGIN { if (base == 0) { printf "N/A" } else { printf "%+.1f%%", 100 * (current - base) / base } }' } print_row() { local variant="$1" local label="$2" local baseline="$3" local comparison if [[ "$variant" == "$baseline" ]]; then comparison="Baseline" else comparison=$(delta \ "${compressed_size[$baseline]}" \ "${compressed_size[$variant]}") fi printf '| %s | %s | %s | %s | %s |\n' \ "$label" \ "$(human "${raw_size[$variant]}")" \ "$(human "${stripped_size[$variant]}")" \ "$(human "${compressed_size[$variant]}")" \ "$comparison" } # --------------------------------------------------------- # Standard builds: stable except MIPS. # --------------------------------------------------------- full_features=client if [[ "$HAS_STATS_FEATURE" == "true" ]]; then full_features="client,stats" fi build_variant full "$standard_toolchain" "$full_features" standard_baseline=full if [[ "$HAS_STATS_FEATURE" == "true" ]]; then build_variant minimal "$standard_toolchain" client standard_baseline=minimal fi # --------------------------------------------------------- # Nightly baseline: identical compiler for all experiments. # # MIPS already uses nightly for standard builds, so reuse # those binaries rather than compiling them twice. # --------------------------------------------------------- if [[ "$standard_toolchain" == "nightly" ]]; then nightly_full=full nightly_baseline="$standard_baseline" else nightly_full=nightly-full build_variant nightly-full nightly "$full_features" nightly_baseline=nightly-full if [[ "$HAS_STATS_FEATURE" == "true" ]]; then build_variant nightly-minimal nightly client nightly_baseline=nightly-minimal fi fi # --------------------------------------------------------- # Nightly size experiments, always enabled. # --------------------------------------------------------- # Rebuild std with the ordinary std feature set. build_variant rebuilt-std nightly client \ -Z build-std # Rebuild std without backtrace support. # Retain normal panic unwinding. build_variant no-backtrace nightly client \ -Z build-std \ -Z build-std-features=panic-unwind # Aggressive option: terminate immediately on panic. build_variant immediate-abort nightly client \ -Z panic-immediate-abort \ -Z build-std \ -Z build-std-features= \ --config 'profile.release.panic="immediate-abort"' # --------------------------------------------------------- # Job summary. # --------------------------------------------------------- { echo "### irtt-client: ${TARGET}" echo echo "#### Standard builds" echo echo "Toolchain: \`${standard_toolchain}\`" echo echo '| Variant | Unstripped | Stripped | Compressed | Compressed vs baseline |' echo '|:--|--:|--:|--:|--:|' print_row full "Full" "$standard_baseline" if [[ "$HAS_STATS_FEATURE" == "true" ]]; then print_row minimal "Minimal" "$standard_baseline" fi echo echo "#### Nightly experiments" echo echo '| Variant | Unstripped | Stripped | Compressed | Compressed vs baseline |' echo '|:--|--:|--:|--:|--:|' print_row "$nightly_full" "Nightly full" "$nightly_baseline" if [[ "$HAS_STATS_FEATURE" == "true" ]]; then print_row "$nightly_baseline" "Nightly minimal" "$nightly_baseline" fi print_row rebuilt-std "Rebuilt std" "$nightly_baseline" print_row no-backtrace "No backtrace" "$nightly_baseline" print_row immediate-abort "Immediate abort" "$nightly_baseline" if [[ "$standard_toolchain" == "stable" ]]; then echo echo "Nightly vs stable baseline (compressed): $(delta \ "${compressed_size[$standard_baseline]}" \ "${compressed_size[$nightly_baseline]}")" else echo echo "MIPS: standard and nightly baselines are identical." echo "Cross.toml already enables build-std for these targets." fi if [[ "$HAS_STATS_FEATURE" != "true" ]]; then echo echo "Statistics feature not detected; client includes statistics." fi } >> "$GITHUB_STEP_SUMMARY" - name: Upload binaries # Gitea's v3 artifact protocol. uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: irtt-client-${{ matrix.target }} path: dist/irtt-client-${{ matrix.target }}-*.tar.gz if-no-files-found: error