Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save sini/b28b169efe309de3a416e721bf5afa48 to your computer and use it in GitHub Desktop.

Select an option

Save sini/b28b169efe309de3a416e721bf5afa48 to your computer and use it in GitHub Desktop.
Design spec: Built-in flake-parts entity kind for den

Built-in flake-parts Entity Kind

Context

Users want to write to top-level flake-parts config paths (outside config.flake) from aspects — e.g., config.inputs, config.perSystem.packages, config.treefmt. The flake-parts-modules template demonstrates this is possible via a custom flake-parts entity with scope transition and manual resolution, but it requires ~80 lines of boilerplate that users must replicate.

Host, user, and home entities are built-in. The flake-parts entity should be too.

Goal

After this change, a user can:

# Register a custom class
den.classes.flake-inputs.description = "Flake input declarations";

# Route it to a top-level flake-parts path
den.policies.route-inputs = _: [
  (den.lib.policy.route {
    fromClass = "flake-inputs";
    intoClass = "flake-parts";
    path = [ "inputs" ];
  })
];
den.schema.flake-parts.includes = [ den.policies.route-inputs ];

# Use the class naturally in aspects
den.aspects.igloo.flake-inputs.nixpkgs.url = "github:NixOS/nixpkgs";

No manual resolveEntity, no isEntity = true, no scope transition policy, no perSystem.imports injection.

What already exists

The flake-parts-modules template (templates/flake-parts-modules/) has the full working pattern:

  1. modules/den.nix:108-118to-flake-parts policy: resolve.to "flake-parts" from flake-system scope, creates per-system flake-parts entity
  2. modules/den.nix:121den.schema.flake-system.excludes = [ den.policies.to-packages ] to suppress conflicting built-in
  3. modules/den.nix:123den.schema.flake-parts.isEntity = true
  4. modules/flake-parts.nix:4 — manual den.lib.aspects.resolve "flake-parts" (den.lib.resolveEntity "flake-parts" {}) injected into perSystem.imports
  5. modules/systems.nixsystems = builtins.attrNames den.hosts so flake-parts knows which systems to evaluate
  6. modules/classes/*.nix — custom classes routed intoClass = "flake-parts" with path and adaptArgs

Design

Extract the template's wiring into den's built-in modules.

1. Schema kind registration

File: modules/context/flake-schema.nix (already registers flake, flake-system, default)

Add flake-parts as a schema entry kind with isEntity = true so the pipeline treats it as a full entity with policy dispatch.

2. Scope transition policy

New file: modules/policies/flake-parts.nix

Built-in policy to-flake-parts that creates the flake-parts entity scope from flake-system:

den.policies.to-flake-parts = { system, ... }: [
  (den.lib.policy.resolve.to "flake-parts" {
    flake-parts = {
      name = "flake-parts-${system}";
      aspect = {};
    };
  })
];
den.schema.flake-system.includes = [ den.policies.to-flake-parts ];

The flake-parts entity carries no aspect content of its own — it only receives routed content from other classes. This differs from host/user/home which have lookupAspect defaults.

3. Resolution injection

New file or extend modules/outputs.nix

Auto-resolve the flake-parts entity and inject into perSystem.imports:

perSystem.imports = [
  (den.lib.aspects.resolve "flake-parts" (den.lib.resolveEntity "flake-parts" {}))
];

4. Systems derivation

Derive systems from den.hosts so flake-parts knows which systems to evaluate perSystem for:

systems = builtins.attrNames den.hosts;

Without this, perSystem has no systems to iterate over. This mirrors the template's systems.nix.

5. Conflicting built-in policy suppression

The built-in to-packages, to-devShells, to-apps, to-checks policies in modules/policies/flake.nix route system-level classes into flake.<output>.<system>. When flake-parts is active, these conflict with flake-parts' own output handling.

Auto-exclude the conflicting policies from flake-system when flake-parts is active:

den.schema.flake-system.excludes = [
  den.policies.to-packages
  # ... other conflicting policies
];

This mirrors the template's den.nix:121.

Guard: flake-parts availability

Guard all pieces behind actual flake-parts evaluation context — not just inputs ? flake-parts (which only checks input existence, not that the module system has flake-parts options). Use the existing no-flake-parts pattern from outputs.nix which checks !inputs ? flake-parts, and additionally verify options ? perSystem before injecting into perSystem.imports.

adaptArgs pattern

All class routes into flake-parts need adaptArgs to give aspect functions access to perSystem module args (pkgs, self', inputs'). This is NOT built into the entity wiring — each class route provides its own adaptArgs. Document the pattern:

den.lib.policy.route {
  fromClass = "my-class";
  intoClass = "flake-parts";
  path = [ "my-path" ];
  adaptArgs = { config, ... }: config.allModuleArgs;
}

Template migration

When this lands, the flake-parts-modules template's files change:

Template file Status
modules/den.nix:108-123 Redundant — scope transition + isEntity + excludes now built-in
modules/flake-parts.nix Redundant — resolution injection now built-in
modules/systems.nix Redundant — systems derivation now built-in
modules/classes/*.nix Kept — class registration + routes are user-defined
modules/perSystem-from-hosts.nix Kept — host-to-flake-parts cross-entity routing is template-specific

Existing template users get no-op duplication (idempotent merge via module system). Migration: remove the redundant files.

Files to modify

File Change
modules/context/flake-schema.nix Add flake-parts entry with isEntity = true
New modules/policies/flake-parts.nix Scope transition + conflicting policy suppression
modules/outputs.nix or new module perSystem.imports injection + systems derivation
templates/flake-parts-modules/ Simplify: remove redundant wiring files

Test plan

  1. New CI test: register custom class, route into flake-parts, verify content lands at expected perSystem path
  2. New CI test: route into top-level flake-parts path (non-perSystem), verify it lands
  3. Existing flake-parts-modules template: nix flake check --override-input den .
  4. Full CI: nix develop -c just ci
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment