diff --git a/.changeset/kan-682-hermetic-nix-build-test-suite-linux-darwin.md b/.changeset/kan-682-hermetic-nix-build-test-suite-linux-darwin.md new file mode 100644 index 000000000..0f3cd3b4e --- /dev/null +++ b/.changeset/kan-682-hermetic-nix-build-test-suite-linux-darwin.md @@ -0,0 +1,9 @@ +--- +bump: patch +--- + +CI now exercises a fully hermetic Nix build and the entire test suite on both +Linux and Darwin for pull requests into master. The workspace test suite runs +inside the Nix sandbox via a new flake check rather than an impure development +shell, and the package builds on darwin with the modern apple-sdk pattern so the +clipboard integration links correctly. This underpins nixpkgs support on macOS. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bc35973b..caf04942e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,21 +37,6 @@ jobs: - name: Run clippy run: nix develop --command cargo clippy --all-targets --all-features -- -D warnings - test: - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: cachix/install-nix-action@v27 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - extra_nix_config: | - experimental-features = nix-command flakes - - - name: Run tests - run: nix develop --command cargo test --all-features --workspace - test-windows: name: Test (Windows) runs-on: windows-latest @@ -65,6 +50,48 @@ jobs: - name: Run tests run: cargo test --all-features --workspace + hermetic: + name: Hermetic Build & Test (${{ matrix.system }}) + if: github.base_ref == 'master' + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + system: x86_64-linux + - os: macos-latest + system: aarch64-darwin + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v27 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + extra_nix_config: | + experimental-features = nix-command flakes + + # Persist /nix/store across runs so crane's cargoArtifacts and the package closure + # are restored instead of recompiled on each PR. + - uses: nix-community/cache-nix-action@v7 + with: + primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.lock', '**/Cargo.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}- + gc-max-store-size-linux: 5G + gc-max-store-size-macos: 5G + purge: true + purge-prefixes: nix-${{ runner.os }}- + purge-created: 0 + purge-primary-key: never + + # Build the release artifact the way nixpkgs would, inside the sandbox. + - name: Build package hermetically + run: nix build .#default -L + + # Build the workspace and run the full test suite inside the sandbox. + - name: Run test suite hermetically + run: nix build .#checks.${{ matrix.system }}.tests -L + build: name: Build runs-on: ubuntu-latest diff --git a/default.nix b/default.nix index c36227d81..5f7a39dfe 100644 --- a/default.nix +++ b/default.nix @@ -19,10 +19,14 @@ rustPlatform.buildRustPackage { cargoLock.lockFile = ./Cargo.lock; nativeBuildInputs = [ pkgs.pkg-config ]; - buildInputs = lib.optionals (pkgs.stdenv.isLinux && withTui) [ - pkgs.wayland - pkgs.xorg.libxcb - ]; + buildInputs = + lib.optionals (pkgs.stdenv.isLinux && withTui) [ + pkgs.wayland + pkgs.xorg.libxcb + ] + ++ lib.optionals (pkgs.stdenv.isDarwin && withTui) [ + pkgs.apple-sdk + ]; cargoBuildFlags = [ "--package" "kanban-cli" ] ++ lib.optionals (!withTui) [ "--no-default-features" ]; diff --git a/flake.lock b/flake.lock index a391334fb..32b876e3a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,20 @@ { "nodes": { + "crane": { + "locked": { + "lastModified": 1780532242, + "narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=", + "owner": "ipetkov", + "repo": "crane", + "rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -52,6 +67,7 @@ }, "root": { "inputs": { + "crane": "crane", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay" diff --git a/flake.nix b/flake.nix index 86170d739..b2bcf06a7 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; rust-overlay.url = "github:oxalica/rust-overlay"; flake-utils.url = "github:numtide/flake-utils"; + crane.url = "github:ipetkov/crane"; }; outputs = { @@ -10,6 +11,7 @@ nixpkgs, rust-overlay, flake-utils, + crane, ... }: flake-utils.lib.eachDefaultSystem ( @@ -18,11 +20,48 @@ pkgs = import nixpkgs { inherit system overlays; }; + lib = pkgs.lib; rustToolchain = pkgs.rust-bin.stable.latest.default.override { extensions = ["rust-src" "rust-analyzer" "clippy" "rustfmt"]; }; + # crane drives the fast, incremental per-PR test check. Dependencies are + # compiled once into cargoArtifacts (keyed on Cargo.lock) and reused, so + # CI only recompiles the first-party crates on each change. The released + # package stays on rustPlatform (default.nix) for nixpkgs parity. + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; + + # kanban-persistence-sqlite pulls schema.sql in via include_str!, so keep + # .sql files that crane's default source cleaner would otherwise strip. + sqlFilter = path: _type: builtins.match ".*\\.sql$" path != null; + srcFilter = path: type: + (sqlFilter path type) || (craneLib.filterCargoSources path type); + craneSrc = lib.cleanSourceWith { + src = self; + filter = srcFilter; + name = "source"; + }; + + commonArgs = { + src = craneSrc; + strictDeps = true; + pname = "kanban-workspace"; + version = (lib.importTOML ./Cargo.toml).workspace.package.version; + cargoExtraArgs = "--workspace --all-features"; + nativeBuildInputs = [pkgs.pkg-config]; + buildInputs = + lib.optionals pkgs.stdenv.isLinux [pkgs.wayland pkgs.xorg.libxcb] + ++ lib.optionals pkgs.stdenv.isDarwin [pkgs.apple-sdk]; + }; + + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + + workspaceTests = craneLib.cargoTest (commonArgs + // { + inherit cargoArtifacts; + }); + changeset = pkgs.writeShellApplication { name = "changeset"; runtimeInputs = with pkgs; [coreutils]; @@ -74,6 +113,8 @@ devShells.demo = import ./demo/shell.nix { inherit pkgs kanban; }; + checks.tests = workspaceTests; + packages = let kanban-cli = pkgs.callPackage ./default.nix { src = self; gitRev = self.rev or null; withTui = false; }; in {