I think the current semantics of the follows attribute of flake inputs are broken.
It looks like how the implementation behaves matches with what is specified in the Flake RFC NixOS/rfcs#49.
I am not sure if this is a bug in the specification or a bug in the implementation. Maybe it's also totally fine, but I personally do not understand when and how to use follows outside of the root flake with the current semantics.
It would be great if someone could point out where I'm going wrong or validate my view.
Let's say we are working on flake B with the flakes A and nixpkgs as dependencies.
dependency graph | code inside flake.nix file of the leftmost flake
--------------------------------------------------------------------------------------
|
A -> nixpkgs | inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
^ |
| |
B -> nixpkgs | inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
A and B both depend on different version of nixpkgs in the listing above.
If this works B can reduce the closure size by doing the following.
dependency graph | code inside flake.nix file of the leftmost flake
--------------------------------------------------------------------------------------
|
A -------- | inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
^ | |
| v |
B -> nixpkgs | inputs.A.inputs.nixpkgs.follows = "nixpkgs" EDIT: added A.inputs to fix this line
This works fine, but now if someone else tries to use B as an input for C we get the following situation.
dependency graph | code inside flake.nix file of the leftmost flake
--------------------------------------------------------------------------------------
|
A -------- | inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
^ | |
| | |
B ---- | | inputs.A.inputs.nixpkgs.follows = "nixpkgs" EDIT: added A.inputs to fix this line
^ v v |
C -> nixpkgs? | ... ?
I now want to focus on the issues the author of C has when using B.
The problems I see are:
- C needs to declare nixpkgs as an input even if they don't need it as a direct dependency for any other reason.
The only way to specify 'give me exactly the version of nixpkgs that B was published with' in C is by using the hash from B's flake.lock file. (Is this true? Am I missing something?) EDIT: not true, thanks @mvnetbiz
- The only way to set the version of nixpkgs that C directly depends on independently of B's version is adding a second copy of nixpkgs under a different name.
The workflow that B used for adding A does something different when C tries to add B, which seems bad.
They may get a compile error if they do not have nixpkgs as an input already.
Then they will probably add
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
and get some arbitrary version of nixpkgs.
If that does not work they have to do non-trivial extra work to get what B published originally to check if that works.
On the other hand when B added A they first got the verbatim thing and then tweaked it with the follows. That tweak might have looked like change with a defined scope, but it was not.
It seems like both of those problems will interact and get worse for larger dependency trees.
Making naming annoying and reproducibility difficult to 'get right'.
This makes me think follows is only usable in root flakes right now.
Fixed semantics
Let's look at the last step again with alternative semantics where the targets of follows are resolved relative to the flake that contains the follows attribute.
dependency graph | code inside flake.nix file of the leftmost flake
--------------------------------------------------------------------------------------
|
A -------- | inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable
^ | |
| v |
B -> nixpkgs | inputs.A.inputs.nixpkgs.follows = "nixpkgs" EDIT: added A.inputs to fix this line
^ |
| |
C | ... ?
- C does not have to declare a dependency to nixpkgs.
- Out of the box C gets exactly what B published, independently of whatever follows declarations are present anywhere upstream. Same as if if no follow was used at all upstream.
- Getting a verbatim copy of B is the default thing, but if C wants to reduce the size of the closure they can still do that with the
follows attribute.
Adding B as an input to C behaves just like adding A as an input to B, because the impact of any follows is contained to the flake where it is declared and its dependencies.
Is there some usage/benefit of the current semantics that I am missing?
Is what I am proposing broken in some way?
I have been using the term root flake from the Flake RFC here, which uses root flake and top-level flake in some places, but I don't think it defines those terms. That seems like a possible source of the issue.
I hope that this is the right place to bring this up.
Thanks for all the great work on Flakes.
I think the current semantics of the
followsattribute of flake inputs are broken.It looks like how the implementation behaves matches with what is specified in the Flake RFC NixOS/rfcs#49.
I am not sure if this is a bug in the specification or a bug in the implementation. Maybe it's also totally fine, but I personally do not understand when and how to use
followsoutside of the root flake with the current semantics.It would be great if someone could point out where I'm going wrong or validate my view.
Let's say we are working on flake B with the flakes A and nixpkgs as dependencies.
A and B both depend on different version of nixpkgs in the listing above.
If this works B can reduce the closure size by doing the following.
This works fine, but now if someone else tries to use B as an input for C we get the following situation.
I now want to focus on the issues the author of C has when using B.
The problems I see are:
The only way to specify 'give me exactly the version of nixpkgs that B was published with' in C is by using the hash from B's flake.lock file. (Is this true? Am I missing something?)EDIT: not true, thanks @mvnetbizThe workflow that B used for adding A does something different when C tries to add B, which seems bad.
They may get a compile error if they do not have nixpkgs as an input already.
Then they will probably add
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstableand get some arbitrary version of nixpkgs.
If that does not work they have to do non-trivial extra work to get what B published originally to check if that works.
On the other hand when B added A they first got the verbatim thing and then tweaked it with the
follows. That tweak might have looked like change with a defined scope, but it was not.It seems like both of those problems will interact and get worse for larger dependency trees.
Making naming annoying and reproducibility difficult to 'get right'.
This makes me think
followsis only usable in root flakes right now.Fixed semantics
Let's look at the last step again with alternative semantics where the targets of follows are resolved relative to the flake that contains the follows attribute.
followsattribute.Adding B as an input to C behaves just like adding A as an input to B, because the impact of any follows is contained to the flake where it is declared and its dependencies.
Is there some usage/benefit of the current semantics that I am missing?
Is what I am proposing broken in some way?
I have been using the term root flake from the Flake RFC here, which uses root flake and top-level flake in some places, but I don't think it defines those terms. That seems like a possible source of the issue.
I hope that this is the right place to bring this up.
Thanks for all the great work on Flakes.