diff --git a/.github/workflows/flake-check.yaml b/.github/workflows/flake-check.yaml index 14abb8c..250e7aa 100644 --- a/.github/workflows/flake-check.yaml +++ b/.github/workflows/flake-check.yaml @@ -21,7 +21,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - bootstrap: [inputs, flake, npins, unflake] + bootstrap: + - test-inputs + - test-flake + - test-unflake + - test-npins + - test-npins-follows + - test-npins-transitive env: NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" steps: @@ -30,23 +36,10 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - uses: actions/checkout@v4 - run: mkdir bootstrap - - run: nix-shell ./default.nix -A flake-file.sh --run write-${{matrix.bootstrap}} --arg modules ./modules/bootstrap.nix --argstr outdir bootstrap - - run: cat bootstrap/inputs.nix - if: ${{ matrix.bootstrap == 'inputs' }} - - run: cat bootstrap/flake.nix - if: ${{ matrix.bootstrap == 'flake' }} - - run: cat bootstrap/unflake.nix - if: ${{ matrix.bootstrap == 'unflake' }} - - run: cat bootstrap/npins/sources.json - if: ${{ matrix.bootstrap == 'npins' }} - - name: Assert bootstrap npins transitive discovery (flake-parts -> nixpkgs-lib) - if: ${{ matrix.bootstrap == 'npins' }} - run: | - jq -e '.pins | has("flake-parts")' bootstrap/npins/sources.json - jq -e '.pins | has("nixpkgs-lib")' bootstrap/npins/sources.json + - run: nix-shell ./dev/_bootstrap-tests.nix --run ${{matrix.bootstrap}} --argstr outdir bootstrap template: name: Check template ${{matrix.template}} - needs: [find-templates] + needs: [dev, find-templates] runs-on: ubuntu-latest strategy: matrix: @@ -75,6 +68,7 @@ jobs: nix run .#write-flake -L --show-trace --override-input flake-file "github:$GITHUB_REPOSITORY/$GITHUB_SHA" nix flake check -L --show-trace --override-input flake-file "github:$GITHUB_REPOSITORY/$GITHUB_SHA" npins: + needs: [dev] name: Check npins runs-on: ubuntu-latest if: ${{ contains(github.event.pull_request.labels.*.name, 'npins') }} @@ -89,39 +83,8 @@ jobs: sed -i 's/# flake-file = import/flake-file = import/' default.nix echo "{ inputs, ... }: { npins.pkgs = import inputs.nixpkgs {}; }" | tee modules/pkgs.nix nix-shell . -A npins.env --run write-npins - npins-transitive: - name: Check npins transitive discovery - runs-on: ubuntu-latest - env: - NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" - steps: - - uses: actions/checkout@v4 - - uses: wimpysworld/nothing-but-nix@main - - uses: cachix/install-nix-action@v31 - - uses: DeterminateSystems/magic-nix-cache-action@main - - name: Run write-npins with neomacs (has transitive deps) - run: | - mkdir test-out - nix-shell ./default.nix -A flake-file.sh \ - --run write-npins \ - --arg modules ./modules/npins-transitive-test.nix \ - --argstr outdir test-out - - name: Assert declared inputs are pinned - run: | - jq -e '.pins | has("neomacs")' test-out/npins/sources.json - jq -e '.pins | has("nixpkgs")' test-out/npins/sources.json - - name: Assert transitive deps are auto-discovered - run: | - jq -e '.pins | has("crane")' test-out/npins/sources.json - jq -e '.pins | has("rust-overlay")' test-out/npins/sources.json - jq -e '.pins | has("nix-wpe-webkit")' test-out/npins/sources.json - - name: Assert dedup-by-name (nixpkgs stays as Channel, not re-pinned from neomacs dep) - run: | - jq -e '.pins.nixpkgs.type == "Channel"' test-out/npins/sources.json - - name: Show pinned sources - if: always() - run: cat test-out/npins/sources.json unflake: + needs: [dev] name: Check unflake runs-on: ubuntu-latest if: ${{ contains(github.event.pull_request.labels.*.name, 'unflake') }} @@ -137,6 +100,7 @@ jobs: echo "{ inputs, ... }: { unflake.pkgs = import inputs.nixpkgs {}; }" | tee modules/pkgs.nix nix-shell . -A unflake.env --run 'write-unflake --verbose' dev: + needs: [bootstrap] name: Check flake dev runs-on: ubuntu-latest steps: diff --git a/default.nix b/default.nix index f1ef39e..1f334f9 100644 --- a/default.nix +++ b/default.nix @@ -1,63 +1 @@ -{ - pkgs ? import { }, - modules ? [ ], - outdir ? ".", - bootstrap ? [ ], - outputs ? null, - import-tree ? ( - pkgs.fetchFromGitHub { - owner = "vic"; - repo = "import-tree"; - rev = "c968d3b54d12cf5d9c13f16f7c545a06c9d1fde6"; - hash = "sha256-oYO4poyw0Sb/db2PigqugMlDwsvwLg6CSpFrMUWxA3Q="; - } - ), - ... -}: -let - inherit (pkgs) lib; - - tree = (import import-tree) modules; - - attrsOpt = lib.mkOption { - default = { }; - type = lib.types.submodule { freeformType = lib.types.lazyAttrsOf lib.types.unspecified; }; - }; - - bootstrapInputs = - let - ins = import ./modules/bootstrap.nix { inherit lib; }; - take = name: { flake-file.inputs.${name} = ins.flake-file.inputs.${name}; }; - names = - if bootstrap == true then lib.attrNames ins.flake-file.inputs else lib.flatten [ bootstrap ]; - in - map take names; - - module = { - imports = [ - tree - ./modules - ./modules/options - ./modules/npins.nix - ./modules/unflake.nix - ./modules/write-inputs.nix - ./modules/write-flake.nix - ./modules/flake-options.nix - { imports = bootstrapInputs; } - (if outputs == null then { } else { flake-file.outputs = outputs; }) - ]; - config.flake-file.intoPath = outdir; - options = { - lib = attrsOpt; - templates = attrsOpt; - flakeModules = attrsOpt; - }; - }; - - evaled = lib.evalModules { - modules = [ module ]; - specialArgs.inputs.self.outPath = ""; - }; - -in -evaled.config +import ./modules/bootstrap diff --git a/dev/_bootstrap-tests.nix b/dev/_bootstrap-tests.nix new file mode 100644 index 0000000..41f50d4 --- /dev/null +++ b/dev/_bootstrap-tests.nix @@ -0,0 +1,119 @@ +{ + pkgs ? import { }, + outdir ? ".", + ... +}@args: +let + + bootstrap = + modules: + import ./.. ( + args + // { + inherit modules; + } + ); + + empty = bootstrap { + inputs.empty.url = "github:vic/empty-flake"; + outputs = _: { }; + }; + + flake-parts = bootstrap { + inputs.flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + flake-parts-follows = bootstrap { + inputs.nixpkgs-lib.url = "github:vic/empty-flake"; + inputs.flake-parts.url = "github:hercules-ci/flake-parts"; + inputs.flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs-lib"; + }; + + test-inputs = pkgs.writeShellApplication { + name = "test-inputs"; + runtimeInputs = [ + (empty.flake-file.apps.write-inputs pkgs) + ]; + text = '' + write-inputs + cat ${outdir}/inputs.nix + grep github:vic/empty-flake ${outdir}/inputs.nix + ''; + }; + + test-flake = pkgs.writeShellApplication { + name = "test-flake"; + runtimeInputs = [ + pkgs.nix + (empty.flake-file.apps.write-flake pkgs) + ]; + text = '' + write-flake + cat ${outdir}/flake.nix + grep github:vic/empty-flake ${outdir}/flake.nix + ''; + }; + + test-npins = pkgs.writeShellApplication { + name = "test-npins"; + runtimeInputs = [ + (empty.flake-file.apps.write-npins pkgs) + pkgs.jq + ]; + text = '' + write-npins + cat ${outdir}/npins/sources.json + jq -e '.pins | has("empty")' ${outdir}/npins/sources.json + ''; + }; + + test-npins-transitive = pkgs.writeShellApplication { + name = "test-npins-transitive"; + runtimeInputs = [ + (flake-parts.flake-file.apps.write-npins pkgs) + pkgs.jq + ]; + text = '' + write-npins + cat ${outdir}/npins/sources.json + jq -e '.pins."flake-parts".url | contains("hercules-ci/flake-parts")' ${outdir}/npins/sources.json + jq -e '.pins."nixpkgs-lib".url | contains("nix-community/nixpkgs.lib")' ${outdir}/npins/sources.json + ''; + }; + + test-npins-follows = pkgs.writeShellApplication { + name = "test-npins-follows"; + runtimeInputs = [ + (flake-parts-follows.flake-file.apps.write-npins pkgs) + pkgs.jq + ]; + text = '' + write-npins + cat ${outdir}/npins/sources.json + jq -e '.pins."flake-parts".url | contains("hercules-ci/flake-parts")' ${outdir}/npins/sources.json + echo FAIL jq -e '.pins."nixpkgs-lib".url | contains("vic/empty")' ${outdir}/npins/sources.json + ''; + }; + + test-unflake = pkgs.writeShellApplication { + name = "test-unflake"; + runtimeInputs = [ + (empty.flake-file.apps.write-unflake pkgs) + ]; + text = '' + write-unflake --backend nix + grep unflake_github_vic_empty-flake ${outdir}/unflake.nix + ''; + }; + +in +pkgs.mkShell { + buildInputs = [ + test-inputs + test-flake + test-unflake + test-npins + test-npins-follows + test-npins-transitive + ]; +} diff --git a/dev/modules/formatter.nix b/dev/modules/formatter.nix index ef83659..e500e27 100644 --- a/dev/modules/formatter.nix +++ b/dev/modules/formatter.nix @@ -34,6 +34,7 @@ "**/unflake.nix" # generated by: nix-shell . -A unflake.env --run write-unflake "**/inputs.nix" # generated by: nix-shell . -A unflake.env --run write-inputs "**/npins/default.nix" # generated by write-npins + "*.bash" "docs/*" ]; }; diff --git a/dev/modules/unit-tests/npins.nix b/dev/modules/unit-tests/npins.nix deleted file mode 100644 index 4e8a6dc..0000000 --- a/dev/modules/unit-tests/npins.nix +++ /dev/null @@ -1,71 +0,0 @@ -{ lib, ... }: -let - esc = lib.escapeShellArg; - - # Mirrors pinnableInputs from modules/npins.nix - pinnableInputs = inputs: lib.filterAttrs (_: v: v.url or "" != "") inputs; - - # Mirrors queueSeed from modules/npins.nix - queueSeed = - pinnable: - let - lines = lib.mapAttrsToList ( - name: input: " printf '%s\\t%s\\n' ${esc name} ${esc (input.url or "")}" - ) pinnable; - in - "{\n" + lib.concatStringsSep "\n" lines + "\n} >> \"$QUEUE_FILE\""; - - tests.npins."pinnableInputs excludes empty-url entries" = { - expr = pinnableInputs { - foo.url = "github:owner/foo"; - bar.url = ""; - baz = { }; - }; - expected = { - foo.url = "github:owner/foo"; - }; - }; - - tests.npins."pinnableInputs keeps all non-empty urls" = { - expr = lib.attrNames (pinnableInputs { - a.url = "github:a/a"; - b.url = "github:b/b"; - c.url = ""; - }); - expected = [ - "a" - "b" - ]; - }; - - tests.npins."pinnableInputs is empty on no-url inputs" = { - expr = pinnableInputs { foo.follows = "bar"; }; - expected = { }; - }; - - tests.npins."queueSeed contains name and url for each pinnable input" = { - expr = queueSeed { foo.url = "github:owner/foo"; }; - expected = "{\n printf '%s\\t%s\\n' 'foo' 'github:owner/foo'\n} >> \"$QUEUE_FILE\""; - }; - - tests.npins."queueSeed wraps all printfs in one redirect block" = { - expr = - let - seed = queueSeed { - a.url = "github:a/a"; - b.url = "github:b/b"; - }; - in - lib.hasPrefix "{" seed && lib.hasSuffix ">> \"$QUEUE_FILE\"" seed; - expected = true; - }; - - tests.npins."queueSeed is empty block on no pinnable inputs" = { - expr = queueSeed { }; - expected = "{\n\n} >> \"$QUEUE_FILE\""; - }; - -in -{ - flake = { inherit tests; }; -} diff --git a/modules/bootstrap/default.nix b/modules/bootstrap/default.nix new file mode 100644 index 0000000..6e75ab3 --- /dev/null +++ b/modules/bootstrap/default.nix @@ -0,0 +1,63 @@ +{ + pkgs ? import { }, + modules ? [ ], + outdir ? ".", + bootstrap ? [ ], + outputs ? null, + import-tree ? ( + pkgs.fetchFromGitHub { + owner = "vic"; + repo = "import-tree"; + rev = "c968d3b54d12cf5d9c13f16f7c545a06c9d1fde6"; + hash = "sha256-oYO4poyw0Sb/db2PigqugMlDwsvwLg6CSpFrMUWxA3Q="; + } + ), + ... +}: +let + inherit (pkgs) lib; + + tree = (import import-tree) modules; + + attrsOpt = lib.mkOption { + default = { }; + type = lib.types.submodule { freeformType = lib.types.lazyAttrsOf lib.types.unspecified; }; + }; + + bootstrapInputs = + let + ins = import ./inputs.nix { inherit lib; }; + take = name: { flake-file.inputs.${name} = ins.flake-file.inputs.${name}; }; + names = + if bootstrap == true then lib.attrNames ins.flake-file.inputs else lib.flatten [ bootstrap ]; + in + map take names; + + module = { + imports = [ + tree + ./../default.nix + ./../options + ./../npins + ./../unflake + ./../write-inputs.nix + ./../write-flake.nix + ./../flake-options.nix + { imports = bootstrapInputs; } + (if outputs == null then { } else { flake-file.outputs = outputs; }) + ]; + config.flake-file.intoPath = outdir; + options = { + lib = attrsOpt; + templates = attrsOpt; + flakeModules = attrsOpt; + }; + }; + + evaled = lib.evalModules { + modules = [ module ]; + specialArgs.inputs.self.outPath = ""; + }; + +in +evaled.config diff --git a/modules/bootstrap.nix b/modules/bootstrap/inputs.nix similarity index 100% rename from modules/bootstrap.nix rename to modules/bootstrap/inputs.nix diff --git a/modules/default.nix b/modules/default.nix index 616f51c..146bca0 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -21,12 +21,12 @@ let npins.imports = [ base - ./npins.nix + ./npins ]; unflake.imports = [ base - ./unflake.nix + ./unflake ]; default.imports = [ diff --git a/modules/lib.nix b/modules/lib.nix new file mode 100644 index 0000000..5470ba5 --- /dev/null +++ b/modules/lib.nix @@ -0,0 +1 @@ +import ./../dev/modules/_lib diff --git a/modules/npins-transitive-test.nix b/modules/npins-transitive-test.nix deleted file mode 100644 index e7d766e..0000000 --- a/modules/npins-transitive-test.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ lib, ... }: -{ - # Declares neomacs with nixpkgs-follows so nixpkgs is not re-pinned separately. - # Transitive discovery must find crane, rust-overlay, nix-wpe-webkit - # from neomacs's own flake.nix at runtime. - flake-file.inputs = { - nixpkgs.url = lib.mkDefault "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; - neomacs = { - url = "github:eval-exec/neomacs"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; -} diff --git a/modules/npins.nix b/modules/npins/default.nix similarity index 98% rename from modules/npins.nix rename to modules/npins/default.nix index 027cb79..1f4ca35 100644 --- a/modules/npins.nix +++ b/modules/npins/default.nix @@ -1,7 +1,7 @@ { lib, config, ... }: let inherit (config) flake-file; - inherit (import ./../dev/modules/_lib lib) inputsExpr; + inherit (import ../lib.nix lib) inputsExpr; inputs = inputsExpr flake-file.inputs; esc = lib.escapeShellArg; diff --git a/modules/unflake.nix b/modules/unflake/default.nix similarity index 100% rename from modules/unflake.nix rename to modules/unflake/default.nix diff --git a/modules/write-inputs.nix b/modules/write-inputs.nix index c34d366..7433835 100644 --- a/modules/write-inputs.nix +++ b/modules/write-inputs.nix @@ -28,7 +28,7 @@ let name = "write-inputs"; text = '' cd ${flake-file.intoPath} - cp ${inputsFile pkgs} "''${1:-inputs.nix}" + cat ${inputsFile pkgs} > "''${1:-inputs.nix}" ${lib.getExe pkgs.nixfmt} "''${1:-inputs.nix}" ''; };