NixOS / nix

Nix, the purely functional package manager
https://nixos.org/
GNU Lesser General Public License v2.1
12.92k stars 1.53k forks source link

error: '/nix/store/.../flake.nix' must be an attribute set #3966

Open nmattia opened 4 years ago

nmattia commented 4 years ago

Describe the bug

This flake.nix evaluates to an attribute set:

let
  foo = "bar";
in
{
  outputs = { self, nixpkgs }:
    {
      packages.x86_64-linux.devShell = nixpkgs.outputs.legacyPackages.x86_64-linux.hello;
    };
}

however nix develop (or nix build .#devShell) results in the following error:

$ nix develop
error: --- Error ------------------------------------------------------------------------------------ nix
file '/nix/store/wvlsr8s3nysj0pmgdl7ndggpxc8za87f-source/flake.nix' must be an attribute set
(use '--show-trace' to show detailed location information)

I can make nix happy by moving the let inside the outputs definition:

{
  outputs = { self, nixpkgs }:
    let
      foo = "bar";
    in
      {
        packages.x86_64-linux.devShell = nixpkgs.outputs.legacyPackages.x86_64-linux.hello;
      };
}

Expected behavior

I'm expecting the flake commands to allow top-level let bindings; but maybe that's on purpose to ensure the flake.nix stays as simple as possible?

nix-env --version output

nix-env (Nix) 2.4pre20200721_ff314f1

zimbatm commented 4 years ago

That's a strange limitation of how flakes are being evaluated. All of the data structures must be pretty much JSON-like data. The only place where more complex nix code is allowed is within the outputs function.

edolstra commented 4 years ago

but maybe that's on purpose to ensure the flake.nix stays as simple as possible?

That's right. It ensures that a command like nix flake info doesn't have to evaluate an arbitrarily complex (and possibly non-terminating) Nix expression.

nmattia commented 4 years ago

Ok I realize it's actually terribly picky, even thunks are forbidden (i.e. in an input's { url = "foo" + "bar"; }):

/tmp/tmp.7jJ3XRKbVP$ nix develop
error: --- Error ------------------------------------------------------------------------------------- nix
expected a string but got a thunk at /nix/store/4yiaxz02925lr1ssyir8nc7cs8dp7bf0-source/flake.nix:2:3
(use '--show-trace' to show detailed location information)
jorsn commented 4 years ago

Why then use nix syntax if it is not nix? I'd say, either use a clearly defined subset of nix code, at best with another name and file ending (tnix – total nix) or the flake file shall be in a total language like plain json and the outputs attr refers to nix expressions, or dhall (but a comparatively complex new language).

If it looks like nix but isn't, to me it is confusing unexpected (”strange“ – zimbatm) behaviour.

Edit: Why not split the flake.nix into flake.json containing metadata and inputs and flake.nix/outputs.nix containing only the outputs function in real Nix lang?

roberth commented 4 years ago

I like the idea of specifying inputs and metadata in json. It's easier to work with for Nix itself and for external tools. With niv I often use niv update some-input -b feature-branch to test things out. nix flake doesn't have such a command and it'd be relatively hard to implement given the current flake format, because we don't have a way to write back modified asts without touching unmodified whitespace and comments.

I always used to think of the Nix language as a predictable element in an otherwise messy domain. Just a lazy evaluator that implicitly writes derivations. I don't think flakes need to change that.

edolstra commented 4 years ago

I like the idea of specifying inputs and metadata in json.

The configs branch adds support for TOML as an alternative to flake.nix (1dc3f5355a3786cab37a4de98ca46a859e015d89). This allows the flake to be modified from the command line (e.g. 3632593bfc2daa82b2527c601604c8d9ae91840e).

nix flake doesn't have such a command

Well there is the --override-input flag. Since it only overwrites the lock file it doesn't have to deal with rewriting Nix expressions.

malte-v commented 3 years ago

Ugh. I just spent 8 hours writing a nix expression that dynamically generates flake inputs from an external registry and now I find out that it won't work. Is this limitation really such a great idea? If it's just there to improve the UX of nix flake info I would consider dropping it and allowing arbitrary Nix code in flakes.

gytis-ivaskevicius commented 3 years ago

Would it be difficult to disallow most builtins instead? :thinking: This would more or less solve the problem with people writing complex expressions and would allow for some flexibility which would be neat.

ggPeti commented 3 years ago

Why are we trying to protect users from their own non-terminating nix expressions? On the other hand, why only in commands like nix flake info?

jorsn commented 3 years ago

Why are we trying to protect users from their own non-terminating nix expressions?

E.g. because you want to safely list an unknown online flake.

aszenz commented 3 years ago

Ok I realize it's actually terribly picky, even thunks are forbidden (i.e. in an input's { url = "foo" + "bar"; }):

/tmp/tmp.7jJ3XRKbVP$ nix develop
error: --- Error ------------------------------------------------------------------------------------- nix
expected a string but got a thunk at /nix/store/4yiaxz02925lr1ssyir8nc7cs8dp7bf0-source/flake.nix:2:3
(use '--show-trace' to show detailed location information)

I just faced the same issue, very confusing to know I can't even concatenate strings inside flake inputs. Maybe improving the error message would help, although i like the suggestion to just use json/toml

kalhauge commented 3 years ago

Can I add to this issue as well. I understand that limiting the computability of flakes are important, but I have some usecases where using expressions in flakes would be very useful.

For example, when all inputs comes from the same server. Here we have a Haskell flake that requires some specific versions of many packages from hackage:

{
  description = "My Picky Haskell Program";
  inputs = let
    hackage = name: {
      url = "https://hackage.haskell.org/package/${name}/${name}.tar.gz";
      flake = false;
    };
  in {
   package1 = hackage "package1-1.2.3";
   package2 = hackage "package2-3.2.1";
   ...
  };
  outputs = { self, package1, package2, ...}: 
    #...
  ;
}

Having to write out "http://hackage.haskell.org..." for every entry, is very error prone, and is hard to change. Being able to template the flakes inputs would really be a huge usability boon.

con-f-use commented 2 years ago

I agree, something like this would be very useful

{
  inputs = {
    foo = rec {
      type = "tarball";
      version = "1.2.3";
      url = "https://very.long/url/${version}/that/${version}/includes/${version}/a/lot/of/${version}"
    };
    # or:
    bar = let version = "1.2.3"; in { ... };
  };
}
VergeDX commented 2 years ago

好的,我意识到它实际上非常挑剔,甚至禁止重击(即在输入中{ url = "foo" + "bar"; }):

/tmp/tmp.7jJ3XRKbVP$ nix develop
error: --- Error ------------------------------------------------------------------------------------- nix
expected a string but got a thunk at /nix/store/4yiaxz02925lr1ssyir8nc7cs8dp7bf0-source/flake.nix:2:3
(use '--show-trace' to show detailed location information)

Same issues here : (

dzmitry-lahoda commented 2 years ago
.
.devcontainer/Dockerfile # can use files from parent directory
.devcontainer/flake.nix    # Each GitHub devcontainer is setup via nix
flake.nix  # I want to reuse setup .devcontainer/flake.nix so that local devs use same nix

that does not work.

will try to optimize child flake.nix so that replicating it in parent is easy.

so I was using nix because it is not json. so I read above that flake.nix is more like json. not nix. and for sure errors are bad.

😢

dzmitry-lahoda commented 2 years ago

may be nix flake info should run some kind of isolation to get info or fail with timeout? may be use docker? or may be slit out static info into json?

UPDATE: https://github.com/NixOS/nix/issues/3978

Atry commented 1 year ago

I wonder if it is possible to allow for curried flake, where the inputs can be specified in multiple stages. For example, suppose you have a Python project, where the requirements.txt includes some git+https urls (e.g. https://github.com/tloen/alpaca-lora/blob/630d1146c8b5a968f5bf4f02f50f153a0c9d449d/requirements.txt#L9) , you can create a Flake github:example-org/requirement-parser to parse requirements.txt into Flake inputs:

{
  inputs = {
    requirementParser.url = "github:example-org/requirement-parser";
  };
  outputs = { self, nixpkgs, requirementParser}: {
    inputs = requirementParser.lib.parse ./requirements.txt;
    outputs = inputs: {
       packages.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.python3.withPackages (requirementParser.lib.buildPythonPackages (builtins.attrValues inputs));
    };
  };
}

Now Nix can generate a flake.lock for the Python dependencies.

FlafyDev commented 1 year ago

I'm currently using this simple patch so I can separate the inputs to many files. So this works.

Atry commented 1 year ago

@FlafyDev Create a PR, please!

nixos-discourse commented 1 year ago

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

https://discourse.nixos.org/t/nix-flake-does-not-support-the-full-nix-language/27697/2

infinisil commented 1 year ago

Related: #4945

tmillr commented 1 year ago

Darn. I got stuck on this for a few hours also. Definitely could use a better error message at least, if things like top-level let aren't going to be allowed. I'm so glad I found this because I was just about to give up. The last thing I tried was builtins.isAttrs (import ./flake.nix) in the repl which returned true, which just makes the error file '/nix/store/*/flake.nix' must be an attribute set even more confusing.

galenhuntington commented 1 year ago

There is also a restriction on the form of outputs. This yields an error:

  outputs = import ./outputs.nix;
error: expected a function but got a thunk

I don't see why this limitation should be needed for nix flake info. However, it can be worked around by η-expanding:

  outputs = inputs: import ./outputs.nix inputs;

This works without issue.

AleXoundOS commented 1 year ago

I think the main rationale behind using a subset of Nix for inputs is to be able to define both inputs and outputs in a single file (having completely different languages is out of question, but one being a subset of another kinda works).

Letting computations in inputs would let the whole ecosystem bloat, be volatile, less readable, mostly losing the point of Nix flakes (for most users). At least some building blocks of the ecosystem should have fixed semantics, IMHO.

Though, I did encounter myself the input restrictions when trying to read inputs URLs using builtins.fromJSON, but realized it would be an overuse even if possible.

nixos-discourse commented 1 year ago

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

https://discourse.nixos.org/t/programmatic-editing-of-flake-nix/35321/1

dzmitry-lahoda commented 1 year ago

hello. so there are 2 issues:

a. nix is not actually nix, but part of it like flake.json. so kind of flake.json flake.lock flake.nix trio. b. bad errors/tooling for that nix not nix. but some turing decidable nix. turing decidable resource limited(stack/heap/references) nix is interesting subset feature.

either one fix of a or b will make else less important. will donate more to nix if a or be handled, as it seems good for increased adoption.

thank you

AleXoundOS commented 1 year ago

I'm sure tooling is the key.

chekoopa commented 10 months ago

We develop a product which includes a server machine based on NixOS with some software modules (developed by us). As for servers, it also implies remote deployment and support.

So we somehow have survived with plain channels (by pinning them using a few forbidden techniques), but now I'm migrating all of this to flakes (because it refuses to build now).

The prime issue is dependency tracking. Every our software module is a separate folder in the monorepo, though they interconnect as dependencies (e.g. libraries). The first plan was just importing nixpkgs commit hash from a certain file. But as that's a very unsafe approach, now I'm brewing a single channel dispatcher to somehow provide a unified nixpkgs revision to all the modules in need.

Thus, yes, tooling is the key, but some unsafe-ty (at least possible to enable implicitly) could help.

jorsn commented 10 months ago

Here is some tooling proposed in the other issue: https://github.com/NixOS/nix/issues/4945#issuecomment-1877896238. The idea is, you have a flake.template.nix written in ordinary Nix, which is used to generate the actual flake.nix. With this, you can write your flake in normal Nix. When it is really worth it (!), then also the small overhead is justified.

The generation of flake.nix could even be wrapped as a flake app. One could make a flake template (that can be used by nix flake init – not the flake.template.nix) that provides a seed flake.nix only containing the generation app.

jorsn commented 10 months ago

The generation of flake.nix could even be wrapped as a flake app. One could make a flake template (that can be used by nix flake init – not the flake.template.nix) that provides a seed flake.nix only containing the generation app.

Implemented: https://github.com/jorsn/flakegen.