NixOS / nix

Nix, the purely functional package manager

Home Page:https://nixos.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

nmattia opened this issue · comments

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

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.

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.

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)

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?

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.

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

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

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.

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.

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

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?

commented

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

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.

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 { ... };
  };
}

好的,我意识到它实际上非常挑剔,甚至禁止重击(即在输入中{ 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 : (

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

😢

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: #3978

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.

commented

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

@FlafyDev Create a PR, please!

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

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.

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.

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.

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

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

I'm sure tooling is the key.

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.

Here is some tooling proposed in the other issue: #4945 (comment).
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.

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.