Merge branch 'feat/autodiscovery' into 'main'

feat: add autodiscovery block

See merge request rensa-nix/core!3
This commit is contained in:
TECHNOFAB 2026-01-20 12:47:35 +01:00
commit 91ac27f96a
11 changed files with 314 additions and 45 deletions

View file

@ -6,6 +6,17 @@
dynamic = name: {
inherit name;
type = name;
cli = true;
actions = args: {};
# TODO: dynamic actions
};
autodiscover = {
name = "__autodiscover";
type = "__autodiscover";
_functor = self: cell:
self
// {
inherit cell;
};
};
}

98
lib/core/autodiscover.nix Normal file
View file

@ -0,0 +1,98 @@
{
l,
cellsFrom,
cellBlocks,
}: let
# find autodiscover blocks
autodiscoverBlocks = l.filter (block: block.name or "" == "__autodiscover") cellBlocks;
explicitBlocks = l.filter (block: block.name or "" != "__autodiscover") cellBlocks;
hasAutodiscover = (l.length autodiscoverBlocks) > 0;
# discover all block names from a single cell's directory
discoverBlocksFromCell = cellPath: let
cellContents = l.readDir cellPath;
# filter for .nix files (but not flake.nix) and dirs with default.nix
blockNames = l.unique (
l.filter (name: name != null) (
l.mapAttrsToList (
name: type:
if type == "regular" && l.hasSuffix ".nix" name && name != "flake.nix"
then l.removeSuffix ".nix" name
else if type == "directory" && l.pathExists (cellPath + "/${name}/default.nix")
then name
else null
)
cellContents
)
);
in
builtins.addErrorContext "[ren] while discovering blocks from cell ${cellPath}"
blockNames;
# discover all blocks from all cells or specific cells
discoverAllBlocks = let
# check if any autodiscover blocks have cell-specific targets (eg. `autodiscover "a"`)
cellSpecificDiscoveries = l.filter (block: block ? cell) autodiscoverBlocks;
globalDiscoveries = l.filter (block: !(block ? cell)) autodiscoverBlocks;
cellSpecificBlocks = l.flatten (
l.map (
block: let
cellPath = cellsFrom + "/${block.cell}";
in
if l.pathExists cellPath
then discoverBlocksFromCell cellPath
else []
)
cellSpecificDiscoveries
);
# global autodiscover
globalBlocks = builtins.addErrorContext "[ren] while discovering global blocks" (
if (l.length globalDiscoveries) > 0
then let
cells = l.readDir cellsFrom;
allBlockNames = l.unique (
l.flatten (
l.mapAttrsToList (
cellName: cellType:
if cellType == "directory"
then discoverBlocksFromCell (cellsFrom + "/${cellName}")
else []
)
cells
)
);
in
allBlockNames
else []
);
allBlockNames = l.unique (cellSpecificBlocks ++ globalBlocks);
in
builtins.addErrorContext "[ren] while discovering all blocks"
l.map (name: {
inherit name;
type = name;
})
allBlockNames;
discoveredBlocks =
if hasAutodiscover
then discoverAllBlocks
else [];
# merge the explicit and autodiscovered blocks (explicit taking precedence)
allBlocks = explicitBlocks ++ discoveredBlocks;
uniqueBlocks =
l.foldl' (
acc: block:
# first occurence of name wins
if l.any (b: b.name == block.name) acc
then acc
else acc ++ [block]
) []
allBlocks;
in
builtins.addErrorContext "[ren] while autodiscovering cell blocks"
uniqueBlocks

View file

@ -2,6 +2,8 @@
l,
utils,
loader,
autodiscover,
blocks,
}: let
inherit (utils) accumulate;
inherit (loader) createCellLoader;
@ -9,7 +11,7 @@
build = {
inputs,
cellsFrom,
cellBlocks,
cellBlocks ? [blocks.autodiscover],
transformInputs ? system: i: i,
...
} @ args: let
@ -28,26 +30,31 @@
]
);
cellBlocks' = autodiscover {inherit l cellsFrom cellBlocks;};
cells = res.output;
loadOutputFor = system: let
loadCell = createCellLoader {
inherit inputs system cells cellsFrom cellBlocks transformInputs;
inherit inputs system cells cellsFrom transformInputs;
cellBlocks = cellBlocks';
};
cells' = l.mapAttrsToList (cell: type: cell) (l.readDir cellsFrom);
res = accumulate (l.map loadCell cells');
in [
{${system} = res.output;}
{${system} = res.actions;}
{
name = system;
value = res.init;
}
];
in
builtins.addErrorContext "[ren] while loading output for ${system}" [
{${system} = res.output;}
{${system} = res.actions;}
{
name = system;
value = res.init;
}
];
res = accumulate (l.map loadOutputFor systems);
in
builtins.addErrorContext "[ren] while building rensa output"
res.output
// {
__ren = {
@ -89,10 +96,12 @@
recursiveUpdate = lhs: rhs:
recursiveUpdateUntil g lhs rhs;
in
build args
// {
__functor = l.flip recursiveUpdate;
};
builtins.addErrorContext "[ren] while building rensa output (buildWith)" (
build args
// {
__functor = l.flip recursiveUpdate;
}
);
in {
inherit build buildWith;
}

View file

@ -112,4 +112,5 @@ ref: inputOverrides: let
)
lockFile.nodes;
in
builtins.addErrorContext "[ren] while loading flake lockfile ${ref}"
allNodes.${lockFile.root}

View file

@ -1,11 +1,13 @@
{
l,
utils,
blocks,
}: let
paths = import ./paths.nix;
callFlake = import ./call-flake.nix;
autodiscover = import ./autodiscover.nix;
loader = import ./loader.nix {inherit l utils paths callFlake;};
builder = import ./builder.nix {inherit l utils loader;};
builder = import ./builder.nix {inherit l utils loader autodiscover blocks;};
in {
inherit (builder) build buildWith;
}

View file

@ -33,14 +33,18 @@
}).outputs
else {};
in
importSignatureFor system updatedCell cells additionalInputs;
builtins.addErrorContext "[ren] while getting input signature for ${blockP}" (
importSignatureFor system updatedCell cells additionalInputs
);
import' = importPath: let
block = import importPath;
in
if l.typeOf block == "set"
then block
else block signature;
else
builtins.addErrorContext "[ren] while importing block at ${importPath}"
(block signature);
importPaths =
if isFile
@ -101,18 +105,20 @@
in
optionalLoad (isFile || isDir) (
assert l.assertMsg isAttrs "cell block does not return an attrset: ${importPaths.displayPath}";
l.traceVerbose "[ren] loading cell block ${cellBlock.name}, type ${cellBlock.type}, from cell ${cellP}"
[
{${cellBlock.name} = imported;}
{${cellBlock.name} = l.mapAttrs (_: set: set.actions) extracted;}
({
cellBlock = cellBlock.name;
blockType = cellBlock.type;
targets = l.mapAttrsToList (_: set: set.init) extracted;
}
// (l.optionalAttrs (l.pathExists blockP.readmeDir) {readme = blockP.readmeDir;})
// (l.optionalAttrs (l.pathExists blockP.readme) {inherit (blockP) readme;}))
]
l.traceVerbose "[ren] loading cell block ${cellBlock.name}, type ${cellBlock.type}, from cell ${cellP}, for system ${system}" (
builtins.addErrorContext "[ren] loading cell block ${cellBlock.name}, type ${cellBlock.type}, from cell ${cellP}, for system ${system}"
[
{${cellBlock.name} = imported;}
{${cellBlock.name} = l.mapAttrs (_: set: set.actions) extracted;}
({
cellBlock = cellBlock.name;
blockType = cellBlock.type;
targets = l.mapAttrsToList (_: set: set.init) extracted;
}
// (l.optionalAttrs (l.pathExists blockP.readmeDir) {readme = blockP.readmeDir;})
// (l.optionalAttrs (l.pathExists blockP.readme) {inherit (blockP) readme;}))
]
)
);
in
loadCellBlock;
@ -133,7 +139,7 @@
# fixes `ìnfinite recursion` errors when accessing cell attributes from sibling blocks
# example: cells/test/a.nix returns `cell.b`, so the same thing as cells/test/b.nix.
# this would previously fail with infinite recursion, this makes it work:
cell = l.listToAttrs (l.concatMap (
cell = builtins.addErrorContext "[ren] while accessing cell '${cellName}' siblings" (l.listToAttrs (l.concatMap (
block: let
blockP = paths.cellBlockPath cellP block;
exists = l.pathExists blockP.file || l.pathExists blockP.dir;
@ -148,18 +154,19 @@
]
else []
)
cellBlocks');
cellBlocks'));
loadCellBlock = createCellBlockLoader {inherit inputs system cells cell transformInputs;};
res = accumulate (l.map (loadCellBlock cellName cellP) cellBlocks');
in [
{${cellName} = res.output;}
{${cellName} = res.actions;}
({
cell = cellName;
cellBlocks = res.init;
}
// (l.optionalAttrs (l.pathExists cellP.readme) {inherit (cellP) readme;}))
];
in
builtins.addErrorContext "[ren] while loading cell ${cellName}" [
{${cellName} = res.output;}
{${cellName} = res.actions;}
({
cell = cellName;
cellBlocks = res.init;
}
// (l.optionalAttrs (l.pathExists cellP.readme) {inherit (cellP) readme;}))
];
in
loadCell;
in {

View file

@ -1,9 +1,9 @@
{lib}: let
l = builtins // lib;
utils = import ./utils {inherit l;};
core = import ./core {inherit l utils;};
compat = import ./compat {inherit l;};
blocks = import ./blocks;
utils = import ./utils {inherit l;};
compat = import ./compat {inherit l;};
core = import ./core {inherit l utils blocks;};
in {
inherit (compat) filter select get;
inherit (core) build buildWith;