diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c4c8b8..3a6c81a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,34 @@ version: 2 updates: -# Maintain dependencies for GitHub Actions -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" -- package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - time: "02:00" - open-pull-requests-limit: 10 + + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + groups: + all-actions-version-updates: + applies-to: version-updates + patterns: + - "*" + all-actions-security-updates: + applies-to: security-updates + patterns: + - "*" + + # Update Rust dependencies + - package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + time: "02:00" + open-pull-requests-limit: 10 + groups: + all-cargo-version-updates: + applies-to: version-updates + patterns: + - "*" + all-cargo-security-updates: + applies-to: security-updates + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5030feb..cdd6120 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,60 +18,90 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - uses: taiki-e/install-action@v2 - with: { tool: 'just,cargo-llvm-cov' } - uses: actions/checkout@v4 with: { submodules: recursive } + - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: { tool: just } - if: github.event_name == 'release' name: Ensure this crate has not yet been published (on release) run: just check-if-published - - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - uses: Swatinem/rust-cache@v2 - - run: RUST_BACKTRACE=1 just -v ci-test + - run: just ci-test - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 - - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - name: Generate code coverage - run: just ci-coverage - - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: target/llvm-cov/codecov.info - fail_ci_if_error: false - - name: Test packaging for publish - run: | - rustup install nightly --profile minimal - just test-publish - msrv: - name: Test MSRV + test-nightly: + name: Nightly-specific tests runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + with: { submodules: recursive } + - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@v2 with: { tool: just } + - run: rustup install nightly --profile minimal + - run: just test-publish + + test-msrv: + name: Test MSRV + runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 with: { submodules: recursive } - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: { tool: just } - name: Read MSRV id: msrv - run: echo "value=$(sed -ne 's/rust-version *= *\"\(.*\)\"/\1/p' Cargo.toml)" >> $GITHUB_OUTPUT + run: echo "value=$(just get-msrv)" >> $GITHUB_OUTPUT - name: Install MSRV Rust ${{ steps.msrv.outputs.value }} uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ steps.msrv.outputs.value }} components: rustfmt - - run: just -v ci-test-msrv + - run: just ci_mode=0 ci-test-msrv # Ignore warnings in MSRV - publish: - if: startsWith(github.ref, 'refs/tags/') + coverage: + name: Code Coverage + if: github.event_name != 'release' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: { submodules: recursive } + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: { tool: 'just,cargo-llvm-cov' } + - name: Generate code coverage + run: just ci-coverage + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: target/llvm-cov/codecov.info + fail_ci_if_error: true + + # This job checks if any of the previous jobs failed or were canceled. + # This approach also allows some jobs to be skipped if they are not needed. + ci-passed: + if: always() + needs: [ test, test-nightly, test-msrv, coverage ] + runs-on: ubuntu-latest + steps: + - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + release: + # Some dependencies of the `ci-passed` job might be skipped, but we still want to run if the `ci-passed` job succeeded. + if: always() && startsWith(github.ref, 'refs/tags/') && needs.ci-passed.result == 'success' name: Publish to crates.io - needs: [ test, msrv ] + needs: [ ci-passed ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: { submodules: recursive } - name: Publish to crates.io run: cargo publish env: diff --git a/.gitignore b/.gitignore index 9d51d3e..be4ca70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,12 @@ *.pdb .idea/ .vscode/ -Cargo.lock codecov* debug/ target/ temp/ tmp/ venv/ + +# This is a library, no lock +Cargo.lock diff --git a/justfile b/justfile index ca8fe02..6a08b7a 100755 --- a/justfile +++ b/justfile @@ -1,30 +1,40 @@ #!/usr/bin/env just --justfile +main_crate := 'fastpfor' + +# if running in CI, treat warnings as errors by setting RUSTFLAGS and RUSTDOCFLAGS to '-D warnings' unless they are already set +# Use `CI=true just ci-test` to run the same tests as in GitHub CI. +# Use `just env-info` to see the current values of RUSTFLAGS and RUSTDOCFLAGS +ci_mode := if env('CI', '') != '' { '1' } else { '' } +export RUSTFLAGS := env('RUSTFLAGS', if ci_mode == '1' {'-D warnings'} else {''}) +export RUSTDOCFLAGS := env('RUSTDOCFLAGS', if ci_mode == '1' {'-D warnings'} else {''}) +export RUST_BACKTRACE := env('RUST_BACKTRACE', if ci_mode == '1' {'1'} else {''}) + @_default: just --list -# Run tests, and accept their results. Requires insta to be installed. -bless: (cargo-install "cargo-insta") - TRYBUILD=overwrite cargo insta test --accept --all-features +# Run integration tests and save its output as the new expected output +bless *args: (cargo-install 'cargo-insta') + TRYBUILD=overwrite cargo insta test --accept --unreferenced=delete --all-features -# Default build -build *ARGS: - cargo build --all-targets --workspace --all-features {{ARGS}} +# Build the project +build: + cargo build --workspace --all-targets --all-features # Quick compile without building a binary check: cargo check --workspace --all-targets --all-features # Verify that the current version of the crate is not the same as the one published on crates.io -check-if-published: (assert "jq") +check-if-published: (assert 'jq') #!/usr/bin/env bash set -euo pipefail - LOCAL_VERSION="$(grep '^version =' Cargo.toml | sed -E 's/version = "([^"]*)".*/\1/')" - echo "Detected crate version: $LOCAL_VERSION" - CRATE_NAME="$(grep '^name =' Cargo.toml | head -1 | sed -E 's/name = "(.*)"/\1/')" - echo "Detected crate name: $CRATE_NAME" + LOCAL_VERSION="$({{just_executable()}} get-crate-field version)" + echo "Detected crate version: '$LOCAL_VERSION'" + CRATE_NAME="$({{just_executable()}} get-crate-field name)" + echo "Detected crate name: '$CRATE_NAME'" PUBLISHED_VERSION="$(cargo search ${CRATE_NAME} | grep "^${CRATE_NAME} =" | sed -E 's/.* = "(.*)".*/\1/')" - echo "Published crate version: $PUBLISHED_VERSION" + echo "Published crate version: '$PUBLISHED_VERSION'" if [ "$LOCAL_VERSION" = "$PUBLISHED_VERSION" ]; then echo "ERROR: The current crate version has already been published." exit 1 @@ -39,14 +49,18 @@ ci-coverage: && \ mkdir -p target/llvm-cov # Run all tests as expected by CI -ci-test: rust-info test-fmt - RUSTFLAGS='-D warnings' {{just_executable()}} build - {{just_executable()}} clippy -- -D warnings - RUSTFLAGS='-D warnings' {{just_executable()}} test - RUSTDOCFLAGS='-D warnings' {{just_executable()}} test-doc +ci-test: env-info test-fmt build clippy test test-doc + #!/usr/bin/env bash + set -euo pipefail + if [ -n "$(git status --untracked-files --porcelain)" ]; then + >&2 echo 'ERROR: git repo is no longer clean. Make sure compilation and tests artifacts are in the .gitignore, and no repo files are modified.' + >&2 echo '######### git status ##########' + git status + exit 1 + fi # Run minimal subset of tests to ensure compatibility with MSRV -ci-test-msrv: rust-info test +ci-test-msrv: env-info test # Clean all build artifacts clean: @@ -54,22 +68,32 @@ clean: rm -f Cargo.lock # Run cargo clippy to lint the code -clippy *ARGS: - cargo clippy --workspace --all-targets --all-features {{ARGS}} +clippy *args: + cargo clippy --workspace --all-targets --all-features {{args}} -# Generate code coverage report -coverage *ARGS="--no-clean --open": - cargo llvm-cov test --workspace --all-targets --all-features --include-build-script {{ARGS}} +# Generate code coverage report. Will install `cargo llvm-cov` if missing. +coverage *args='--no-clean --open': (cargo-install 'cargo-llvm-cov') + cargo llvm-cov --workspace --all-targets --all-features --include-build-script {{args}} # Build and open code documentation docs: cargo doc --no-deps --all-features --open +# Print environment info +env-info: + @echo "Running {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}" + {{just_executable()}} --version + rustc --version + cargo --version + rustup --version + @echo "RUSTFLAGS='$RUSTFLAGS'" + @echo "RUSTDOCFLAGS='$RUSTDOCFLAGS'" + # Reformat all code `cargo fmt`. If nightly is available, use it for better results fmt: #!/usr/bin/env bash set -euo pipefail - if command -v cargo +nightly &> /dev/null; then + if rustup component list --toolchain nightly | grep rustfmt &> /dev/null; then echo 'Reformatting Rust code using nightly Rust fmt to sort imports' cargo +nightly fmt --all -- --config imports_granularity=Module,group_imports=StdExternalCrate else @@ -77,26 +101,29 @@ fmt: cargo fmt --all fi +# Get any package's field from the metadata +get-crate-field field package=main_crate: + cargo metadata --format-version 1 | jq -r '.packages | map(select(.name == "{{package}}")) | first | .{{field}}' + +# Get the minimum supported Rust version (MSRV) for the crate +get-msrv: (get-crate-field 'rust_version') + # Find the minimum supported Rust version (MSRV) using cargo-msrv extension, and update Cargo.toml -msrv: +msrv: (cargo-install 'cargo-msrv') cargo msrv find --write-msrv --component rustfmt --all-features --ignore-lockfile -- {{just_executable()}} ci-test-msrv -rust-info: - rustc --version - cargo --version - # Check semver compatibility with prior published version. Install it with `cargo install cargo-semver-checks` -semver *ARGS: - cargo semver-checks {{ARGS}} +semver *args: (cargo-install 'cargo-semver-checks') + cargo semver-checks {{args}} # Run all tests -test *ARGS: - cargo test --all-targets --workspace --all-features {{ARGS}} +test: + cargo test --workspace --all-targets --all-features # Test documentation test-doc: cargo test --doc --all-features - cargo doc --no-deps --all-features + cargo doc --all-features --no-deps # Test code formatting test-fmt: @@ -107,7 +134,7 @@ test-publish: cargo +nightly -Z package-workspace publish --dry-run # Find unused dependencies. Install it with `cargo install cargo-udeps` -udeps: +udeps: (cargo-install 'cargo-udeps') cargo +nightly udeps --all-targets --workspace --all-features # Update all dependencies, including breaking changes. Requires nightly toolchain (install with `rustup install nightly`) @@ -117,23 +144,23 @@ update: # Ensure that a certain command is available [private] -assert $COMMAND: - @if ! type "{{COMMAND}}" > /dev/null; then \ - echo "Command '{{COMMAND}}' could not be found. Please make sure it has been installed on your computer." ;\ +assert command: + @if ! type {{command}} > /dev/null; then \ + echo "Command '{{command}}' could not be found. Please make sure it has been installed on your computer." ;\ exit 1 ;\ fi # Check if a certain Cargo command is installed, and install it if needed [private] -cargo-install $COMMAND $INSTALL_CMD="" *ARGS="": +cargo-install $COMMAND $INSTALL_CMD='' *args='': #!/usr/bin/env bash set -euo pipefail if ! command -v $COMMAND > /dev/null; then if ! command -v cargo-binstall > /dev/null; then - echo "$COMMAND could not be found. Installing it with cargo install ${INSTALL_CMD:-$COMMAND} {{ARGS}}" - cargo install ${INSTALL_CMD:-$COMMAND} {{ARGS}} + echo "$COMMAND could not be found. Installing it with cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}}" + cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}} else - echo "$COMMAND could not be found. Installing it with cargo binstall ${INSTALL_CMD:-$COMMAND} {{ARGS}}" - cargo binstall ${INSTALL_CMD:-$COMMAND} {{ARGS}} + echo "$COMMAND could not be found. Installing it with cargo binstall ${INSTALL_CMD:-$COMMAND} --locked {{args}}" + cargo binstall ${INSTALL_CMD:-$COMMAND} --locked {{args}} fi fi diff --git a/tests/common.rs b/tests/common.rs index 1d8bae2..bd78bea 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -7,8 +7,7 @@ use fastpfor::rust::{ DEFAULT_PAGE_SIZE, }; use rand::rngs::StdRng; -use rand::Rng as _; -use rand::SeedableRng as _; +use rand::{Rng as _, SeedableRng as _}; pub enum TestCodec { VariableByte(VariableByte, String), diff --git a/tests/cpp_compat_tests.rs b/tests/cpp_compat_tests.rs index 896d6ba..c85fec3 100644 --- a/tests/cpp_compat_tests.rs +++ b/tests/cpp_compat_tests.rs @@ -1,9 +1,10 @@ #![cfg(all(feature = "rust", feature = "cpp"))] +use std::io::Cursor; + use fastpfor::cpp::Codec32 as _; use fastpfor::rust::Integer as _; use fastpfor::{cpp, rust}; -use std::io::Cursor; mod common; use common::{get_test_cases, test_input_sizes};