Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

follows attribute of flake inputs seems broken as is #4808

Closed
mschwaig opened this issue May 14, 2021 · 8 comments
Closed

follows attribute of flake inputs seems broken as is #4808

mschwaig opened this issue May 14, 2021 · 8 comments
Labels

Comments

@mschwaig
Copy link
Member

mschwaig commented May 14, 2021

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.

@mschwaig mschwaig added the bug label May 14, 2021
@mvnetbiz
Copy link

I think this setup describes the behavior you are thinking of.
A Uses nixpkgs.
B Uses nixpkgs-unstable as well as uses a version of A that is overridden to also use nixpkgs-unstable
If C wants to use whatever nixpkgs B has, it just references it from B's inputs.
A/flake.nix

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs";
}

B/flake.nix

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.A.url = "http://example/A.git";
  inputs.A.inputs.nixpkgs.follows = "nixpkgs";
}

C/flake.nix

{
  inputs.B.url = "http://example/B.git";
  outputs = { self, B }:
  let
    inherit (B.inputs) nixpkgs;
  in
  {
    package = nixpkgs.hello;
  };
}

@mschwaig
Copy link
Member Author

mschwaig commented May 22, 2021

I had not thought of doing what you are doing in C/flake.nix. I think that solves the 'give me exactly the version from B' issue that I mentioned. I also think writing down the full flake files is probably a better approach to illustrating the problem than what I did.

It looks like other issues I mentioned still remain, but I will have to take a closer look at that and try it out in code.

My main concern with those issues is that that follows is confusing/difficult to use in any flake but C. Users can solve all those problems, but different semantics for follows seem to avoid them.

@mschwaig mschwaig changed the title follow attribute of flake inputs seems broken as is follows attribute of flake inputs seems broken as is Jun 9, 2021
@mschwaig
Copy link
Member Author

mschwaig commented Jun 9, 2021

When I wrote this issue I was not sure if this is a bug with how things are specified or with how they are implemented.

I now found an inconsistency where some cases follows actually does behave as I describe under fixed semantics already, which makes makes me think those semantics are the intended ones.

A/flake.nix looks like this:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self } : {};
}

B/flake.nix looks like this:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.A.url = "path:../A";
  inputs.A.inputs.nixpkgs.follows = "nixpkgs";
  inputs.A_alt.url = "path:../A";
  inputs.A_alt.follows = "A/nixpkgs";
  outputs = { self, A, A_alt, nixpkgs } : {};
}

All of this is very close to @mvnetbiz's code, but here I added A_alt which behaves differently.

C/flake.nix looks like this:

{
  inputs.B.url = "path:../B";
  inputs.B.inputs.nixpkgs.follows = "B/A/nixpkgs";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  outputs = { self, nixpkgs, B }: {}
}

Now if we look at the output of nix flake metadata

Resolved URL:  git+file:///home/mschwaig/tmp/broken_follows?dir=C
Locked URL:    git+file:///home/mschwaig/tmp/broken_follows?dir=C&ref=main&rev=6904f135e66d91ad1e44b91961b389230ac55aee
Path:          /nix/store/6v7ysawr07200x8p7c523xkc9xgxd9hk-source
Revision:      6904f135e66d91ad1e44b91961b389230ac55aee
Revisions:     1
Last modified: 2021-06-09 15:52:49
Inputs:
├───B: path:../B?narHash=sha256-iAdHRZhr4zpHl4lWNJ2wY1mckZJI6hxWO56l2uzMQpM=
│   ├───A: path:../A?narHash=sha256-QvN8ovS+kZoeHW+bKbJMxHECdw5DFYf+tGGP0baxrJM=
│   │   └───nixpkgs follows input 'nixpkgs'  <---
│   ├───A_alt follows input 'B/A/nixpkgs'    <---
│   └───nixpkgs follows input 'B/A/nixpkgs'
└───nixpkgs: github:NixOS/nixpkgs/3bc8e5cd23b84b2e149e7aaad57117da16a19e6f

we can see that both of the follows that I marked with arrows <--- were written inside B/flake.nix, but

  inputs.A.inputs.nixpkgs.follows = "nixpkgs";

is resolved relative to C, which is what I have been complaining about, while

  inputs.A_alt.follows = "A/nixpkgs";

is resolved relative to B, which is what I would have expected in both cases.

This also means that the line

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

has to be present in C, while declaring A (the target of the A_alt follow) as an input of C to make the follows with A_alt work is also not required at all.

You can find my PoC with this code here: https://github.com/mschwaig/broken_follows_poc

Thanks @mvnetbiz referring to a specific version of a transitive dependency works how you described it.

@12Boti
Copy link

12Boti commented Jul 13, 2021

@mschwaig
What does the line

inputs.A_alt.follows = "A/nixpkgs";

mean in B/flake.nix?
Shouldn't it be

inputs.A_alt.inputs.nixpkgs.follows = "A/nixpkgs";

?

@stale
Copy link

stale bot commented Jan 9, 2022

I marked this as stale due to inactivity. → More info

@stale stale bot added the stale label Jan 9, 2022
@nixos-discourse
Copy link

This issue has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/1000-instances-of-nixpkgs/17347/23

@ilkecan
Copy link
Member

ilkecan commented Feb 5, 2022

I don't think what you are describing is happening, at least with the latest stable version of nix (nix (Nix) 2.6.0). From my experience, the behaviour is as you described under "fixed semantics", i.e. they are self contained and relative to the file they are defined in. So the following is not true:

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 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.

You seem to confirm this to some degree in your last comment

I now found an inconsistency where some cases follows actually does behave as I describe under fixed semantics already, which makes makes me think those semantics are the intended ones.

And you gave an example with three flakes. In there, you are creating a circular follows between
B/A/nixpkgs and B/nixpkgs from the perspective of flake C. But I was surprised to see that B/A/nixpkgs had resolved to just nixpkgs. I was wondering what would happen if C wouldn't have an input named nixpkgs, so I wanted to test the example myself.

I had to made the following changes:

  • inputs.A_alt.follows = "A/nixpkgs"; -> inputs.A_alt.inputs.nixpkgs.follows = "A/nixpkgs"; (as pointed out by 12Boti)
  • replace relative paths with absolute paths since the former is not allowed in later versions of nix

So now we have the following

# A/flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  outputs = { self, nixpkgs } : {};
}
# B/flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.A.url = "path:/home/ilkecan/nix/test/github/nix/4808/A";
  inputs.A.inputs.nixpkgs.follows = "nixpkgs";
  inputs.A_alt.url = "path:/home/ilkecan/nix/test/github/nix/4808/A";
  inputs.A_alt.inputs.nixpkgs.follows = "A/nixpkgs";
  outputs = { self, A, A_alt, nixpkgs } : {};
}

# C/flake.nix
{
  inputs.B.url = "path:/home/ilkecan/nix/test/github/nix/4808/B";
  inputs.B.inputs.nixpkgs.follows = "B/A/nixpkgs";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  outputs = { self, nixpkgs, B }: {};
}

If we run nix flake info for each flake we get

❯ nix flake metadata 'path:A'
Resolved URL:  path:A
Locked URL:    path:A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
Path:          /nix/store/x1k2dj60dcqq52rwsk75jw5hc6i11ln3-source
Inputs:
└───nixpkgs: github:NixOS/nixpkgs/a529f0c125a78343b145a8eb2b915b0295e4f459
❯ nix flake metadata 'path:B'
Resolved URL:  path:B
Locked URL:    path:B?narHash=sha256-Q2zRAMnLT3V5AItAyR7Q0R+g1Vf7qldhyr8DMgDOBr8=
Path:          /nix/store/rryjn0z6d0dy1cixf4wrb2f9ad24jyhi-source
Inputs:
├───A: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   └───nixpkgs follows input 'nixpkgs'
├───A_alt: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   └───nixpkgs follows input 'A/nixpkgs'
└───nixpkgs: github:NixOS/nixpkgs/a529f0c125a78343b145a8eb2b915b0295e4f459
❯ nix flake metadata 'path:C'
Resolved URL:  path:C
Locked URL:    path:C?narHash=sha256-eiNXWpMQL6HkoItmScwppil01pdOoI3N5QUbCkSYvng=
Path:          /nix/store/2prfwj0yahgy527ilalbib11b9dlg3bd-source
Inputs:
├───B: path:/home/ilkecan/nix/test/github/nix/4808/B?narHash=sha256-Q2zRAMnLT3V5AItAyR7Q0R+g1Vf7qldhyr8DMgDOBr8=
│   ├───A: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   │   └───nixpkgs follows input 'B/nixpkgs'
│   ├───A_alt: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   │   └───nixpkgs follows input 'B/A/nixpkgs'
│   └───nixpkgs follows input 'B/A/nixpkgs'
└───nixpkgs: github:NixOS/nixpkgs/a529f0c125a78343b145a8eb2b915b0295e4f459

For flake C, it shows the circular follows as I expected. Note that flake C doesn't have to have an input named nixpkgs. If we renamed nixpkgs to nixpkgs2 and run the command again, we get:

❯ nix flake metadata 'path:C'
Resolved URL:  path:C
Locked URL:    path:C?narHash=sha256-e7CWHklYwyo2HWKAI%2fgFz1VDxMz3BfTHoP97yh93+eY=
Path:          /nix/store/2063yliiyjlgjwrnm19j2fr5mm193z0q-source
Inputs:
├───B: path:/home/ilkecan/nix/test/github/nix/4808/B?narHash=sha256-Q2zRAMnLT3V5AItAyR7Q0R+g1Vf7qldhyr8DMgDOBr8=
│   ├───A: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   │   └───nixpkgs follows input 'B/nixpkgs'
│   ├───A_alt: path:/home/ilkecan/nix/test/github/nix/4808/A?narHash=sha256-gwGX+9ZoLlEbjgqUzaQOtGq+1Rle0t62gaErjYndTOI=
│   │   └───nixpkgs follows input 'B/A/nixpkgs'
│   └───nixpkgs follows input 'B/A/nixpkgs'
└───nixpkgs2: github:NixOS/nixpkgs/a529f0c125a78343b145a8eb2b915b0295e4f459

To summarize, the problem described in this issue doesn't happen with the latest version of nix, so I think the issue can be closed.

@stale stale bot removed the stale label Feb 5, 2022
@mschwaig
Copy link
Member Author

mschwaig commented Feb 5, 2022

Thanks for taking a look at this. I saw some tests in nix recently that made me think this got fixed, but did not find the time to reply or revisit this issue to verify until now.

Shouldn't it be

inputs.A_alt.inputs.nixpkgs.follows = "A/nixpkgs";

?

@12Boti yes, thanks. That was an error.

To summarize, the problem described in this issue doesn't happen with the latest version of nix, so I think the issue can be closed.

@ilkecan yes thanks. I went through what you wrote and also verified it locally myself. This is indeed fixed now.

I managed to find the PR that added those tests which should be the PR that fixed the issue: #4641

So thanks to @citadelcore and everyone else who got that PR merged. I think that was an important issue to solve.

@mschwaig mschwaig closed this as completed Feb 5, 2022
mschwaig added a commit to mschwaig/broken_follows_poc that referenced this issue Feb 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants