Search code examples
nix

nix nixlang: undefined variable pkgs in default.nix via nix-build -A hello but works in nix repl


I wrote a very simple default.nix file with which I should be able to build the gnu hello package (similiar to nix-pills).

But now i've come across an error:

[jane@nixos:~/graphviz]$ nix-build -A hello

error: undefined variable 'pkgs' at /home/jane/graphviz/default.nix:3:47

this is the source code :

[jane@nixos:~/graphviz]$ cat default.nix 
{
    pkgs = import <nixpkgs> {}; 
    mkDerivation = import ./autotools.nix pkgs;
    hello = import ./hello.nix { inherit mkDerivation ;};
    
}

which makes absolutely no sense (to me) as right the line above i defined pkgs.

As i couldn't see what's wrong i opened nix repl and entered the lines.

nix-repl> pkgs = import <nixpkgs> {} 

nix-repl> mkDerivation = import ./autotools.nix pkgs

nix-repl> hello = import ./hello.nix { inherit mkDerivation ;}  

nix-repl> hello
«derivation /nix/store/g2y6sf5q236icvv2gwyg0lnij3mkr36j-hellooo.drv»

And voila there it works. So i don't understand why it fails with default.nix. I could only imagine that default.nix is somewhat special but syntax wise it must be fine otherwise nix repl wouldn't work as well.

Can anyone explain why i get this undefined variable error ?

Edit: Just after asking the question i found one way to solve the undefined variable error, if I put it like this:

let pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs;
in
{
    hello = import ./hello.nix { inherit mkDerivation ;};
    
}

it works.

But my original question still remains.


Solution

  • The { } syntax only defines a value. It does not bring the attributes in it into scope. You can use the rec { } syntax which does do both.

    rec {
      pkgs = import <nixpkgs> {}; 
      mkDerivation = import ./autotools.nix pkgs;
      hello = import ./hello.nix { inherit mkDerivation ;};
    }
    

    What the nix repl does is essentially create a let binding every time name something. Your repl session can be thought of as this expression being built interactively:

    let pkgs = import <nixpkgs> {};
    in /* nixlang scope on 2nd prompt */
      let mkDerivation = import ./autotools.nix pkgs
      in /* nixlang scope on 3rd prompt */
        let hello = import ./hello.nix { inherit mkDerivation ;}
          /* nixlang scope on 4th prompt */
        in hello
    

    To illustrate the distinction between attribute set creation and name binding, you could name the attribute set and use it in its own definition

    let
      myScope = {
        # myScope is like any other attribute set
        pkgs = import <nixpkgs> {};
    
        # laziness permits the attributes inside to reference myScope
        mkDerivation = import ./autotools.nix myScope.pkgs;
        hello = import ./hello.nix { inherit (myScope) mkDerivation ;};
      };
    in
      # the value of this file is the myScope attribute set
      myScope
    

    This is equivalent to the rec example, but brings only a single name into scope: myScope.