`runCommand`'s 2nd argument (`env`) - what does it accept and how does it work?

What does it accept?

The runCommand source simply states that

  /* Run the shell command `buildCommand' to produce a store path named
  * `name'.  The attributes in `env' are added to the environment
  * prior to running the command.

and the runCommand section in the Nixpkgs manual goes further with

env is an attribute set specifying environment variables that will be set for this derivation.

The latter seems misleading though as I was immediately thinking of shell environment variables (especially because the phrase “environment variable” is used exclusively in this context in the Nixpkgs manual) but, looking at the examples, this is not the case:

  • from the NixOS manual:

    Nix expressions can also be given on the command line. For instance, the following starts a shell containing the packages sqlite and libX11 :

    $ nix-shell -E 'with import <nixpkgs> { }; runCommand "dummy" { buildInputs = [ sqlite xorg.libX11 ]; } ""'
  • from various parts in the the Nixpkgs manual:

      runCommand "test.pdf" {
        nativeBuildInputs = [ latex_with_foiltex ];
      } ''
    runCommand "${pname}-tests" { meta.timeout = 3; } ''

and so on.

Would it be fair to say that env in runCommand name env buildCommand is basically

an arbitrary Nix expression to satisfy certain prerequisites (dependencies, shell environment variables, etc.)


The snippet below from this post seems to corroborate this:

runCommand "foo"
           { buildInputs = [ python imagemagick ]; }
             I am an indented string
             I will be executed as a bash script, with the following
             dependencies available:
              - python
              - imagemagick

How does it work?

Based on my understanding looking at the source, it shouldn’t even work.

update: Totally misinterpreted the very last line (// builtins.removeAttrs derivationArgs [ "passAsFile" ]); which is merging derivationArgs (nee env) into mkDerivation's input attribute set…


runCommand = name: env: runCommandWith {
  stdenv = stdenvNoCC;
  runLocal = false;
  inherit name;
  derivationArgs = env;


  runCommandWith =
      # prevent infinite recursion for the default stdenv value
      defaultStdenv = stdenv;
    { stdenv ? defaultStdenv
    # which stdenv to use, defaults to a stdenv with a C compiler, pkgs.stdenv
    , runLocal ? false
    # whether to build this derivation locally instead of substituting
    , derivationArgs ? {}
    # extra arguments to pass to stdenv.mkDerivation
    , name
    # name of the resulting derivation
    }: buildCommand:
    stdenv.mkDerivation ({
      name = lib.strings.sanitizeDerivationName name;
      inherit buildCommand;
      passAsFile = [ "buildCommand" ]
        ++ (derivationArgs.passAsFile or []);
    // (lib.optionalAttrs runLocal {
          preferLocalBuild = true;
          allowSubstitutes = false;
    // builtins.removeAttrs derivationArgs [ "passAsFile" ]);

The argument to runCommand is basically passed forward to mkDerivation. And the argument to mkDerivation is essentially just an attribute set of environment variables; stdenv just sets up a bunch of shell scripting that interprets many of those environment variables at build time, e.g. by putting everything from $nativeBuildInputs into $PATH.