summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeth Flynn <[email protected]>2025-03-11 16:31:03 -0400
committerSeth Flynn <[email protected]>2025-03-11 17:57:46 -0400
commit336dc00a94e39337c64decd6d0f4f6e4a4d43187 (patch)
tree7e5cb2b56913e398afab4de495bed0401a16493f
initial commitHEADmain
-rw-r--r--.github/dependabot.yml12
-rw-r--r--.github/workflows/ci.yaml29
-rw-r--r--.gitignore83
-rw-r--r--README.md43
-rw-r--r--default.nix17
-rw-r--r--demo/default.nix9
-rw-r--r--demo/packages.nix7
-rw-r--r--lib.nix52
-rw-r--r--modules/default.nix7
-rw-r--r--modules/outputs.nix15
-rw-r--r--modules/pkgs.nix22
-rw-r--r--modules/sources.nix20
-rw-r--r--npins/default.nix80
-rw-r--r--npins/sources.json11
-rw-r--r--release.nix56
-rw-r--r--shell.nix17
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;
+ };
+}
diff --git a/lib.nix b/lib.nix
new file mode 100644
index 0000000..e5a019c
--- /dev/null
+++ b/lib.nix
@@ -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
+ ;
+ };
+}