Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,56 @@ 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.<ns>` — 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.
- per-system `#pointy.steps.<id>` 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 (never `self`); `repo`
(optional) is the dependency's pointy registry name and defaults to
resolving the input's locked URL against the registry.
- Because the per-system `packages.<system>.pointy` package shadows the
top-level `pointy` output for installable resolution, `#pointy.namespaces.<ns>`
resolves to the per-system merged surface:
- `#pointy.namespaces.edit-tooling.steps.<id>` — the dependency's built steps
- `#pointy.namespaces.edit-tooling.stepDefs` etc. — its domain objects
(equivalently via the explicit per-system path
`#packages.<system>.pointy.namespaces.edit-tooling.steps.<id>`).
- `#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.

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.
94 changes: 90 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,47 @@

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 {
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 =
Expand All @@ -39,6 +72,56 @@
type = "derivation";
name = "";
};

# Resolve a declared dependency to its flake input (the full input
# flake attrset); callers pick `.pointy` / `.packages.<sys>.pointy` off it.
# Throws a helpful error when the input is unknown or not a pointy flake.
depOutput =
ns: decl:
let
inputName = decl.input;
in
# `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."
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.
depsContract = builtins.mapAttrs (
_: decl:
{ input = decl.input; }
// (if decl.repo != null then { inherit (decl) repo; } else { })
) cfg.deps;

# 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.
# Strips the dependency's own fakeDrv package markers (type/name) from the
# namespace root so `#pointy.namespaces.<ns>` is a clean pointy surface.
perSystemNamespaces =
pkgs:
builtins.mapAttrs (
ns: decl:
let
dep = depOutput ns decl;
in
(dep.pointy or { })
// (builtins.removeAttrs (dep.packages.${pkgs.system}.pointy or { }) [
"type"
"name"
])
) cfg.deps;
in
{
flake.pointy = with pointyLib; {
Expand All @@ -48,6 +131,8 @@
stepDefs = evalStepDefs cfg;
srcFiles = cfg.srcFiles;
dependencies = evalDependencies cfg;
deps = depsContract;
namespaces = domainNamespaces;
};
perSystem =
{ pkgs, ... }:
Expand All @@ -61,6 +146,7 @@
steps = evalSteps <| cfg // { inherit pkgs; };
projectOutPaths = evalProjectOutPaths <| cfg // { inherit pkgs; };
autocomplete = evalAutocomplete <| cfg // { inherit pkgs; };
namespaces = perSystemNamespaces pkgs;
};
};
};
Expand Down