diff --git a/.changeset/native-debug-builds-outside-release.md b/.changeset/native-debug-builds-outside-release.md new file mode 100644 index 0000000000..b78331575e --- /dev/null +++ b/.changeset/native-debug-builds-outside-release.md @@ -0,0 +1,7 @@ +--- +"@prosopo/native-ja4": patch +"@prosopo/native-merkle": patch +"@prosopo/native-puzzle": patch +--- + +The Rust addons now build in debug mode unless `NODE_ENV=production`, so local builds and CI tests stop paying for full link-time optimisation. Production builds (npm publish, the provider image) still run `build:release` with full LTO. Dependencies are optimised even in debug so tests stay fast, and the CI `cargo test` job now runs in debug with a cargo cache instead of a cold release build. diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml new file mode 100644 index 0000000000..54b4f80752 --- /dev/null +++ b/.github/workflows/cargo-test.yml @@ -0,0 +1,82 @@ +# The napi crates carry Rust unit tests that `pnpm run test:all` never runs: +# turbo only drives the JS suites. Its own workflow, not a tests.yml job, so a +# Rust failure is reported on its own and does not wait for the full JS build, +# and so it runs only when a crate can have changed. + +name: cargo-test + +on: + pull_request: + branches: [main, dev, staging, release/*] + # Safe against the "required check never reports" trap: required-check.yml + # publishes `check` with no path filter and waits on every check run, so a + # PR touching neither path still gets a `check`. + paths: + - 'packages/native-*/**' + - '.github/workflows/cargo-test.yml' + types: + - opened + - synchronize + - reopened + - ready_for_review + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: "0" + CARGO_PROFILE_DEV_DEBUG: line-tables-only + +defaults: + run: + shell: bash + +jobs: + cargo-test: + name: cargo test (${{ matrix.crate }}) + # Hosted for the same reason as tests.yml: this repository is public. + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + strategy: + fail-fast: false + matrix: + # Independent crates, each with its own lockfile. A new + # packages/native-* crate is not tested until it is listed here. + crate: + [packages/native-ja4, packages/native-merkle, packages/native-puzzle] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + submodules: false + + - name: Record the rustc version for the cache key + id: rustc-version + run: | + release=$(rustc -vV | sed -n 's/^release: //p') + commit=$(rustc -vV | sed -n 's/^commit-hash: //p') + if [ -z "$release" ]; then + echo "::error::could not read release from rustc -vV"; exit 1 + fi + echo "version=${release}${commit:+-${commit:0:9}}" >> "$GITHUB_OUTPUT" + + # The compiler goes in prefix-key, not just key: rust-cache derives its + # restore-keys from the prefix, so a version left outside it can restore a + # target/ built by another rustc, which cargo rejects with E0514. + - name: Cache cargo + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + with: + workspaces: ${{ matrix.crate }} + prefix-key: v0-rust-${{ steps.rustc-version.outputs.version }} + + # Debug, not --release: the release profile's fat LTO and single codegen + # unit are for the shipped addon, and only slow a test build down. The + # dependencies are still optimised via profile.dev.package."*". + - name: cargo test + working-directory: ${{ matrix.crate }} + run: cargo test --locked diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0129066d9b..aefcfcd0ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -162,8 +162,8 @@ jobs: # way CSS allows, and the ones that size it by *measuring* it have broken # repeatedly and silently: the widget renders, just at its minimum width. # - # Kept out of `check` for the same reason as cargo-test, and because it needs - # none of what that job builds -- no docker, no provider, no workspace build, + # Kept out of `check` so its failure is reported on its own, and because it + # needs none of what that job builds -- no docker, no provider, no workspace build, # since the package imports nothing from the workspace at runtime. It still # gates the merge: required-check.yml waits on every check run on the commit, # so there is no list to add this to. @@ -189,26 +189,3 @@ jobs: run: pnpm --filter @prosopo/widget-skeleton exec playwright install --with-deps chromium - run: pnpm --filter @prosopo/widget-skeleton run test:browser - - # The napi crates carry Rust unit tests that `pnpm run test:all` never runs: - # turbo only drives the JS suites. Kept out of `check` so a Rust failure is - # reported on its own and does not wait for the full JS build. - cargo-test: - name: cargo test (${{ matrix.crate }}) - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - strategy: - fail-fast: false - matrix: - # Independent crates, each with its own lockfile. A new - # packages/native-* crate is not tested until it is listed here. - crate: - [packages/native-ja4, packages/native-merkle, packages/native-puzzle] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - submodules: false - - - name: cargo test - working-directory: ${{ matrix.crate }} - run: cargo test --release --locked diff --git a/packages/native-ja4/Cargo.toml b/packages/native-ja4/Cargo.toml index 65dcb2b088..c73c4c6921 100644 --- a/packages/native-ja4/Cargo.toml +++ b/packages/native-ja4/Cargo.toml @@ -26,5 +26,10 @@ strip = "symbols" # Debug symbols for our own crate only. Dependency debuginfo is the bulk of a # dev build and nobody steps through third-party code here; CI narrows our own # to line tables via CARGO_PROFILE_DEV_DEBUG. +# +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-ja4/package.json b/packages/native-ja4/package.json index e76dd56177..7889d15aa7 100644 --- a/packages/native-ja4/package.json +++ b/packages/native-ja4/package.json @@ -20,10 +20,11 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", + "build:release": "napi build --platform --release", "test": "node --test \"tests/**/*.test.ts\"", - "test:cargo": "cargo test --release", + "test:cargo": "cargo test", "clean": "cargo clean && rm -f *.node index.js index.d.ts" }, "devDependencies": { diff --git a/packages/native-merkle/Cargo.toml b/packages/native-merkle/Cargo.toml index 720ca0b00b..9d3a64a951 100644 --- a/packages/native-merkle/Cargo.toml +++ b/packages/native-merkle/Cargo.toml @@ -30,5 +30,10 @@ strip = "symbols" # Debug symbols for our own crate only. Dependency debuginfo is the bulk of a # dev build and nobody steps through third-party code here; CI narrows our own # to line tables via CARGO_PROFILE_DEV_DEBUG. +# +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-merkle/package.json b/packages/native-merkle/package.json index 46b4262168..91f8a0196c 100644 --- a/packages/native-merkle/package.json +++ b/packages/native-merkle/package.json @@ -20,9 +20,10 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", - "test:cargo": "cargo test --release", + "build:release": "napi build --platform --release", + "test:cargo": "cargo test", "clean": "cargo clean && rm -f *.node index.js index.d.ts" }, "devDependencies": { diff --git a/packages/native-puzzle/Cargo.toml b/packages/native-puzzle/Cargo.toml index e1001883d6..9cfe613ce7 100644 --- a/packages/native-puzzle/Cargo.toml +++ b/packages/native-puzzle/Cargo.toml @@ -20,5 +20,9 @@ lto = true codegen-units = 1 strip = "symbols" +# Dependencies optimised even in debug: they compile once and are cached, so +# tests run near release speed while our own crate still rebuilds quickly. +# Releases keep full LTO via `build:release` (`NODE_ENV=production pnpm build`). [profile.dev.package."*"] debug = false +opt-level = 2 diff --git a/packages/native-puzzle/package.json b/packages/native-puzzle/package.json index b8ee52698e..8ec1165c3c 100644 --- a/packages/native-puzzle/package.json +++ b/packages/native-puzzle/package.json @@ -20,10 +20,11 @@ "targets": ["x86_64-unknown-linux-gnu"] }, "scripts": { - "build": "napi build --platform --release", + "build": "if [ \"$NODE_ENV\" = production ]; then pnpm run build:release; else pnpm run build:debug; fi", "build:debug": "napi build --platform", - "bench": "node bench.mjs", - "test:cargo": "cargo test --release", + "build:release": "napi build --platform --release", + "bench": "pnpm run build:release && node bench.mjs", + "test:cargo": "cargo test", "test": "node --test tests/dimensions.test.ts", "clean": "cargo clean && rm -f *.node" },