feat: restructure dev-components/dev-registry with direnv

This commit is contained in:
2026-04-01 22:50:00 +02:00
parent 5a37795151
commit 71cd268f79
7 changed files with 212 additions and 98 deletions

View File

@@ -9,7 +9,6 @@
pkgs.git pkgs.git
packages.git-linearize packages.git-linearize
packages.ggman packages.ggman
pkgs.go
pkgs.python3 pkgs.python3
pkgs.mdbook pkgs.mdbook
pkgs.marksman pkgs.marksman
@@ -22,9 +21,5 @@
pkgs.osc pkgs.osc
]; ];
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
}; };
} }

View File

@@ -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;
};
} }

View File

@@ -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" <<DEVFLAKE
# dev-components: $components_hash
{
inputs.dev = { url = "path:${dev_path}"; flake = false; };
inputs.nixpkgs.url = "path:${nixpkgs_path}";
outputs = { dev, nixpkgs, ... }:
let
system = "${system}";
pkgs = nixpkgs.legacyPackages.\${system};
devLib = import "\${dev}/flake/dev-components.nix" { inherit pkgs; lib = nixpkgs.lib; };
in {
devShells.\${system}.default = devLib.mkComponentShell [$nix_list ];
};
}
DEVFLAKE
if ! cmp -s "$cache_dir/flake.nix.tmp" "$cache_dir/flake.nix" 2>/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"
}

111
flake/dev-components.nix Normal file
View File

@@ -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;
}

View File

@@ -1,93 +1,6 @@
_: { _: {
perSystem = perSystem =
{ pkgs, lib, ... }: { pkgs, ... }:
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
{ {
formatter = pkgs.nixfmt-tree; formatter = pkgs.nixfmt-tree;
@@ -103,7 +16,6 @@ _: {
pkgs.ssh-to-age pkgs.ssh-to-age
]; ];
}; };
} };
// componentShells;
}; };
} }

View File

@@ -30,7 +30,7 @@ in
"yubikey" "yubikey"
"calibre" "calibre"
"steam" "steam"
"dev-registry" "direnv"
"neovim" "neovim"
"dev" "dev"
"claude" "claude"
@@ -57,7 +57,7 @@ in
"calibre" "calibre"
"steam" "steam"
"initrd-ssh" "initrd-ssh"
"dev-registry" "direnv"
"neovim" "neovim"
"dev" "dev"
"claude" "claude"

View File

@@ -16,7 +16,13 @@ let
hasUser = user != null; hasUser = user != null;
# path helpers # 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; userFeaturePath = u: ../features/user-${u}.nix;
hostConfig = ../hosts/${name}/configuration.nix; hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix;