- Nix 99.7%
- Just 0.3%
Previously, you could infuse to `__input.foo` of a function which didn't have a
`foo` argument, so long as that function was a `{ ... }:`-function which doesn't
reject extraneous arguments.
Unfortunately this meant that there was no guard against spelling mistakes for
`__input` infusions. Even worse, when infusing to a nixpkgs package, you might
infuse to an argument which it has today, but a later version of nixpkgs might
remove that argument -- in that situation, after the upgrade, the infusion would
silently do nothing. This turned out to be the root cause of a very large
number of very frustrating bugs.
Therefore, this commit restricts the `__input` sugar -- it can only infuse to
explicitly-named arguments of a function. If you really do want to infuse to a
non-existant argument of a `{ ... }:`-function, you'll have to do it manually by
calling `.override` yourself, without using the `__input` sugar.
Although this is a behavior change, it is of the kind that cannot silently cause
problems: what we're doing here is changing a behavior that previously did not
throw an exception so that it now throws an exception. Anybody who relied on
the old behavior will find out immediately at eval time. There are no "silent"
behavior changes from this commit. Therefore, this is only a minor-version bump.
|
||
|---|---|---|
| doc | ||
| examples | ||
| tests | ||
| .mailmap | ||
| default.nix | ||
| justfile | ||
| README.md | ||
infuse.nix
- canonical repository: https://codeberg.org/amjoseph/infuse.nix.
- questions:
#six/hackint(be patient, i am asynchronous) - the commutative diagram alluded to in the 38c3 talk is in
doc/commutative-diagram.md
What?
infuse is a "deep" version of both .override and .overrideAttrs which
generalizes both lib.pipe and recursiveUpdate. It can be used as a leaner,
untyped alternative to lib.modules. If you want dynamic typechecking, it
works well with yants. Infusion has specified semantics which
preserve identity and associativity laws at all three of nix's non-finite types
Why?
Would you rather write this:
final: prev: {
python311 = prev.python311.override
(previousArgs: previousArgs // {
packageOverrides =
lib.composeExtensions
(previousArgs.packageOverrides or {})
(final: prev: {
dnspython = prev.dnspython.overrideAttrs(previousAttrs: {
doCheck = false;
});
});
});
xrdp = (prev.xrdp.override {
systemd = null;
})
.overrideAttrs(previousAttrs: {
env = previousAttrs.env or {} // {
NIX_CFLAGS_COMPILE =
(previousAttrs.env.NIX_CFLAGS_COMPILE or "")
+ " -w";
};
passthru = previousAttrs.passthru or {} // {
xorgxrdp = previousAttrs.passthru.xorgxrdp
.overrideAttrs (previousAttrs: {
configureFlags = (previousAttrs.configureFlags or []) ++ [
"--without-fuse"
];
});
};
});
}
... or this?
final: prev: infuse prev {
python311.__input.packageOverrides.__overlay.dnspython.__output.doCheck.__assign = false;
xrdp.__input.systemd.__assign = null;
xrdp.__output.env.NIX_CFLAGS_COMPILE.__append = " -w";
xrdp.__output.passthru.xorgxrdp.__output.configureFlags.__append = ["--without-fuse"];
}
If you think the second expression is easier to read, write, and maintain, you
would probably be interested in infuse.nix.
How?
The basic idea is that when its second argument is an attrset, infuse acts
like lib.recursiveUpdate, except that in the second argument you must mark
any subtrees where you want the automatic merging to stop. You mark those
subtrees by changing them from whatever value they were into a function which
returns that value (and ignores its argument).
In the following example, fred gets clobbered because we used //:
{ bob.fred = 3; } // { bob.jill = 4; } == { bob.jill = 4; }
If we instead use infuse, we end up with both bob.fred and bob.jill,
because we have marked bob.jill by making its value a function (_: 4):
infuse { bob.fred = 3; } { bob.jill = _: 4; } == { bob.fred = 3; bob.jill = 4; }
Unlike lib.modules, we can get back the "clobbering" behavior if we want, by
marking an attribute higher up the tree:
infuse { bob.fred = 3; } { bob = _: { jill = 4; }; } == { bob.jill = 4; }
This allows to merge attrsets containing structured values which should replace each other (or report an error) rather than getting mixed together.
Lists
When the second argument is a list, infuse acts like lib.pipe:
infuse
{ x = 3; }
[ { x = x: x*x; } (fred: fred.x+1) ]
==
10
You can even mix lists with attrsets:
infuse
{ bob.fred.x = 3; }
{ bob.fred = [ { x = x: x*x; } (fred: fred.x+1) ]; }
==
{ bob.fred = 10; }
There is No Magic Under the Hood
All those double-underscore attributes you see, like __input and __output
are just sugar. You can omit them, or even define your own sugars:
let
infuse = import ../default.nix {
inherit lib;
sugars = infuse.v1.default-sugars ++ lib.attrsToList {
__concatStringsSep =
path: infusion: target:
lib.strings.concatStringsSep infusion target;
};
};
in
infuse.v1.infuse
{ fred = [ "woo" "hoo" ]; }
{ fred.__concatStringsSep = "-"; }
==
{ fred = "woo-hoo"; }
The process of replacing these double-underscore attributes by expanded definitions is called desugaring. After desugaring, what's left is a desugared infusion: an attrset whose leaf values are all functions -- no integers, booleans, strings, etc. In other words: it is an error to try to infuse an attrset whose desugaring contains any non-function leaf values.
When you infuse a desugared infusion into a target, each function in the infusion is applied to the target attrvalue which has the same attrpath.
Semantics
Let's take a look at lib.pipe. It has two important properties:
flip pipe []does the same thing aslib.idflip pipe (a ++ b)does the same thing asflip pipe [ (flip pipe a) (flip pipe b) ]
Infuse has both of these properties as well, when used on lists:
flip infuse []does the same thing aslib.idflip infuse (a ++ b)does the same thing asflip pipe [ (flip infuse a) (flip infuse b) ]
What makes infuse special is that it also works on attrsets, and does so in
the same way that it (and lib.pipe) work on lists:
flip infuse {}does the same thing aslib.idflip infuse (a // b)does the same thing asflip pipe [ (flip infuse a) (flip infuse b) ]
In fact, infuse does the same trick for functions too!
flip infuse lib.iddoes the same thing aslib.idflip infuse (pipe [ a b ])does the same thing asflip pipe [ (flip infuse a) (flip infuse b) ]
Most important of all, these are not three separate tricks (one for lists, one for attrsets, and one for functions). It is one single trick that works at all three of the non-finite Nix types (lists, attrsets, and functions).
Examples
For an example of what infuse.nix can do, see amjoseph's overlays.