How does a `callPackage` produce a store derivation?

I used this slightly modified hello.nix from the nix.dev tutorial for testing:

# hello.nix
{
, stdenv
, fetchzip
, name ? "name_to_be_ignored"
}:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "";
  };
}

Simply calling callPackage will produce a derivation:

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

nix-repl> pkgs.callPackage ./hello.nix { name = "whatever_2024_08_13"; }
...
«derivation /nix/store/g0f045b7k2m1hwnbwpjdhixhh1f2hvaq-whatever_2024_08_13-2.12.1.drv»

The overridden callPackage call will also produce a derivation, but the callPackage call itself won’t:

nix-repl> h = pkgs.callPackage ./hello.nix {}
nix-repl> h.override { name = "name_new_2024_08_13"; }
...
«derivation /nix/store/9c8f6996fxdk8ygxzxsd7k9b2wk2c7yh-name_new_2024_08_13-2.12.1.drv»


$ find /nix/store -name '*2024_08_13*'
/nix/store/g0f045b7k2m1hwnbwpjdhixhh1f2hvaq-whatever_2024_08_13-2.12.1.drv
/nix/store/9c8f6996fxdk8ygxzxsd7k9b2wk2c7yh-name_new_2024_08_13-2.12.1.drv
# ... no `name_to_be_ignored` derivation here ...
$

I get it that the Nix language is lazy, so the h = callPackage call is only evaluated when h.override is called, and even then only the override attribute. But then a derivation attribute set is created and something triggers the store derivation creation. is it a set of attributes (e.g., pname & type & version & builder & …)?

Just in case, here’s the skeleton of the generated derivation attribute set:

{
  __darwinAllowLocalNetworking = false;
  __ignoreNulls = true;
  __impureHostDeps = [
    "/bin/sh"
    "/usr/lib/libSystem.B.dylib"
    "/usr/lib/system/libunc.dylib"
    "/dev/zero"
    "/dev/random"
    "/dev/urandom"
    "/bin/sh"
  ];
  __propagatedImpureHostDeps = [ ];
  __propagatedSandboxProfile = [ "" ];
  __sandboxProfile = "";
  __structuredAttrs = false;
  all = <CODE>;
  args = [
    "-e"
    /nix/store/h8ankbqczm1wm84libmi3harjsddrcqx-nixpkgs/nixpkgs/pkgs/stdenv/generic/default-builder.sh   
  ];
  buildInputs = [ ];
  builder = "/nix/store/ncw57j62jmhsi8l019anl1k8a13g0ykw-bash-5.2-p15/bin/bash";
  cmakeFlags = [ ];
  configureFlags = [ ];
  depsBuildBuild = [ ];
  depsBuildBuildPropagated = [ ];
  depsBuildTarget = [ ];
  depsBuildTargetPropagated = [ ];
  depsHostHost = [ ];
  depsHostHostPropagated = [ ];
  depsTargetTarget = [ ];
  depsTargetTargetPropagated = [ ];
  doCheck = false;
  doInstallCheck = false;
  drvAttrs = {
                       # ... 95750 lines omitted ...
                   };
  drvPath = "/nix/store/riyf1584pvnbg1sw1j4rwrayvjz98q76-hello-2.12.1.drv";
  inputDerivation = <CODE>;
  mesonFlags = [ ];
  meta = <CODE>;
  name = "hello-2.12.1";
  nativeBuildInputs = [ ];
  out = <CODE>;
  outPath = <CODE>;
  outputName = "out";
  outputs = "";
  override = <CODE>;
  overrideAttrs = <CODE>;
  overrideDerivation = <CODE>;
  passthru = { };
  patches = [ ];
  pname = "hello";
  propagatedBuildInputs = [ ];
  propagatedNativeBuildInputs = [ ];
  src = "";
  stdenv = "";
  strictDeps = false;
  system = "aarch64-darwin";
  type = "derivation";
  userHook = null;
  version = "2.12.1";
}

If by ‘produce a derivation’ you mean instantiate it in the Nix store, it’s not callPackage that’s doing that. It’s the REPL, which prints derivation values specially, and instantiates them as a side effect.

callPackage will happily return non-derivation things if that’s what the function returns in the file given to it. All it does is load that file, look for formal parameters that it can provide from its corresponding scope, and then call the function with those parameters. It’s hello.nix that’s creating the derivation, but derivations are just data in memory until they’re instantiated by something like coercing them to a string or being printed in the REPL or being the final result of a nix-instantiate expression or a variety of other things.

2 Likes