chore: bump version to 0.1.4 #85
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Avoid running CI twice for branches with an open PR: `push` is | |
| # scoped to the default branch and release tags (so merges to main | |
| # and tag pushes still get CI), while feature branches get CI | |
| # exclusively through `pull_request`. | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| # Cancel any still-running CI for the same workflow + ref when a new | |
| # push lands; amend/force-push cycles no longer queue up obsolete runs. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Static checks + the dataset round-trip. Doesn't run the long | |
| # cargo-test suite; that's the job below. Splitting these two | |
| # lets fmt/clippy failures surface in under a minute even when | |
| # tests are in flight. | |
| checks: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| pkg-config \ | |
| libfontconfig1-dev | |
| - name: cargo fmt | |
| run: cargo fmt --all -- --check | |
| - name: cargo clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| - name: notebook executability | |
| # Splice each evcxr notebook's cells into one program and | |
| # compile + run it against this checkout, so a renamed module | |
| # or changed signature fails the build instead of a user's | |
| # kernel. Builds into the shared target dir, reusing deps from | |
| # the clippy step. See notebooks/check.py for the model/caveats. | |
| run: python3 notebooks/check.py | |
| - name: round-trip test (asset emit + tools/reproduce.sh + diff) | |
| run: | | |
| set -euo pipefail | |
| # Small but non-trivial input: ZZ12 n=7 enumerates 99 | |
| # rats and the asset uses the CLI default | |
| # `--target-block-bytes`, so it lands in a single block. | |
| # The round-trip exercise still validates manifest shape, | |
| # content-hash filenames, decode.py, reproduce.sh | |
| # invocation, and byte-identical re-emit. Total ~20s with | |
| # a warm cache. | |
| export SOURCE_DATE_EPOCH=1780000000 | |
| cargo build --release --bin rat_enum --features cli | |
| rm -rf /tmp/asset-original /tmp/repro | |
| ./target/release/rat_enum \ | |
| --ring 12 -n 7 --free \ | |
| --mode dafsa-blocks --threads 0 \ | |
| --oeis-a-number A316192 \ | |
| -o /tmp/asset-original | |
| # tools/decode.py round-trips through n_sequences. | |
| decoded=$(python3 /tmp/asset-original/tools/decode.py /tmp/asset-original | wc -l) | |
| declared=$(python3 -c "import json; print(json.load(open('/tmp/asset-original/block_index.json'))['n_sequences'])") | |
| test "$decoded" = "$declared" || { echo "decode.py produced $decoded lines, manifest says $declared"; exit 1; } | |
| # README's verification recipe: tools/verify_sha256.py | |
| # exits non-zero if any recorded sha256 disagrees with the | |
| # on-disk bytes. | |
| # --strict: also fail on non-pristine provenance. CI builds | |
| # rat_enum from a clean checkout, so the emitted asset must | |
| # carry a bare commit -- if it ever shows -dirty/unknown here, | |
| # the build environment regressed. | |
| python3 /tmp/asset-original/tools/verify_sha256.py /tmp/asset-original --strict | |
| # tools/count.py --verify re-derives the variableMeasured | |
| # sub-family sequences (free, oneSided, achiral, | |
| # rotationSymmetric, symmetric, subring, coset) from the | |
| # DAFSA and checks them against what the writer emitted into | |
| # ro-crate-metadata.json -- an independent (Python) check of | |
| # the (Rust) emitter on a real asset. | |
| python3 /tmp/asset-original/tools/count.py /tmp/asset-original --verify | tee /tmp/py-count.out | |
| # tools/verify_canonical.py independently recomputes each | |
| # stored rat's dihedral-canonical CCW form and asserts the | |
| # stored sequence equals it -- catches a non-canonical entry | |
| # (and, with the DAFSA's one-string-per-set guarantee, any | |
| # non-canonical duplicate) that a hash/count check cannot see. | |
| python3 /tmp/asset-original/tools/verify_canonical.py /tmp/asset-original | |
| # tools/verify.rs is the verifier the pipeline runs by | |
| # default (compiled + multi-threaded; the Python tools above | |
| # are the zero-dependency fallback). It performs the SAME | |
| # checks -- per-block sha256, canonical-CCW, and the seven | |
| # per-perimeter family re-derivation -- so CI runs it too and | |
| # asserts the two agree on every family series. A disagreement | |
| # means one of the two reimplementations (or the emitter) | |
| # drifted. | |
| cargo install rust-script --locked | |
| rust-script /tmp/asset-original/tools/verify.rs /tmp/asset-original | tee /tmp/rust-verify.out | |
| fams='^(free|oneSided|achiral|rotationSymmetric|symmetric|subring|coset):' | |
| grep -E "$fams" /tmp/rust-verify.out | sort > /tmp/rust-series.txt | |
| grep -E "$fams" /tmp/py-count.out | sort > /tmp/py-series.txt | |
| diff -u /tmp/py-series.txt /tmp/rust-series.txt \ | |
| || { echo "Rust verify.rs and Python count.py disagree on the family series"; exit 1; } | |
| grep -q '^# OK' /tmp/rust-verify.out \ | |
| || { echo "verify.rs did not report overall OK"; exit 1; } | |
| # Run tools/reproduce.sh against the local checkout and | |
| # the same SOURCE_DATE_EPOCH. The pre-built target dir | |
| # inside the clone is fresh, so cargo build inside the | |
| # clone DOES run; that's the cost of testing the real | |
| # path users will take. | |
| mkdir /tmp/repro | |
| ( cd /tmp/repro && \ | |
| REPO="$GITHUB_WORKSPACE" \ | |
| SRC_DIR=/tmp/repro/tilezz-local \ | |
| bash /tmp/asset-original/tools/reproduce.sh ) | |
| # Reproduced asset must match the original byte-for-byte | |
| # under fixed SOURCE_DATE_EPOCH (including | |
| # ro-crate-metadata.json -- timestamps are deterministic). | |
| # Exclude __pycache__: the verify_*/count tools above | |
| # import the sibling decode module, so Python writes | |
| # tools/__pycache__/ into the original; it's a transient | |
| # byproduct of running the tools, not part of the asset, and | |
| # the freshly reproduced copy (tools never run) has none. | |
| diff -r --exclude=__pycache__ \ | |
| /tmp/repro/tilezz-local/tilezz-rat-zz12-n7-free \ | |
| /tmp/asset-original | |
| # `cargo test --all-targets --all-features` in both profiles, in | |
| # parallel. The slowest combinatorial pins (oeis_a316192_*, deep | |
| # cross-validate, dihedral quotient) are `#[cfg_attr(debug_assertions, | |
| # ignore)]` so they only run in the release matrix entry -- in debug | |
| # mode they took 5-17 minutes each, vs. tens of seconds in release. | |
| # The debug job still covers every cheap test, including the i128- | |
| # overflow-sensitive arithmetic where `debug_assertions` panics on | |
| # the bugs `release` would silently wrap. | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| profile: [debug, release] | |
| name: test (${{ matrix.profile }}) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| # Separate cache per profile -- target/debug and target/release | |
| # are independent artifact trees. | |
| key: ${{ matrix.profile }} | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| pkg-config \ | |
| libfontconfig1-dev | |
| - name: cargo test | |
| run: | | |
| if [ "${{ matrix.profile }}" = "release" ]; then | |
| cargo test --release --all-targets --all-features | |
| else | |
| cargo test --all-targets --all-features | |
| fi | |
| # Tag-gated release pipeline. Fires only on `v*` pushes and only | |
| # after `checks` + the full `test` matrix go green. Verifies the | |
| # tag matches `Cargo.toml`'s version (so a stray `git tag v0.0.5` | |
| # doesn't publish 0.0.4 by accident), then publishes the crate. | |
| # Token: CARGO_REGISTRY_TOKEN repo secret (Settings -> Secrets | |
| # and variables -> Actions). Default features are empty by design, | |
| # so what lands on crates.io is the lean library; CLI binaries | |
| # stay behind the explicit `cli` feature. | |
| publish-crate: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: [checks, test] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| pkg-config \ | |
| libfontconfig1-dev | |
| - name: Verify tag matches Cargo.toml version | |
| run: | | |
| TAG_VERSION="${GITHUB_REF#refs/tags/v}" | |
| PKG_VERSION="$(grep '^version *= *"' Cargo.toml | head -1 | cut -d'"' -f2)" | |
| if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then | |
| echo "::error::Tag is v$TAG_VERSION but Cargo.toml says version = \"$PKG_VERSION\". Bump one or the other." | |
| exit 1 | |
| fi | |
| echo "OK: publishing tilezz $PKG_VERSION from tag v$TAG_VERSION" | |
| - name: cargo publish | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| run: cargo publish --locked |