Skip to content

Commit 39e5d72

Browse files
committed
RFC: TOML Flakes
1 parent c655bda commit 39e5d72

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

rfcs/0000-flake-toml.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
---
2+
feature: toml-flakes
3+
start-date: 2025-12-07
4+
author: Robert Hensing (@roberth)
5+
co-authors: (find a buddy later to help out with the RFC)
6+
shepherd-team: (names, to be nominated and accepted by RFC steering committee)
7+
shepherd-leader: (name to be appointed by RFC steering committee)
8+
related-issues: (will contain links to implementation PRs)
9+
---
10+
11+
# Summary
12+
[summary]: #summary
13+
14+
Make flakes easier to use, automate and learn.
15+
16+
Introduce `flake.toml` as the new leading file for flakes,
17+
separating input specifications from output definitions.
18+
The `flake.toml` file contains declarative metadata and input declarations,
19+
while `flake.nix` (read by the default entrypoint) or a framework focuses on output definitions.
20+
21+
# Motivation
22+
[motivation]: #motivation
23+
24+
Currently, flakes combine two distinct concerns in a single `flake.nix` file:
25+
declaring dependencies (inputs) and implementing functionality (outputs).
26+
This creates several problems:
27+
28+
1. **User uncertainty**: The current structure creates confusion about what language features are available
29+
and when evaluation restrictions apply,
30+
because inputs and outputs are mixed in the same file.
31+
See [issue #4945](https://github.com/NixOS/nix/issues/4945).
32+
Note that while the question was originally asked in a partly rhetorical manner,
33+
it is still a valid question,
34+
a variation of which pops into new users' minds.
35+
Some learning is always required,
36+
but this is an unnecessary bump in the curve.
37+
38+
2. **Limited automation**: Programmatically editing flake inputs requires Nix AST manipulation,
39+
which is complex and error-prone compared to editing structured data formats.
40+
41+
3. **Boilerplate**: Common flake patterns require repetitive Nix code
42+
that could be handled by frameworks if inputs were separated from implementation.
43+
44+
By moving input specifications to a simpler format,
45+
we enable better tooling,
46+
reduce user confusion about evaluation restrictions,
47+
and create a clearer separation of concerns between dependency declarations and output implementations.
48+
49+
# Detailed design
50+
[design]: #detailed-design
51+
52+
## File structure
53+
54+
`flake.toml` becomes the leading file,
55+
containing input sources, follows relationships, and entrypoint selection.
56+
It complies with the [Nix JSON guideline](https://nix.dev/manual/nix/latest/development/json-guideline.html) (modulo `null`).
57+
58+
Flake output invocation is changed to go through an "entrypoint", which is the flake that provides a function to help produce the flake output attributes based on the source tree of `flake.toml`.
59+
If `flake.toml` has an `entrypoint` field, it must name one of the `inputs` which will serve as the entrypoint.
60+
Otherwise, if an input named `"entrypoint"` exists, it becomes the entrypoint.
61+
Finally if `flake.nix` exists, the entrypoint is the default entrypoint.
62+
63+
`flake.nix` is read by the default entrypoint and defines outputs.
64+
65+
## Choice of TOML
66+
67+
See also the [alternatives] section.
68+
69+
A nice aspect of TOML is that its non-nested syntax aligns with part of a definition of _declarative systems_, having a set of _independent statements_.
70+
71+
It has a wide ecosystem of libraries for parsing, as well as good number of libraries that support round tripping edits.
72+
73+
## Relationship to existing files
74+
75+
- `flake.lock` remains unchanged
76+
- Alternative entrypoints (referenced in `flake.toml`) can read different files
77+
- `flake.nix` remains supported as the default entrypoint, and as a legacy format for the `inputs` and other metadata.
78+
79+
## Compatibility
80+
81+
The design should support reading legacy `flake.nix` files that contain inline input specifications.
82+
83+
The following negative space is changed and may require a few projects to adapt if they already use these:
84+
- A flake input named `entrypoint` is assigned meaning, changing how flake outputs are formed.
85+
- A file named `flake.toml` will shadow `flake.nix` in file discovery
86+
87+
Flakes that do not have any of the above elements remain compatible.
88+
89+
A TOML flake can not be evaluated by older implementations of Nix.
90+
91+
Other than those constraints, TOML and traditional flakes can be used and migrated back and forth without compatibility problems, as their usages in the CLI or as an `inputs` dependency do not change.
92+
93+
Validation and adoption can start in flakes with fewer users, such as those without reverse dependencies.
94+
95+
# Examples and Interactions
96+
[examples-and-interactions]: #examples-and-interactions
97+
98+
## Current flake.nix
99+
100+
```nix
101+
{
102+
description = "A simple example flake exporting GNU hello for x86_64-linux";
103+
104+
inputs = {
105+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
106+
};
107+
108+
outputs = { self, nixpkgs }:
109+
let
110+
system = "x86_64-linux";
111+
pkgs = nixpkgs.legacyPackages.${system};
112+
in {
113+
packages.${system}.default = pkgs.hello;
114+
};
115+
}
116+
```
117+
118+
## Proposed structure, simple example
119+
120+
**flake.toml**:
121+
```toml
122+
description = "A simple example flake exporting GNU hello for x86_64-linux"
123+
124+
[inputs.nixpkgs]
125+
url = "github:NixOS/nixpkgs/nixos-unstable"
126+
```
127+
128+
**flake.nix** (read by default entrypoint):
129+
```nix
130+
{
131+
outputs = { self, nixpkgs }:
132+
let
133+
system = "x86_64-linux";
134+
pkgs = nixpkgs.legacyPackages.${system};
135+
in {
136+
packages.${system}.default = pkgs.hello;
137+
};
138+
}
139+
```
140+
141+
**flake.lock** remains unchanged.
142+
143+
## Proposed structure, framework entrypoint
144+
145+
**flake.toml**:
146+
```toml
147+
description = "A simple example flake exporting GNU hello for x86_64-linux"
148+
149+
[inputs.nixpkgs]
150+
url = "github:NixOS/nixpkgs/nixos-unstable"
151+
152+
[inputs.entrypoint]
153+
url = "github:hercules-ci/flake-parts"
154+
155+
[inputs.entrypoint.inputs.nixpkgs-lib]
156+
follows = "nixpkgs"
157+
```
158+
159+
**parts.nix** (read by default entrypoint):
160+
```nix
161+
{ ... }: {
162+
perSystem = { pkgs }: {
163+
packages.default = pkgs.hello;
164+
};
165+
}
166+
```
167+
168+
**flake.lock** remains unchanged.
169+
170+
## Entrypoint function
171+
172+
`flake.nix` serves as the base that bootstraps entrypoints.
173+
This example shows what authoring a framework looks like.
174+
An entrypoint is invoked as `<flake outputs>.lib.callFlakeEntrypoint`:
175+
176+
```nix
177+
{
178+
outputs = { self, nixpkgs }:
179+
let
180+
system = "x86_64-linux";
181+
pkgs = nixpkgs.legacyPackages.${system};
182+
in {
183+
packages.${system}.default = pkgs.hello;
184+
185+
# Entrypoints are accessed here
186+
lib.callFlakeEntrypoint =
187+
{
188+
# The resolved and invoked/dependency-injected flake inputs
189+
inputs,
190+
# The parsed flake.toml
191+
flakeToml,
192+
# The invoked/dependency-injected *result*
193+
self,
194+
# The base directory of flake.toml
195+
flakeDir,
196+
# The sourceInfo, where `sourceInfo.outPath` may be `flakeDir` or any
197+
# parent of it (if applicable; subdirectory flakes)
198+
sourceInfo,
199+
# ... is mandatory for forward compatibility
200+
...
201+
}:
202+
# Imagine some useful expression, returning the usual flake outputs
203+
{
204+
packages = { ... };
205+
apps = { ... };
206+
};
207+
};
208+
}
209+
```
210+
211+
The default entrypoint reads `flake.nix` and expects an `outputs` attribute.
212+
Alternative entrypoints (specified in `flake.toml`) can implement different conventions.
213+
214+
The exact schema shown above is illustrative.
215+
The final design will follow the Nix JSON guideline for extensibility,
216+
using records at appropriate levels and `null` for optional values.
217+
218+
# Drawbacks
219+
[drawbacks]: #drawbacks
220+
221+
- Introduces another file format into the Nix ecosystem
222+
- Migration cost for existing flakes
223+
- Potential confusion during transition period with two ways to specify inputs
224+
- Requires tooling updates across the ecosystem
225+
226+
# Alternatives
227+
[alternatives]: #alternatives
228+
229+
## Keep current structure
230+
231+
Maintain the status quo including drawbacks described in the [motivation].
232+
233+
## Pure Nix with better tooling
234+
235+
Improve Nix AST manipulation tools instead of introducing a new format.
236+
Does not solve user uncertainty.
237+
Does not solve boilerplate.
238+
239+
## Use JSONC
240+
241+
JSONC is somewhat more aligned with the Nix language, including the presence of `null`,
242+
and the approximate syntax for objects/attrsets, while still allowing comments.
243+
244+
## Use YAML
245+
246+
YAML is widely supported but is despised among many for such things as [the Norway Problem](https://hitchdev.com/strictyaml/why/implicit-typing-removed/).
247+
248+
It is hard to parse correctly,
249+
which poses a significant risk for an ecosystem with a major goal of supporting reproducibility,
250+
as even an innocuous whitespace fix in parser output can cause a rift
251+
where binary caches aren't shared between Nix versions and old expressions produce new outcomes.
252+
253+
# Prior art
254+
[prior-art]: #prior-art
255+
256+
## Experimental `configs` commit by Eelco
257+
258+
[1dc3f53](https://github.com/NixOS/nix/commit/1dc3f5355a3786cab37a4de98ca46a859e015d89), part of stable branch [`configs`](https://github.com/NixOS/nix/compare/configs) implemented a `flake.toml` file.
259+
It incorporated a "poor man's module system" into Nix where it would be of very limited functionality,
260+
while it ossifies due the consequences in Nix of [Hyrum's law](https://www.hyrumslaw.com/) and the second order effects of reproducibility.
261+
262+
## devenv
263+
264+
The devenv tool uses a separate configuration format for specifying dependencies
265+
and generating Nix configurations,
266+
demonstrating that declarative input specifications can work well in practice.
267+
268+
They have also integrated input declarations and input usage in their application of the module system.
269+
This RFC could be extended or followed up with a metadata feature that generalizes this idea and allows an entrypoint to achieve a similar effect.
270+
271+
```toml
272+
[inputs.entrypoint]
273+
url = "github:cachix/git-hooks.nix"
274+
# forwarded as e.g. `inputUsages.<input>` to entrypoint, so it can import/enable/etc as intended.
275+
usage = [ "flake-parts" ]
276+
```
277+
278+
## flake-parts, flake-utils-plus and blueprint
279+
280+
Framework systems like flake-parts, flake-utils-plus and blueprint already provide abstractions over flake outputs.
281+
This proposal would enable these frameworks to be specified declaratively,
282+
in a standard way,
283+
without boilerplate.
284+
285+
## Other language ecosystems
286+
287+
Most package managers separate dependency declarations from implementation code (`Cargo.toml`, `requirements.txt`, and to a fair degree `package.json`).
288+
This proposal brings Nix flakes closer to that common pattern.
289+
290+
# Unresolved questions
291+
[unresolved]: #unresolved-questions
292+
293+
- What outcomes result from a prototype of this feature?
294+
- Schema validation for the inputs file
295+
- How follows relationships are expressed in TOML
296+
297+
# Future work
298+
[future]: #future-work
299+
300+
- Evaluate TOML round-tripping libraries
301+
302+
# Credit
303+
304+
Having been part of the Nix team,
305+
I suspect that I've learned some of these ideas from the team, especially Eelco,
306+
who has experimented in this direction.
307+
308+
Also another thank you to username-generic for asking the question in a recent [discourse thread](https://discourse.nixos.org/t/outlining-the-differences-between-flakes-and-nix-configs/72996/7),
309+
and together with TLATER for making me realise I should just go ahead and write this RFC.

0 commit comments

Comments
 (0)