From eca5d99c62ffd3c550d58a31f501f0d60e655014 Mon Sep 17 00:00:00 2001 From: backend Date: Wed, 5 Aug 2026 13:53:35 +0000 Subject: [PATCH 1/4] pointy.deps: declare dependent pointy repos and mount their outputs under namespaces - flakeModules.default gains pointy.deps (namespace -> { input, repo? }) - outputs.pointy.deps: backend registry contract (lazy, own-state-only) - outputs.pointy.namespaces.: dependency pointy domain output mount - packages..pointy.namespaces.: + built steps/projectOutPaths/autocomplete - helpful throws for unknown/non-pointy dep inputs --- flake.nix | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/flake.nix b/flake.nix index a3bc1b2..cf02e96 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,27 @@ }; projects = top.lib.mkOption { type = top.lib.types.attrsOf pointyLib.types.pointy.project; }; srcFiles = top.lib.mkOption { type = top.lib.types.raw; }; + deps = top.lib.mkOption { + type = top.lib.types.attrsOf ( + top.lib.types.submodule { + options.input = top.lib.mkOption { + type = top.lib.types.str; + description = "The flake-input key in this repo's flake.nix that pins the dependency."; + }; + options.repo = top.lib.mkOption { + type = top.lib.types.nullOr top.lib.types.str; + default = null; + description = "The pointy registry name of the dependency. Optional; when absent Pointy resolves the input's locked URL against the registry."; + }; + } + ); + default = { }; + description = '' + Pointy repositories this repo depends on, keyed by namespace. + Each entry maps a namespace to `{ input, repo ? null }` where + `input` is the flake input key and `repo` the registry name. + ''; + }; }; config = @@ -39,6 +60,45 @@ type = "derivation"; name = ""; }; + + # Resolve a declared dependency to its flake output. Throws a + # helpful error when the input is unknown or not a pointy flake. + depOutput = + ns: decl: + let + inputName = decl.input; + in + if !(builtins.hasAttr inputName top.inputs) then + throw "pointy.deps: namespace `${ns}` refers to input `${inputName}` which is not declared in the flake's `inputs`." + else if !(top.inputs.${inputName} ? pointy) then + throw "pointy.deps: input `${inputName}` (namespace `${ns}`) is not a pointy flake: it has no `pointy` output." + else + top.inputs.${inputName}; + + # Backend contract (`#pointy.deps`): namespace -> { input, repo? }. + # Pure metadata of this repo's own committed state; does not force + # evaluation of dependency content. + depsJson = builtins.mapAttrs ( + _: decl: + { input = decl.input; } + // (if decl.repo != null then { inherit (decl) repo; } else { }) + ) cfg.deps; + + # Mount each co-managed dependency's pointy output under a + # namespace so this repo can refer to its stepDefs/templates / projects / presets / srcFiles. + domainNamespaces = builtins.mapAttrs (ns: decl: (depOutput ns decl).pointy) cfg.deps; + + # Per-system mount: also exposes dependency built steps / projectOutPaths / autocomplete. + perSystemNamespaces = + pkgs: + builtins.mapAttrs ( + ns: decl: + let + dep = depOutput ns decl; + in + (dep.pointy or { }) + // (dep.packages.${pkgs.system}.pointy or { }) + ) cfg.deps; in { flake.pointy = with pointyLib; { @@ -48,6 +108,8 @@ stepDefs = evalStepDefs cfg; srcFiles = cfg.srcFiles; dependencies = evalDependencies cfg; + deps = depsJson; + namespaces = domainNamespaces; }; perSystem = { pkgs, ... }: @@ -61,6 +123,7 @@ steps = evalSteps <| cfg // { inherit pkgs; }; projectOutPaths = evalProjectOutPaths <| cfg // { inherit pkgs; }; autocomplete = evalAutocomplete <| cfg // { inherit pkgs; }; + namespaces = perSystemNamespaces pkgs; }; }; }; From 32ecedeaadda6f4509b3504aee87c90c7dae57a9 Mon Sep 17 00:00:00 2001 From: backend Date: Wed, 5 Aug 2026 14:25:42 +0000 Subject: [PATCH 2/4] docs: pointy.deps + namespace mount usage --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/README.md b/README.md index e9d15c3..95bd57e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,51 @@ Nix flake library that turns a user repository into the flake outputs that [Poin - `#pointy.projects` — project membership and ordering - `#pointy.srcFiles` — per-step source files - `#pointy.dependencies` — step dependency graph +- `#pointy.deps` — declared pointy repository dependencies: + `{ namespace = { input, repo? }; }` +- `#pointy.namespaces.` — the `pointy` output of each declared dependency, + mounted so a repo can reach another repo's stepDefs, stepConfig, templates, + projects, presets and srcFiles (plus, per-system, + `#pointy.packages..pointy.namespaces..steps`) under a namespace - per-system `#pointy.steps.` and `#pointy.projectOutPaths` — buildable derivations +## Depending on another pointy repo + +A repo imports another pointy user repo as a dependency by declaring a flake +input and listing it in `pointy.deps`: + +```nix +{ + # repo B's flake + inputs = { + a.url = "git@example.com:org/edit-tooling.git"; # A's flake (a pointy repo) + pointy-stdlib.url = "github:421anon/pointy-stdlib"; + }; + outputs = { pointy-stdlib, ... }@inputs: + pointy-stdlib.lib.mkFlake { inherit inputs; } { + pointy = { + # ... own stepDefs/templates/projects/srcFiles ... + deps = { + edit-tooling = { input = "a"; repo = "edit-tooling"; }; + }; + }; + }; +} +``` + +- `input` must name a flake input of this repo; `repo` (optional) is the + dependency's pointy registry name and defaults to resolving the input's + locked URL against the registry. +- The dependency's own `pointy` output becomes reachable as + `pointy.namespaces.edit-tooling` (and its built steps under + `packages..pointy.namespaces.edit-tooling.steps`) inside B's + evaluation. +- `#pointy.deps` is pure metadata of B's own committed state: evaluating it + does not force evaluation of dependency content. +- Unknown inputs / non-pointy inputs fail with a descriptive error when the + namespace is forced. + +In the single-repo backend variant the dependency resolves from the +committed flake.lock at evaluation time, exactly like any other flake input. + See [Setting Up the User Repository](https://github.com/421anon/pointy/blob/main/docs/pages/user-repo-setup.md) for a minimal `flake.nix` and template examples. From 82aaa4a07aa569389589e8ac69ecf2c136bc189b Mon Sep 17 00:00:00 2001 From: backend Date: Wed, 5 Aug 2026 16:30:47 +0000 Subject: [PATCH 3/4] deps: address review findings - guard input=self (injected by withDefaultNixpkgs) with a descriptive throw instead of a stack overflow - strip the dependency fakeDrv markers (type/name) from the per-system namespace merge so #pointy.namespaces. is a clean pointy surface - default stepDefs/templates/projects/srcFiles to {} so deps-only hub repos evaluate (#pointy.stepConfig etc.) - README: correct the per-system steps path (#packages..pointy... not #pointy.packages...) and describe the namespace surface accurately - verified via consumer harness: clean namespace root, reachable built steps, deps-only stepConfig={}, self-dep descriptive error --- README.md | 27 +++++++++++++++------------ flake.nix | 34 ++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 95bd57e..9a7223a 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ Nix flake library that turns a user repository into the flake outputs that [Poin - `#pointy.dependencies` — step dependency graph - `#pointy.deps` — declared pointy repository dependencies: `{ namespace = { input, repo? }; }` -- `#pointy.namespaces.` — the `pointy` output of each declared dependency, - mounted so a repo can reach another repo's stepDefs, stepConfig, templates, - projects, presets and srcFiles (plus, per-system, - `#pointy.packages..pointy.namespaces..steps`) under a namespace +- `#pointy.namespaces.` — each declared dependency's full pointy surface + mounted under a namespace: its stepDefs, stepConfig, templates, projects, + presets, srcFiles and deps, plus (resolved from the per-system package view) + its built `steps`, `projectOutPaths` and `autocomplete`. The namespace root + is a clean attrset (no fakeDrv markers). - per-system `#pointy.steps.` and `#pointy.projectOutPaths` — buildable derivations ## Depending on another pointy repo @@ -41,16 +42,18 @@ input and listing it in `pointy.deps`: } ``` -- `input` must name a flake input of this repo; `repo` (optional) is the - dependency's pointy registry name and defaults to resolving the input's - locked URL against the registry. -- The dependency's own `pointy` output becomes reachable as - `pointy.namespaces.edit-tooling` (and its built steps under - `packages..pointy.namespaces.edit-tooling.steps`) inside B's - evaluation. +- `input` must name a flake input of this repo (never `self`); `repo` + (optional) is the dependency's pointy registry name and defaults to + resolving the input's locked URL against the registry. +- Because `packages..pointy` shadows the top-level `pointy` output for + installable resolution, `#pointy.namespaces.` resolves to the per-system + merged surface, so both of these reach the dependency's built steps: + - `#pointy.namespaces.edit-tooling.steps.` (docs-friendly form) + - `#packages..pointy.namespaces.edit-tooling.steps.` (explicit) + and `#pointy.namespaces.edit-tooling.stepDefs` etc. reach its domain objects. - `#pointy.deps` is pure metadata of B's own committed state: evaluating it does not force evaluation of dependency content. -- Unknown inputs / non-pointy inputs fail with a descriptive error when the +- Unknown / non-pointy / `self` inputs fail with a descriptive error when the namespace is forced. In the single-repo backend variant the dependency resolves from the diff --git a/flake.nix b/flake.nix index cf02e96..f0e0783 100644 --- a/flake.nix +++ b/flake.nix @@ -22,14 +22,26 @@ flakeModules.default = top: { options.pointy = { - stepDefs = top.lib.mkOption { type = top.lib.types.attrsOf pointyLib.types.pointy.stepDef; }; - templates = top.lib.mkOption { type = top.lib.types.attrs; }; + stepDefs = top.lib.mkOption { + type = top.lib.types.attrsOf pointyLib.types.pointy.stepDef; + default = { }; + }; + templates = top.lib.mkOption { + type = top.lib.types.attrs; + default = { }; + }; presets = top.lib.mkOption { type = top.lib.types.attrsOf pointyLib.types.pointy.preset; default = { }; }; - projects = top.lib.mkOption { type = top.lib.types.attrsOf pointyLib.types.pointy.project; }; - srcFiles = top.lib.mkOption { type = top.lib.types.raw; }; + projects = top.lib.mkOption { + type = top.lib.types.attrsOf pointyLib.types.pointy.project; + default = { }; + }; + srcFiles = top.lib.mkOption { + type = top.lib.types.raw; + default = { }; + }; deps = top.lib.mkOption { type = top.lib.types.attrsOf ( top.lib.types.submodule { @@ -68,7 +80,12 @@ let inputName = decl.input; in - if !(builtins.hasAttr inputName top.inputs) then + # `self` is always injected into inputs by mkFlake's + # withDefaultNixpkgs; depending on it would recurse into this very + # flake's pointy output (stack overflow). + if inputName == "self" then + throw "pointy.deps: namespace `${ns}` may not depend on `self` (a repo cannot depend on its own pointy output)." + else if !(builtins.hasAttr inputName top.inputs) then throw "pointy.deps: namespace `${ns}` refers to input `${inputName}` which is not declared in the flake's `inputs`." else if !(top.inputs.${inputName} ? pointy) then throw "pointy.deps: input `${inputName}` (namespace `${ns}`) is not a pointy flake: it has no `pointy` output." @@ -89,6 +106,8 @@ domainNamespaces = builtins.mapAttrs (ns: decl: (depOutput ns decl).pointy) cfg.deps; # Per-system mount: also exposes dependency built steps / projectOutPaths / autocomplete. + # Strips the dependency's own fakeDrv package markers (type/name) from the + # namespace root so `#pointy.namespaces.` is a clean pointy surface. perSystemNamespaces = pkgs: builtins.mapAttrs ( @@ -97,7 +116,10 @@ dep = depOutput ns decl; in (dep.pointy or { }) - // (dep.packages.${pkgs.system}.pointy or { }) + // (builtins.removeAttrs (dep.packages.${pkgs.system}.pointy or { }) [ + "type" + "name" + ]) ) cfg.deps; in { From abdae99417fd37ed58b3dc68c3615abdb43b8b7a Mon Sep 17 00:00:00 2001 From: backend Date: Wed, 5 Aug 2026 17:39:50 +0000 Subject: [PATCH 4/4] deps: readability cleanup from review - depOutput comment: resolve to the flake input (not 'output'), which is what it actually returns - rename depsJson -> depsContract (it is an attrset, not JSON; matches the Backend contract comment and #pointy.deps framing) - fix mangled 'stepDefs/templates / projects' comment list, drop 'co-managed' - README: drop leaked fakeDrv name, restructure the shadowing bullet, reword the stale single-repo-backend-variant paragraph --- README.md | 20 +++++++++++--------- flake.nix | 13 +++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9a7223a..6810ff9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Nix flake library that turns a user repository into the flake outputs that [Poin mounted under a namespace: its stepDefs, stepConfig, templates, projects, presets, srcFiles and deps, plus (resolved from the per-system package view) its built `steps`, `projectOutPaths` and `autocomplete`. The namespace root - is a clean attrset (no fakeDrv markers). + is a clean attrset. - per-system `#pointy.steps.` and `#pointy.projectOutPaths` — buildable derivations ## Depending on another pointy repo @@ -45,18 +45,20 @@ input and listing it in `pointy.deps`: - `input` must name a flake input of this repo (never `self`); `repo` (optional) is the dependency's pointy registry name and defaults to resolving the input's locked URL against the registry. -- Because `packages..pointy` shadows the top-level `pointy` output for - installable resolution, `#pointy.namespaces.` resolves to the per-system - merged surface, so both of these reach the dependency's built steps: - - `#pointy.namespaces.edit-tooling.steps.` (docs-friendly form) - - `#packages..pointy.namespaces.edit-tooling.steps.` (explicit) - and `#pointy.namespaces.edit-tooling.stepDefs` etc. reach its domain objects. +- Because the per-system `packages..pointy` package shadows the + top-level `pointy` output for installable resolution, `#pointy.namespaces.` + resolves to the per-system merged surface: + - `#pointy.namespaces.edit-tooling.steps.` — the dependency's built steps + - `#pointy.namespaces.edit-tooling.stepDefs` etc. — its domain objects + (equivalently via the explicit per-system path + `#packages..pointy.namespaces.edit-tooling.steps.`). - `#pointy.deps` is pure metadata of B's own committed state: evaluating it does not force evaluation of dependency content. - Unknown / non-pointy / `self` inputs fail with a descriptive error when the namespace is forced. -In the single-repo backend variant the dependency resolves from the -committed flake.lock at evaluation time, exactly like any other flake input. +Dependencies are ordinary flake inputs, resolved from the dependent repo's +committed `flake.lock` at evaluation time; the backend evaluates the dependent +exactly as today. See [Setting Up the User Repository](https://github.com/421anon/pointy/blob/main/docs/pages/user-repo-setup.md) for a minimal `flake.nix` and template examples. diff --git a/flake.nix b/flake.nix index f0e0783..73430f6 100644 --- a/flake.nix +++ b/flake.nix @@ -73,8 +73,9 @@ name = ""; }; - # Resolve a declared dependency to its flake output. Throws a - # helpful error when the input is unknown or not a pointy flake. + # Resolve a declared dependency to its flake input (the full input + # flake attrset); callers pick `.pointy` / `.packages..pointy` off it. + # Throws a helpful error when the input is unknown or not a pointy flake. depOutput = ns: decl: let @@ -95,14 +96,14 @@ # Backend contract (`#pointy.deps`): namespace -> { input, repo? }. # Pure metadata of this repo's own committed state; does not force # evaluation of dependency content. - depsJson = builtins.mapAttrs ( + depsContract = builtins.mapAttrs ( _: decl: { input = decl.input; } // (if decl.repo != null then { inherit (decl) repo; } else { }) ) cfg.deps; - # Mount each co-managed dependency's pointy output under a - # namespace so this repo can refer to its stepDefs/templates / projects / presets / srcFiles. + # Mount each declared dependency's pointy domain output (stepDefs, + # stepConfig, templates, projects, presets, srcFiles, deps) under a namespace. domainNamespaces = builtins.mapAttrs (ns: decl: (depOutput ns decl).pointy) cfg.deps; # Per-system mount: also exposes dependency built steps / projectOutPaths / autocomplete. @@ -130,7 +131,7 @@ stepDefs = evalStepDefs cfg; srcFiles = cfg.srcFiles; dependencies = evalDependencies cfg; - deps = depsJson; + deps = depsContract; namespaces = domainNamespaces; }; perSystem =