Help building a complicated nix expression

I have a local file, default.nix that I want to use to build a nix derivation. The file builds a finite element library called Firedrake. This is a large library with many dependencies. The inputs to my nix expression are:

{
  lib,
  pkgs,
  buildPythonPackage,
  callPackage,
  substituteAll,
  fetchFromGitHub,
  mpich,
  petsc,
  hdf5-mpi,
  openssh,
  mpiCheckPhaseHook,
  libspatialindex,

  setuptools,
  cython_0,
  cached-property,
  cachetools,
  packaging,
  progress,
  requests,
  rtree,
  scipy,
  sympy,
  vtk,
  mpi4py,
  h5py-mpi,

  pytestCheckHook,
  pytest-xdist,
  pylit,
}:

Where some of these correspond to other .nix files in the same directory.

Before, when I’ve built smaller projects, I would just run something like nix-build -A firedrake in the directory. However, when I do that I get errors for each input like:

error: cannot evaluate a function that has an argument without a value ('buildPythonPackage')
       Nix attempted to evaluate a function as a top level expression; in
       this case it must have its arguments supplied either by default
       values, or passed explicitly with '--arg' or '--argstr'. See
       https://nixos.org/manual/nix/stable/language/constructs.html#functions.

So I can start passing arguments like the error suggests:
nix-build -A firedrake --arg lib '<nixpkgs/lib>' --arg pkgs '<nixpkgs>' --arg buildPythonPackage '<nixpkgs/buildPythonPackage>'

But this can’t be the right way to go about it.

I think there is a way to do this where use a flake.nix file that takes nixpkgs unstable as the input and tries to build firedrake as the output, but I cannot figure out how to do this.

Could you help me figure out how to achieve this? The question boils down to:

What directory structure, and what nix build type command do I need, in order to attempt to build local nix derivations I am currently working on, so I can see the errors that still exist, and debug the building process.

Thank you!

1 Like

Flakes add another layer on top of what you’re asking about.

Let’s take a simpler example. Here is a package recipe, my-cowsay.nix:

{ cowsay, writeScriptBin }:

writeScriptBin "cowsay1" ''
  echo "moo" | ${cowsay}/bin/cowsay
''

(Hopefully this is clear: this is a function which takes in an attribute set with two attributes…, and then the function evaluates to a ‘derivation’ that has a simple script).

Running nix-build with this and --arg would be tedious. Although commonly suggested would be nix-build --expr '(import <nixpkgs> {}).callPackage ./my-cowsay.nix {}'.

We can write that expr in a file, so it’s simpler to call, e.g. scratch.nix:

let
  pkgs = import <nixpkgs> {};
in
pkgs.callPackage ./my-cowsay.nix {}

then build this with nix-build ./scratch.nix. Sometimes instead of let pkgs = ...; in, it’s common to see { pkgs ? import <nixpkgs> {} }:, which has pkgs as an attribute with a default value, in an attrset argument to a function. In practice, there’s not much difference compared to using the let ... in, for this case. (More generally, it’s nicer to have a function with attributes with default values).

There’s more discussion of callPackage on the nix.dev tutorial about it; and it’s discussed in the Nix Pills series. If callPackage is too magical/implicit for you, here’s a more explicit/verbose way of writing it:

let
  pkgs = import <nixpkgs> {};
in
import ./my-cowsay.nix {
  writeScriptBin = pkgs.writeScriptBin;
  cowsay = pkgs.cowsay;
}

For flakes… might depend on what boilerplate setup you’ve got; but the least boilerplate/abstraction would be something like:

{
  inputs = { ... };
  outputs = { self, nixpkgs, ... }: {
    packages.x86_64-linux.my-cowsay = let
      pkgs = nixpkgs.legacyPackages.x86_64-linux;
    in pkgs.callPackage ./my-cowsay.nix {};
  };
}

Point being, you’d take that same pkgs.callPackage expression, and use it within the packages definition of the flake output.

3 Likes

Thank you very much. I spent a while with the nix.dev tutorial that you mentioned, and it really helped clear a lot of things up. I really appreciate your help.