diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3a6c81a..f07da5f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,32 +3,25 @@ updates: # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly + directory: '/' + schedule: { interval: weekly } groups: all-actions-version-updates: applies-to: version-updates - patterns: - - "*" + patterns: [ '*' ] all-actions-security-updates: applies-to: security-updates - patterns: - - "*" + patterns: [ '*' ] # Update Rust dependencies - package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - time: "02:00" + directory: '/' + schedule: { interval: daily, time: '02:00' } open-pull-requests-limit: 10 groups: all-cargo-version-updates: applies-to: version-updates - patterns: - - "*" + patterns: [ '*' ] all-cargo-security-updates: applies-to: security-updates - patterns: - - "*" + patterns: [ '*' ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fe191d..b0a0924 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,6 @@ on: branches: [ main ] pull_request: branches: [ main ] - release: - types: [ published ] workflow_dispatch: defaults: @@ -23,13 +21,8 @@ jobs: - 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 + with: { tool: 'just,cargo-binstall' } - run: just ci-test - - name: Check semver - uses: obi1kenobi/cargo-semver-checks-action@v2 test-nightly: name: Nightly-specific tests @@ -40,7 +33,7 @@ jobs: - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@v2 - with: { tool: just } + with: { tool: 'just' } - run: rustup install nightly --profile minimal - run: just test-publish @@ -53,7 +46,7 @@ jobs: - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@v2 - with: { tool: just } + with: { tool: 'just' } - name: Read MSRV id: msrv run: echo "value=$(just get-msrv)" >> $GITHUB_OUTPUT @@ -81,15 +74,16 @@ jobs: 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 ] + if: always() runs-on: ubuntu-latest steps: + - name: Result of the needed steps + run: echo "${{ toJSON(needs) }}" - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} run: exit 1 diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 6be67fd..b8c6334 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.4.0 + uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Approve Dependabot PRs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a99592..fc2e3a1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,12 @@ repos: args: [ --fix=lf ] - id: trailing-whitespace + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: forbid-tabs + - id: remove-tabs + - repo: local hooks: - id: cargo-fmt diff --git a/Cargo.toml b/Cargo.toml index 5702f6f..cfdfeae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,10 @@ name = "fastpfor" version = "0.6.2" description = "FastPFOR library written in Rust." -authors = ["Francisco Jimenez ", "Yuri Astrakhan "] +authors = [ + "Francisco Jimenez ", + "Yuri Astrakhan ", +] repository = "https://github.com/jjcfrancisco/fastpfor" edition = "2021" license = "MIT OR Apache-2.0" @@ -21,19 +24,20 @@ rust = ["dep:thiserror"] cxx = { version = "1.0.136", optional = true } thiserror = { version = "2.0.7", optional = true } -[dev-dependencies] -rand = "0.9.0" - [build-dependencies] cmake = { version = "0.1.52", optional = true } cxx-build = { version = "1.0.136", optional = true } +[dev-dependencies] +rand = "0.9.0" + [lints.rust] dead_code = "allow" unused_assignments = "allow" unused_qualifications = "warn" [lints.clippy] +cargo = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } cast_possible_truncation = "allow" cast_possible_wrap = "allow" @@ -41,6 +45,4 @@ cast_sign_loss = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" must_use_candidate = "allow" -needless_range_loop = "allow" -precedence = "allow" unreadable_literal = "allow" diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 16fe87b..11069ed 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -192,7 +192,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/README.md b/README.md index 268bf6d..ba6fb1a 100644 --- a/README.md +++ b/README.md @@ -118,5 +118,5 @@ Licensed under either of Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the -Apache-2.0 license, shall be dual licensed as above, without any +Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions. diff --git a/justfile b/justfile index 6a08b7a..63eaf53 100755 --- a/justfile +++ b/justfile @@ -1,63 +1,42 @@ #!/usr/bin/env just --justfile main_crate := 'fastpfor' +features_flag := '--all-features' # 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 { '' } +ci_mode := if env('CI', '') != '' {'1'} else {''} +# cargo-binstall needs a workaround due to caching +# ci_mode might be manually set by user, so re-check the env var +binstall_args := if env('CI', '') != '' {'--no-track'} 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 + {{just_executable()}} --list # 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 + cargo insta test --accept --unreferenced=delete {{features_flag}} {{args}} # Build the project build: - cargo build --workspace --all-targets --all-features + cargo build --workspace --all-targets {{features_flag}} # 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') - #!/usr/bin/env bash - set -euo pipefail - 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'" - if [ "$LOCAL_VERSION" = "$PUBLISHED_VERSION" ]; then - echo "ERROR: The current crate version has already been published." - exit 1 - else - echo "The current crate version has not yet been published." - fi + cargo check --workspace --all-targets {{features_flag}} # Generate code coverage report to upload to codecov.io -ci-coverage: && \ +ci-coverage: env-info && \ (coverage '--codecov --output-path target/llvm-cov/codecov.info') # ATTENTION: the full file path above is used in the CI workflow mkdir -p target/llvm-cov # Run all tests as expected by CI -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 +ci-test: env-info test-fmt build clippy test test-doc && assert-git-is-clean # Run minimal subset of tests to ensure compatibility with MSRV ci-test-msrv: env-info test @@ -69,31 +48,33 @@ clean: # Run cargo clippy to lint the code clippy *args: - cargo clippy --workspace --all-targets --all-features {{args}} + cargo clippy --workspace --all-targets {{features_flag}} {{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}} + cargo llvm-cov --workspace --all-targets {{features_flag}} --include-build-script {{args}} # Build and open code documentation -docs: - cargo doc --no-deps --all-features --open +docs *args='--open': + DOCS_RS=1 cargo doc --no-deps {{args}} --workspace {{features_flag}} # Print environment info env-info: @echo "Running {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}" + @echo "PWD $(pwd)" {{just_executable()}} --version rustc --version cargo --version rustup --version @echo "RUSTFLAGS='$RUSTFLAGS'" @echo "RUSTDOCFLAGS='$RUSTDOCFLAGS'" + @echo "RUST_BACKTRACE='$RUST_BACKTRACE'" # Reformat all code `cargo fmt`. If nightly is available, use it for better results fmt: #!/usr/bin/env bash set -euo pipefail - if rustup component list --toolchain nightly | grep rustfmt &> /dev/null; then + if (rustup toolchain list | grep nightly && 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 @@ -101,32 +82,39 @@ fmt: cargo fmt --all fi +# Reformat all Cargo.toml files using cargo-sort +fmt-toml *args: (cargo-install 'cargo-sort') + cargo sort --workspace --grouped {{args}} + # 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-crate-field field package=main_crate: (assert-cmd 'jq') + cargo metadata --format-version 1 | jq -e -r '.packages | map(select(.name == "{{package}}")) | first | .{{field}} | select(. != null)' # Get the minimum supported Rust version (MSRV) for the crate -get-msrv: (get-crate-field 'rust_version') +get-msrv package=main_crate: (get-crate-field 'rust_version' package) # Find the minimum supported Rust version (MSRV) using cargo-msrv extension, and update Cargo.toml msrv: (cargo-install 'cargo-msrv') - cargo msrv find --write-msrv --component rustfmt --all-features --ignore-lockfile -- {{just_executable()}} ci-test-msrv + cargo msrv find --write-msrv --component rustfmt {{features_flag}} --ignore-lockfile -- {{just_executable()}} ci-test-msrv + +# Run cargo-release +release *args='': (cargo-install 'release-plz') + release-plz {{args}} # Check semver compatibility with prior published version. Install it with `cargo install cargo-semver-checks` semver *args: (cargo-install 'cargo-semver-checks') - cargo semver-checks {{args}} + cargo semver-checks {{features_flag}} {{args}} -# Run all tests +# Run all unit and integration tests test: - cargo test --workspace --all-targets --all-features + cargo test --workspace --all-targets {{features_flag}} + cargo test --workspace --doc {{features_flag}} -# Test documentation -test-doc: - cargo test --doc --all-features - cargo doc --all-features --no-deps +# Test documentation generation +test-doc: (docs '') # Test code formatting -test-fmt: +test-fmt: && (fmt-toml '--check' '--check-format') cargo fmt --all -- --check # Use the experimental workspace publishing with --dry-run. Requires nightly Rust. @@ -135,7 +123,7 @@ test-publish: # Find unused dependencies. Install it with `cargo install cargo-udeps` udeps: (cargo-install 'cargo-udeps') - cargo +nightly udeps --all-targets --workspace --all-features + cargo +nightly udeps --workspace --all-targets {{features_flag}} # Update all dependencies, including breaking changes. Requires nightly toolchain (install with `rustup install nightly`) update: @@ -144,12 +132,23 @@ update: # Ensure that a certain command is available [private] -assert command: +assert-cmd 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 +# Make sure the git repo has no uncommitted changes +[private] +assert-git-is-clean: + @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 ;\ + git --no-pager diff ;\ + exit 1 ;\ + fi + # Check if a certain Cargo command is installed, and install it if needed [private] cargo-install $COMMAND $INSTALL_CMD='' *args='': @@ -160,7 +159,7 @@ cargo-install $COMMAND $INSTALL_CMD='' *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} --locked {{args}}" - cargo binstall ${INSTALL_CMD:-$COMMAND} --locked {{args}} + echo "$COMMAND could not be found. Installing it with cargo binstall ${INSTALL_CMD:-$COMMAND} {{binstall_args}} --locked {{args}}" + cargo binstall ${INSTALL_CMD:-$COMMAND} {{binstall_args}} --locked {{args}} fi fi diff --git a/src/rust/bytebuffer.rs b/src/rust/bytebuffer.rs index e3c0482..0678b4e 100644 --- a/src/rust/bytebuffer.rs +++ b/src/rust/bytebuffer.rs @@ -107,8 +107,8 @@ impl IntBuffer { // FIXME: why is self not used here?? assert!(offset + length as usize <= src.len(), "Buffer underflow"); let mut result: Vec = vec![]; - for i in offset..offset + length as usize { - result.extend_from_slice(&src[i].to_le_bytes()); + for value in &src[offset..offset + (length as usize)] { + result.extend_from_slice(&value.to_le_bytes()); } result } diff --git a/src/rust/integer_compression/bitpacking.rs b/src/rust/integer_compression/bitpacking.rs index d4080eb..cf3ff2a 100644 --- a/src/rust/integer_compression/bitpacking.rs +++ b/src/rust/integer_compression/bitpacking.rs @@ -1339,9 +1339,7 @@ pub fn fast_unpack(input: &[u32], inpos: usize, output: &mut [u32], outpos: usiz } fn fast_unpack0(output: &mut [u32], outpos: usize) { - for i in outpos..outpos + 32 { - output[i] = 0; - } + output[outpos..outpos + 32].fill(0); } fn fast_unpack1(input: &[u32], inpos: usize, output: &mut [u32], outpos: usize) { diff --git a/tests/basic_tests.rs b/tests/basic_tests.rs index 99ddd1a..fc511bd 100644 --- a/tests/basic_tests.rs +++ b/tests/basic_tests.rs @@ -1,4 +1,5 @@ #![cfg(feature = "rust")] +#![allow(clippy::needless_range_loop)] use std::io::Cursor; @@ -36,7 +37,7 @@ fn saul_test() { &mut output_offset, ) .unwrap_or_else(|e| { - panic!("Failed to compress with {}: {:?}", codec.name(), e); + panic!("Failed to compress with {}: {e:?}", codec.name()); }); let len = output_offset.position() as u32 - x; @@ -51,7 +52,7 @@ fn saul_test() { &mut Cursor::new(0), ) .unwrap_or_else(|e| { - panic!("Failed to uncompress with {}: {:?}", codec.name(), e); + panic!("Failed to uncompress with {}: {e:?}", codec.name()); }); assert_eq!(input, answer); @@ -81,7 +82,7 @@ fn test_varying_length() { &mut Cursor::new(0), ) .unwrap_or_else(|e| { - panic!("Failed to compress with {}: {:?}", codec.name(), e); + panic!("Failed to compress with {}: {e:?}", codec.name()); }); let mut answer = vec![0; l + 1024]; codec @@ -93,7 +94,7 @@ fn test_varying_length() { &mut Cursor::new(0), ) .unwrap_or_else(|e| { - panic!("Failed to uncompress with {}: {:?}", codec.name(), e); + panic!("Failed to uncompress with {}: {e:?}", codec.name()); }); for k in 0..l { assert_eq!(answer[k], data[k]); @@ -122,7 +123,7 @@ fn test_varying_length_two() { &mut Cursor::new(0), ) .unwrap_or_else(|e| { - panic!("Failed to compress with {}: {:?}", codec.name(), e); + panic!("Failed to compress with {}: {e:?}", codec.name()); }); let mut answer = vec![0; data_copy.len() + 1024]; codec @@ -134,7 +135,7 @@ fn test_varying_length_two() { &mut Cursor::new(0), ) .unwrap_or_else(|e| { - panic!("Failed to uncompress with {}: {:?}", codec.name(), e); + panic!("Failed to uncompress with {}: {e:?}", codec.name()); }); for k in 1..l { if answer[k] != data[k] { diff --git a/tomlfmt.toml b/tomlfmt.toml new file mode 100644 index 0000000..50d651e --- /dev/null +++ b/tomlfmt.toml @@ -0,0 +1,13 @@ +table_order = [ + 'package', + 'lib', + 'bin', + 'example', + 'bench', + 'features', + 'dependencies', + 'build-dependencies', + 'dev-dependencies', + 'profile', + 'lints', +]