Flakes - impure error installing package from local file system

I get the following error:

error: access to absolute path '/home/myuser/scripts/sway/swaynagmode' is forbidden in pure eval mode (use '--impure' to override)

I’m calling a module in my /etc/nixos/flake.nix call pkgs_local.nix which contains:

{ config, pkgs, lib, ... }:
let
  swaynagmode = pkgs.callPackage "/home/myuser/scripts/sway/swaynagmode" {};
in
{
  environment.systemPackages = with pkgs; [
    swaynagmode
  ];
}

This worked in my pre-flakes set up without errors. What is the recommended route to converting this to a flakes setup that is properly pure?

Either turn the package into a flake and add it to your flake inputs, or make it part of your flake and import the file relatively instead of via an absolute path.

“impure” in this context means that, for the same inputs, a function might not always produce the same output.

In the context of a nix flake this would mean that your configuration isn’t reproducible, which is generally something worth avoiding - ideally, using just your flake, you should be able to spin up the same system regardless of when or where you try.

import-ing a file from an untracked place on your computer is obviously impure. That file likely doesn’t exist on other computers, and if it does, might not have the same contents - so nix might not be able to build your config, or produce unexpected outputs. You’ve snuck in an extra input that isn’t properly tracked, basically.

Nix used to not be very strict about this, which was a source of reproducibility problems. Flakes are more explicit about when your configuration might not actually be reproducible.

Flake inputs are permitted to not be inside your project because their hashes are tracked in flake.lock, so nix is able to assert that your inputs didn’t change without your knowledge, and because they are explicit inputs aren’t treated as part of the flake “function”, so it’s still considered “pure”.

For this, I would definitely recommend adding the package to your flake, perhaps even as part of the packages output. You don’t want to be left without this package if you ever need to reinstall your system with just your flake, and keeping it as an external flake that only lives on your drive at least makes the backup story more complex.

If this package is in your home directory because you prefer editing without sudo, consider moving your whole system configuration into your home directory and building using nixos-rebuild build --flake ~/my-config#.

1 Like

This makes sense. The directory should always be there as it’s a folder managed by syncthing, which is itself managed by nixos.

I will try moving the file and/or moving the whole config to see what works best for me.

I’m getting an error on my first attempt to convert this to a flake.

Original swaynagmode/default.nix

{ fetchFromGitHub
, stdenv
, lib
}:

stdenv.mkDerivation rec {
  pname = "swaynagmode";
  version = "0.2.1";

  src = fetchFromGitHub {
    owner = "b0o";
    repo = "swaynagmode";
    rev = "v${version}";
    sha256 = "BuPnP9PerPpxi0DJgp0Cfkaddi8QAYzcvbDTiMehkJw=";
  };

  installPhase = ''
    mkdir -p $out/bin
    cp swaynagmode $out/bin
  '';

  meta = with lib; {
    description = "A wrapper script which provides programmatic control over swaynag, intended for use with keyboard bindings.";
    homepage = "https://github.com/b0o/swaynagmode";
    license = licenses.gpl3;
    maintainers = [ ];
    platforms = platforms.linux;    
  };
}

And my first attempt at creating the flake.nix

{     
  # https://garnix.io/blog/converting-to-flakes
  inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05;
  inputs.flake-utils.url = github:numtide/flake-utils;

  outputs = { self, nixpkgs, flake-utils }: 
    flake-utils.lib.eachDefaultSystem (system:
      { 
        packages = (import ./default.nix)
        { 
          pkgs = nixpkgs.legacyPackages.${system};
        };
      } 
    );
}

Doing a nix build . gives me

nix build .
error: anonymous function at /nix/store/qpbqdrahv5ayilxjwffq380pf4wcba8j-source/default.nix:1:1 called without required argument 'lib'

       at /nix/store/qpbqdrahv5ayilxjwffq380pf4wcba8j-source/flake.nix:9:21:

            8|       {
            9|         packages = (import ./default.nix)
             |                     ^
           10|         {
(use '--show-trace' to show detailed location information)

I commented out the lib input and entire meta section in the default.nix which gave me the same error but this time for fetchFromGitHub. I’ve tried a few things but am not quite sure how to pass the required inputs to satisfy the import requirements.

Try:

{ 
  packages.swaynagmode = nixpkgs.legacyPackages.${system}.callPackage ./default.nix {};
} 

Your current code:

Will substitute the contents of default.nix, which defines a function that takes an attrset with 3 named arguments:

You then give it this argument, an attrset containing pkgs, which the function doesn’t ask for:

So you get an error, because you didn’t give it any of the arguments it was asking for (and it fails before telling you it has no idea what to do with that pkgs argument).

pkgs.callPackage is a function that automatically figures out what things from nixpkgs a function asks for, and then calls the function with exactly those arguments (and any extra ones you add). It’s always used when building packages written in the style that you wrote yours.

Alternatively, you could take those arguments out of nixpkgs by hand:

# Note the brackets around `import x` are actually unnecessary in your case
packages.swaynagmode = import ./default.nix {
  # inherit (x) y; means "take attribute y out of x and put it in this attrset with the same name"
  inherit (nixpkgs.legacyPackages.${system}) fetchFromGitHub stdenv lib;
};

The nix pills explain callPackage really well: Nix Pills. They’re a good read in general if this stuff still confuses you.


Also note that you actually need to give your package a name in that attrset. Nix is only not complaining because so far it’s been unable to see whether the function would return an appropriate attrset. I’ve taken the liberty in the above snippets of calling your package output swaynagmode.

You could also name it “default”, in which case some nix commands would use that package by default if you don’t specify anything else.


And since I’m already reviewing your code, you might want to know that rec is an antipattern (as is with, but frankly, I don’t think it’s too bad when used in meta like you do here).

It’s a minor nit, and I’m still guilty of using it for version like you do sometimes. There’s a cool new package definition function that makes the use of rec for this obsolete, though: Nixpkgs 22.05 manual.

This sounds like it will cause bootstrap issues in the case you have to rebuild the system from scratch, eg after replacing the primary harddisk.

1 Like

True, although my usual method for bringing a system back up is to rsync my nixos folder from another machine. I hadn’t actually gotten this far into a rebuild from scratch yet, so consolidating everything into my nixos folder makes sense.

Thanks for all of the help so far! This all makes perfect sense. I read nix pills when I was first learning nix, although it made less and less sense as I progressed through it. Now that I’ve used nix and nixos for most nearly a year, I should probably go back through it.

I currently have a working build. My /etc/nixos is moved to my syncthing folder, and the swaynagmode folder is within syncthing/scripts/nixos/swaynagmode.

I’m inputting the folder in flake.nix

  inputs.swaynagmode.url = "path:swaynagmode";

I import my sway.nix in flake.nix (in part)

      modules = [
        ./sway.nix

and within their I have (in part)

{ config, pkgs, lib, swaynagmode, ... }:
{
  programs.sway = {
    enable = true;
    wrapperFeatures.gtk = true; # so that gtk works properly
    extraPackages = with pkgs; [
        #swaynagmode

In that directory, I tested that everything is working as expected by running:

# nixos-rebuild test --flake .#

When I comment out the swaynagmode, I get the error

# nixos-rebuild test --flake .#
building the system configuration...
error: attribute 'swaynagmode' missing

       at /nix/store/8gwzhqn67qm8r5biakx2hxsqiy3jfqmz-source/lib/modules.nix:496:28:

          495|         builtins.addErrorContext (context name)
          496|           (args.${name} or config._module.args.${name})
             |                            ^
          497|       ) (lib.functionArgs f);
(use '--show-trace' to show detailed location information)

I’ve tried a few different things after searching around and looking at others’ nixos configurations I could find, but I don’t see how to get this to recognize.

You misunderstood me a little.


I didn’t really mean that you should do both of these things :slight_smile: One is enough; either make swaynagmode an explicit flake, or have it part of your general flake.

Part of the general flake (easy)

The latter is much simpler, if you just add your swaynagmode.nix to that folder as you already have, you can just use it as you probably did before, e.g.:

{ config, pkgs, lib, ... }:
{
  programs.sway = {
    enable = true;
    wrapperFeatures.gtk = true; # so that gtk works properly
    extraPackages = with pkgs; [
        (callPackage ./swaynagmode { })

Assuming syncthing/scripts/nixos has your sway.nix file, and swaynagmode/ is also in the same directory, and your actual swaynagmode package is called default.nix.

You don’t need another flake for swaynagmode if you’re happy with having them all in the same flake.


Part of a separate flake (harder)

If you did want to have your swaynagmode in a directory that is not a subdirectory of the directory with flake.nix in it still, you could create a flake for it, exactly like you did, and you wouldn’t need to move your files at all.

But you would need to also actually use it correctly. Just having the input present in flake.nix is not enough to expose it to the NixOS module system. You need to glue it in yourself.

Part of the problem is that there is no single way of achieving this. We need to use the furnishings of lib.nixosSystem, which are partially deprecated and before flakes were clearly not intended to be a public interface. It’s simple in practice, but confusing and more complex than it needs to be.

There are basically two options (you might see some other suggestions such as specialArgs and extraArgs, but these are deprecated):

  1. Use _module.args
  2. Use nixpkgs.overlays

Typically I would recommend an overlay, but in this case you already added swaynagmode as a module arg to sway.nix and we just need to tell it where to actually get the argument. In your flake.nix, you should have something like this:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
    swaynagmode.url = "path:swaynagmode";
  };

  outputs = { nixpkgs, swaynagmode }: {
    nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
      inherit system;
      modules = [
        (import ./configuration.nix)
      ];
    };
  };
}

Note that the outputs function has two arguments, nixpkgs and swaynagmode. This is why those inputs can be used - nix will take the inputs, and put the correctly named attributes into arguments for the outputs function.

The nixpkgs.lib.nixosSystem isn’t given swaynagmode, though, so NixOS doesn’t know anything about the other input. It is implicitly given nixpkgs, because we call it through nixpkgs.

To give NixOS access to the other argument, what we can do is add swaynagmode to the arguments each of your modules takes using _module.args in an inline module like so:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
    swaynagmode.url = "path:swaynagmode";
  };

  outputs = { nixpkgs, swaynagmode }: {
    nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
      inherit system;
      modules = [
        # This is an inline module - looks exactly like e.g.
        # `configuration.nix`, but it isn't a separate file,
        # and therefore retains full access to the outer
        # scope (which includes `swaynagmode`).
        ({...}: {
          _module.args = {
            inherit swaynagmode;
          };
        })

        (import ./configuration.nix)
      ];
    };
  };
}

swaynagmode is in scope because it’s an argument of outputs. Adding it to the _module.args in a module means that now whenever nix evaluates another module, it will be available in the arguments for that module.

So your sway.nix will have it available in {pkgs, lib, swaynagmode, ...}. It will also be available in configuration.nix, for that matter.

The alternative of using an overlay would make it available through pkgs.swaynagmode instead, but I’m sure I’ve already given you far too much detail, so I’ll leave that explanation for another day.

Personally, I think this is all black magic, and unnecessarily so. We really need a better way of doing this, but it’s the current state of flakes. Maybe it will get better when it’s not just a hack to have experimental flake support.

1 Like

Perfect. This is now working.

I had already put these in, so I took them out and cleaned up my other experiments and all is well. I agree that there’s some black magic here. Hopefully discussions like this can help inform documentation.

1 Like