From 71cd268f7906be7d81aac5fee769506ae15339a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Wed, 1 Apr 2026 22:50:00 +0200 Subject: [PATCH] feat: restructure dev-components/dev-registry with direnv --- features/dev.nix | 5 - .../{dev-registry.nix => direnv/default.nix} | 10 ++ features/direnv/use_dev.sh | 80 +++++++++++++ flake/dev-components.nix | 111 ++++++++++++++++++ flake/devshell.nix | 92 +-------------- flake/hosts.nix | 4 +- lib/mkHost.nix | 8 +- 7 files changed, 212 insertions(+), 98 deletions(-) rename features/{dev-registry.nix => direnv/default.nix} (54%) create mode 100644 features/direnv/use_dev.sh create mode 100644 flake/dev-components.nix diff --git a/features/dev.nix b/features/dev.nix index c3cd8e3..feb5ceb 100644 --- a/features/dev.nix +++ b/features/dev.nix @@ -9,7 +9,6 @@ pkgs.git packages.git-linearize packages.ggman - pkgs.go pkgs.python3 pkgs.mdbook pkgs.marksman @@ -22,9 +21,5 @@ pkgs.osc ]; - programs.direnv = { - enable = true; - nix-direnv.enable = true; - }; }; } diff --git a/features/dev-registry.nix b/features/direnv/default.nix similarity index 54% rename from features/dev-registry.nix rename to features/direnv/default.nix index 22b06c8..a50c083 100644 --- a/features/dev-registry.nix +++ b/features/direnv/default.nix @@ -13,4 +13,14 @@ }; }; }; + + home = _: { + programs.direnv = { + enable = true; + nix-direnv.enable = true; + config.global.hide_env_diff = true; + }; + + xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh; + }; } diff --git a/features/direnv/use_dev.sh b/features/direnv/use_dev.sh new file mode 100644 index 0000000..3eb8c55 --- /dev/null +++ b/features/direnv/use_dev.sh @@ -0,0 +1,80 @@ +# shellcheck shell=bash +# composable nix devshell from matej.nix +# usage in .envrc: use dev uv_14 pg_18 + +# generates a flake and delegates to use_flake at the calling scope +use_dev() { + local nix_list="" + for c in "$@"; do + if [[ ! "$c" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then + log_error "use_dev: invalid component name: $c" + return 1 + fi + nix_list="$nix_list \"$c\"" + done + + local system + case "$(uname -s)-$(uname -m)" in + Linux-x86_64) system="x86_64-linux" ;; + Linux-aarch64) system="aarch64-linux" ;; + Darwin-x86_64) system="x86_64-darwin" ;; + Darwin-arm64) system="aarch64-darwin" ;; + esac + + if [[ -z "$system" ]]; then + log_error "use_dev: unsupported platform: $(uname -s)-$(uname -m)" + return 1 + fi + + local dev_path nixpkgs_path registry_filter + # shellcheck disable=SC2016 # $id is a jq variable, not shell + registry_filter='.flakes[] | select(.from.id == $id) | .to.path' + + local registry_file="${XDG_CONFIG_HOME:-$HOME/.config}/nix/registry.json" + if [[ ! -f "$registry_file" ]]; then + registry_file="/etc/nix/registry.json" + fi + + dev_path="$(jq -re --arg id dev "$registry_filter" "$registry_file" 2>/dev/null)" + nixpkgs_path="$(jq -re --arg id nixpkgs "$registry_filter" "$registry_file" 2>/dev/null)" + + if [[ -z "$dev_path" ]]; then + log_error "use_dev: 'dev' not found in nix registry" + return 1 + fi + if [[ -z "$nixpkgs_path" ]]; then + log_error "use_dev: 'nixpkgs' not found in nix registry" + return 1 + fi + + local components_hash project_hash cache_dir + components_hash="$(sha256sum "$dev_path/flake/dev-components.nix" 2>/dev/null | cut -c1-16)" + project_hash="$(echo "$PWD" | sha256sum | cut -c1-16)" + cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/dev-flakes/$project_hash" + mkdir -p "$cache_dir" + + cat >"$cache_dir/flake.nix.tmp" </dev/null; then + mv "$cache_dir/flake.nix.tmp" "$cache_dir/flake.nix" + rm -f "$cache_dir/flake.lock" + else + rm "$cache_dir/flake.nix.tmp" + fi + + use_flake "path:$cache_dir" +} diff --git a/flake/dev-components.nix b/flake/dev-components.nix new file mode 100644 index 0000000..f207925 --- /dev/null +++ b/flake/dev-components.nix @@ -0,0 +1,111 @@ +# composable dev environment components +# imported by generated per-project flakes via use_dev +{ pkgs, lib }: +let + # libraries needed by python native extensions + pythonLibraries = [ + pkgs.stdenv.cc.cc.lib + pkgs.zlib + pkgs.openssl + pkgs.curl + ]; + + mkNode = nodejs: { + packages = [ + nodejs + pkgs.corepack + ]; + env = { + COREPACK_ENABLE_STRICT = "0"; + }; + }; + + mkUv = python: { + packages = [ + python + pkgs.uv + ]; + libraries = pythonLibraries; + env = { + UV_PYTHON_DOWNLOADS = "never"; + UV_PYTHON_PREFERENCE = "only-system"; + UV_PYTHON = "${python}/bin/python"; + }; + shellHook = '' + unset PYTHONPATH + export UV_PROJECT_ENVIRONMENT="''${XDG_DATA_HOME:-$HOME/.local/share}/dev-venvs/$(basename "$PWD")-$(echo "$PWD" | sha256sum | cut -c1-8)" + ''; + }; + + components = { + uv_10 = mkUv pkgs.python310; + uv_11 = mkUv pkgs.python311; + uv_12 = mkUv pkgs.python312; + uv_13 = mkUv pkgs.python313; + uv_14 = mkUv pkgs.python314; + + pg_15 = { + packages = [ pkgs.postgresql_15 ]; + }; + pg_16 = { + packages = [ pkgs.postgresql_16 ]; + }; + pg_17 = { + packages = [ pkgs.postgresql_17 ]; + }; + pg_18 = { + packages = [ pkgs.postgresql_18 ]; + }; + + node_20 = mkNode pkgs.nodejs_20; + node_22 = mkNode pkgs.nodejs_22; + node_24 = mkNode pkgs.nodejs_24; + + go = { + packages = [ pkgs.go ]; + }; + + rust = { + packages = [ + pkgs.rustc + pkgs.cargo + pkgs.rust-analyzer + pkgs.openssl + pkgs.pkg-config + ]; + }; + + cmake = { + packages = [ + pkgs.cmake + pkgs.ninja + ]; + }; + }; + + # build a single mkShell from one or more component names + mkComponentShell = + names: + let + selected = map (n: components.${n}) names; + allPackages = lib.concatMap (c: c.packages or [ ]) selected; + allLibraries = lib.concatMap (c: c.libraries or [ ]) selected; + allHooks = lib.concatMapStrings (c: c.shellHook or "") selected; + allEnvs = lib.foldl' (acc: c: acc // (c.env or { })) { } selected; + libPath = lib.makeLibraryPath allLibraries; + in + pkgs.mkShell ( + { + packages = allPackages; + shellHook = + (lib.optionalString (allLibraries != [ ]) '' + export LD_LIBRARY_PATH="${libPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + '') + + allHooks; + } + // lib.optionalAttrs (allEnvs != { }) { env = allEnvs; } + ); +in +{ + inherit components mkComponentShell; +} diff --git a/flake/devshell.nix b/flake/devshell.nix index 60fd6f9..bce210d 100644 --- a/flake/devshell.nix +++ b/flake/devshell.nix @@ -1,93 +1,6 @@ _: { perSystem = - { pkgs, lib, ... }: - let - # libraries needed by python native extensions - pythonLibraries = [ - pkgs.stdenv.cc.cc.lib - pkgs.zlib - pkgs.openssl - pkgs.curl - ]; - - mkUv = python: { - packages = [ - python - pkgs.uv - ]; - libraries = pythonLibraries; - env = { - UV_PYTHON_DOWNLOADS = "never"; - UV_PYTHON_PREFERENCE = "only-system"; - UV_PYTHON = "${python}/bin/python"; - }; - shellHook = '' - unset PYTHONPATH - export UV_PROJECT_ENVIRONMENT="$HOME/.venvs/$(basename "$PWD")-$(echo "$PWD" | md5sum | cut -c1-8)" - ''; - }; - - # composable dev environment components - # each is exposed as its own devShell, layered via `use dev` in .envrc - components = { - uv_10 = mkUv pkgs.python310; - uv_11 = mkUv pkgs.python311; - uv_12 = mkUv pkgs.python312; - uv_13 = mkUv pkgs.python313; - uv_14 = mkUv pkgs.python314; - - pg_15 = { - packages = [ pkgs.postgresql_15 ]; - }; - pg_16 = { - packages = [ pkgs.postgresql_16 ]; - }; - pg_17 = { - packages = [ pkgs.postgresql_17 ]; - }; - pg_18 = { - packages = [ pkgs.postgresql_18 ]; - }; - - rust = { - packages = [ - pkgs.rustc - pkgs.cargo - pkgs.rust-analyzer - pkgs.openssl - pkgs.pkg-config - ]; - }; - - cmake = { - packages = [ - pkgs.cmake - pkgs.ninja - ]; - }; - }; - - mkComponentShell = - component: - let - c = components.${component}; - libraries = c.libraries or [ ]; - libPath = lib.makeLibraryPath libraries; - in - pkgs.mkShell ( - { - packages = c.packages or [ ]; - shellHook = - (lib.optionalString (libraries != [ ]) '' - export LD_LIBRARY_PATH="${libPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" - '') - + (c.shellHook or ""); - } - // lib.optionalAttrs (c ? env) { inherit (c) env; } - ); - - componentShells = lib.mapAttrs (name: _: mkComponentShell name) components; - in + { pkgs, ... }: { formatter = pkgs.nixfmt-tree; @@ -103,7 +16,6 @@ _: { pkgs.ssh-to-age ]; }; - } - // componentShells; + }; }; } diff --git a/flake/hosts.nix b/flake/hosts.nix index 6765844..c811d34 100644 --- a/flake/hosts.nix +++ b/flake/hosts.nix @@ -30,7 +30,7 @@ in "yubikey" "calibre" "steam" - "dev-registry" + "direnv" "neovim" "dev" "claude" @@ -57,7 +57,7 @@ in "calibre" "steam" "initrd-ssh" - "dev-registry" + "direnv" "neovim" "dev" "claude" diff --git a/lib/mkHost.nix b/lib/mkHost.nix index db0faf8..0d5bd44 100644 --- a/lib/mkHost.nix +++ b/lib/mkHost.nix @@ -16,7 +16,13 @@ let hasUser = user != null; # path helpers - featurePath = f: ../features/${f}.nix; + featurePath = + f: + let + file = ../features/${f}.nix; + dir = ../features/${f}; + in + if builtins.pathExists file then file else dir; userFeaturePath = u: ../features/user-${u}.nix; hostConfig = ../hosts/${name}/configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix;