diff options
| -rw-r--r-- | .github/dependabot.yml | 12 | ||||
| -rw-r--r-- | .github/workflows/ci.yaml | 29 | ||||
| -rw-r--r-- | .gitignore | 83 | ||||
| -rw-r--r-- | README.md | 43 | ||||
| -rw-r--r-- | default.nix | 17 | ||||
| -rw-r--r-- | demo/default.nix | 9 | ||||
| -rw-r--r-- | demo/packages.nix | 7 | ||||
| -rw-r--r-- | lib.nix | 52 | ||||
| -rw-r--r-- | modules/default.nix | 7 | ||||
| -rw-r--r-- | modules/outputs.nix | 15 | ||||
| -rw-r--r-- | modules/pkgs.nix | 22 | ||||
| -rw-r--r-- | modules/sources.nix | 20 | ||||
| -rw-r--r-- | npins/default.nix | 80 | ||||
| -rw-r--r-- | npins/sources.json | 11 | ||||
| -rw-r--r-- | release.nix | 56 | ||||
| -rw-r--r-- | shell.nix | 17 |
16 files changed, 480 insertions, 0 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9e0313c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..afab016 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + test: + name: Test + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install Nix + uses: cachix/install-nix-action@91a071959513ca103b54280ac0bef5b825791d4d # v31 + + - name: Run tests + run: | + nix build \ + --file release.nix \ + --no-link \ + --print-build-logs \ + --print-out-paths \ + --show-trace >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64c5580 --- /dev/null +++ b/.gitignore @@ -0,0 +1,83 @@ +### Nix + +result +result-* +repl-result-* + +###------------------------### +### GitHub Global: Linux ### +###------------------------### + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +###------------------------### +### GitHub Global: macOS ### +###------------------------### + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +###--------------------------### +### GitHub Global: Windows ### +###--------------------------### + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1b8f49 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# cores + +> [!WARNING] +> I made this on a whim in an afternoon. It's not production ready and may never be. Here be dragons! 🐲 + +Your Nix code, in ~~modules~~ cores! + +## What does it do? + +`cores` brings the module system from NixOS right into your stable Nix code. This comes with a few big advantages, like: + +- No more manual importing of files +- Improved re-usability of code +- Self documenting interfaces +- Less boilerplate +- A lot of composability! + +## Usage + +`cores` is best used with tools like [`npins`](https://github.com/andir/npins) and [`niv`](https://github.com/nmattia/niv). In this example, we'll use the former: + +```console +$ npins init +$ npins add github --branch main getchoo cores +``` + +Then create a `default.nix`: + +```nix +let + sources = import ./npins; +in + +import sources.cores { inherit sources; } { + outputs = { + hello = "this is cores!"; + }; +} +``` + +## Why? + +As someone who primarily uses Flakes, one of my favorite parts of them for a while has been [flake-parts](https://github.com/hercules-ci/flake-parts). There isn't much of an equivalent in stable Nix for it's functions though, so after a couple [social media](https://hachyderm.io/@jakehamilton/114126394605099447) [posts](https://wetdry.world/@getchoo/114129209075883077) and taking inspiration from [previous work I've done](https://github.com/getchoo/borealis/blob/90c094cb3dfd4a68bd04202695373500394ee5f4/secrets/secrets.nix), I came up with this diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..f85152f --- /dev/null +++ b/default.nix @@ -0,0 +1,17 @@ +{ + sources ? import ./npins, + lib ? import ( + sources.nixpkgs + or (throw "cores: could not find `nixpkgs` in `sources.` Please pass `lib` manually") + + "/lib" + ), + specialArgs ? { }, +}: + +let + coresLib = import ./lib.nix { inherit lib; }; +in + +module: + +coresLib.mkCores { inherit module specialArgs sources; } diff --git a/demo/default.nix b/demo/default.nix new file mode 100644 index 0000000..5fb6c12 --- /dev/null +++ b/demo/default.nix @@ -0,0 +1,9 @@ +let + sources = { + nixpkgs = <nixpkgs>; + }; +in + +import ../default.nix { inherit sources; } { + imports = [ ./packages.nix ]; +} diff --git a/demo/packages.nix b/demo/packages.nix new file mode 100644 index 0000000..7204100 --- /dev/null +++ b/demo/packages.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: + +{ + outputs = { + inherit (pkgs) hello; + }; +} @@ -0,0 +1,52 @@ +{ + lib ? import <nixpkgs/lib>, +}: + +lib.fix (self: { + evalCores = + { + modules, + specialArgs, + }: + + lib.evalModules { + modules = [ ./modules ] ++ modules; + inherit specialArgs; + class = "core"; + }; + + mkCores = + { + module, + specialArgs, + sources, + }: + + self.outputsWithExtend ( + self.evalCores { + modules = [ + module + + ( + { lib, ... }: + + { + sources = lib.mkDefault sources; + } + ) + ]; + + inherit specialArgs; + } + ); + + outputsWithExtend = + cores: + + assert cores.class == "core"; + + cores.config.outputs + // { + extendCores = module: self.outputsWithExtend (cores.extendModules { modules = [ module ]; }); + }; +}) diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..0c1f907 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,7 @@ +{ + imports = [ + ./outputs.nix + ./pkgs.nix + ./sources.nix + ]; +} diff --git a/modules/outputs.nix b/modules/outputs.nix new file mode 100644 index 0000000..3ceaf7c --- /dev/null +++ b/modules/outputs.nix @@ -0,0 +1,15 @@ +{ lib, ... }: + +let + outputsSubmodule = { + freeformType = lib.types.lazyAttrsOf lib.types.raw; + }; +in + +{ + options.outputs = lib.mkOption { + type = lib.types.submodule outputsSubmodule; + default = { }; + description = "Outputs to pass to the top-level file."; + }; +} diff --git a/modules/pkgs.nix b/modules/pkgs.nix new file mode 100644 index 0000000..d984859 --- /dev/null +++ b/modules/pkgs.nix @@ -0,0 +1,22 @@ +{ config, lib, ... }: + +{ + options.pkgs = lib.mkOption { + type = lib.types.nullOr (lib.types.lazyAttrsOf lib.types.raw); + default = + if (config.sources ? "nixpkgs") then + import config.sources.nixpkgs { + config = { }; + overlays = [ ]; + } + else + null; + defaultText = "if (sources has nixpkgs) import sources.nixpkgs { ... } else null"; + description = "The instance of `nixpkgs` to pass as a module argument."; + example = lib.literalExpression "import <nixpkgs> { config = { allowUnfree = true; }; }"; + }; + + config = { + _module.args = { inherit (config) pkgs; }; + }; +} diff --git a/modules/sources.nix b/modules/sources.nix new file mode 100644 index 0000000..6fb8d32 --- /dev/null +++ b/modules/sources.nix @@ -0,0 +1,20 @@ +{ config, lib, ... }: + +let + sourcesSubmodule = { + freeformType = lib.types.lazyAttrsOf lib.types.raw; + }; +in + +{ + options.sources = lib.mkOption { + type = lib.types.submodule sourcesSubmodule; + default = { }; + description = "Sources used across modules."; + example = lib.literalExpression "import ./npins"; + }; + + config = { + _module.args = { inherit (config) sources; }; + }; +} diff --git a/npins/default.nix b/npins/default.nix new file mode 100644 index 0000000..5e7d086 --- /dev/null +++ b/npins/default.nix @@ -0,0 +1,80 @@ +# Generated by npins. Do not modify; will be overwritten regularly +let + data = builtins.fromJSON (builtins.readFile ./sources.json); + version = data.version; + + mkSource = + spec: + assert spec ? type; + let + path = + if spec.type == "Git" then + mkGitSource spec + else if spec.type == "GitRelease" then + mkGitSource spec + else if spec.type == "PyPi" then + mkPyPiSource spec + else if spec.type == "Channel" then + mkChannelSource spec + else + builtins.throw "Unknown source type ${spec.type}"; + in + spec // { outPath = path; }; + + mkGitSource = + { + repository, + revision, + url ? null, + hash, + branch ? null, + ... + }: + assert repository ? type; + # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository + # In the latter case, there we will always be an url to the tarball + if url != null then + (builtins.fetchTarball { + inherit url; + sha256 = hash; # FIXME: check nix version & use SRI hashes + }) + else + assert repository.type == "Git"; + let + urlToName = + url: rev: + let + matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url; + + short = builtins.substring 0 7 rev; + + appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; + in + "${if matched == null then "source" else builtins.head matched}${appendShort}"; + name = urlToName repository.url revision; + in + builtins.fetchGit { + url = repository.url; + rev = revision; + inherit name; + # hash = hash; + }; + + mkPyPiSource = + { url, hash, ... }: + builtins.fetchurl { + inherit url; + sha256 = hash; + }; + + mkChannelSource = + { url, hash, ... }: + builtins.fetchTarball { + inherit url; + sha256 = hash; + }; +in +if version == 3 then + builtins.mapAttrs (_: mkSource) data.pins +else + throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" diff --git a/npins/sources.json b/npins/sources.json new file mode 100644 index 0000000..fafd5ba --- /dev/null +++ b/npins/sources.json @@ -0,0 +1,11 @@ +{ + "pins": { + "nixpkgs": { + "type": "Channel", + "name": "nixos-unstable", + "url": "https://releases.nixos.org/nixos/unstable/nixos-25.05beta764837.e3e32b642a31/nixexprs.tar.xz", + "hash": "1wc6jv1b8wrshckiw1avb11cj40riiydaqqpipcbz3w4km7cis2d" + } + }, + "version": 3 +} diff --git a/release.nix b/release.nix new file mode 100644 index 0000000..d967759 --- /dev/null +++ b/release.nix @@ -0,0 +1,56 @@ +let + sources = import ./npins; +in + +# Dogfood the library +import ./default.nix { inherit sources; } ( + { lib, pkgs, ... }: + + let + src = lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.gitTracked ./.; + }; + + # Check a minimal example + basicTest = import ./default.nix { inherit sources; } { + outputs = { + foo = "bar"; + }; + }; + + # Make sure extending outputs works + extendedTest = basicTest.extendCores ( + { lib, ... }: + + { + outputs.foo = lib.mkForce "baz"; + } + ); + in + + assert basicTest.foo == "bar"; + assert extendedTest.foo == "baz"; + + { + outputs = { + deadnix = pkgs.runCommand "check-deadnix" { nativeBuildInputs = [ pkgs.deadnix ]; } '' + deadnix --exclude ${src}/npins/default.nix --fail ${src} + touch $out + ''; + + nixfmt = pkgs.runCommand "check-nixfmt" { nativeBuildInputs = [ pkgs.nixfmt-rfc-style ]; } '' + nixfmt --check ${src}/**.nix + touch $out + ''; + + statix = pkgs.runCommand "check-statix" { nativeBuildInputs = [ pkgs.statix ]; } '' + statix check --ignore 'npins/default.nix' ${src} + touch $out + ''; + + # Make sure our demo works too + demo-hello = ((import ./demo/default.nix).extendCores { inherit sources; }).hello; + }; + } +) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..6c255a2 --- /dev/null +++ b/shell.nix @@ -0,0 +1,17 @@ +{ + pkgs ? import <nixpkgs> { + config = { }; + overlays = [ ]; + }, +}: + +pkgs.mkShellNoCC { + packages = builtins.attrValues { + inherit (pkgs) + deadnix + nixfmt-rfc-style + npins + statix + ; + }; +} |
