From 1a525587854af3659fd0dc383c7bde056a918b02 Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Mon, 6 Apr 2026 19:23:30 -0300 Subject: [PATCH 1/8] Add process tree to dev shell --- flake.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 1cdadcc..0d6f2ac 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,7 @@ packages = with pkgs; [ self.taskRunners.${system}.default self.formatter.${system} + self.processTrees.${system}.postgres ]; shellHook = '' ${self.taskRunners.${system}.default.shellHook} @@ -57,7 +58,8 @@ processTrees = forEachSupportedSystem ( { pkgs, system }: { - data = pkgs.lib.mkProcessTree { + postgres = pkgs.lib.mkProcessTree { + name = "postgres-process-tree"; description = "Run Postgres locally"; packages = with pkgs; [ From 5b100bb8f73067a13ce165bef404b5b6f6b0209a Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Mon, 6 Apr 2026 19:42:44 -0300 Subject: [PATCH 2/8] Undo allowBroken = true --- flake.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/flake.nix b/flake.nix index 0d6f2ac..84e6645 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,6 @@ pkgs = import inputs.nixpkgs { inherit system; config = { - allowBroken = true; allowUnfree = true; }; overlays = [ self.overlays.default ]; From a44b6ad823819a1da30fa4e39eb722e4892e4c9d Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Mon, 6 Apr 2026 19:54:48 -0300 Subject: [PATCH 3/8] Allow for a non-default package for process-compose --- nix/processes.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nix/processes.nix b/nix/processes.nix index dc3553e..28402fb 100644 --- a/nix/processes.nix +++ b/nix/processes.nix @@ -106,6 +106,10 @@ let type = types.nullOr types.str; default = null; }; + package = mkOption { + type = types.package; + default = pkgs.process-compose; + }; log_level = mkOption { type = types.enum [ "info" @@ -206,7 +210,7 @@ let in pkgs.writeShellApplication { inherit (config) name; - runtimeInputs = [ pkgs.process-compose ]; + runtimeInputs = [ config.package ]; text = '' export PATH="${lib.concatStringsSep ":" (map (p: "${p}/bin") config.packages)}:$PATH" From 542b0fe9fb81a67a970640cb78dd7f8019fb38a1 Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Thu, 9 Apr 2026 17:54:39 -0300 Subject: [PATCH 4/8] Some basic refactoring --- flake.nix | 15 +++++++++------ nix/lib.nix | 4 ++-- nix/{processes.nix => process-tree.nix} | 0 nix/{runner.nix => task-runner.nix} | 0 4 files changed, 11 insertions(+), 8 deletions(-) rename nix/{processes.nix => process-tree.nix} (100%) rename nix/{runner.nix => task-runner.nix} (100%) diff --git a/flake.nix b/flake.nix index 84e6645..8ce86eb 100644 --- a/flake.nix +++ b/flake.nix @@ -46,6 +46,7 @@ shellHook = '' ${self.taskRunners.${system}.default.shellHook} ''; + env = self.envVars.postgres; }; } ); @@ -66,12 +67,7 @@ redis ]; - environment = { - PGDATA = ".state/postgres"; - PGDATABASE = "testing"; - PGHOST = "127.0.0.1"; - PGPORT = "5432"; - }; + environment = self.envVars.postgres; processes = { postgres-setup = { @@ -158,6 +154,13 @@ } ); + envVars.postgres = { + PGDATA = ".state/postgres"; + PGDATABASE = "testing"; + PGHOST = "127.0.0.1"; + PGPORT = "5432"; + }; + overlays.default = final: prev: { lib = prev.lib diff --git a/nix/lib.nix b/nix/lib.nix index c3f573f..e8d4bd9 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -4,14 +4,14 @@ let taskModule = import ./task.nix { inherit lib pkgs; }; in { - mkTaskRunner = import ./runner.nix { + mkTaskRunner = import ./task-runner.nix { inherit lib pkgs taskModule ; }; - mkProcessTree = import ./processes.nix { + mkProcessTree = import ./process-tree.nix { inherit lib pkgs diff --git a/nix/processes.nix b/nix/process-tree.nix similarity index 100% rename from nix/processes.nix rename to nix/process-tree.nix diff --git a/nix/runner.nix b/nix/task-runner.nix similarity index 100% rename from nix/runner.nix rename to nix/task-runner.nix From 7c1805ff3a4a0537359bf7fce8c015e400f2808c Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Fri, 10 Apr 2026 14:20:12 -0300 Subject: [PATCH 5/8] Add toolchains concept --- README.md | 29 ++++++++ flake.lock | 38 ++++++++++ flake.nix | 128 ++++++++++++++++++++++++++++++++- nix/lib.nix => lib/default.nix | 2 + {nix => lib}/process-tree.nix | 0 {nix => lib}/task-runner.nix | 0 {nix => lib}/task.nix | 0 lib/toolchain.nix | 0 8 files changed, 195 insertions(+), 2 deletions(-) rename nix/lib.nix => lib/default.nix (99%) rename {nix => lib}/process-tree.nix (100%) rename {nix => lib}/task-runner.nix (100%) rename {nix => lib}/task.nix (100%) create mode 100644 lib/toolchain.nix diff --git a/README.md b/README.md index 7b1dc00..ac9f26e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,35 @@ The flake schemas are also helpful: } ``` +## Toolchains + +**Toolchains** are configurable sets of tools that you can add to a dev shell. + +```nix +{ + devShells = forEachSupportedSystem ( + { pkgs, system }: + { + default = + let + pythonToolchain = inputs.up.toolchains.${system}.python { uv = true; }; + rustToolchain = inputs.up.toolchains.${system}.rust { channel = "nightly"; }; + in + pkgs.mkShell { + packages = [ + pythonToolchain.packages + rustToolchain.packages + ]; + + shellHook = '' + ${pythonToolchain.shellHook} + ''; + }; + } + ); +} +``` + ## Process trees **Process trees** are process-compose configurations built with Nix. diff --git a/flake.lock b/flake.lock index d735ee2..0ccde9e 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,25 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1775029908, + "narHash": "sha256-QuPn+EN/097aBLeSqbQ7vOwc5TSOb68bAxg1+mknfmw=", + "rev": "380f1969f440e683333af5746caac76811b4a1a8", + "revCount": 2612, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/nix-community/fenix/0.1.2612%2Brev-380f1969f440e683333af5746caac76811b4a1a8/019d4845-48ec-7293-9e63-11b3620f4a54/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/nix-community/fenix/0.1" + } + }, "flake-schemas": { "locked": { "lastModified": 1775244557, @@ -30,9 +50,27 @@ }, "root": { "inputs": { + "fenix": "fenix", "flake-schemas": "flake-schemas", "nixpkgs": "nixpkgs" } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1774948198, + "narHash": "sha256-oVPo0/3CXM/5uFKu1ZwP7osSV2tiQIFU09Y3UzNbm7g=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "63b3eff38ef1c216480147dd53b0e4365d55f269", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 8ce86eb..98b24f5 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,10 @@ inputs = { nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/0"; + fenix = { + url = "https://flakehub.com/f/nix-community/fenix/0.1"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -42,6 +46,12 @@ self.taskRunners.${system}.default self.formatter.${system} self.processTrees.${system}.postgres + (self.toolchains.${system}.go { version = 25; }).packages + (self.toolchains.${system}.rust { channel = "stable"; }).packages + (self.toolchains.${system}.python { + uv = true; + }).packages + (self.toolchains.${system}.js { nodejs = true; }).packages ]; shellHook = '' ${self.taskRunners.${system}.default.shellHook} @@ -53,7 +63,7 @@ formatter = forEachSupportedSystem ({ pkgs, ... }: pkgs.nixfmt); - lib = import ./nix/lib.nix { inherit lib; }; + lib = import ./lib { inherit lib; }; processTrees = forEachSupportedSystem ( { pkgs, system }: @@ -154,6 +164,99 @@ } ); + toolchains = forEachSupportedSystem ( + { pkgs, system }: + { + go = + { + version ? null, + }: + { + packages = + if version == null then + pkgs.go + else + pkgs.${"go_1_${builtins.replaceStrings [ "." ] [ "_" ] (toString version)}"}; + }; + + js = + { + nodejs ? false, + bun ? false, + npm ? false, + pnpm ? false, + }: + { + packages = pkgs.symlinkJoin { + name = "js-env"; + paths = + lib.optional nodejs pkgs.nodejs + ++ lib.optional bun pkgs.bun + ++ lib.optional npm pkgs.npm + ++ lib.optional pnpm pkgs.pnpm; + }; + }; + + python = + { + version ? null, + uv ? false, + venv ? { + enable = false; + requirements = ""; + }, + }: + let + pythonPkg = + if version == null then + pkgs.python3 + else + pkgs.${"python${builtins.replaceStrings [ "." ] [ "" ] version}"} + or (throw "unknown python version: ${version}"); + in + { + packages = pkgs.symlinkJoin { + name = "python-env"; + paths = [ pythonPkg ] ++ lib.optional uv pkgs.uv; + }; + + shellHook = lib.optionalString (venv.enable or false) '' + uv venv .venv + ${lib.optionalString (venv.requirements or "" != "") '' + uv pip install ${venv.requirements} + ''} + ''; + }; + + rust = + { + stable ? true, + channel ? null, + targets ? [ ], + }: + let + fenixPkgs = inputs.fenix.packages.${system}; + ch = + if channel != null then + { + "stable" = fenixPkgs.stable; + "nightly" = fenixPkgs.latest; + "beta" = fenixPkgs.beta; + } + .${channel} or (throw "unknown rust channel: ${channel}") + else if stable then + fenixPkgs.stable + else + fenixPkgs.latest; + + targetStdlibs = map (target: fenixPkgs.targets.${target}.stable.rust-std) targets; + in + { + packages = fenixPkgs.combine ([ ch.toolchain ] ++ targetStdlibs); + }; + } + ); + envVars.postgres = { PGDATA = ".state/postgres"; PGDATABASE = "testing"; @@ -164,7 +267,7 @@ overlays.default = final: prev: { lib = prev.lib - // (import ./nix/lib.nix { + // (import ./lib { inherit (prev) lib; pkgs = prev; }); @@ -200,6 +303,27 @@ ); }; + toolchains = { + version = 1; + doc = '' + The `toolchains` output provides language toolchain builder functions. + Each toolchain returns `{ packages, shellHook }`. + ''; + appendSystem = true; + inventory = + output: + inputs.flake-schemas.lib.mkChildren ( + builtins.mapAttrs (system: toolchains: { + forSystems = [ system ]; + children = builtins.mapAttrs (_name: toolchain: { + forSystems = [ system ]; + evalChecks.isFunction = builtins.isFunction toolchain; + what = "language toolchain builder"; + }) toolchains; + }) output + ); + }; + envVars = { version = 1; doc = '' diff --git a/nix/lib.nix b/lib/default.nix similarity index 99% rename from nix/lib.nix rename to lib/default.nix index e8d4bd9..c691fc8 100644 --- a/nix/lib.nix +++ b/lib/default.nix @@ -11,6 +11,7 @@ in taskModule ; }; + mkProcessTree = import ./process-tree.nix { inherit lib @@ -18,6 +19,7 @@ in taskModule ; }; + mkTask = args: (lib.evalModules { diff --git a/nix/process-tree.nix b/lib/process-tree.nix similarity index 100% rename from nix/process-tree.nix rename to lib/process-tree.nix diff --git a/nix/task-runner.nix b/lib/task-runner.nix similarity index 100% rename from nix/task-runner.nix rename to lib/task-runner.nix diff --git a/nix/task.nix b/lib/task.nix similarity index 100% rename from nix/task.nix rename to lib/task.nix diff --git a/lib/toolchain.nix b/lib/toolchain.nix new file mode 100644 index 0000000..e69de29 From 211f813d54afd61ad178e91fde7a4d4398aad149 Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Fri, 10 Apr 2026 14:31:29 -0300 Subject: [PATCH 6/8] Add env output for Rust --- flake.nix | 76 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/flake.nix b/flake.nix index 98b24f5..e7fa09d 100644 --- a/flake.nix +++ b/flake.nix @@ -41,23 +41,31 @@ devShells = forEachSupportedSystem ( { pkgs, system }: { - default = pkgs.mkShellNoCC { - packages = with pkgs; [ - self.taskRunners.${system}.default - self.formatter.${system} - self.processTrees.${system}.postgres - (self.toolchains.${system}.go { version = 25; }).packages - (self.toolchains.${system}.rust { channel = "stable"; }).packages - (self.toolchains.${system}.python { + default = + let + pythonToolchain = self.toolchains.${system}.python { uv = true; - }).packages - (self.toolchains.${system}.js { nodejs = true; }).packages - ]; - shellHook = '' - ${self.taskRunners.${system}.default.shellHook} - ''; - env = self.envVars.postgres; - }; + }; + + rustToolchain = self.toolchains.${system}.rust { + channel = "stable"; + envSrcPath = true; + }; + in + pkgs.mkShellNoCC { + packages = with pkgs; [ + self.taskRunners.${system}.default + self.formatter.${system} + + pythonToolchain.packages + rustToolchain.packages + ]; + shellHook = '' + ${self.taskRunners.${system}.default.shellHook} + ${pythonToolchain.shellHook} + ''; + env = rustToolchain.env // self.envVars.postgres; + }; } ); @@ -233,26 +241,36 @@ stable ? true, channel ? null, targets ? [ ], + envSrcPath ? false, }: let fenixPkgs = inputs.fenix.packages.${system}; - ch = - if channel != null then - { - "stable" = fenixPkgs.stable; - "nightly" = fenixPkgs.latest; - "beta" = fenixPkgs.beta; - } - .${channel} or (throw "unknown rust channel: ${channel}") - else if stable then - fenixPkgs.stable - else - fenixPkgs.latest; + + rustToolchain = + ( + if channel != null then + { + "stable" = fenixPkgs.stable; + "nightly" = fenixPkgs.latest; + "beta" = fenixPkgs.beta; + } + .${channel} or (throw "unknown rust channel: ${channel}") + else if stable then + fenixPkgs.stable + else + fenixPkgs.latest + ).toolchain; targetStdlibs = map (target: fenixPkgs.targets.${target}.stable.rust-std) targets; + + packages = fenixPkgs.combine ([ rustToolchain ] ++ targetStdlibs); in { - packages = fenixPkgs.combine ([ ch.toolchain ] ++ targetStdlibs); + env = lib.optionalAttrs envSrcPath { + RUST_SRC_PATH = "${rustToolchain}/lib/rustlib/src/rust/library"; + }; + + inherit packages; }; } ); From 53b1ca6047e995d593a25e4f65f203c384228d03 Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Fri, 10 Apr 2026 15:01:49 -0300 Subject: [PATCH 7/8] Add PHP toolchain --- flake.nix | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index e7fa09d..6257662 100644 --- a/flake.nix +++ b/flake.nix @@ -43,26 +43,32 @@ { default = let - pythonToolchain = self.toolchains.${system}.python { + toolchains = self.toolchains.${system}; + + pythonToolchain = toolchains.python { uv = true; }; - rustToolchain = self.toolchains.${system}.rust { + rustToolchain = toolchains.rust { channel = "stable"; envSrcPath = true; }; + + phpToolchain = toolchains.php { }; in pkgs.mkShellNoCC { packages = with pkgs; [ self.taskRunners.${system}.default self.formatter.${system} + phpToolchain.packages pythonToolchain.packages rustToolchain.packages ]; shellHook = '' ${self.taskRunners.${system}.default.shellHook} ${pythonToolchain.shellHook} + ${phpToolchain.shellHook} ''; env = rustToolchain.env // self.envVars.postgres; }; @@ -205,6 +211,51 @@ }; }; + php = + { + version ? "8.3", + ini ? "", + fpm ? { + pools = { }; + }, + }: + let + phpPkg = + pkgs.${"php${builtins.replaceStrings [ "." ] [ "" ] version}"} + or (throw "unknown php version: ${version}"); + + fpmConf = pkgs.writeText "php-fpm.conf" ( + lib.concatStrings ( + lib.mapAttrsToList ( + poolName: pool: + '' + [${poolName}] + '' + + lib.concatStrings (lib.mapAttrsToList (k: v: "${k} = ${v}\n") (pool.settings or { })) + ) (fpm.pools or { }) + ) + ); + + shellHook = lib.optionalString (fpm.pools != { }) '' + php-fpm -y ${fpmConf} -D + trap "php-fpm -y ${fpmConf} -F -R 2>/dev/null" EXIT + ''; + in + { + packages = + if ini == "" then + phpPkg + else + phpPkg.buildEnv { + extraConfig = ini; + }; + shellHook = lib.optionalString (fpm.pools != { }) '' + php-fpm -y ${fpmConf} -D + trap "php-fpm -y ${fpmConf} -F -R 2>/dev/null" EXIT + ''; + env = { }; + }; + python = { version ? null, From f2f105d8d1839bb92f9bd02365dc4dfc7ce921e2 Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Fri, 10 Apr 2026 15:56:55 -0300 Subject: [PATCH 8/8] Add Terraform toolchain --- flake.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/flake.nix b/flake.nix index 6257662..5b99885 100644 --- a/flake.nix +++ b/flake.nix @@ -55,6 +55,14 @@ }; phpToolchain = toolchains.php { }; + + terraformToolchain = toolchains.terraform { + plugins = [ + "hashicorp_aws" + "hashicorp_google" + "hashicorp_kubernetes" + ]; + }; in pkgs.mkShellNoCC { packages = with pkgs; [ @@ -64,6 +72,7 @@ phpToolchain.packages pythonToolchain.packages rustToolchain.packages + terraformToolchain.packages ]; shellHook = '' ${self.taskRunners.${system}.default.shellHook} @@ -193,6 +202,21 @@ pkgs.${"go_1_${builtins.replaceStrings [ "." ] [ "_" ] (toString version)}"}; }; + terraform = + { + plugins ? [ ], + }: + let + tfPkg = pkgs.terraform; + in + { + packages = if plugins == [ ] then tfPkg else tfPkg.withPlugins (p: map (name: p.${name}) plugins); + shellHook = ""; + env = { + TF_CLI_ARGS = "-no-color"; + }; + }; + js = { nodejs ? false,