The next few chapters take place in the standard environment “part,” but we have left the standard environment “chapter.” I suppose that that these are details only relevant to stdenv.mkDerivation
? Or I shouldn’t read too much into the grouping here.
Chapter 7. Meta-attributes
We learn that meta
attributes are not passed to the derivation’s builder, and thus they do not trigger a rebuild when they change. So that’s nice. It would be pretty annoying if changing the maintainer
caused the hash of a package to change.
We already learned about passthru
, whose attributes are also not passed along to the derivation, but which are slightly different than meta
in that passthru
attributes still wind up attached to the top-level derivation. meta
attributes get no such treatment:
nix-repl> pkgs.mercurial.maintainers
error: attribute 'maintainers' missing, at (string):1:1
nix-repl> pkgs.mercurial.meta.maintainers
[ { ... } ]
Nixpkgs teaches us the --json
flag to nix-env -q
. That’s something we discovered for ourselves, way back in the day. No mention of the apparently-vestigial --meta
flag.
Then we see a list of “expected” meta
attributes.
description
longDescription
branch
homepage
downloadPage
changelog
license
maintainers
priority
platforms
tests
timeout
hydraPlatforms
broken
updateWalker
Mostly very straightforward. priority
is bad and unnecessary and it should feel bad. But we don’t really have to use it.
tests
is weird because it’s not meta.tests
, it’s passthru.tests
, but for some reason it’s documented here, as a “meta attribute,” instead of documented when we learned about passthru
. So.
timeout
seems like… a weird thing to be here. Who respects timeout
? All Nix operations? I hope not. I hope that’s only, like, a CI thing.
$ cat sleeper.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "timeout-tester-1.0";
builder = ./sleeper.sh;
}
$ cat sleeper.sh
source $stdenv/setup
sleep 5
echo "hi" > "$out"
$ nix-build sleeper.nix
these derivations will be built:
/nix/store/whgkpqbpm9mzzknvdbkd8xinx7akrl2r-timeout-tester-1.0.drv
building '/nix/store/whgkpqbpm9mzzknvdbkd8xinx7akrl2r-timeout-tester-1.0.drv'...
/nix/store/lnd1qc1liv7bcr505kz3hpccgvyfx80c-timeout-tester-1.0
Okay. That works. But when I add a timeout:
$ cat sleeper.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "timeout-tester-1.0";
builder = ./sleeper.sh;
meta = { timeout = 1; };
}
$ nix-build sleeper.nix
/nix/store/lnd1qc1liv7bcr505kz3hpccgvyfx80c-timeout-tester-1.0
Er, right. Changing meta attributes doesn’t change the hash, so the package is the same…
$ rm result
$ nix-store --delete /nix/store/lnd1qc1liv7bcr505kz3hpccgvyfx80c-timeout-tester-1.0
finding garbage collector roots...
removing stale link from '/nix/var/nix/gcroots/auto/qqrfq5975mbpf5p9xpsqwhfpyj7q5jkk' to '/Users/ian/scratch/result'
...
1 store paths deleted, 0.00 MiB freed
Second time’s the charm…
$ nix-build sleeper.nix these derivations will be built:
/nix/store/whgkpqbpm9mzzknvdbkd8xinx7akrl2r-timeout-tester-1.0.drv
building '/nix/store/whgkpqbpm9mzzknvdbkd8xinx7akrl2r-timeout-tester-1.0.drv'...
/nix/store/lnd1qc1liv7bcr505kz3hpccgvyfx80c-timeout-tester-1.0
Okay. So this value is not magic to Nix. This is something that certain systems – I’m guessing just Hydra – can choose to look at.
hydraPlatforms
says:
The list of Nix platform types for which the Hydra instance at
hydra.nixos.org
will build the package. (Hydra is the Nix-based continuous build system.) It defaults to the value ofmeta.platforms
. Thus, the only reason to setmeta.hydraPlatforms
is if you wanthydra.nixos.org
to build the package on a subset ofmeta.platforms
, or not at all.
In my head “Hydra” means “the thing that populates cache.nixos.org.” But I don’t really know why I think that. Maybe it isn’t? Maybe it’s really just a piece of test infrastructure? I hope that the manual will go into that a bit, eventually.
updateWalker
is the last interesting one – it’s only relevant to a script called update-walker.sh
, which appears to exist to automate version bumping of packages.
Lastly the chapter talks about different “generic” licenses to use if you don’t know the actual license a package is available under (?). I don’t know why you would ever do that. Nothing too interesting here.
Chapter 8. Multiple-output packages
We already know that a derivation can specify multiple outputs. Now we learn why:
The main motivation is to save disk space by reducing runtime closure sizes; consequently also sizes of substituted binaries get reduced. Splitting can be used to have more granular runtime dependencies, for example the typical reduction is to split away development-only files, as those are typically not needed during runtime. As a result, closure sizes of many packages can get reduced to a half or even much less.
Hmm. Okay. I can definitely understand, like, “I depend on perl
but I don’t depend on the perl
man pages,” or something. I don’t really understand why “development-only files” would be included in the first place. I feel like I don’t really understand the distinction between runtime and build-time dependencies in Nix.
The attribute
meta.outputsToInstall
is used to determine the default set of outputs to install when using the derivation name unqualified.
That seems like kind of an important meta
field! Maybe it could have been documented in the section about meta fields, that we just read?
I remember from my testing that this defaulted to the first entry in the output
list. But that does not appear to be the case. The manual links directly to the source:
# If the packager hasn't specified `outputsToInstall`, choose a default,
# which is the name of `p.bin or p.out or p`;
# if he has specified it, it will be overridden below in `// meta`.
# Note: This default probably shouldn't be globally configurable.
# Services and users should specify outputs explicitly,
# unless they are comfortable with this default.
outputsToInstall =
let
hasOutput = out: builtins.elem out outputs;
in [( lib.findFirst hasOutput null (["bin" "out"] ++ outputs) )]
++ lib.optional (hasOutput "man") "man";
As a native English speaker, it feels very jarring to see a gendered pronoun applied to “the packager.” I had to re-read that a couple times before I parsed it; the strange line break after the semicolon didn’t help.
Anyway, it seems that I remembered incorrectly. Interesting that you can specify a bin
output and it will take precedence over the default output name out
.
There’s a little bit about NixOS here that I don’t really understand; I skipped it. Then we get:
nix-env
lacks an easy way to select the outputs to install. When installing a package,nix-env
always installs the outputs listed inmeta.outputsToInstall
, even when the user explicitly selects an output.
Well I’ll be darned. It then says that the only way to actually install specific outputs is to add an overlay that changes that meta attribute.
But hang on! I remember a weird meta-specific override facility hidden deep in the Nix command reference. I couldn’t really come up with a decent use-case for it at the time. But could I use that to change the outputs that nix-env
installs? That seems like exactly what this might be useful for.
Let’s test it. We’ll use coreutils
, as we did already:
$ nix-env -iA nixpkgs.coreutils.info -p ~/scratch/profile
installing 'coreutils-8.32'
building '/nix/store/8z1s1xm3b3ljy5mkw30r216n9197mcjs-user-environment.drv'...
created 2 symlinks in user environment
$ tree -l ~/scratch/profile/
/Users/ian/scratch/profile
├── bin -> /nix/store/cpvjym13fdglv6zmdr5xav20g5rbafbx-coreutils-8.32/bin
│ ├── [ -> coreutils
│ ├── b2sum -> coreutils
│ ├── base32 -> coreutils
│ ├── base64 -> coreutils
│ ├── basename -> coreutils
│ ├── basenc -> coreutils
│ ├── cat -> coreutils
│ ├── chcon -> coreutils
│ ├── chgrp -> coreutils
│ ├── chmod -> coreutils
│ ├── chown -> coreutils
│ ├── chroot -> coreutils
│ ├── cksum -> coreutils
│ ├── comm -> coreutils
│ ├── coreutils
│ ├── cp -> coreutils
│ ├── csplit -> coreutils
│ ├── cut -> coreutils
│ ├── date -> coreutils
│ ├── dd -> coreutils
│ ├── df -> coreutils
│ ├── dir -> coreutils
│ ├── dircolors -> coreutils
│ ├── dirname -> coreutils
│ ├── du -> coreutils
│ ├── echo -> coreutils
│ ├── env -> coreutils
│ ├── expand -> coreutils
│ ├── expr -> coreutils
│ ├── factor -> coreutils
│ ├── false -> coreutils
│ ├── fmt -> coreutils
│ ├── fold -> coreutils
│ ├── groups -> coreutils
│ ├── head -> coreutils
│ ├── hostid -> coreutils
│ ├── id -> coreutils
│ ├── install -> coreutils
│ ├── join -> coreutils
│ ├── kill -> coreutils
│ ├── link -> coreutils
│ ├── ln -> coreutils
│ ├── logname -> coreutils
│ ├── ls -> coreutils
│ ├── md5sum -> coreutils
│ ├── mkdir -> coreutils
│ ├── mkfifo -> coreutils
│ ├── mknod -> coreutils
│ ├── mktemp -> coreutils
│ ├── mv -> coreutils
│ ├── nice -> coreutils
│ ├── nl -> coreutils
│ ├── nohup -> coreutils
│ ├── nproc -> coreutils
│ ├── numfmt -> coreutils
│ ├── od -> coreutils
│ ├── paste -> coreutils
│ ├── pathchk -> coreutils
│ ├── pinky -> coreutils
│ ├── pr -> coreutils
│ ├── printenv -> coreutils
│ ├── printf -> coreutils
│ ├── ptx -> coreutils
│ ├── pwd -> coreutils
│ ├── readlink -> coreutils
│ ├── realpath -> coreutils
│ ├── rm -> coreutils
│ ├── rmdir -> coreutils
│ ├── runcon -> coreutils
│ ├── seq -> coreutils
│ ├── sha1sum -> coreutils
│ ├── sha224sum -> coreutils
│ ├── sha256sum -> coreutils
│ ├── sha384sum -> coreutils
│ ├── sha512sum -> coreutils
│ ├── shred -> coreutils
│ ├── shuf -> coreutils
│ ├── sleep -> coreutils
│ ├── sort -> coreutils
│ ├── split -> coreutils
│ ├── stat -> coreutils
│ ├── stdbuf -> coreutils
│ ├── stty -> coreutils
│ ├── sum -> coreutils
│ ├── sync -> coreutils
│ ├── tac -> coreutils
│ ├── tail -> coreutils
│ ├── tee -> coreutils
│ ├── test -> coreutils
│ ├── timeout -> coreutils
│ ├── touch -> coreutils
│ ├── tr -> coreutils
│ ├── true -> coreutils
│ ├── truncate -> coreutils
│ ├── tsort -> coreutils
│ ├── tty -> coreutils
│ ├── uname -> coreutils
│ ├── unexpand -> coreutils
│ ├── uniq -> coreutils
│ ├── unlink -> coreutils
│ ├── uptime -> coreutils
│ ├── users -> coreutils
│ ├── vdir -> coreutils
│ ├── wc -> coreutils
│ ├── who -> coreutils
│ ├── whoami -> coreutils
│ └── yes -> coreutils
├── libexec -> /nix/store/cpvjym13fdglv6zmdr5xav20g5rbafbx-coreutils-8.32/libexec
│ └── coreutils
│ └── libstdbuf.so
└── manifest.nix -> /nix/store/jsmdf1ik7lqm5ajsrjc7bll5158m167g-env-manifest.nix
Indeed: no info
pages. But I should be able to use…
$ nix-env --set-flag outputsToInstall '["info"]' coreutils -p ~/scratch/profile
setting flag on 'coreutils-8.32'
error: this derivation has bad 'meta.outputsToInstall'
Uhhhh. Well that’s… what? I’m guessing that --set-flag
only takes a literal string, doesn’t it. I can’t give it a list. Anyway, it didn’t do anything:
$ nix-env -qaA nixpkgs.coreutils --json -p ~/scratch/profile | jq '."nixpkgs.coreutils".meta.outputsToInstall'
[
"out"
]
Note the annoying nested quoting in that jq
expression because the key in the result object is the literal string nixpkgs.coreutils
.
So, okay. No. It’s not a thing. Still no idea what the point of --set-flag
is.
Alright, the next section is a little dense:
In the Nix language the individual outputs can be reached explicitly as attributes, e.g.
coreutils.info
, but the typical case is just using packages as build inputs.
Okay, sure. Just not if you’re using nix-env
. Got it.
When a multiple-output derivation gets into a build input of another derivation, the
dev
output is added if it exists, otherwise the first output is added. In addition to that,propagatedBuildOutputs
of that package which by default contain$outputBin
and$outputLib
are also added. (See Section 8.4.2, “File type groups”.)
Okay. No idea what $outputBin
or $outputLib
are, but we’ll get to that very soon.
In some cases it may be desirable to combine different outputs under a single store path. A function
symlinkJoin
can be used to do this. (Note that it may negate some closure size benefits of using a multiple-output package.)
Okay. No explanation of that function or how to use it, but ⌘F tells me we’ll get to it in a few more chapters.
In nixpkgs there is a framework supporting multiple-output derivations. It tries to cover most cases by default behavior. You can find the source separated in <
nixpkgs/pkgs/build-support/setup-hooks/multiple-outputs.sh
>; it’s relatively well-readable. The whole machinery is triggered by defining theoutputs
attribute to contain the list of desired output names (strings).outputs = [ "bin" "dev" "out" "doc" ];
Wait.
This is a Nixpkgs thing?
No. I’ve written a multi-output derivation myself. I didn’t use mkDerivation
or anything else. outputs
is respected by the builtin.derivation
function. Hmm. I don’t understand. Let’s keep going.
I learn that I should usually make the first output
the one with the binaries in it, because apparently derivations stringify to the path of their first output, so this lets me write $drv/bin/exe
. I learn an exception to this is glibc
, whose first output is libs
. Because you’re more likely to be referencing a library. Easy enough.
Now we learn about “file type groups.”
The support code currently recognizes some particular kinds of outputs and either instructs the build system of the package to put files into their desired outputs or it moves the files during the fixup phase. Each group of file types has an
outputFoo
variable specifying the output name where they should go. If that variable isn’t defined by the derivation writer, it is guessed – a default output name is defined, falling back to other possibilities if the output isn’t defined.
The manual really doesn’t describe what this means. What are “variables” here? Environment variables? Are these supposed to be derivation attributes? It lists a bunch of examples of the form:
$outputDev
is for development-only files. These include C(++) headers, pkg-config, cmake and aclocal files. They go todev
orout
by default.
I don’t know what it means by “they go to dev
or out
by default.” What does… what?
$outputMan
is for man pages (except for section 3). They go toman
or$outputBin
by default.
Am I supposed to say, like outputBin = "my-executables";
or something? Are aliases for output “roles”? I don’t understand.
I am very suspicious, however, about outputDev
and the error I got back in part 13. Oh, yeah! That’s exactly it – it seems that dev
and out
are not particularly special. From multiple-outputs.sh
:
_overrideFirst outputDev "dev" "out"
_overrideFirst outputBin "bin" "out"
If I had specified dev
(but not out
), it would have complained about “bin
or out
” next. And then lib
, and then doc
… but by providing an output named out
, it’s used as a fallback for every one of these.
And yes, I now see what these things are. These are either shell variables or environment variables that are relevant to the $stdenv/setup.sh
script. And it seems I could have avoided those errors by specifying every one of outputDev
, outputBin
, outputLib
, and outputDoc
to be the string name of my weird unconventional output, if I’m reading the script correctly.
Neat.
The chapter ends with some “common caveats:” no circular references. Don’t needlessly split things up if depending on one output will cause you to depend on all the other outputs. Nothing too exciting.
And that’s it! Pretty good chapters. Made some things more concrete for me.
- Is Hydra responsible for populating https://cache.nixos.org?
- Does Nix not explicitly distinguish between runtime and build-time dependencies?
- If I use an override or an overlay or whatever, does that only affect
nix-env
? Or does that affect any Nix command?