name: CI env: CARGO_TERM_COLOR: always RUST_TOOLCHAIN: stable RUST_TOOLCHAIN_NIGHTLY: nightly RUST_TOOLCHAIN_MSRV: 1.91.0 RUST_TOOLCHAIN_BETA: beta CARGO_INCREMENTAL: 0 CARGO_PROFILE_TEST_DEBUG: 0 on: push: branches: - main pull_request: {} jobs: precheck-rust: strategy: matrix: toolchain: - 1.91.0 # MSRV - stable - beta os: - ubuntu-latest - macos-latest - windows-latest runs-on: ${{ matrix.os }} env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: ilammy/setup-nasm@v1 if: startsWith(matrix.os, 'windows') - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 with: env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" key: ${{ matrix.os }}-${{ matrix.toolchain }} - name: check run: | cargo check --workspace --all-targets --all-features precheck-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: extra-checks run: | ./scripts/extra-checks.sh check-rust: strategy: matrix: toolchain: - 1.91.0 # MSRV - stable - beta os: - ubuntu-latest - macos-latest - windows-latest runs-on: ${{ matrix.os }} needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: ilammy/setup-nasm@v1 if: startsWith(matrix.os, 'windows') - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 with: env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" key: ${{ matrix.os }}-${{ matrix.toolchain }} - name: clippy run: | cargo clippy --workspace --all-targets --all-features - name: rustfmt run: | cargo fmt --all --check - name: doc run: | cargo doc --all-features --no-deps --workspace --exclude rama-cli cargo doc --all-features --no-deps -p rama-cli test-rust-base: strategy: matrix: toolchain: - stable os: - ubuntu-latest - ubuntu-24.04-arm - macos-15-intel - macos-15 - windows-2025 - windows-11-arm name: test-rust-base-${{ matrix.toolchain }}-${{ matrix.os }} runs-on: ${{ matrix.os }} needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: ilammy/setup-nasm@v1 if: startsWith(matrix.os, 'windows') - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} components: clippy, rustfmt - uses: taiki-e/install-action@v2 with: tool: nextest - uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.os }}-${{ matrix.toolchain }}-${{ matrix.target }} env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" - name: Run tests (cargo test) run: cargo nextest run --all-features --workspace - name: Run doc tests (cargo test) run: cargo test --doc --all-features --workspace - name: Run example tests (cargo test) run: cargo nextest run --all-features --examples --workspace --no-tests=pass test-rust-linux-musl: strategy: matrix: include: - os: ubuntu-latest toolchain: stable target: x86_64-unknown-linux-musl - os: ubuntu-24.04-arm toolchain: stable target: aarch64-unknown-linux-musl name: test-rust-linux-musl-${{ matrix.toolchain }}-${{ matrix.os }}-${{ matrix.target }} runs-on: ${{ matrix.os }} needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.os }}-${{ matrix.toolchain }}-${{ matrix.target }} env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" - name: Install Zig if: contains(matrix.target, 'unknown-linux-musl') uses: mlugg/setup-zig@v2 with: version: 0.13.0 - name: Install cargo-zigbuild run: cargo install cargo-zigbuild --locked - name: Build Workspace run: | cargo zigbuild -p rama -p rama-cli --target ${{ matrix.target }} --all-features - name: Setup Docker buildx uses: docker/setup-buildx-action@v3 - name: Scratch run test (rama-cli, with diagnostics) shell: bash run: | set -euo pipefail bin="target/${{ matrix.target }}/debug/rama" echo "Binary path: $bin" ls -lah "$bin" echo "file:" file "$bin" || true echo "readelf program headers (interpreter should be absent for static):" readelf -l "$bin" || true echo "readelf dynamic section (NEEDED should be absent for static):" readelf -d "$bin" || true dir="$(mktemp -d)" cp "$bin" "$dir/rama" chmod +x "$dir/rama" cat > "$dir/Dockerfile" <<'EOF' FROM scratch COPY rama /rama ENTRYPOINT ["/rama"] EOF echo "Building scratch image" docker buildx build --progress=plain --load -t rama-cli-scratch-test "$dir" echo "Smoke test in scratch: --help" docker run --rm rama-cli-scratch-test --help || true echo "HTTPS test in scratch" set +e docker run --rm --name rama-cli-scratch-test-run \ rama-cli-scratch-test -k https://example.com rc="$?" set -e if [ "$rc" -ne 0 ]; then echo "rama-cli exited with code $rc" echo "docker inspect:" docker inspect rama-cli-scratch-test-run || true echo "docker logs:" docker logs rama-cli-scratch-test-run || true echo "docker rm:" docker rm -f rama-cli-scratch-test-run >/dev/null 2>&1 || true exit "$rc" fi precheck-rust-tier2: strategy: matrix: thing: - arm-android - arm64-android - i686-android - x86_64-android - aarch64-ios - x86_64-ios include: - custom_env: {} - thing: arm-android target: armv7-linux-androideabi rust: stable os: ubuntu-latest - thing: arm64-android target: aarch64-linux-android rust: stable os: ubuntu-latest - thing: i686-android target: i686-linux-android rust: stable os: ubuntu-latest - thing: x86_64-android target: x86_64-linux-android rust: stable os: ubuntu-latest - thing: aarch64-ios target: aarch64-apple-ios rust: stable os: macos-latest custom_env: IPHONEOS_DEPLOYMENT_TARGET: 17.5 - thing: x86_64-ios target: x86_64-apple-ios os: macos-latest rust: stable custom_env: IPHONEOS_DEPLOYMENT_TARGET: 17.5 needs: - precheck-rust - precheck-docs runs-on: ${{ matrix.os }} env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 with: env-vars: "RUST_TOOLCHAIN=${{ matrix.rust }}" key: ${{ matrix.os }}-${{ matrix.rust }}-${{ matrix.target }} - run: rustup target add ${{ matrix.target }} - name: Configure Android toolchain env if: endsWith(matrix.thing, '-android') shell: bash run: | ANDROID_API=21 # Normalize NDK env vars echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$ANDROID_NDK}" >> "$GITHUB_ENV" echo "ANDROID_NDK=${ANDROID_NDK:-$ANDROID_NDK_HOME}" >> "$GITHUB_ENV" TOOLCHAIN="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" echo "${TOOLCHAIN}" >> "$GITHUB_PATH" TRIPLE="${{ matrix.target }}" TARGET_UPPER="$(echo "${TRIPLE}" | tr '[:lower:]-' '[:upper:]_')" TARGET_LOWER_UNDERSCORES="$(echo "${TRIPLE}" | tr '-' '_' | tr '[:upper:]' '[:lower:]')" NDK_TRIPLE="$(echo "${TRIPLE}" | sed 's/^armv7/armv7a/')" CC_PATH="${TOOLCHAIN}/${NDK_TRIPLE}${ANDROID_API}-clang" CXX_PATH="${TOOLCHAIN}/${NDK_TRIPLE}${ANDROID_API}-clang++" AR_PATH="${TOOLCHAIN}/llvm-ar" echo "CARGO_TARGET_${TARGET_UPPER}_LINKER=${CXX_PATH}" >> "$GITHUB_ENV" # Target specific for cc-rs style echo "CC_${TARGET_LOWER_UNDERSCORES}=${CC_PATH}" >> "$GITHUB_ENV" echo "CXX_${TARGET_LOWER_UNDERSCORES}=${CXX_PATH}" >> "$GITHUB_ENV" echo "AR_${TARGET_LOWER_UNDERSCORES}=${AR_PATH}" >> "$GITHUB_ENV" # Also set generic vars in case your Config only reads CC/CXX/AR echo "CC=${CC_PATH}" >> "$GITHUB_ENV" echo "CXX=${CXX_PATH}" >> "$GITHUB_ENV" echo "AR=${AR_PATH}" >> "$GITHUB_ENV" - name: check env: ${{ matrix.custom_env }} run: | cargo check --workspace --target ${{ matrix.target }} --all-features - name: Build tests env: ${{ matrix.custom_env }} # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. run: | cargo build --target ${{ matrix.target }} --tests --all-features test-rust-extra: runs-on: ubuntu-latest needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{env.RUST_TOOLCHAIN}} - uses: taiki-e/install-action@v2 with: tool: nextest - uses: Swatinem/rust-cache@v2 with: env-vars: "RUST_TOOLCHAIN=${{env.RUST_TOOLCHAIN}}" key: ubuntu-latest-${{env.RUST_TOOLCHAIN}} # Extra: Loom Tests - name: Run Loom tests (rama-utils) run: | RUSTFLAGS="--cfg loom -Dwarnings" \ cargo nextest run --all-features -p rama-utils # Extra: Cargo Hack - name: install cargo-hack uses: taiki-e/install-action@cargo-hack - name: cargo hack check run: cargo hack check --each-feature --no-dev-deps --workspace # Extra: Cargo Deny - name: cargo deny uses: EmbarkStudios/cargo-deny-action@v2 with: rust-version: ${{env.RUST_TOOLCHAIN}} # Extra: sort dependencies (Cargo) - name: cargo sort (dependencies) run: | cargo install cargo-sort 2>/dev/null || true cargo sort --workspace --grouped --check test-rust-e2e: strategy: matrix: toolchain: - stable os: - ubuntu-latest - macos-latest runs-on: ${{ matrix.os }} needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: ilammy/setup-nasm@v1 if: startsWith(matrix.os, 'windows') - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} components: clippy, rustfmt - uses: taiki-e/install-action@v2 with: tool: nextest - uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.os }}-${{ matrix.toolchain }} env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" - name: Run tests (--ignored) run: | cargo nextest run --all-features --workspace --run-ignored=only test-rust-e2e-release: strategy: matrix: toolchain: - stable os: - ubuntu-latest runs-on: ${{ matrix.os }} needs: - precheck-rust - precheck-docs env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: ilammy/setup-nasm@v1 if: startsWith(matrix.os, 'windows') - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} components: clippy, rustfmt - uses: taiki-e/install-action@v2 with: tool: nextest - uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.os }}-${{ matrix.toolchain }} env-vars: "RUST_TOOLCHAIN=${{ matrix.toolchain }}" - name: Run tests in release mode (--ignored) run: | cargo nextest run --all-features --release --workspace --run-ignored=only test-ws-autobahn: runs-on: ubuntu-latest needs: - precheck-rust - precheck-docs timeout-minutes: 90 env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_TOOLCHAIN }} - uses: Swatinem/rust-cache@v2 with: key: ubuntu-latest-${{ env.RUST_TOOLCHAIN }} env-vars: "RUST_TOOLCHAIN=${{env.RUST_TOOLCHAIN}}" - name: Install jq run: sudo apt-get update && sudo apt-get install -y jq - name: Run autobahn ws server test-suite shell: bash working-directory: "./rama-ws" run: ./autobahn/server.sh - name: Run autobahn ws client test-suite shell: bash working-directory: "./rama-ws" run: ./autobahn/client.sh fuzz-rust: runs-on: ubuntu-latest needs: - precheck-rust - precheck-docs steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly with: toolchain: ${{env.RUST_TOOLCHAIN_NIGHTLY}} - name: Install cargo-fuzz run: | cargo install cargo-fuzz 2>/dev/null || true - name: fuzz — ua_parse run: | cargo +nightly fuzz run ua_parse -- -max_len=131072 -max_total_time=30 - name: fuzz — http_header_x_robots_tag run: | cargo +nightly fuzz run http_header_x_robots_tag -- -max_len=131072 -max_total_time=30 - name: fuzz — h2 run: | cargo +nightly fuzz run h2_client -- -max_total_time=30 cargo +nightly fuzz run h2_e2e -- -max_total_time=30 cargo +nightly fuzz run h2_hpack -- -max_total_time=30 test-spec-h2: runs-on: ubuntu-latest needs: - precheck-rust - precheck-docs steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{env.RUST_TOOLCHAIN}} - name: h2spec run: | bash rama-http-core/ci/h2spec.sh -F semver-checks: runs-on: ubuntu-latest needs: - precheck-rust - precheck-docs steps: - uses: actions/checkout@v6 - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 with: rust-toolchain: ${{env.RUST_TOOLCHAIN}} deploy-rama-cli-docker: runs-on: ubuntu-latest needs: - precheck-rust-tier2 - check-rust - test-rust-base - test-rust-linux-musl - test-rust-extra - test-rust-e2e - test-rust-e2e-release - test-ws-autobahn - fuzz-rust - test-spec-h2 - semver-checks if: github.ref == 'refs/heads/main' steps: - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: push: true tags: glendc/rama:edge file: rama-cli/infra/Dockerfile deploy-rama-fp-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/fp flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} deploy-rama-fp-h1-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/fp-h1 flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} deploy-rama-ip-ipv4-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/ipv4 flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} deploy-rama-ip-ipv6-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/ipv6 flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} deploy-rama-echo-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/echo flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} deploy-rama-http-test-fly: runs-on: ubuntu-latest needs: deploy-rama-cli-docker steps: - uses: actions/checkout@v6 - uses: superfly/flyctl-actions/setup-flyctl@master - run: | cd rama-fp/infra/deployments/http-test flyctl deploy --verbose --remote-only env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}