From 4ef0fb4fc9c23547329728fce2549de3740854c7 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Tue, 20 Oct 2026 12:02:35 +0100 Subject: [PATCH 1/5] Sharpen docs and tests around get_representative_least_absolute_residue functions --- Cargo.toml | 2 +- .../mat_polynomial_ring_zq/get.rs | 47 +++++++++++++++++-- src/integer_mod_q/mat_zq/get.rs | 27 +++++++++-- src/integer_mod_q/poly_over_zq/get.rs | 40 ++++++++++++++-- src/integer_mod_q/polynomial_ring_zq/get.rs | 45 ++++++++++++++++-- src/integer_mod_q/z_q/get.rs | 37 +++++++++++++-- 6 files changed, 182 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cad3cc9..9f12457c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qfall-math" -version = "0.2.0" +version = "0.2.1" edition = "2024" rust-version = "1.86" description = "Mathematical foundations for rapid prototyping of lattice-based cryptography" diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/get.rs b/src/integer_mod_q/mat_polynomial_ring_zq/get.rs index 80699185..b2b59252 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/get.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/get.rs @@ -40,8 +40,10 @@ impl MatPolynomialRingZq { /// Creates a [`MatPolyOverZ`] where each entry is a representative of the /// equivalence class of each entry from a [`MatPolynomialRingZq`] with coefficients centered around `0`. /// - /// The representation of the coefficients is in the range `[-modulus/2, modulus/2]` and - /// the representation of the polynomials is in the range `[0, modulus_polynomial)`. + /// Each polynomial is reduced modulo the modulus polynomial and + /// each of its coefficients is in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`, + /// i.e. `[-(modulus-1)/2, (modulus-1)/2]` for odd moduli and `(-modulus/2, modulus/2]` for even moduli. + /// Hence, the positive representative is chosen for the element `modulus/2` of even moduli. /// Use [`MatPolynomialRingZq::get_representative_least_nonnegative_residue`] if they should be /// in the range `[0, modulus)`. /// @@ -76,7 +78,7 @@ impl MatPolynomialRingZq { /// The representation of the coefficients is in the range `[0, modulus)` and /// the representation of the polynomials is in the range `[0, modulus_polynomial)`. /// Use [`MatPolynomialRingZq::get_representative_least_absolute_residue`] if they should be - /// in the range `[-modulus/2, modulus/2]`. + /// in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`. /// /// # Examples /// ``` @@ -511,6 +513,45 @@ mod test_get_representative_least_absolute_residue { let cmp_mat = MatPolyOverZ::from_str("[[3 10 0 -10]]").unwrap(); assert_eq!(cmp_mat, lar_mat); } + + /// Returns the coefficients `0, 1, ..., modulus - 1` and their representatives + /// according to `mod±` as defined in FIPS 204 as strings. + fn coefficients_and_representatives(modulus: i64) -> (String, String) { + let coefficients: Vec = (0..modulus).map(|value| value.to_string()).collect(); + let representatives: Vec = (0..modulus) + .map(|value| { + if value > modulus / 2 { + value - modulus + } else { + value + } + .to_string() + }) + .collect(); + (coefficients.join(" "), representatives.join(" ")) + } + + /// Checks whether every coefficient is mapped into `(-⌈modulus/2⌉, ⌊modulus/2⌋]` + /// for an odd and an even modulus, i.e. `modulus/2` is mapped to the positive representative. + #[test] + fn full_range() { + for modulus in [10, 11] { + let (coefficients, representatives) = coefficients_and_representatives(modulus); + // modulus polynomial `X^modulus + 1` s.t. no coefficient is reduced + let mat = MatPolynomialRingZq::from_str(&format!( + "[[{modulus} {coefficients}]] / {} 1 {}1 mod {modulus}", + modulus + 1, + "0 ".repeat(modulus as usize - 1) + )) + .unwrap(); + + let lar_mat = mat.get_representative_least_absolute_residue(); + + let cmp_mat = + MatPolyOverZ::from_str(&format!("[[{modulus} {representatives}]]")).unwrap(); + assert_eq!(cmp_mat, lar_mat); + } + } } #[cfg(test)] diff --git a/src/integer_mod_q/mat_zq/get.rs b/src/integer_mod_q/mat_zq/get.rs index 09871ac9..a270453c 100644 --- a/src/integer_mod_q/mat_zq/get.rs +++ b/src/integer_mod_q/mat_zq/get.rs @@ -26,7 +26,7 @@ impl MatZq { /// /// The values in the output matrix are in the range of `[0, modulus)`. /// Use [`MatZq::get_representative_least_absolute_residue`] if they should be - /// in the range `[-modulus/2, modulus/2]`. + /// in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`. /// /// Returns the matrix as a [`MatZ`]. /// @@ -52,8 +52,9 @@ impl MatZq { /// equivalence class of each entry from a [`MatZq`] with the /// representatives close to `0`. /// - /// The values in the output matrix are in the range of `[-modulus/2, modulus/2]`. - /// For even moduli, the positive representative is chosen for the element `modulus / 2`. + /// The values in the output matrix are in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`, + /// i.e. `[-(modulus-1)/2, (modulus-1)/2]` for odd moduli and `(-modulus/2, modulus/2]` for even moduli. + /// Hence, the positive representative is chosen for the element `modulus/2` of even moduli. /// Use [`MatZq::get_representative_least_nonnegative_residue`] if they should be /// in the range `[0, modulus)`. /// @@ -964,4 +965,24 @@ mod test_get_representative_least_absolute_residue { let mat_cmp = MatZ::from_str("[[0,1,2,3,4,5,-5,-4,-3,-2,-1,0]]").unwrap(); assert_eq!(mat_z.to_string(), mat_cmp.to_string()); } + + /// Checks whether every entry is mapped to the same representative as by + /// [`Zq::get_representative_least_absolute_residue`] for an odd and an even modulus. + #[test] + fn consistent_with_zq() { + for modulus in [256, 257] { + let mut mat_zq = MatZq::new(1, modulus, modulus); + for value in 0..modulus { + mat_zq.set_entry(0, value, value).unwrap(); + } + + let mat_z = mat_zq.get_representative_least_absolute_residue(); + + for value in 0..modulus { + let entry: Z = mat_z.get_entry(0, value).unwrap(); + let cmp = Zq::from((value, modulus)).get_representative_least_absolute_residue(); + assert_eq!(cmp, entry); + } + } + } } diff --git a/src/integer_mod_q/poly_over_zq/get.rs b/src/integer_mod_q/poly_over_zq/get.rs index d1eaaa5b..866e8d82 100644 --- a/src/integer_mod_q/poly_over_zq/get.rs +++ b/src/integer_mod_q/poly_over_zq/get.rs @@ -133,8 +133,9 @@ impl PolyOverZq { /// Returns a representative polynomial of the [`PolyOverZq`] element with coefficients centered around `0`. /// - /// The output [`PolyOverZ`] has coefficients in the range of `[-modulus/2, modulus/2]`. - /// For even moduli, the positive representative is chosen for the element `modulus / 2`. + /// The output [`PolyOverZ`] has coefficients in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`, + /// i.e. `[-(modulus-1)/2, (modulus-1)/2]` for odd moduli and `(-modulus/2, modulus/2]` for even moduli. + /// Hence, the positive representative is chosen for the element `modulus/2` of even moduli. /// Use [`PolyOverZq::get_representative_least_nonnegative_residue`] if they should be /// in the range `[0, modulus)`. /// @@ -164,7 +165,7 @@ impl PolyOverZq { /// /// The representation of the coefficients is in the range `[0, modulus)`. /// Use [`PolyOverZq::get_representative_least_absolute_residue`] if they should be - /// in the range `[-modulus/2, modulus/2]`. + /// in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`. /// /// # Examples /// ``` @@ -400,6 +401,39 @@ mod test_get_representative_least_absolute_residue { let cmp_poly = PolyOverZ::from_str("3 10 0 -10").unwrap(); assert_eq!(cmp_poly, poly_z); } + + /// Returns the coefficients `0, 1, ..., modulus - 1` and their representatives + /// according to `mod±` as defined in FIPS 204 as strings. + fn coefficients_and_representatives(modulus: i64) -> (String, String) { + let coefficients: Vec = (0..modulus).map(|value| value.to_string()).collect(); + let representatives: Vec = (0..modulus) + .map(|value| { + if value > modulus / 2 { + value - modulus + } else { + value + } + .to_string() + }) + .collect(); + (coefficients.join(" "), representatives.join(" ")) + } + + /// Checks whether every coefficient is mapped into `(-⌈modulus/2⌉, ⌊modulus/2⌋]` + /// for an odd and an even modulus, i.e. `modulus/2` is mapped to the positive representative. + #[test] + fn full_range() { + for modulus in [10, 11] { + let (coefficients, representatives) = coefficients_and_representatives(modulus); + let poly_zq = + PolyOverZq::from_str(&format!("{modulus} {coefficients} mod {modulus}")).unwrap(); + + let poly_z = poly_zq.get_representative_least_absolute_residue(); + + let cmp_poly = PolyOverZ::from_str(&format!("{modulus} {representatives}")).unwrap(); + assert_eq!(cmp_poly, poly_z); + } + } } #[cfg(test)] diff --git a/src/integer_mod_q/polynomial_ring_zq/get.rs b/src/integer_mod_q/polynomial_ring_zq/get.rs index 87ea98cb..b30c1fce 100644 --- a/src/integer_mod_q/polynomial_ring_zq/get.rs +++ b/src/integer_mod_q/polynomial_ring_zq/get.rs @@ -118,8 +118,9 @@ impl PolynomialRingZq { /// Returns a representative polynomial of the [`PolynomialRingZq`] element with coefficients centered around `0`. /// - /// The output [`PolyOverZ`] has coefficients in the range of `[-modulus/2, modulus/2]`. - /// For even moduli, the positive representative is chosen for the element `modulus / 2`. + /// The output [`PolyOverZ`] has coefficients in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`, + /// i.e. `[-(modulus-1)/2, (modulus-1)/2]` for odd moduli and `(-modulus/2, modulus/2]` for even moduli. + /// Hence, the positive representative is chosen for the element `modulus/2` of even moduli. /// Use [`PolynomialRingZq::get_representative_least_nonnegative_residue`] if they should be /// in the range `[0, modulus)`. /// @@ -150,7 +151,7 @@ impl PolynomialRingZq { /// The representation of the coefficients is in the range `[0, modulus)` and /// the representation of the polynomial is in the range `[0, modulus_polynomial)`. /// Use [`PolynomialRingZq::get_representative_least_absolute_residue`] if they should be - /// in the range `[-modulus/2, modulus/2]`. + /// in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`. /// /// # Examples /// ``` @@ -321,6 +322,44 @@ mod test_get_representative_least_absolute_residue { let cmp_poly = PolyOverZ::from_str("3 10 0 -10").unwrap(); assert_eq!(cmp_poly, poly_z); } + + /// Returns the coefficients `0, 1, ..., modulus - 1` and their representatives + /// according to `mod±` as defined in FIPS 204 as strings. + fn coefficients_and_representatives(modulus: i64) -> (String, String) { + let coefficients: Vec = (0..modulus).map(|value| value.to_string()).collect(); + let representatives: Vec = (0..modulus) + .map(|value| { + if value > modulus / 2 { + value - modulus + } else { + value + } + .to_string() + }) + .collect(); + (coefficients.join(" "), representatives.join(" ")) + } + + /// Checks whether every coefficient is mapped into `(-⌈modulus/2⌉, ⌊modulus/2⌋]` + /// for an odd and an even modulus, i.e. `modulus/2` is mapped to the positive representative. + #[test] + fn full_range() { + for modulus in [10, 11] { + let (coefficients, representatives) = coefficients_and_representatives(modulus); + // modulus polynomial `X^modulus + 1` s.t. no coefficient is reduced + let poly_ring = PolynomialRingZq::from_str(&format!( + "{modulus} {coefficients} / {} 1 {}1 mod {modulus}", + modulus + 1, + "0 ".repeat(modulus as usize - 1) + )) + .unwrap(); + + let poly_z = poly_ring.get_representative_least_absolute_residue(); + + let cmp_poly = PolyOverZ::from_str(&format!("{modulus} {representatives}")).unwrap(); + assert_eq!(cmp_poly, poly_z); + } + } } #[cfg(test)] diff --git a/src/integer_mod_q/z_q/get.rs b/src/integer_mod_q/z_q/get.rs index f9177f53..32ba1dca 100644 --- a/src/integer_mod_q/z_q/get.rs +++ b/src/integer_mod_q/z_q/get.rs @@ -18,7 +18,7 @@ impl Zq { /// /// The representation in the range `[0, modulus)` is returned. /// Use [`Zq::get_representative_least_absolute_residue`] if they should be - /// in the range `[-modulus/2, modulus/2]`. + /// in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`. /// /// # Examples /// ``` @@ -36,8 +36,9 @@ impl Zq { /// Returns the [`Z`] value of the [`Zq`] element with the representatives close to `0`. /// - /// The output value is in the range of `[-modulus/2, modulus/2]`. - /// For even moduli, the positive representative is chosen for the element `modulus / 2`. + /// The output value is in the range `(-⌈modulus/2⌉, ⌊modulus/2⌋]`, + /// i.e. `[-(modulus-1)/2, (modulus-1)/2]` for odd moduli and `(-modulus/2, modulus/2]` for even moduli. + /// Hence, the positive representative is chosen for the element `modulus/2` of even moduli. /// Use [`Zq::get_representative_least_nonnegative_residue`] if they should be /// in the range `[0, modulus)`. /// @@ -155,6 +156,36 @@ mod test_get_representative_least_absolute_residue { assert_eq!(res_1, Z::from(0)); assert_eq!(res_2, Z::from(-10)); } + + /// Checks whether an element of the upper half is mapped to its negative representative, + /// i.e. `256 mod 257` to `-1`. + #[test] + fn upper_half_negative() { + let value = Zq::from((256, 257)); + + let res = value.get_representative_least_absolute_residue(); + + assert_eq!(Z::MINUS_ONE, res); + } + + /// Checks whether every element is mapped into `(-⌈modulus/2⌉, ⌊modulus/2⌋]` + /// according to `mod±` as defined in FIPS 204 for an odd and an even modulus. + #[test] + fn full_range() { + for modulus in [256, 257] { + for value in 0..modulus { + let expected = if value > modulus / 2 { + value - modulus + } else { + value + }; + + let res = Zq::from((value, modulus)).get_representative_least_absolute_residue(); + + assert_eq!(Z::from(expected), res); + } + } + } } #[cfg(test)] From a5811553778015a942fdb7797c045b55e2632578 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Tue, 20 Oct 2026 12:06:12 +0100 Subject: [PATCH 2/5] Update min Rust version --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9f12457c..27a96769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "qfall-math" version = "0.2.1" edition = "2024" -rust-version = "1.86" +rust-version = "1.88" description = "Mathematical foundations for rapid prototyping of lattice-based cryptography" readme = "README.md" homepage = "https://qfall.github.io" diff --git a/README.md b/README.md index 6c41ff66..dc695840 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This `math`-crate is a memory-safe wrapper of [FLINT](https://flintlib.org/) in ## Quick-Start First, ensure that you use a Unix-like distribution (Linux or MacOS). Setup [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) if you're using Windows. This is required due to this crate's dependency on FLINT. -Then, make sure your `rustc --version` is `1.87` or newer. +Then, make sure your `rustc --version` is `1.88` or newer. Furthermore, it's required that `m4`, a C-compiler such as `gcc`, and `make` are installed. ```bash From a574fdbcd5f690603055935709867a7b4173967c Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Tue, 20 Oct 2026 12:15:27 +0100 Subject: [PATCH 3/5] Reformat to make clippy happy --- src/integer_mod_q/mat_zq/solve.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/integer_mod_q/mat_zq/solve.rs b/src/integer_mod_q/mat_zq/solve.rs index 7b4e28eb..dbbd109f 100644 --- a/src/integer_mod_q/mat_zq/solve.rs +++ b/src/integer_mod_q/mat_zq/solve.rs @@ -412,10 +412,10 @@ fn find_invertible_entry_column( fn find_not_invertible_entry_column(matrix: &MatZq, column: i64, used_rows: &[i64]) -> Option { for i in (0..matrix.get_num_rows()).filter(|x| !used_rows.contains(x)) { let entry: Zq = unsafe { matrix.get_entry_unchecked(i, column) }; - if !entry.is_zero() { - if let Err(_inv) = entry.pow(-1) { - return Some(i); - } + if !entry.is_zero() + && let Err(_inv) = entry.pow(-1) + { + return Some(i); } } None From fc9be0654eca1182a086158588cbe12129161eae Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Tue, 20 Oct 2026 12:45:28 +0100 Subject: [PATCH 4/5] Specify comment of test --- src/integer_mod_q/mat_polynomial_ring_zq/get.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/get.rs b/src/integer_mod_q/mat_polynomial_ring_zq/get.rs index b2b59252..26f42b6a 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/get.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/get.rs @@ -532,7 +532,7 @@ mod test_get_representative_least_absolute_residue { } /// Checks whether every coefficient is mapped into `(-⌈modulus/2⌉, ⌊modulus/2⌋]` - /// for an odd and an even modulus, i.e. `modulus/2` is mapped to the positive representative. + /// for an odd and an even modulus, i.e. `modulus/2` is mapped to the positive representative for even `modulus`. #[test] fn full_range() { for modulus in [10, 11] { From d37c988a165b869e6326d2312326aa39c51cb17c Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Tue, 20 Oct 2026 13:33:08 +0100 Subject: [PATCH 5/5] Update testing pipeline --- .github/workflows/main.yml | 49 ++++++++++++++++-------------- .github/workflows/pull_request.yml | 43 +++++++++++++++----------- .github/workflows/push.yml | 37 +++++++++++++--------- 3 files changed, 75 insertions(+), 54 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bb8a87c9..b199f4dd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,14 @@ on: push: branches: [main] +permissions: + contents: read + +# a new run for the same branch or pull request cancels the outdated run +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUSTDOCFLAGS: "-Dwarnings" @@ -12,29 +20,30 @@ jobs: full_pipeline: name: Full Pipeline runs-on: ubuntu-latest + timeout-minutes: 90 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup dtolnay/rust-toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: stable components: clippy, rustfmt - # load project cache to reduce compilation time + # The native FLINT library is compiled for the CPU of the runner it is built on. + # Hence, the cache may only be reused on runners with the same CPU (model and features). + - name: Determine CPU of the runner + id: cpu + run: | + grep -m1 'model name' /proc/cpuinfo + echo "hash=$({ grep -m1 'model name' /proc/cpuinfo; grep -m1 '^flags' /proc/cpuinfo; } | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" + + # load project cache to reduce compilation time, whose key additionally covers + # the Rust version, the Cargo.toml files, and build-relevant environment variables - name: Setup project cache - uses: actions/cache@v3 - continue-on-error: false + uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: release-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - restore-keys: release-${{ runner.os }}-cargo- + key: ${{ steps.cpu.outputs.hash }} - name: Set environment variables run: | @@ -46,18 +55,18 @@ jobs: - name: Build run: cargo build --release - name: Generate docs - run: cargo doc + run: cargo doc --no-deps - name: Run doc tests # Unit tests are run by tarpaulin run: cargo test --doc --verbose - name: Install cargo-tarpaulin - uses: baptiste0928/cargo-install@v2 + uses: baptiste0928/cargo-install@v3 with: crate: cargo-tarpaulin - name: Calculate test coverage run: cargo tarpaulin --out Html - name: Archive code coverage results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.PROJECT_NAME }}-code_coverage_report-v${{ env.PROJECT_VERSION }} path: tarpaulin-report.html @@ -70,7 +79,7 @@ jobs: # Cargo check for security issues - name: Install cargo-audit - uses: baptiste0928/cargo-install@v2 + uses: baptiste0928/cargo-install@v3 with: crate: cargo-audit - name: Security audit @@ -81,9 +90,3 @@ jobs: uses: dtolnay/install@cargo-outdated - name: Outdated dependencies run: cargo outdated --root-deps-only --exit-code 1 - - - name: Archive release build - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PROJECT_NAME }}-release_build-v${{ env.PROJECT_VERSION }} - path: target/release/${{ env.PROJECT_NAME }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 17e23fee..d841c414 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,6 +9,14 @@ on: branches: - "**" +permissions: + contents: read + +# a new run for the same branch or pull request cancels the outdated run +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUSTDOCFLAGS: "-Dwarnings" @@ -17,29 +25,30 @@ jobs: pipeline: name: Pipeline - code coverage + dependency check runs-on: ubuntu-latest + timeout-minutes: 90 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup dtolnay/rust-toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: stable components: clippy, rustfmt - # load project cache to reduce compilation time + # The native FLINT library is compiled for the CPU of the runner it is built on. + # Hence, the cache may only be reused on runners with the same CPU (model and features). + - name: Determine CPU of the runner + id: cpu + run: | + grep -m1 'model name' /proc/cpuinfo + echo "hash=$({ grep -m1 'model name' /proc/cpuinfo; grep -m1 '^flags' /proc/cpuinfo; } | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" + + # load project cache to reduce compilation time, whose key additionally covers + # the Rust version, the Cargo.toml files, and build-relevant environment variables - name: Setup project cache - uses: actions/cache@v3 - continue-on-error: false + uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ steps.cpu.outputs.hash }} - name: Set environment variables run: | @@ -51,18 +60,18 @@ jobs: - name: Build run: cargo build - name: Generate docs - run: cargo doc + run: cargo doc --no-deps - name: Run doc tests # Unit tests are run by tarpaulin run: cargo test --doc --verbose - name: Install cargo-tarpaulin - uses: baptiste0928/cargo-install@v2 + uses: baptiste0928/cargo-install@v3 with: crate: cargo-tarpaulin - name: Calculate test coverage run: cargo tarpaulin --out Html - name: Archive code coverage results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.PROJECT_NAME }}-code_coverage_report-v${{ env.PROJECT_VERSION }} path: tarpaulin-report.html @@ -75,7 +84,7 @@ jobs: # Cargo check for security issues - name: Install cargo-audit - uses: baptiste0928/cargo-install@v2 + uses: baptiste0928/cargo-install@v3 with: crate: cargo-audit - name: Security audit diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9342bc71..6d5e9907 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -7,6 +7,14 @@ on: - main - dev +permissions: + contents: read + +# a new run for the same branch or pull request cancels the outdated run +concurrency: + group: ${{ github.workflow_ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUSTDOCFLAGS: "-Dwarnings" @@ -15,36 +23,37 @@ jobs: pipeline: name: Pipeline - test and lints runs-on: ubuntu-latest + timeout-minutes: 90 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup dtolnay/rust-toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: stable components: clippy, rustfmt - # load project cache to reduce compilation time + # The native FLINT library is compiled for the CPU of the runner it is built on. + # Hence, the cache may only be reused on runners with the same CPU (model and features). + - name: Determine CPU of the runner + id: cpu + run: | + grep -m1 'model name' /proc/cpuinfo + echo "hash=$({ grep -m1 'model name' /proc/cpuinfo; grep -m1 '^flags' /proc/cpuinfo; } | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" + + # load project cache to reduce compilation time, whose key additionally covers + # the Rust version, the Cargo.toml files, and build-relevant environment variables - name: Setup project cache - uses: actions/cache@v3 - continue-on-error: false + uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ steps.cpu.outputs.hash }} - name: Update dependencies run: cargo update - name: Build run: cargo build - name: Generate docs - run: cargo doc + run: cargo doc --no-deps - name: Test run: cargo test --verbose