affogato is a suite of tools for the Nix language, written in Java:
-
a formatter, a port of nixfmt that leaves all of nixpkgs unchanged;
-
a linter, with rules drawn from deadnix, statix and nixd, many with an automatic fix;
-
a syntax highlighter, which works from the lexer alone and so copes with syntax errors;
-
an evaluator on GraalVM, as a Truffle AST interpreter, which evaluates nixpkgs unmodified and computes store paths and
.drvpaths in memory, without writing anything to/nix/store; -
a playground that runs the formatter, linter and highlighter in the browser, compiled to JavaScript.
Getting started
Build it with Nix, either on the JVM or as a static native binary (linked against musl), which starts in milliseconds rather than half a second:
nix build .#affogato-truffle # on OpenJDK 25
nix build .#affogato-truffle-native # native image, built with GraalVM
To format and lint only, affogato and affogato-native provide a affogato
command with only format, check and fix, without the evaluator and
Truffle.
Each package installs bash and zsh completions; affogato generate-completion
prints the script, to source in a shell where it is not installed.
To try the formatter and linter in a browser, open the
playground; they run in the page, compiled to
JavaScript. To build it locally, then open result/index.html in a browser:
nix build .#playground
Evaluate an expression:
$ ./result/bin/affogato eval --strict -E 'builtins.map (x: x * 2) [ 1 2 3 ]'
[ 2 4 6 ]
Compute the derivation path of a nixpkgs attribute:
./result/bin/affogato drv-path -I nixpkgs=/path/to/nixpkgs -E 'import <nixpkgs> {}' -A hello
Command line
Each command has its own page, generated from the command line itself;
-h or --help after any command prints the same.
Commands
- format
-
Format Nix files in place, as nixfmt does.
- check
-
Report the linter’s findings in Nix files without changing them.
- fix
-
Apply the linter’s fixes to Nix files in place, then format them.
- eval
-
Evaluate an expression and print the result, like nix-instantiate --eval.
- drv-path
-
Evaluate an expression to a derivation and print its drvPath.
treefmt
format and fix follow the
treefmt formatter spec:
[formatter.affogato]
command = "affogato"
options = ["fix"]
includes = ["*.nix"]
Status
Checked against Nix 2.34.9 and nixpkgs 31ecbaca1bde on x86_64-linux.
- Language tests
-
affogato passes NixOS/nix’s
tests/functional/langsuite (303 tests) and Tvix’seval/src/tests/tvix_tests, apart from the cases listed with reasons inlanguage/src/test/java/lang-skip.txt. - Top-level packages
-
For every top-level nixpkgs attribute (
scripts/toplevel-drvpaths.nix), affogato andnix-instantiateproduce 24,268 identical drvPaths. 680 attributes fail in both, and none differ or fail in only one evaluator. The run takes 1 min 40 s on the JVM, against 2 min 08 s for Nix. - Heavier attributes
-
scripts/parity.shfinds identical drvPaths forfirefox,chromium,libreoffice,texliveFull,haskellPackages.pandoc,nixosTests.firefox,pkgsCross.aarch64-multiplatform.helloandpkgsStatic.hello.
Architecture
Each top-level folder is a Java module.
parser/-
A hand-written lexer and parser. It builds a concrete syntax tree that keeps every token, then lowers it to the evaluator’s AST.
store/-
NAR hashing, Nix base32 and store-path computation.
language/-
The Truffle language:
-
Translator: static scope resolution into Truffle nodes. -
nodes/andruntime/: the evaluator and its values. -
builtins/: the builtins. -
corepkgs/: Nix’sderivation.nixandfetchurl.nix, vendored from NixOS/nix.
-
launcher/-
The command line, built on the polyglot API, plus its native-image build.
format/-
The formatter.
lint/-
The linter.
Formatter
org.affogato.format.Formatter ports nixfmt
1.5.0 (MPL-2.0) to the parser’s concrete syntax tree. Formatter.format(src)
formats with nixfmt’s defaults. format(src, width, indent, strict) sets the
line width, the indentation and nixfmt’s strict mode.
Linter
org.affogato.lint.Linter runs checks in the spirit of
deadnix (unused bindings),
statix (simpler forms) and
nixd (undefined variables, unknown
builtins, unused with and rec). Linter.lint(src) returns the diagnostics,
and Linter.fix(src) applies the fixes that come with most of them. Both take
an optional set of Rule values to restrict the checks. Lint rules lists
the rules.
Tests
language/src/test/java/-
The evaluator’s test module. Its data are packaged with it, so the tests rerun whenever the data change.
-
lang/: NixOS/nix’stests/functional/lang(LGPL-2.1). -
tvix/: Tvix’seval/src/tests/tvix_tests. -
affogato/: affogato’s own regression tests.
-
format/src/test/java/nixfmt/-
nixfmt’s own test suite.
lint/src/test/java/cases/-
The linter’s cases.
The upstream suites are kept in sync with vendir. To
follow a new Nix release, change ref in vendir.yml, run vendir sync, and
update lang-symlinks.txt if upstream added symlinks (the jar cannot hold them).
Lint rules
Each rule shows a case from the linter’s tests: the original code, what the rule’s fixes make of it, and what the rule reports, with [fix] after the findings that come with a fix.
| Rule | Severity | Description |
|---|---|---|
❌ error |
The code does not parse, or breaks a rule Nix checks while parsing, such as defining an attribute twice. No other rule runs then. |
|
⚠️ warning |
A |
|
⚠️ warning |
A function argument that the body never uses. The fix renames it to |
|
⚠️ warning |
An attribute of a function’s pattern that the body never uses. It is not reported when the |
|
⚠️ warning |
An |
|
⚠️ warning |
A comparison with |
|
⚠️ warning |
|
|
⚠️ warning |
A |
|
⚠️ warning |
A |
|
⚠️ warning |
The deprecated |
|
⚠️ warning |
An |
|
⚠️ warning |
A function taking |
|
⚠️ warning |
|
|
⚠️ warning |
|
|
⚠️ warning |
|
|
💡 hint |
|
|
⚠️ warning |
Parentheses around a simple expression, or around a whole binding value, |
|
⚠️ warning |
A URI literal, which is deprecated. The fix quotes it. |
|
⚠️ warning |
|
|
⚠️ warning |
|
|
⚠️ warning |
A concatenation with |
|
⚠️ warning |
|
|
💡 hint |
|
|
💡 hint |
Three or more bindings that start with the same attribute, such as |
|
❌ error |
A variable that no binding defines, outside any |
|
❌ error |
|
|
⚠️ warning |
|
|
⚠️ warning |
A binding that hides a global such as |
|
⚠️ warning |
A |
|
⚠️ warning |
A |
|
⚠️ warning |
A phase such as |
|
⚠️ warning |
A derivation built from a |
|
💡 hint |
A derivation that does not set |
|
💡 hint |
A derivation that does not set |
|
⚠️ warning |
|
|
⚠️ warning |
A fetcher given |
|
⚠️ warning |
A |
Dead code
Bindings that nothing uses, as deadnix finds them. Names starting with _ are never reported.
unused-let-binding
Severity: ⚠️ warning. A let binding that the body never reaches. A binding used only by itself, or by other unused bindings, counts as unused too. The fix removes it.
{ f }:
let
used = 1;
unused = 2;
self = x: self x;
a = b;
b = a;
_ignored = 3;
inherit (f) kept dropped;
inherit (f) gone;
nested.x = 1;
nested.y = 2;
# Only `dead` is reported: the uses of `ver` and `pkg` are inside it too.
dead = ver: pkg: pkg.${ver};
in
[
used
kept
]
{ f }:
let
used = 1;
_ignored = 3;
inherit (f) kept;
# Only `dead` is reported: the uses of `ver` and `pkg` are inside it too.
in
[
used
kept
]
unused-let-binding.nix:4:3: warning[unused-let-binding]: unused let binding 'unused' [fix]
unused-let-binding.nix:5:3: warning[unused-let-binding]: unused let binding 'self' [fix]
unused-let-binding.nix:6:3: warning[unused-let-binding]: unused let binding 'a' [fix]
unused-let-binding.nix:7:3: warning[unused-let-binding]: unused let binding 'b' [fix]
unused-let-binding.nix:9:20: warning[unused-let-binding]: unused let binding 'dropped' [fix]
unused-let-binding.nix:10:15: warning[unused-let-binding]: unused let binding 'gone' [fix]
unused-let-binding.nix:11:3: warning[unused-let-binding]: unused let binding 'nested' [fix]
unused-let-binding.nix:14:3: warning[unused-let-binding]: unused let binding 'dead' [fix]
unused-lambda-arg
Severity: ⚠️ warning. A function argument that the body never uses. The fix renames it to , unless the body uses an outer .
let
ignored = x: 1;
underscore = _x: 1;
used = x: x;
# Renaming `y` to `_` would capture the outer `_`.
capture = _: y: _;
in
[
ignored
underscore
used
capture
]
let
ignored = _: 1;
underscore = _x: 1;
used = x: x;
# Renaming `y` to `_` would capture the outer `_`.
capture = _: y: _;
in
[
ignored
underscore
used
capture
]
unused-lambda-arg.nix:2:13: warning[unused-lambda-arg]: unused lambda argument 'x' [fix]
unused-lambda-arg.nix:6:16: warning[unused-lambda-arg]: unused lambda argument 'y'
unused-formal
Severity: ⚠️ warning. An attribute of a function’s pattern that the body never uses. It is not reported when the @ binding is used, as that may pass it on. The fix removes it only when the pattern has ..., so that callers passing it still work.
[
({ a, b, ... }: a)
# Without `...`, removing `b` would change which arguments are accepted.
({ a, b }: a)
# `args` may pass `b` on.
({ a, b, ... }@args: args)
({ a, b ? a, ... }: b)
(
{
a,
b,
c,
...
}:
c
)
]
[
({ a, ... }: a)
# Without `...`, removing `b` would change which arguments are accepted.
({ a, b }: a)
# `args` may pass `b` on.
({ a, b, ... }@args: args)
({ a, b ? a, ... }: b)
(
{
c,
...
}:
c
)
]
unused-formal.nix:2:9: warning[unused-formal]: unused formal argument 'b' [fix]
unused-formal.nix:4:9: warning[unused-formal]: unused formal argument 'b'
unused-formal.nix:10:7: warning[unused-formal]: unused formal argument 'a' [fix]
unused-formal.nix:11:7: warning[unused-formal]: unused formal argument 'b' [fix]
unused-at-pattern
Severity: ⚠️ warning. An @ binding that the body never uses. The fix removes it.
[
(args@{ a, ... }: a)
({ a, ... } @ args: a)
(args@{ a, ... }: args)
]
[
({ a, ... }: a)
({ a, ... }: a)
(args@{ a, ... }: args)
]
unused-at-pattern.nix:2:4: warning[unused-at-pattern]: unused pattern binding 'args' [fix]
unused-at-pattern.nix:3:17: warning[unused-at-pattern]: unused pattern binding 'args' [fix]
Simpler forms
Code with a simpler or more idiomatic form, as statix finds it.
bool-comparison
Severity: ⚠️ warning. A comparison with true or false. There is no fix: v == true is also a type test, where v alone fails on other types.
{ x, y }:
[
(x == true)
(false != y)
(x == y)
]
bool-comparison.nix:3:4: warning[bool-comparison]: comparison with 'true'; if the other side is always a boolean, use it directly
bool-comparison.nix:4:4: warning[bool-comparison]: comparison with 'false'; if the other side is always a boolean, use it directly
bool-simplification
Severity: ⚠️ warning. !(a == b), which is a != b, and !(a != b), which is a == b.
{ a, b }:
[
(!(a == b))
(!(a != b) || a)
(!(a && b))
]
{ a, b }:
[
(a != b)
(a == b || a)
(!(a && b))
]
bool-simplification.nix:3:4: warning[bool-simplification]: negated comparison; use '!=' [fix]
bool-simplification.nix:4:4: warning[bool-simplification]: negated comparison; use '==' [fix]
empty-let-in
Severity: ⚠️ warning. A let with no bindings.
let
in
{
a = let in 1;
}
{
a = 1;
}
empty-let-in.nix:1:1: warning[empty-let-in]: 'let' with no bindings [fix]
empty-let-in.nix:4:7: warning[empty-let-in]: 'let' with no bindings [fix]
collapsible-let-in
Severity: ⚠️ warning. A let right in the body of another. The fix merges them unless the outer bindings use or bind a name of the inner ones.
{
merged =
let
a = 1;
in
let
b = a;
in
b;
# Merged, the inner `b` would replace the argument in `a = b;`.
capture =
b:
let
a = b;
in
let
b = 2;
in
a + b;
}
{
merged =
let
a = 1;
b = a;
in
b;
# Merged, the inner `b` would replace the argument in `a = b;`.
capture =
b:
let
a = b;
in
let
b = 2;
in
a + b;
}
collapsible-let-in.nix:6:5: warning[collapsible-let-in]: this 'let' can be merged into the outer one [fix]
collapsible-let-in.nix:16:5: warning[collapsible-let-in]: this 'let' can be merged into the outer one
legacy-let-syntax
Severity: ⚠️ warning. The deprecated let { ...; body = ...; }. The fix turns it into rec { ...; body = ...; }.body.
let {
x = 1;
body = x;
}
rec {
x = 1;
body = x;
}.body
legacy-let-syntax.nix:1:1: warning[legacy-let-syntax]: legacy 'let { }' syntax; use 'let ... in' [fix]
empty-inherit
Severity: ⚠️ warning. An inherit with no names.
{
a = let inherit; in 1;
inherit;
b = 2;
}
{
a = let in 1;
b = 2;
}
empty-inherit.nix:2:11: warning[empty-inherit]: 'inherit' with no names [fix]
empty-inherit.nix:3:3: warning[empty-inherit]: 'inherit' with no names [fix]
empty-pattern
Severity: ⚠️ warning. A function taking { }. There is no fix: _: would also accept arguments that are not an empty attribute set.
[
({ }: 1)
({ ... }: 1)
]
empty-pattern.nix:2:4: warning[empty-pattern]: empty pattern; use '_' if the argument is not needed
redundant-pattern-bind
Severity: ⚠️ warning. { ... }@x: body, which can be written x: body.
[
({ ... }@args: args)
(args @ { ... }: args)
({ a, ... }@args: args)
]
[
(args: args)
(args: args)
({ a, ... }@args: args)
]
redundant-pattern-bind.nix:2:4: warning[redundant-pattern-bind]: '{ ... }@args' is the same as 'args' [fix]
redundant-pattern-bind.nix:3:4: warning[redundant-pattern-bind]: '{ ... }@args' is the same as 'args' [fix]
manual-inherit
Severity: ⚠️ warning. a = a; in an attribute set that is not recursive, which is inherit a;.
{ a, b }:
{
plain = {
a = a;
};
# In a `rec`, `b = b;` refers to itself.
recursive = rec {
b = b;
};
}
{ a, b }:
{
plain = {
inherit a;
};
# In a `rec`, `b = b;` refers to itself.
recursive = rec {
b = b;
};
}
manual-inherit.nix:4:5: warning[manual-inherit]: 'a = a;' can be written 'inherit a;' [fix]
manual-inherit-from
Severity: ⚠️ warning. a = e.a;, which is inherit (e) a;.
{ pkgs, lib }:
{
hello = pkgs.hello;
name = pkgs.stdenv.hostPlatform.name;
options = (lib.evalModules { modules = [ ]; }).options;
lib = lib.lib;
other = pkgs.notOther;
fallback = pkgs.fallback or null;
}
{ pkgs, lib }:
{
inherit (pkgs) hello;
inherit (pkgs.stdenv.hostPlatform) name;
inherit (lib.evalModules { modules = [ ]; }) options;
inherit (lib) lib;
other = pkgs.notOther;
fallback = pkgs.fallback or null;
}
manual-inherit-from.nix:3:3: warning[manual-inherit-from]: can be written 'inherit (...) hello;' [fix]
manual-inherit-from.nix:4:3: warning[manual-inherit-from]: can be written 'inherit (...) name;' [fix]
manual-inherit-from.nix:5:3: warning[manual-inherit-from]: can be written 'inherit (...) options;' [fix]
manual-inherit-from.nix:6:3: warning[manual-inherit-from]: can be written 'inherit (...) lib;' [fix]
eta-reduction
Severity: 💡 hint. x: f x, which is f when f is a name or an attribute path that does not use x.
{ f, lib }:
[
(x: f x)
(x: lib.length x)
(x: f x x)
# Partial applications are kept.
(x: lib.map f x)
(map (x: f x) [ ])
]
{ f, lib }:
[
(f)
(lib.length)
(x: f x x)
# Partial applications are kept.
(x: lib.map f x)
(map (f) [ ])
]
eta-reduction.nix:3:4: hint[eta-reduction]: 'x: f x' can be written 'f' [fix]
eta-reduction.nix:4:4: hint[eta-reduction]: 'x: f x' can be written 'f' [fix]
eta-reduction.nix:8:9: hint[eta-reduction]: 'x: f x' can be written 'f' [fix]
useless-parens
Severity: ⚠️ warning. Parentheses around a simple expression, or around a whole binding value, let body, interpolation or file.
{ f, a }:
{
value = (f a);
atom = f (a);
select = (a.b).c;
fallback = f (a.b or null);
call = (f a) a;
interpolation = "${(f a)}";
string = f ("x");
nested = f ((a));
tight = f(a);
tighter = f((a));
path = f(./x);
operator = (a + a) * 2;
}
{ f, a }:
{
value = f a;
atom = f a;
select = a.b.c;
fallback = f (a.b or null);
call = (f a) a;
interpolation = "${f a}";
string = f "x";
nested = f a;
tight = f a;
tighter = f a;
path = f ./x;
operator = (a + a) * 2;
}
useless-parens.nix:3:11: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:4:12: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:5:12: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:8:22: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:9:14: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:10:15: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:11:12: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:12:15: warning[useless-parens]: useless parentheses [fix]
useless-parens.nix:13:11: warning[useless-parens]: useless parentheses [fix]
unquoted-uri
Severity: ⚠️ warning. A URI literal, which is deprecated. The fix quotes it.
{
url = https://example.org/x?y=1;
}
{
url = "https://example.org/x?y=1";
}
unquoted-uri.nix:2:9: warning[unquoted-uri]: unquoted URI; URI literals are deprecated [fix]
deprecated-is-null
Severity: ⚠️ warning. isNull e, which is e == null.
{ x }:
[
(isNull x)
(builtins.isNull x)
(!isNull x)
]
{ x }:
[
(x == null)
(x == null)
(!(x == null))
]
deprecated-is-null.nix:3:4: warning[deprecated-is-null]: 'isNull' is deprecated; use '== null' [fix]
deprecated-is-null.nix:4:4: warning[deprecated-is-null]: 'isNull' is deprecated; use '== null' [fix]
deprecated-is-null.nix:5:5: warning[deprecated-is-null]: 'isNull' is deprecated; use '== null' [fix]
deprecated-to-path
Severity: ⚠️ warning. builtins.toPath, which is deprecated. There is no fix.
{ x }:
[
(builtins.toPath x)
(__toPath x)
]
deprecated-to-path.nix:3:4: warning[deprecated-to-path]: 'builtins.toPath' is deprecated
deprecated-to-path.nix:4:4: warning[deprecated-to-path]: '__toPath' is deprecated
empty-list-concat
Severity: ⚠️ warning. A concatenation with [ ].
{ x }:
[
([ ] ++ x)
(x ++ [ ])
([ ] ++ [ ])
]
{ x }:
[
(x)
(x)
([ ])
]
empty-list-concat.nix:3:4: warning[empty-list-concat]: concatenation with an empty list [fix]
empty-list-concat.nix:4:4: warning[empty-list-concat]: concatenation with an empty list [fix]
empty-list-concat.nix:5:4: warning[empty-list-concat]: concatenation with an empty list [fix]
useless-has-attr
Severity: ⚠️ warning. if x ? a then x.a else d, which is x.a or d.
{ x, d }:
[
(if x ? a then x.a else d)
(if x.y ? a.b then x.y.a.b else d + 1)
(if x ? a then x.b else d)
]
{ x, d }:
[
(x.a or d)
(x.y.a.b or (d + 1))
(if x ? a then x.b else d)
]
useless-has-attr.nix:3:4: warning[useless-has-attr]: can be written 'x.a or ...' [fix]
useless-has-attr.nix:4:4: warning[useless-has-attr]: can be written 'x.y.a.b or ...' [fix]
faster-builtin
Severity: 💡 hint. lib.groupBy and lib.zipAttrsWith, which the builtins of the same names implement natively.
{ lib }:
[
lib.groupBy
lib.attrsets.zipAttrsWith
lib.mapAttrs
(builtins: lib.groupBy)
]
{ lib }:
[
builtins.groupBy
builtins.zipAttrsWith
lib.mapAttrs
(builtins: lib.groupBy)
]
faster-builtin.nix:3:3: hint[faster-builtin]: 'lib.groupBy' can be 'builtins.groupBy' [fix]
faster-builtin.nix:4:3: hint[faster-builtin]: 'lib.attrsets.zipAttrsWith' can be 'builtins.zipAttrsWith' [fix]
faster-builtin.nix:6:14: hint[faster-builtin]: 'lib.groupBy' can be 'builtins.groupBy'
repeated-keys
Severity: 💡 hint. Three or more bindings that start with the same attribute, such as a.b, a.c and a.d, which could form one nested attribute set. The fix moves them into a = { ... }; in place of the first, with the comments on their lines; a.b.c, a.b.d and a.b.e go into a.b = { ... };. It is withheld when the set also binds a whole, or when bindings over several lines do not each start a line.
{
a.b = 1;
a.c = 2; # This comment moves with its binding.
x = 0;
# So does this one, above its binding.
a.d = {
e = ''
kept
'';
# Its second line is part of the string, so it stays.
f = "not
moved";
};
f = { g.h = 1; g.i = 2; g.j = 3; };
# Grouped under the prefix they share.
k.l.m = 1;
k.l.n = 2;
k.l.o = 3;
# Not fixed: `p` is also set whole.
p = { };
p.q = 1;
p.r = 2;
p.s = 3;
w.a = 1;
w.b = 2;
w.c = 3;
y = 0;
t.u = 1;
t.v = 2;
}
{
a = {
b = 1;
c = 2; # This comment moves with its binding.
# So does this one, above its binding.
d = {
e = ''
kept
'';
# Its second line is part of the string, so it stays.
f = "not
moved";
};
};
x = 0;
f = { g = { h = 1; i = 2; j = 3; }; };
# Grouped under the prefix they share.
k.l = {
m = 1;
n = 2;
o = 3;
};
# Not fixed: `p` is also set whole.
p = { };
p.q = 1;
p.r = 2;
p.s = 3;
w = {
a = 1;
b = 2;
c = 3;
};
y = 0;
t.u = 1;
t.v = 2;
}
repeated-keys.nix:2:3: hint[repeated-keys]: 'a' is set 3 times; group its attributes in one attribute set [fix]
repeated-keys.nix:16:9: hint[repeated-keys]: 'g' is set 3 times; group its attributes in one attribute set [fix]
repeated-keys.nix:19:3: hint[repeated-keys]: 'k.l' is set 3 times; group its attributes in one attribute set [fix]
repeated-keys.nix:25:3: hint[repeated-keys]: 'p' is set 3 times; group its attributes in one attribute set
repeated-keys.nix:29:3: hint[repeated-keys]: 'w' is set 3 times; group its attributes in one attribute set [fix]
Semantics
Checks that need name resolution, as nixd makes them.
undefined-variable
Severity: ❌ error. A variable that no binding defines, outside any with. Nix rejects the file before evaluating it.
{ known }:
[
known
unknown
true
__curPos
<nixpkgs>
# `with` may bind it.
(with known; maybe)
]
undefined-variable.nix:4:3: error[undefined-variable]: undefined variable 'unknown'
unknown-builtin
Severity: ❌ error. builtins.x where x is not a builtin, unless the file tests builtins ? x or gives an or fallback.
[
builtins.map
builtins.mapAttrs'
(builtins.fetchClosure or null)
# Tested first, so the file works where Nix has it.
(if builtins ? getFlakeAttr then builtins.getFlakeAttr else null)
(
let
builtins = { };
in
builtins.custom
)
]
unknown-builtin.nix:3:3: error[unknown-builtin]: unknown builtin 'builtins.mapAttrs''
prefixed-builtin
Severity: ⚠️ warning. __x for the builtin builtins.x.
[
(__add 1 2)
__curPos
(
let
builtins = { };
in
__sub builtins 1
)
]
[
(builtins.add 1 2)
__curPos
(
let
builtins = { };
in
__sub builtins 1
)
]
prefixed-builtin.nix:2:4: warning[prefixed-builtin]: '__add' is 'builtins.add' [fix]
prefixed-builtin.nix:8:5: warning[prefixed-builtin]: '__sub' is 'builtins.sub'
shadowed-builtin
Severity: ⚠️ warning. A binding that hides a global such as map, true or builtins. Attributes of a rec set and names inherited from an expression are not reported.
{ map, lib }:
let
inherit (lib) toString;
true = false;
in
rec {
import = 1;
x = [
map
toString
true
import
];
}
shadowed-builtin.nix:1:3: warning[shadowed-builtin]: 'map' shadows a builtin
shadowed-builtin.nix:4:3: warning[shadowed-builtin]: 'true' shadows a builtin
extra-rec
Severity: ⚠️ warning. A rec attribute set whose attributes never refer to each other.
{
plain = rec {
a = 1;
b = 2;
};
used = rec {
a = 1;
b = a;
};
}
{
plain = {
a = 1;
b = 2;
};
used = rec {
a = 1;
b = a;
};
}
extra-rec.nix:2:11: warning[extra-rec]: 'rec' is not needed: no attribute refers to another [fix]
extra-with
Severity: ⚠️ warning. A with that no variable can come from.
{ lib, pkgs }:
{
unused = with lib; [ ];
used = with pkgs; [ hello ];
# Either may bind `hello`.
both = with lib; with pkgs; [ hello ];
shadowed = x: with lib; x;
}
{ lib, pkgs }:
{
unused = [ ];
used = with pkgs; [ hello ];
# Either may bind `hello`.
both = with lib; with pkgs; [ hello ];
shadowed = x: x;
}
extra-with.nix:3:12: warning[extra-with]: unused 'with': no variable comes from it [fix]
extra-with.nix:7:17: warning[extra-with]: unused 'with': no variable comes from it [fix]
Packaging
Conventions of nixpkgs for derivations.
missing-phase-hooks
Severity: ⚠️ warning. A phase such as installPhase set to a string that does not run its hooks: nixpkgs expects runHook preInstall first and runHook postInstall last, so that preInstall and postInstall still work. The fix adds the missing calls to strings of several lines.
{ stdenv }:
stdenv.mkDerivation {
pname = "example";
version = "1.0";
installPhase = ''
mkdir -p $out/bin
cp example $out/bin/
'';
# Only the missing call is added.
buildPhase = ''
runHook preBuild
make
'';
checkPhase = ''
runHook preCheck
make check
runHook postCheck
'';
# Not fixed: the calls need lines of their own.
configurePhase = "./configure --prefix=$out";
fixupPhase = ''true'';
}
{ stdenv }:
stdenv.mkDerivation {
pname = "example";
version = "1.0";
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp example $out/bin/
runHook postInstall
'';
# Only the missing call is added.
buildPhase = ''
runHook preBuild
make
runHook postBuild
'';
checkPhase = ''
runHook preCheck
make check
runHook postCheck
'';
# Not fixed: the calls need lines of their own.
configurePhase = "./configure --prefix=$out";
fixupPhase = ''true'';
}
missing-phase-hooks.nix:6:3: warning[missing-phase-hooks]: 'installPhase' does not run 'runHook preInstall' and 'runHook postInstall' [fix]
missing-phase-hooks.nix:12:3: warning[missing-phase-hooks]: 'buildPhase' does not run 'runHook postBuild' [fix]
missing-phase-hooks.nix:24:3: warning[missing-phase-hooks]: 'configurePhase' does not run 'runHook preConfigure' and 'runHook postConfigure'
missing-phase-hooks.nix:25:3: warning[missing-phase-hooks]: 'fixupPhase' does not run 'runHook preFixup' and 'runHook postFixup'
missing-final-attrs
Severity: ⚠️ warning. A derivation built from a rec set whose attributes refer to each other, such as stdenv.mkDerivation rec { ... }. With finalAttrs: { ... }, an override of version through overrideAttrs also reaches the attributes that use it. The fix rewrites the references to finalAttrs.version and the like. For builders other than stdenv.mkDerivation, finalAttrs holds the attributes the builder made, so the fix is only offered when the references are to pname, version and src.
{
stdenv,
fetchurl,
buildPythonPackage,
fetchPypi,
lib,
}:
{
hello = stdenv.mkDerivation rec {
pname = "hello";
version = "2.12";
src = fetchurl {
url = "mirror://gnu/hello/${pname}-${version}.tar.gz";
hash = "sha256-AAAA";
};
passthru = {
inherit lib version;
};
meta.changelog = "https://example.org/${version}";
};
# Other builders give `finalAttrs` what they made of `meta`, so only `pname`, `version` and `src` are rewritten.
python = buildPythonPackage rec {
pname = "example";
version = "1.0";
src = fetchPypi { inherit pname version; };
passthru.homepage = meta.homepage;
meta.homepage = "https://example.org";
};
fixable = buildPythonPackage rec {
pname = "other";
version = "2.0";
src = fetchPypi { inherit pname version; };
};
# Not fixed: the inner `finalAttrs` would capture `pname` and `version`.
nested = stdenv.mkDerivation rec {
pname = "nested";
version = "1.0";
modules = stdenv.mkDerivation (finalAttrs: {
pname = "${pname}-modules";
inherit version;
});
};
# Left to `extra-rec`: no attribute refers to another.
plain = stdenv.mkDerivation rec {
pname = "plain";
};
}
{
stdenv,
fetchurl,
buildPythonPackage,
fetchPypi,
lib,
}:
{
hello = stdenv.mkDerivation (finalAttrs: {
pname = "hello";
version = "2.12";
src = fetchurl {
url = "mirror://gnu/hello/${finalAttrs.pname}-${finalAttrs.version}.tar.gz";
hash = "sha256-AAAA";
};
passthru = {
inherit lib; inherit (finalAttrs) version;
};
meta.changelog = "https://example.org/${finalAttrs.version}";
});
# Other builders give `finalAttrs` what they made of `meta`, so only `pname`, `version` and `src` are rewritten.
python = buildPythonPackage rec {
pname = "example";
version = "1.0";
src = fetchPypi { inherit pname version; };
passthru.homepage = meta.homepage;
meta.homepage = "https://example.org";
};
fixable = buildPythonPackage (finalAttrs: {
pname = "other";
version = "2.0";
src = fetchPypi { inherit (finalAttrs) pname version; };
});
# Not fixed: the inner `finalAttrs` would capture `pname` and `version`.
nested = stdenv.mkDerivation rec {
pname = "nested";
version = "1.0";
modules = stdenv.mkDerivation (finalAttrs: {
pname = "${pname}-modules";
inherit version;
});
};
# Left to `extra-rec`: no attribute refers to another.
plain = stdenv.mkDerivation rec {
pname = "plain";
};
}
missing-final-attrs.nix:9:31: warning[missing-final-attrs]: use 'finalAttrs: { ... }' instead of 'rec { ... }' [fix]
missing-final-attrs.nix:23:31: warning[missing-final-attrs]: use 'finalAttrs: { ... }' instead of 'rec { ... }'
missing-final-attrs.nix:31:32: warning[missing-final-attrs]: use 'finalAttrs: { ... }' instead of 'rec { ... }' [fix]
missing-final-attrs.nix:38:32: warning[missing-final-attrs]: use 'finalAttrs: { ... }' instead of 'rec { ... }'
missing-structured-attrs
Severity: 💡 hint. A derivation that does not set structuredAttrs, which nixpkgs expects to be true for new packages. The builders that default it to true, such as those for Python, are not reported. The fix adds structuredAttrs = true; after pname and version; it changes the build, since phases then see lists as Bash arrays, so check that the package still builds.
{
stdenv,
buildGoModule,
buildPythonPackage,
buildHomeAssistantComponent,
pname,
version,
}:
{
plain = stdenv.mkDerivation {
pname = "plain";
version = "1.0";
strictDeps = true;
};
final = stdenv.mkDerivation (finalAttrs: {
inherit pname version;
meta.description = "${finalAttrs.pname} example";
});
go = buildGoModule {
# The comment stays with `src`.
src = ./.;
vendorHash = null;
};
set = stdenv.mkDerivation {
pname = "set";
__structuredAttrs = true;
};
# Deliberately off.
off = stdenv.mkDerivation {
pname = "off";
__structuredAttrs = false;
};
# Defaults to `true`.
python = buildPythonPackage {
inherit pname version;
};
# So does Home Assistant's builder.
component = buildHomeAssistantComponent {
inherit pname version;
};
}
{
stdenv,
buildGoModule,
buildPythonPackage,
buildHomeAssistantComponent,
pname,
version,
}:
{
plain = stdenv.mkDerivation {
pname = "plain";
version = "1.0";
strictDeps = true;
__structuredAttrs = true;
};
final = stdenv.mkDerivation (finalAttrs: {
inherit pname version;
__structuredAttrs = true;
meta.description = "${finalAttrs.pname} example";
});
go = buildGoModule {
__structuredAttrs = true;
# The comment stays with `src`.
src = ./.;
vendorHash = null;
};
set = stdenv.mkDerivation {
pname = "set";
__structuredAttrs = true;
};
# Deliberately off.
off = stdenv.mkDerivation {
pname = "off";
__structuredAttrs = false;
};
# Defaults to `true`.
python = buildPythonPackage {
inherit pname version;
};
# So does Home Assistant's builder.
component = buildHomeAssistantComponent {
inherit pname version;
};
}
missing-structured-attrs.nix:10:11: hint[missing-structured-attrs]: set '__structuredAttrs = true;' [fix]
missing-structured-attrs.nix:17:11: hint[missing-structured-attrs]: set '__structuredAttrs = true;' [fix]
missing-structured-attrs.nix:22:8: hint[missing-structured-attrs]: set '__structuredAttrs = true;' [fix]
missing-strict-deps
Severity: 💡 hint. A derivation that does not set strictDeps, which nixpkgs expects to be true for new packages. The builders that set it already, such as those for Python, Go and Rust, are not reported. The fix adds strictDeps = true; after pname and version, or under __structuredAttrs; it changes the build, since tools must then be in nativeBuildInputs and libraries in buildInputs, so check that the package still builds.
{
stdenv,
buildGoModule,
buildDotnetModule,
pname,
version,
}:
{
# The two fixes put both flags together.
both = stdenv.mkDerivation {
pname = "both";
version = "1.0";
src = ./.;
};
structured = stdenv.mkDerivation (finalAttrs: {
inherit pname version;
__structuredAttrs = true;
meta.description = "${finalAttrs.pname} example";
});
set = stdenv.mkDerivation {
pname = "set";
__structuredAttrs = true;
strictDeps = true;
};
# Deliberately off.
off = stdenv.mkDerivation {
pname = "off";
__structuredAttrs = true;
strictDeps = false;
};
# Go's builder sets it already.
go = buildGoModule {
__structuredAttrs = true;
src = ./.;
};
dotnet = buildDotnetModule {
inherit pname version;
__structuredAttrs = true;
};
}
{
stdenv,
buildGoModule,
buildDotnetModule,
pname,
version,
}:
{
# The two fixes put both flags together.
both = stdenv.mkDerivation {
pname = "both";
version = "1.0";
strictDeps = true;
src = ./.;
};
structured = stdenv.mkDerivation (finalAttrs: {
inherit pname version;
__structuredAttrs = true;
strictDeps = true;
meta.description = "${finalAttrs.pname} example";
});
set = stdenv.mkDerivation {
pname = "set";
__structuredAttrs = true;
strictDeps = true;
};
# Deliberately off.
off = stdenv.mkDerivation {
pname = "off";
__structuredAttrs = true;
strictDeps = false;
};
# Go's builder sets it already.
go = buildGoModule {
__structuredAttrs = true;
src = ./.;
};
dotnet = buildDotnetModule {
inherit pname version;
__structuredAttrs = true;
strictDeps = true;
};
}
missing-strict-deps.nix:10:10: hint[missing-strict-deps]: set 'strictDeps = true;' [fix]
missing-strict-deps.nix:17:16: hint[missing-strict-deps]: set 'strictDeps = true;' [fix]
missing-strict-deps.nix:44:12: hint[missing-strict-deps]: set 'strictDeps = true;' [fix]
deprecated-replace
Severity: ⚠️ warning. substituteInPlace and the other substitute functions called with --replace, which is deprecated and behaves as --replace-warn: a pattern that no longer matches only prints a warning. The fix uses --replace-fail, which makes such a build fail instead, so check that the package still builds.
{ stdenv, python3 }:
stdenv.mkDerivation {
pname = "example";
version = "1.0";
__structuredAttrs = true;
strictDeps = true;
postPatch = ''
# Don't let this apostrophe open a quote, nor this one: "''${out}".
substituteInPlace Makefile --replace /usr/bin/env env
substituteInPlace $(pwd)/config.mk --replace gcc cc
substituteInPlace setup.py \
--replace-fail "version='0'" "version='1.0'" \
--replace "python" "${python3.interpreter}"
substitute config.in config --replace @out@ $out
# Quotes and interpolations over several lines are part of the same call.
substituteInPlace tagtime.pl \
--replace-fail '$logf = 1;' \
'$logf = 2;
$more = 3;' \
--replace '"__ED__"' '"vi"'
substituteInPlace latex.py --replace-fail "pdflatex" "${
python3
}/bin/pdflatex" \
--replace "pdf2svg" "x"
# Not a substitute call.
patchelf --replace-needed libfoo.so libbar.so example
./configure --replace
'';
}
{ stdenv, python3 }:
stdenv.mkDerivation {
pname = "example";
version = "1.0";
__structuredAttrs = true;
strictDeps = true;
postPatch = ''
# Don't let this apostrophe open a quote, nor this one: "''${out}".
substituteInPlace Makefile --replace-fail /usr/bin/env env
substituteInPlace $(pwd)/config.mk --replace-fail gcc cc
substituteInPlace setup.py \
--replace-fail "version='0'" "version='1.0'" \
--replace-fail "python" "${python3.interpreter}"
substitute config.in config --replace-fail @out@ $out
# Quotes and interpolations over several lines are part of the same call.
substituteInPlace tagtime.pl \
--replace-fail '$logf = 1;' \
'$logf = 2;
$more = 3;' \
--replace-fail '"__ED__"' '"vi"'
substituteInPlace latex.py --replace-fail "pdflatex" "${
python3
}/bin/pdflatex" \
--replace-fail "pdf2svg" "x"
# Not a substitute call.
patchelf --replace-needed libfoo.so libbar.so example
./configure --replace
'';
}
deprecated-replace.nix:11:32: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
deprecated-replace.nix:12:40: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
deprecated-replace.nix:15:7: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
deprecated-replace.nix:16:33: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
deprecated-replace.nix:22:7: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
deprecated-replace.nix:26:7: warning[deprecated-replace]: '--replace' is deprecated; use '--replace-fail' [fix]
sri-hash
Severity: ⚠️ warning. A fetcher given sha256, where nixpkgs uses hash with an SRI hash such as sha256-…=. The fix renames it and converts a literal in Nix base32, hex or base64; lib.fakeSha256 becomes lib.fakeHash. Fetchers that take only sha256, such as fetchHex, are not reported.
{
lib,
fetchurl,
fetchFromGitHub,
fetchHex,
sha256,
}:
# Each hash is sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ= in another encoding.
[
(fetchurl {
url = "https://example.org/a.tar.gz";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
})
(fetchurl {
url = "https://example.org/b.tar.gz";
sha256 = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
})
(fetchFromGitHub {
owner = "example";
repo = "c";
rev = "v1";
sha256 = "sha256:LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
})
(fetchFromGitHub {
owner = "example";
repo = "d";
rev = "v1";
sha256 = lib.fakeSha256;
})
# Not fixed: only a literal can be converted.
(fetchurl {
url = "https://example.org/e.tar.gz";
inherit sha256;
})
(fetchurl {
url = "https://example.org/f.tar.gz";
sha256 = sha256;
})
# Not fixed: the url refers to it.
(fetchurl rec {
url = "https://example.org/i_${sha256}.deb";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
})
# A fetcher of the file's own, which may take only sha256.
(
let
fetchurl = { url, sha256 }: builtins.fetchurl { inherit url sha256; };
in
fetchurl {
url = "https://example.org/h.tar.gz";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
}
)
# fetchHex takes only sha256.
(fetchHex {
pkg = "g";
version = "1";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
})
]
{
lib,
fetchurl,
fetchFromGitHub,
fetchHex,
sha256,
}:
# Each hash is sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ= in another encoding.
[
(fetchurl {
url = "https://example.org/a.tar.gz";
hash = "sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
})
(fetchurl {
url = "https://example.org/b.tar.gz";
hash = "sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
})
(fetchFromGitHub {
owner = "example";
repo = "c";
rev = "v1";
hash = "sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
})
(fetchFromGitHub {
owner = "example";
repo = "d";
rev = "v1";
hash = lib.fakeHash;
})
# Not fixed: only a literal can be converted.
(fetchurl {
url = "https://example.org/e.tar.gz";
inherit sha256;
})
(fetchurl {
url = "https://example.org/f.tar.gz";
sha256 = sha256;
})
# Not fixed: the url refers to it.
(fetchurl rec {
url = "https://example.org/i_${sha256}.deb";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
})
# A fetcher of the file's own, which may take only sha256.
(
let
fetchurl = { url, sha256 }: builtins.fetchurl { inherit url sha256; };
in
fetchurl {
url = "https://example.org/h.tar.gz";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
}
)
# fetchHex takes only sha256.
(fetchHex {
pkg = "g";
version = "1";
sha256 = "094qif9n4cq4fdg459qzbhg1c6wywawwaaivx0k0x8xhbyx4vwic";
})
]
sri-hash.nix:12:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256' [fix]
sri-hash.nix:16:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256' [fix]
sri-hash.nix:22:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256' [fix]
sri-hash.nix:28:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256' [fix]
sri-hash.nix:33:13: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256'
sri-hash.nix:37:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256'
sri-hash.nix:42:5: warning[sri-hash]: use 'hash' with an SRI hash instead of 'sha256'
env-variable
Severity: ⚠️ warning. A NIX_ variable such as NIX_CFLAGS_COMPILE set beside the attributes of a derivation: with __structuredAttrs, only env is exported, so the compiler wrapper would not see it. The fix moves it to env, through toString unless it is a string already. It is withheld when env is not an attribute set written in place, or the file refers to the variable elsewhere, as finalAttrs.NIX_LDFLAGS.
{
lib,
stdenv,
buildGoModule,
}:
{
string = stdenv.mkDerivation {
pname = "string";
NIX_CFLAGS_COMPILE = "-Wno-error";
NIX_LDFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-framework Foundation";
# A list, which env does not take as such.
NIX_CFLAGS_LINK = [
"-lm"
"-lpthread"
];
NIX_DEBUG = lib.optionals stdenv.cc.isClang [ "1" ];
NIX_ENFORCE_PURITY = 0;
NIX_HARDENING_ENABLE = lib.optionalString stdenv.isLinux "pie" + " fortify";
# Shell code reads it from env as well.
preBuild = ''
export NIX_CFLAGS_COMPILE+=" -DNDEBUG"
'';
};
# Merged with the env written in place.
merged = buildGoModule (finalAttrs: {
pname = "merged";
env = {
CGO_ENABLED = 0;
};
NIX_LDFLAGS = "-lz";
});
# Not fixed: env is not written in place.
computed = stdenv.mkDerivation {
pname = "computed";
env = lib.optionalAttrs stdenv.hostPlatform.isDarwin { CC = "clang"; };
NIX_LDFLAGS = "-lz";
};
# Not fixed: the file refers to it.
referred = stdenv.mkDerivation (finalAttrs: {
pname = "referred";
NIX_HARDENING_ENABLE = "fortify";
passthru.hardening = finalAttrs.NIX_HARDENING_ENABLE;
});
}
{
lib,
stdenv,
buildGoModule,
}:
{
string = stdenv.mkDerivation {
pname = "string";
env.NIX_CFLAGS_COMPILE = "-Wno-error";
env.NIX_LDFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-framework Foundation";
# A list, which env does not take as such.
env.NIX_CFLAGS_LINK = toString [
"-lm"
"-lpthread"
];
env.NIX_DEBUG = toString (lib.optionals stdenv.cc.isClang [ "1" ]);
env.NIX_ENFORCE_PURITY = 0;
NIX_HARDENING_ENABLE = lib.optionalString stdenv.isLinux "pie" + " fortify";
# Shell code reads it from env as well.
preBuild = ''
export NIX_CFLAGS_COMPILE+=" -DNDEBUG"
'';
};
# Merged with the env written in place.
merged = buildGoModule (finalAttrs: {
pname = "merged";
env = {
CGO_ENABLED = 0;
};
env.NIX_LDFLAGS = "-lz";
});
# Not fixed: env is not written in place.
computed = stdenv.mkDerivation {
pname = "computed";
env = lib.optionalAttrs stdenv.hostPlatform.isDarwin { CC = "clang"; };
NIX_LDFLAGS = "-lz";
};
# Not fixed: the file refers to it.
referred = stdenv.mkDerivation (finalAttrs: {
pname = "referred";
NIX_HARDENING_ENABLE = "fortify";
passthru.hardening = finalAttrs.NIX_HARDENING_ENABLE;
});
}
env-variable.nix:9:5: warning[env-variable]: set 'NIX_CFLAGS_COMPILE' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:10:5: warning[env-variable]: set 'NIX_LDFLAGS' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:12:5: warning[env-variable]: set 'NIX_CFLAGS_LINK' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:16:5: warning[env-variable]: set 'NIX_DEBUG' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:17:5: warning[env-variable]: set 'NIX_ENFORCE_PURITY' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:18:5: warning[env-variable]: set 'NIX_HARDENING_ENABLE' in 'env', or it is not exported with __structuredAttrs
env-variable.nix:31:5: warning[env-variable]: set 'NIX_LDFLAGS' in 'env', or it is not exported with __structuredAttrs [fix]
env-variable.nix:38:5: warning[env-variable]: set 'NIX_LDFLAGS' in 'env', or it is not exported with __structuredAttrs
env-variable.nix:44:5: warning[env-variable]: set 'NIX_HARDENING_ENABLE' in 'env', or it is not exported with __structuredAttrs
Building
The build uses Jenesis, vendored in build/jenesis.
nix develop
java build/jenesis/Make.java # compile, test, format and render these docs
java build/jenesis/Execute.java eval -E '1 + 1' # run the launcher on the JVM
The command line comes in two modules. cli holds format, check and fix
and its own affogato command with only these. launcher adds eval and
drv-path, which need the evaluator and so Truffle. jenesis.properties makes
Execute.java run the launcher; -Djenesis.execute.module=cli/src/main/java
runs the other. The native profile of cli is native-lite.
Playground
The web module wraps the formatter and linter for JavaScript: Playground
exports format, fix and lint, each returning JSON. TeaVM compiles it with
its dependencies to affogato.js at the module’s package step, through the
project plugin in plugins/teavm, which jenesis.plugins.properties declares
and the module’s plugin-teavm.properties switches on. TeaVM’s jars are no
modules, so the plugin resolves them onto a class path and runs TeaVM in a JVM
of its own. Their pins live in the web declaration: TeaVM’s asm is older than
the tools', so they cannot share the bill of materials.
TeaVM’s class library has no HexFormat and no String.lines: code the
playground reaches must do without them, or the build fails naming the method.
java build/jenesis/Make.java build/modules/compose/module/package-module-web+src+main+java
nix shell nixpkgs#nodejs -c node web/check.cjs \
target/build/modules/compose/module/package-module-web+src+main+java/custom/teavm/delegate/teavm/output/javascript/affogato.js \
format/src/test/java lint/src/test/java/cases # the formatter and linter test cases, in JavaScript
Documentation
This documentation is written in AsciiDoc in docs/. Files whose names start
with _ are only included. The Jenesis Asciidoctor plugin, switched on by
build.jenesis/asciidoctor.properties, renders it during the build. Run stage
to get the HTML in target/stage/project/output/docs/, or build it with Nix:
nix build .#docs
Dependencies
Dependency versions and checksums live in one bill of materials,
build.jenesis/pin-affogato.properties, which every module imports with
@jenesis.bom. After changing a dependency, regenerate it:
java build/jenesis/Make.java pin
jenesis.properties points pin at that file and names the groups it holds:
the dependencies, and the tools the modules run (palantir-java-format, JaCoCo),
which the modules import it into as @jenesis.bom palantir:tool/pin-affogato.properties.
A plugin’s own version stays pinned in each declaration, as
palantir/module/…: Jenesis does not read it from a file. The project
plugins' tools are pinned in jenesis.plugins.pin.properties.
Nix
nix/package.nix builds offline. nix/pins.nix reads the pins of the module
declarations and of both pin files, and fetches each pinned jar and POM, so a
new pin needs nothing more.
The flake provides:
affogato-
the
climodule on OpenJDK 25:format,checkandfixonly, without Truffle. It is the default package. affogato-native-
the
climodule as a static native image. affogato-truffle-
the launcher on OpenJDK 25. It runs the bundle the launcher’s
packaging.propertiesasks for: its jars and thejavaargument file that names them. Without GraalVM’s compiler, Truffle runs its fallback runtime, which only interprets; runtime compilation is off by default anyway. affogato-truffle-native-
the launcher as a static native image, built with GraalVM.
playground-
web/playground/index.htmlandaffogato.js: a page that formats, fixes and lints Nix in the browser, opened as a file, with no server. Its rule selector loads the linter’s cases fromexamples.js, whichPlaygroundExamplesTestregenerates. docs-
this documentation, rendered to HTML.
tests-
a check that runs the test suite on the JVM and outputs its JaCoCo report.
playground-tests-
a check that runs the formatter and linter test cases against
affogato.jsin Node.js.