From 1b14f9770d09db6b5ea2fae352eb67e19f10c99f Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Tue, 28 Jul 2026 10:54:25 -0300 Subject: [PATCH 1/3] flake: modernize the development shell and pin it to the real MSRV The shell had drifted far from what CI actually builds: nixpkgs was still on release-22.11 and the toolchain was pinned to 1.65.0, three years and seventeen Rust releases behind the 1.82.0 the CI matrix has been gating on. Anyone using the shell was therefore developing against a compiler the project no longer supports. Bump nixpkgs to nixos-26.05, refresh fenix and flake-utils, and pin the toolchain to 1.82.0. That is not merely CI's claim: the workspace uses std::iter::repeat_n, stabilized in 1.82, so 1.82.0 is the true floor. Cargo.lock is v4, which independently rules out anything below 1.78. Rename devShell to devShells.default, the modern form, so the shell can be discovered and built by name. Add what CI needs to run entirely inside the shell: socat for the listener example test, and llvm-tools plus cargo-llvm-cov for the coverage report. rustfmt stays on nightly because rustfmt.toml uses nightly-only options. --- .gitignore | 1 + flake.lock | 49 ++++++++++++++++++++++++++++++++++--------------- flake.nix | 18 +++++++++++++----- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 0706c839..3bf76189 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ *.pending-snap *.yaml /.direnv/ +/lcov.info diff --git a/flake.lock b/flake.lock index 1dc39fae..e224f646 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1649676176, - "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -17,17 +20,18 @@ }, "nixpkgs": { "locked": { - "lastModified": 1679748960, - "narHash": "sha256-BP8XcYHyj1NxQi04RpyNW8e7KiXSoI+Fy1tXIK2GfdA=", + "lastModified": 1785104993, + "narHash": "sha256-eKbrvPoAOFutbYMdbB3r5EQVmFxKv24iKqHPPUXA0gM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "da26ae9f6ce2c9ab380c0f394488892616fc5a6a", + "rev": "8623c4c20aa4ca2f5fb81510d2944066c3fb0d96", "type": "github" }, "original": { - "id": "nixpkgs", - "ref": "release-22.11", - "type": "indirect" + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" } }, "root": { @@ -45,11 +49,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1679725300, - "narHash": "sha256-by45QIEbKDOqTfXqd4YDfcSFqzGvdyq7OMuOche5H0k=", + "lastModified": 1785228137, + "narHash": "sha256-mEC1gtoL6bPy4QpurqhtNOGQeyaetspMIn4dmA+GkBs=", "owner": "nix-community", "repo": "fenix", - "rev": "d143afc6110296af610d7f77f54808e946d2e62d", + "rev": "62014c793a319d723cde515ad3f568dab03ed530", "type": "github" }, "original": { @@ -61,11 +65,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1679520343, - "narHash": "sha256-AJGSGWRfoKWD5IVTu1wEsR990wHbX0kIaolPqNMEh0c=", + "lastModified": 1785198566, + "narHash": "sha256-oV7S7ptvaqT1XTBZwDB4bZSHlS2sMCGQYoxTpu6MT4o=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "eb791f31e688ae00908eb75d4c704ef60c430a92", + "rev": "ffc323296a3500c6f4546ea6c966e54f4917fedd", "type": "github" }, "original": { @@ -74,6 +78,21 @@ "repo": "rust-analyzer", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index bcaec0f3..4b30cc28 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "UpdateHub Development Environment"; inputs = { - nixpkgs.url = "nixpkgs/release-22.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; flake-utils.url = "github:numtide/flake-utils"; rust = { @@ -16,20 +16,24 @@ let pkgs = nixpkgs.legacyPackages.${system}; + # The MSRV. CI builds and tests with this exact toolchain, so bumping + # it here is what bumps the minimum supported Rust version. rust-toolchain = with rust.packages.${system}; let msrv = toolchainOf { - channel = "1.65.0"; - sha256 = "sha256-DzNEaW724O8/B8844tt5AVHmSjSQ3cmzlU4BP90oRlY="; + channel = "1.82.0"; + sha256 = "sha256-yMuSb5eQPO/bHv+Bcf/US8LVMbf/G/0MSfiPwBhiPpk="; }; in combine [ - (msrv.withComponents [ "rustc" "cargo" "rust-src" "clippy" ]) + (msrv.withComponents [ "rustc" "cargo" "rust-src" "clippy" "llvm-tools" ]) + # rustfmt.toml uses nightly-only options, so rustfmt comes from + # nightly regardless of the MSRV used to build. (latest.withComponents [ "rustfmt" "rust-analyzer" ]) ]; in { - devShell = pkgs.mkShell { + devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ libarchive linuxHeaders @@ -40,11 +44,15 @@ cargo-insta cargo-limit + cargo-llvm-cov cargo-outdated cargo-release cargo-watch rust-toolchain + # used by the listener example test + socat + # used by excluded tests mtdutils ]; From c21b5c8d8f6a8a3ee4d7f6d94207dcdaa3bee88a Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Tue, 28 Jul 2026 10:54:31 -0300 Subject: [PATCH 2/3] fix: runtime_settings: drop the needless question mark when parsing With the v1-parsing feature enabled the or_else branch already yields the crate's own error type, so wrapping it back up as Ok(runtime_settings?) is a no-op that clippy rejects. Without the feature the types differ and the conversion is real, so plainly returning the value would not compile. map_err(Into::into) is correct either way: it performs the conversion when one is needed and resolves to the identity impl when it is not. Nightly clippy does not flag this today, which is why it went unnoticed; the MSRV clippy does. --- updatehub/src/runtime_settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updatehub/src/runtime_settings.rs b/updatehub/src/runtime_settings.rs index 3ec7b506..3215b15f 100644 --- a/updatehub/src/runtime_settings.rs +++ b/updatehub/src/runtime_settings.rs @@ -103,7 +103,7 @@ impl RuntimeSettings { .map(|s| RuntimeSettings { inner: s, v1_content: Some(content.to_string()) }) }); - Ok(runtime_settings?) + runtime_settings.map_err(Into::into) } fn save(&self) -> Result<()> { From 37cea41d411e1153e99231618d245b7e91887530 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Tue, 28 Jul 2026 10:54:42 -0300 Subject: [PATCH 3/3] ci: run the checks inside the flake's development shell CI is currently red for a reason that has nothing to do with the code: the actions-rs/* actions are archived and drag in actions/cache v2, which GitHub now fails automatically. They also duplicated the environment by hand, installing libarchive, protobuf and socat through apt and the toolchain through actions-rs/toolchain, so the versions CI used were never the versions the development shell provides. Use OSSystems/nix-actions instead and run every step inside the flake's shell, so contributors and CI build with the same pinned toolchain and the same native dependencies. The environment is now described in exactly one place, and a change to the shell is exercised by CI on the commit that makes it. A separate job runs nix flake check and builds the shell, so a broken flake is reported on its own rather than as a confusing failure inside the test job; it also warms the Nix store cache the other jobs restore. The MSRV/stable/nightly matrix collapses to a single job. The shell is pinned to the MSRV, so that job is the MSRV check. Coverage no longer needs a nightly cell because cargo-llvm-cov now comes from the shell, and clippy consequently runs at the MSRV, which is the compiler the code actually has to satisfy. Swatinem/rust-cache replaces the hand-rolled actions/cache plus the cargo-cache trimming step that existed to keep that cache from growing without bound. Note that this renames the check runs: the three "Test MSRV|stable|nightly - x86_64-unknown-linux-gnu" contexts become "Test - x86_64-unknown-linux-gnu" plus a new "Flake check", so branch protection needs updating to match. --- .github/workflows/code_style.yml | 28 +++++------ .github/workflows/linux.yml | 83 +++++++++----------------------- 2 files changed, 36 insertions(+), 75 deletions(-) diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml index b8116d13..2e048727 100644 --- a/.github/workflows/code_style.yml +++ b/.github/workflows/code_style.yml @@ -10,23 +10,21 @@ on: jobs: code_style: runs-on: ubuntu-latest + steps: - - name: Install Dependencies - run: sudo apt-get update; sudo apt-get install libarchive-dev protobuf-compiler - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + # rustfmt.toml uses nightly-only options, so the dev shell pairs a nightly + # rustfmt with the MSRV toolchain the rest of the shell provides. Clippy + # therefore runs at the MSRV, which is what the code has to build with. + - uses: ossystems/nix-actions@v1 with: - toolchain: nightly - override: true - components: rustfmt, clippy + install-nix: true + export-devshell: true + + - name: Cache build contents + uses: Swatinem/rust-cache@v2 - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --all --tests -- -D clippy::all + - name: Run cargo clippy + run: cargo clippy --locked --all-features --all --tests -- -D clippy::all diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4dd4e621..60094d96 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -8,73 +8,47 @@ on: pull_request: jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - name: [MSRV, stable, nightly] - include: - - name: MSRV - version: 1.82.0 - - name: stable - version: stable - - name: nightly - version: nightly - - name: Test ${{ matrix.name }} - x86_64-unknown-linux-gnu + # Evaluates the flake and builds the dev shell, so a broken flake is reported + # on its own instead of as a confusing failure inside the test job. It also + # warms the Nix store cache the test job restores. + nix: + name: Flake check runs-on: ubuntu-latest steps: - - name: Install Dependencies - run: sudo apt-get update -y; sudo apt-get install -y libarchive-dev libclang-dev protobuf-compiler socat - - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 + - uses: ossystems/nix-actions@v1 with: - toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu - profile: minimal - override: true + install-nix: true - - name: Install cargo tree tool - if: matrix.name == 'MSRV' - uses: actions-rs/install@v0.1 + build_and_test: + name: Test - x86_64-unknown-linux-gnu + runs-on: ubuntu-latest + + steps: + # Loads the flake's dev shell into the job, so every step below runs with + # the toolchain and the native dependencies (libarchive, protobuf, socat, + # ...) the flake pins. The dev shell is pinned to the MSRV, so the whole + # job doubles as the MSRV check. + - uses: ossystems/nix-actions@v1 with: - crate: cargo-tree - use-tool-cache: true + install-nix: true + export-devshell: true - name: Cache build contents - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }} + uses: Swatinem/rust-cache@v2 # Every other step below enables all the features, which leaves the # default feature set never being built. Check it on its own so a change # guarded by a feature flag cannot break the plain `cargo build`. - name: Check build with the default features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --release --all --bins --examples --tests + run: cargo check --locked --release --all --bins --examples --tests - name: Check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --release --all --bins --examples --tests --all-features + run: cargo check --locked --release --all --bins --examples --tests --all-features - name: Tests - uses: actions-rs/cargo@v1 timeout-minutes: 10 - with: - command: test - args: --locked --release --all --all-features --no-fail-fast -- --nocapture + run: cargo test --locked --release --all --all-features --no-fail-fast -- --nocapture - name: Run listener test run: | @@ -102,22 +76,11 @@ jobs: exit 4 fi - - name: Install cargo-llvm-cov - if: matrix.name == 'nightly' - uses: taiki-e/install-action@cargo-llvm-cov - - name: Generate code coverage - if: matrix.name == 'nightly' run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - name: Upload coverage to Coveralls - if: matrix.name == 'nightly' uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: lcov.info - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache