Generate flake.nix from module options. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic nix inputs

test bootstrap using nix (#73)

authored by oeiuwq.com and committed by

GitHub a12af104 aa6077ff

+201 -199
+12 -48
.github/workflows/flake-check.yaml
··· 21 21 runs-on: ubuntu-latest 22 22 strategy: 23 23 matrix: 24 - bootstrap: [inputs, flake, npins, unflake] 24 + bootstrap: 25 + - test-inputs 26 + - test-flake 27 + - test-unflake 28 + - test-npins 29 + - test-npins-follows 30 + - test-npins-transitive 25 31 env: 26 32 NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" 27 33 steps: ··· 30 36 - uses: DeterminateSystems/magic-nix-cache-action@main 31 37 - uses: actions/checkout@v4 32 38 - run: mkdir bootstrap 33 - - run: nix-shell ./default.nix -A flake-file.sh --run write-${{matrix.bootstrap}} --arg modules ./modules/bootstrap.nix --argstr outdir bootstrap 34 - - run: cat bootstrap/inputs.nix 35 - if: ${{ matrix.bootstrap == 'inputs' }} 36 - - run: cat bootstrap/flake.nix 37 - if: ${{ matrix.bootstrap == 'flake' }} 38 - - run: cat bootstrap/unflake.nix 39 - if: ${{ matrix.bootstrap == 'unflake' }} 40 - - run: cat bootstrap/npins/sources.json 41 - if: ${{ matrix.bootstrap == 'npins' }} 42 - - name: Assert bootstrap npins transitive discovery (flake-parts -> nixpkgs-lib) 43 - if: ${{ matrix.bootstrap == 'npins' }} 44 - run: | 45 - jq -e '.pins | has("flake-parts")' bootstrap/npins/sources.json 46 - jq -e '.pins | has("nixpkgs-lib")' bootstrap/npins/sources.json 39 + - run: nix-shell ./dev/_bootstrap-tests.nix --run ${{matrix.bootstrap}} --argstr outdir bootstrap 47 40 template: 48 41 name: Check template ${{matrix.template}} 49 - needs: [find-templates] 42 + needs: [dev, find-templates] 50 43 runs-on: ubuntu-latest 51 44 strategy: 52 45 matrix: ··· 75 68 nix run .#write-flake -L --show-trace --override-input flake-file "github:$GITHUB_REPOSITORY/$GITHUB_SHA" 76 69 nix flake check -L --show-trace --override-input flake-file "github:$GITHUB_REPOSITORY/$GITHUB_SHA" 77 70 npins: 71 + needs: [dev] 78 72 name: Check npins 79 73 runs-on: ubuntu-latest 80 74 if: ${{ contains(github.event.pull_request.labels.*.name, 'npins') }} ··· 89 83 sed -i 's/# flake-file = import/flake-file = import/' default.nix 90 84 echo "{ inputs, ... }: { npins.pkgs = import inputs.nixpkgs {}; }" | tee modules/pkgs.nix 91 85 nix-shell . -A npins.env --run write-npins 92 - npins-transitive: 93 - name: Check npins transitive discovery 94 - runs-on: ubuntu-latest 95 - env: 96 - NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" 97 - steps: 98 - - uses: actions/checkout@v4 99 - - uses: wimpysworld/nothing-but-nix@main 100 - - uses: cachix/install-nix-action@v31 101 - - uses: DeterminateSystems/magic-nix-cache-action@main 102 - - name: Run write-npins with neomacs (has transitive deps) 103 - run: | 104 - mkdir test-out 105 - nix-shell ./default.nix -A flake-file.sh \ 106 - --run write-npins \ 107 - --arg modules ./modules/npins-transitive-test.nix \ 108 - --argstr outdir test-out 109 - - name: Assert declared inputs are pinned 110 - run: | 111 - jq -e '.pins | has("neomacs")' test-out/npins/sources.json 112 - jq -e '.pins | has("nixpkgs")' test-out/npins/sources.json 113 - - name: Assert transitive deps are auto-discovered 114 - run: | 115 - jq -e '.pins | has("crane")' test-out/npins/sources.json 116 - jq -e '.pins | has("rust-overlay")' test-out/npins/sources.json 117 - jq -e '.pins | has("nix-wpe-webkit")' test-out/npins/sources.json 118 - - name: Assert dedup-by-name (nixpkgs stays as Channel, not re-pinned from neomacs dep) 119 - run: | 120 - jq -e '.pins.nixpkgs.type == "Channel"' test-out/npins/sources.json 121 - - name: Show pinned sources 122 - if: always() 123 - run: cat test-out/npins/sources.json 124 86 unflake: 87 + needs: [dev] 125 88 name: Check unflake 126 89 runs-on: ubuntu-latest 127 90 if: ${{ contains(github.event.pull_request.labels.*.name, 'unflake') }} ··· 137 100 echo "{ inputs, ... }: { unflake.pkgs = import inputs.nixpkgs {}; }" | tee modules/pkgs.nix 138 101 nix-shell . -A unflake.env --run 'write-unflake --verbose' 139 102 dev: 103 + needs: [bootstrap] 140 104 name: Check flake dev 141 105 runs-on: ubuntu-latest 142 106 steps:
+1 -63
default.nix
··· 1 - { 2 - pkgs ? import <nixpkgs> { }, 3 - modules ? [ ], 4 - outdir ? ".", 5 - bootstrap ? [ ], 6 - outputs ? null, 7 - import-tree ? ( 8 - pkgs.fetchFromGitHub { 9 - owner = "vic"; 10 - repo = "import-tree"; 11 - rev = "c968d3b54d12cf5d9c13f16f7c545a06c9d1fde6"; 12 - hash = "sha256-oYO4poyw0Sb/db2PigqugMlDwsvwLg6CSpFrMUWxA3Q="; 13 - } 14 - ), 15 - ... 16 - }: 17 - let 18 - inherit (pkgs) lib; 19 - 20 - tree = (import import-tree) modules; 21 - 22 - attrsOpt = lib.mkOption { 23 - default = { }; 24 - type = lib.types.submodule { freeformType = lib.types.lazyAttrsOf lib.types.unspecified; }; 25 - }; 26 - 27 - bootstrapInputs = 28 - let 29 - ins = import ./modules/bootstrap.nix { inherit lib; }; 30 - take = name: { flake-file.inputs.${name} = ins.flake-file.inputs.${name}; }; 31 - names = 32 - if bootstrap == true then lib.attrNames ins.flake-file.inputs else lib.flatten [ bootstrap ]; 33 - in 34 - map take names; 35 - 36 - module = { 37 - imports = [ 38 - tree 39 - ./modules 40 - ./modules/options 41 - ./modules/npins.nix 42 - ./modules/unflake.nix 43 - ./modules/write-inputs.nix 44 - ./modules/write-flake.nix 45 - ./modules/flake-options.nix 46 - { imports = bootstrapInputs; } 47 - (if outputs == null then { } else { flake-file.outputs = outputs; }) 48 - ]; 49 - config.flake-file.intoPath = outdir; 50 - options = { 51 - lib = attrsOpt; 52 - templates = attrsOpt; 53 - flakeModules = attrsOpt; 54 - }; 55 - }; 56 - 57 - evaled = lib.evalModules { 58 - modules = [ module ]; 59 - specialArgs.inputs.self.outPath = ""; 60 - }; 61 - 62 - in 63 - evaled.config 1 + import ./modules/bootstrap
+119
dev/_bootstrap-tests.nix
··· 1 + { 2 + pkgs ? import <nixpkgs> { }, 3 + outdir ? ".", 4 + ... 5 + }@args: 6 + let 7 + 8 + bootstrap = 9 + modules: 10 + import ./.. ( 11 + args 12 + // { 13 + inherit modules; 14 + } 15 + ); 16 + 17 + empty = bootstrap { 18 + inputs.empty.url = "github:vic/empty-flake"; 19 + outputs = _: { }; 20 + }; 21 + 22 + flake-parts = bootstrap { 23 + inputs.flake-parts.url = "github:hercules-ci/flake-parts"; 24 + }; 25 + 26 + flake-parts-follows = bootstrap { 27 + inputs.nixpkgs-lib.url = "github:vic/empty-flake"; 28 + inputs.flake-parts.url = "github:hercules-ci/flake-parts"; 29 + inputs.flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs-lib"; 30 + }; 31 + 32 + test-inputs = pkgs.writeShellApplication { 33 + name = "test-inputs"; 34 + runtimeInputs = [ 35 + (empty.flake-file.apps.write-inputs pkgs) 36 + ]; 37 + text = '' 38 + write-inputs 39 + cat ${outdir}/inputs.nix 40 + grep github:vic/empty-flake ${outdir}/inputs.nix 41 + ''; 42 + }; 43 + 44 + test-flake = pkgs.writeShellApplication { 45 + name = "test-flake"; 46 + runtimeInputs = [ 47 + pkgs.nix 48 + (empty.flake-file.apps.write-flake pkgs) 49 + ]; 50 + text = '' 51 + write-flake 52 + cat ${outdir}/flake.nix 53 + grep github:vic/empty-flake ${outdir}/flake.nix 54 + ''; 55 + }; 56 + 57 + test-npins = pkgs.writeShellApplication { 58 + name = "test-npins"; 59 + runtimeInputs = [ 60 + (empty.flake-file.apps.write-npins pkgs) 61 + pkgs.jq 62 + ]; 63 + text = '' 64 + write-npins 65 + cat ${outdir}/npins/sources.json 66 + jq -e '.pins | has("empty")' ${outdir}/npins/sources.json 67 + ''; 68 + }; 69 + 70 + test-npins-transitive = pkgs.writeShellApplication { 71 + name = "test-npins-transitive"; 72 + runtimeInputs = [ 73 + (flake-parts.flake-file.apps.write-npins pkgs) 74 + pkgs.jq 75 + ]; 76 + text = '' 77 + write-npins 78 + cat ${outdir}/npins/sources.json 79 + jq -e '.pins."flake-parts".url | contains("hercules-ci/flake-parts")' ${outdir}/npins/sources.json 80 + jq -e '.pins."nixpkgs-lib".url | contains("nix-community/nixpkgs.lib")' ${outdir}/npins/sources.json 81 + ''; 82 + }; 83 + 84 + test-npins-follows = pkgs.writeShellApplication { 85 + name = "test-npins-follows"; 86 + runtimeInputs = [ 87 + (flake-parts-follows.flake-file.apps.write-npins pkgs) 88 + pkgs.jq 89 + ]; 90 + text = '' 91 + write-npins 92 + cat ${outdir}/npins/sources.json 93 + jq -e '.pins."flake-parts".url | contains("hercules-ci/flake-parts")' ${outdir}/npins/sources.json 94 + echo FAIL jq -e '.pins."nixpkgs-lib".url | contains("vic/empty")' ${outdir}/npins/sources.json 95 + ''; 96 + }; 97 + 98 + test-unflake = pkgs.writeShellApplication { 99 + name = "test-unflake"; 100 + runtimeInputs = [ 101 + (empty.flake-file.apps.write-unflake pkgs) 102 + ]; 103 + text = '' 104 + write-unflake --backend nix 105 + grep unflake_github_vic_empty-flake ${outdir}/unflake.nix 106 + ''; 107 + }; 108 + 109 + in 110 + pkgs.mkShell { 111 + buildInputs = [ 112 + test-inputs 113 + test-flake 114 + test-unflake 115 + test-npins 116 + test-npins-follows 117 + test-npins-transitive 118 + ]; 119 + }
+1
dev/modules/formatter.nix
··· 34 34 "**/unflake.nix" # generated by: nix-shell . -A unflake.env --run write-unflake 35 35 "**/inputs.nix" # generated by: nix-shell . -A unflake.env --run write-inputs 36 36 "**/npins/default.nix" # generated by write-npins 37 + "*.bash" 37 38 "docs/*" 38 39 ]; 39 40 };
-71
dev/modules/unit-tests/npins.nix
··· 1 - { lib, ... }: 2 - let 3 - esc = lib.escapeShellArg; 4 - 5 - # Mirrors pinnableInputs from modules/npins.nix 6 - pinnableInputs = inputs: lib.filterAttrs (_: v: v.url or "" != "") inputs; 7 - 8 - # Mirrors queueSeed from modules/npins.nix 9 - queueSeed = 10 - pinnable: 11 - let 12 - lines = lib.mapAttrsToList ( 13 - name: input: " printf '%s\\t%s\\n' ${esc name} ${esc (input.url or "")}" 14 - ) pinnable; 15 - in 16 - "{\n" + lib.concatStringsSep "\n" lines + "\n} >> \"$QUEUE_FILE\""; 17 - 18 - tests.npins."pinnableInputs excludes empty-url entries" = { 19 - expr = pinnableInputs { 20 - foo.url = "github:owner/foo"; 21 - bar.url = ""; 22 - baz = { }; 23 - }; 24 - expected = { 25 - foo.url = "github:owner/foo"; 26 - }; 27 - }; 28 - 29 - tests.npins."pinnableInputs keeps all non-empty urls" = { 30 - expr = lib.attrNames (pinnableInputs { 31 - a.url = "github:a/a"; 32 - b.url = "github:b/b"; 33 - c.url = ""; 34 - }); 35 - expected = [ 36 - "a" 37 - "b" 38 - ]; 39 - }; 40 - 41 - tests.npins."pinnableInputs is empty on no-url inputs" = { 42 - expr = pinnableInputs { foo.follows = "bar"; }; 43 - expected = { }; 44 - }; 45 - 46 - tests.npins."queueSeed contains name and url for each pinnable input" = { 47 - expr = queueSeed { foo.url = "github:owner/foo"; }; 48 - expected = "{\n printf '%s\\t%s\\n' 'foo' 'github:owner/foo'\n} >> \"$QUEUE_FILE\""; 49 - }; 50 - 51 - tests.npins."queueSeed wraps all printfs in one redirect block" = { 52 - expr = 53 - let 54 - seed = queueSeed { 55 - a.url = "github:a/a"; 56 - b.url = "github:b/b"; 57 - }; 58 - in 59 - lib.hasPrefix "{" seed && lib.hasSuffix ">> \"$QUEUE_FILE\"" seed; 60 - expected = true; 61 - }; 62 - 63 - tests.npins."queueSeed is empty block on no pinnable inputs" = { 64 - expr = queueSeed { }; 65 - expected = "{\n\n} >> \"$QUEUE_FILE\""; 66 - }; 67 - 68 - in 69 - { 70 - flake = { inherit tests; }; 71 - }
modules/bootstrap.nix modules/bootstrap/inputs.nix
+63
modules/bootstrap/default.nix
··· 1 + { 2 + pkgs ? import <nixpkgs> { }, 3 + modules ? [ ], 4 + outdir ? ".", 5 + bootstrap ? [ ], 6 + outputs ? null, 7 + import-tree ? ( 8 + pkgs.fetchFromGitHub { 9 + owner = "vic"; 10 + repo = "import-tree"; 11 + rev = "c968d3b54d12cf5d9c13f16f7c545a06c9d1fde6"; 12 + hash = "sha256-oYO4poyw0Sb/db2PigqugMlDwsvwLg6CSpFrMUWxA3Q="; 13 + } 14 + ), 15 + ... 16 + }: 17 + let 18 + inherit (pkgs) lib; 19 + 20 + tree = (import import-tree) modules; 21 + 22 + attrsOpt = lib.mkOption { 23 + default = { }; 24 + type = lib.types.submodule { freeformType = lib.types.lazyAttrsOf lib.types.unspecified; }; 25 + }; 26 + 27 + bootstrapInputs = 28 + let 29 + ins = import ./inputs.nix { inherit lib; }; 30 + take = name: { flake-file.inputs.${name} = ins.flake-file.inputs.${name}; }; 31 + names = 32 + if bootstrap == true then lib.attrNames ins.flake-file.inputs else lib.flatten [ bootstrap ]; 33 + in 34 + map take names; 35 + 36 + module = { 37 + imports = [ 38 + tree 39 + ./../default.nix 40 + ./../options 41 + ./../npins 42 + ./../unflake 43 + ./../write-inputs.nix 44 + ./../write-flake.nix 45 + ./../flake-options.nix 46 + { imports = bootstrapInputs; } 47 + (if outputs == null then { } else { flake-file.outputs = outputs; }) 48 + ]; 49 + config.flake-file.intoPath = outdir; 50 + options = { 51 + lib = attrsOpt; 52 + templates = attrsOpt; 53 + flakeModules = attrsOpt; 54 + }; 55 + }; 56 + 57 + evaled = lib.evalModules { 58 + modules = [ module ]; 59 + specialArgs.inputs.self.outPath = ""; 60 + }; 61 + 62 + in 63 + evaled.config
+2 -2
modules/default.nix
··· 21 21 22 22 npins.imports = [ 23 23 base 24 - ./npins.nix 24 + ./npins 25 25 ]; 26 26 27 27 unflake.imports = [ 28 28 base 29 - ./unflake.nix 29 + ./unflake 30 30 ]; 31 31 32 32 default.imports = [
+1
modules/lib.nix
··· 1 + import ./../dev/modules/_lib
-13
modules/npins-transitive-test.nix
··· 1 - { lib, ... }: 2 - { 3 - # Declares neomacs with nixpkgs-follows so nixpkgs is not re-pinned separately. 4 - # Transitive discovery must find crane, rust-overlay, nix-wpe-webkit 5 - # from neomacs's own flake.nix at runtime. 6 - flake-file.inputs = { 7 - nixpkgs.url = lib.mkDefault "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; 8 - neomacs = { 9 - url = "github:eval-exec/neomacs"; 10 - inputs.nixpkgs.follows = "nixpkgs"; 11 - }; 12 - }; 13 - }
+1 -1
modules/npins.nix modules/npins/default.nix
··· 1 1 { lib, config, ... }: 2 2 let 3 3 inherit (config) flake-file; 4 - inherit (import ./../dev/modules/_lib lib) inputsExpr; 4 + inherit (import ../lib.nix lib) inputsExpr; 5 5 6 6 inputs = inputsExpr flake-file.inputs; 7 7 esc = lib.escapeShellArg;
modules/unflake.nix modules/unflake/default.nix
+1 -1
modules/write-inputs.nix
··· 28 28 name = "write-inputs"; 29 29 text = '' 30 30 cd ${flake-file.intoPath} 31 - cp ${inputsFile pkgs} "''${1:-inputs.nix}" 31 + cat ${inputsFile pkgs} > "''${1:-inputs.nix}" 32 32 ${lib.getExe pkgs.nixfmt} "''${1:-inputs.nix}" 33 33 ''; 34 34 };