зеркало из
https://github.com/iharh/notes.git
synced 2025-11-01 06:06:08 +02:00
269 строки
11 KiB
Plaintext
269 строки
11 KiB
Plaintext
https://nixos.org/guides/nix-pills/inputs-design-pattern.html
|
|
|
|
# 12.1. Repositories in Nix
|
|
|
|
Nix is a tool for build and deployment, it does not enforce any particular repository format.
|
|
A repository of packages is the main usage for Nix, but not the only possibility.
|
|
It's more like a consequence due to the need of organizing packages.
|
|
|
|
Nix is a language, and it is powerful enough to let you choose the format of your own repository.
|
|
In this sense, it is not declarative, but functional.
|
|
|
|
There is no preset directory structure or preset packaging policy.
|
|
It's all about you and Nix.
|
|
|
|
The nixpkgs repository has a certain structure, which evolved and evolves with the time.
|
|
Like other languages, Nix has its own history and therefore I'd like to say that it also has its own design patterns.
|
|
Especially when packaging, you often do the same task again and again except for different software.
|
|
It's inevitable to identify patterns during this process.
|
|
Some of these patterns get reused if the community thinks it's a good way to package the software.
|
|
|
|
Some of the patterns I'm going to show do not apply only to Nix, but to other systems of course.
|
|
|
|
|
|
# 12.2. The single repository pattern
|
|
|
|
Before introducing the "inputs" pattern, we can start talking about another pattern first which I'd like to call
|
|
"single repository" pattern.
|
|
|
|
Systems like Debian scatter packages in several small repositories.
|
|
This can make it hard to track interdependent changes and to contribute to new packages.
|
|
|
|
Alternatively, systems like Gentoo put package descriptions all in a single repository.
|
|
|
|
The nix reference for packages is nixpkgs,
|
|
https://github.com/NixOS/nixpkgs
|
|
a single repository of all descriptions of all packages.
|
|
I find this approach very natural and attractive for new contributions.
|
|
|
|
For the rest of this chapter, we will adopt the single repository technique.
|
|
The natural implementation in Nix is to create a top-level Nix expression, and one expression for each package.
|
|
The top-level expression imports and combines all expressions in a giant attribute set with
|
|
name -> package
|
|
pairs.
|
|
|
|
But isn't that heavy?
|
|
It isn't, because Nix is a lazy language, it evaluates only what's needed!
|
|
And that's why nixpkgs is able to maintain such a big software repository in a giant attribute set.
|
|
|
|
|
|
# 12.3. Packaging graphviz
|
|
|
|
We have packaged GNU hello world, imagine you would like to package something else for creating at least a repository of two projects :-).
|
|
I chose graphviz, which uses the standard autotools build system, requires no patching and dependencies are optional.
|
|
|
|
Download graphviz from here. The graphviz.nix expression is straightforward:
|
|
|
|
let
|
|
pkgs = import <nixpkgs> {};
|
|
mkDerivation = import ./autotools.nix pkgs;
|
|
in mkDerivation {
|
|
name = "graphviz";
|
|
src = ./graphviz-2.38.0.tar.gz;
|
|
}
|
|
|
|
Build with nix-build graphviz.nix and you will get runnable binaries under result/bin.
|
|
Notice how we reused the same autotools.nix of hello.nix. Let's create a simple png:
|
|
|
|
$ echo 'graph test { a -- b }' | result/bin/dot -Tpng -o test.png
|
|
Format: "png" not recognized. Use one of: canon cmap [...]
|
|
|
|
Oh of course... graphviz doesn't know about png.
|
|
It built only the output formats it supports natively, without using any extra library.
|
|
|
|
Remember, in autotools.nix there's a buildInputs variable which gets concatenated to baseInputs.
|
|
That would be the perfect place to add a build dependency.
|
|
We created that variable exactly for this reason to be overridable from package expressions.
|
|
|
|
This 2.38 version of graphviz has several plugins to output png.
|
|
For simplicity, we will use libgd.
|
|
|
|
|
|
# 12.4. Digression about gcc and ld wrappers
|
|
|
|
The gd, jpeg, fontconfig and bzip2 libraries (dependencies of gd) don't use pkg-config to specify which flags to pass to the compiler.
|
|
Since there's no global location for libraries, we need to tell gcc and ld where to find includes and libs.
|
|
|
|
The nixpkgs provides gcc and binutils, which we are currently using for our packaging.
|
|
It also provides wrappers for them which allow passing extra arguments to gcc and ld, bypassing the project build systems:
|
|
* NIX_CFLAGS_COMPILE: extra flags to gcc at compile time
|
|
* NIX_LDFLAGS: extra flags to ld
|
|
|
|
What can we do about it?
|
|
We can employ the same trick we did for PATH: automatically filling the variables from buildInputs.
|
|
This is the relevant snippet of setup.sh:
|
|
|
|
for p in $baseInputs $buildInputs; do
|
|
if [ -d $p/bin ]; then
|
|
export PATH="$p/bin${PATH:+:}$PATH"
|
|
fi
|
|
if [ -d $p/include ]; then
|
|
export NIX_CFLAGS_COMPILE="-I $p/include${NIX_CFLAGS_COMPILE:+ }$NIX_CFLAGS_COMPILE"
|
|
fi
|
|
if [ -d $p/lib ]; then
|
|
export NIX_LDFLAGS="-rpath $p/lib -L $p/lib${NIX_LDFLAGS:+ }$NIX_LDFLAGS"
|
|
fi
|
|
done
|
|
|
|
Now adding derivations to buildInputs will add their lib, include and bin paths automatically in setup.sh.
|
|
|
|
The [-rpath] flag in ld is needed because at runtime, the executable must use exactly that version of the library.
|
|
|
|
If unneeded paths are specified, the fixup phase will shrink the rpath for us!
|
|
|
|
|
|
# 12.5. Completing graphviz with gd
|
|
|
|
Finish the expression for graphviz with gd support (note the use of the with expression in buildInputs to avoid repeating pkgs):
|
|
|
|
let
|
|
pkgs = import <nixpkgs> {};
|
|
mkDerivation = import ./autotools.nix pkgs;
|
|
in mkDerivation {
|
|
name = "graphviz";
|
|
src = ./graphviz-2.38.0.tar.gz;
|
|
buildInputs = with pkgs; [ gd fontconfig libjpeg bzip2 ];
|
|
}
|
|
|
|
Now you can create the png! Ignore any error from fontconfig, especially if you are in a chroot.
|
|
|
|
|
|
# 12.6. The repository expression
|
|
|
|
Now that we have two packages, what's a good way to put them together in a single repository?
|
|
We'll do something like nixpkgs does.
|
|
With nixpkgs, we import it and then we pick derivations by accessing the giant attribute set.
|
|
|
|
For us nixers, this is a good technique, because it abstracts from the file names.
|
|
We don't refer to a package by REPO/some/sub/dir/package.nix but by importedRepo.package (or pkgs.package in our examples).
|
|
|
|
Create a default.nix in the current directory:
|
|
|
|
{
|
|
hello = import ./hello.nix;
|
|
graphviz = import ./graphviz.nix;
|
|
}
|
|
|
|
Ready to use! Try it with nix repl:
|
|
|
|
$ nix repl
|
|
nix-repl> :l default.nix
|
|
Added 2 variables.
|
|
nix-repl> hello
|
|
"derivation /nix/store/dkib02g54fpdqgpskswgp6m7bd7mgx89-hello.drv"
|
|
nix-repl> graphviz
|
|
"derivation /nix/store/zqv520v9mk13is0w980c91z7q1vkhhil-graphviz.drv"
|
|
|
|
With nix-build:
|
|
|
|
$ nix-build default.nix -A hello
|
|
[...]
|
|
$ result/bin/hello
|
|
Hello, world!
|
|
|
|
The [-A] argument is used to access an attribute of the set from the given .nix expression.
|
|
|
|
Important: why did we choose the default.nix?
|
|
Because when a directory (by default the current directory) has a default.nix, that default.nix will be used
|
|
(see import here: https://nixos.org/manual/nix/stable/#ssec-builtins).
|
|
In fact you can run nix-build -A hello without specifying default.nix.
|
|
|
|
For pythoners, it is similar to __init__.py.
|
|
|
|
With nix-env, install the package into your user environment:
|
|
|
|
$ nix-env -f . -iA graphviz
|
|
[...]
|
|
$ dot -V
|
|
|
|
The [-f] option is used to specify the expression to use, in this case the current directory, therefore ./default.nix.
|
|
|
|
The [-i] stands for installation.
|
|
|
|
The [-A] is the same as above for nix-build.
|
|
|
|
We reproduced the very basic behavior of nixpkgs.
|
|
|
|
|
|
# 12.7. The inputs pattern
|
|
|
|
After a long preparation, we finally arrived.
|
|
I know you're having a big doubt in this moment.
|
|
It's about the hello.nix and graphviz.nix. They are very, very dependent on nixpkgs:
|
|
* First big problem: they import nixpkgs directly. In autotools.nix instead we pass nixpkgs as an argument. That's a much better approach.
|
|
* Second problem: what if we want a variant of graphviz without libgd support?
|
|
* Third problem: what if we want to test graphviz with a particular libgd version?
|
|
|
|
The current answers to the above questions are:
|
|
change the expression to match your needs
|
|
(or change the callee to match your needs).
|
|
|
|
With the inputs pattern, we decided to provide another answer:
|
|
let the user change the inputs of the expression (or change the caller to pass different inputs).
|
|
|
|
By inputs of an expression, we refer to the set of derivations needed to build that expression. In this case:
|
|
* mkDerivation from autotools. Recall that mkDerivation has an implicit dependency on the toolchain.
|
|
* libgd and its dependencies.
|
|
|
|
The src is also an input but it's pointless to change the source from the caller.
|
|
For version bumps, in nixpkgs we prefer to write another expression
|
|
(e.g. because patches are needed or different inputs are needed).
|
|
|
|
Goal: make package expressions independent of the repository.
|
|
|
|
How do we achieve that?
|
|
The answer is simple: use functions to declare inputs for a derivation.
|
|
Doing it for graphviz.nix, will make the derivation independent of the repository and customizable:
|
|
|
|
{ mkDerivation, gdSupport ? true, gd, fontconfig, libjpeg, bzip2 }:
|
|
|
|
mkDerivation {
|
|
name = "graphviz";
|
|
src = ./graphviz-2.38.0.tar.gz;
|
|
buildInputs = if gdSupport then [ gd fontconfig libjpeg bzip2 ] else [];
|
|
}
|
|
|
|
I recall that "{...}: ..." is the syntax for defining functions accepting an attribute set as argument.
|
|
|
|
We made gd and its dependencies optional.
|
|
If gdSupport is true (by default), we will fill buildInputs and thus graphviz will be built with gd support, otherwise it won't.
|
|
|
|
Now back to default.nix:
|
|
|
|
let
|
|
pkgs = import <nixpkgs> {};
|
|
mkDerivation = import ./autotools.nix pkgs;
|
|
in with pkgs; {
|
|
hello = import ./hello.nix { inherit mkDerivation; };
|
|
graphviz = import ./graphviz.nix { inherit mkDerivation gd fontconfig libjpeg bzip2; };
|
|
graphvizCore = import ./graphviz.nix {
|
|
inherit mkDerivation gd fontconfig libjpeg bzip2;
|
|
gdSupport = false;
|
|
};
|
|
}
|
|
|
|
So we factorized the import of nixpkgs and mkDerivation, and also added a variant of graphviz with gd support disabled.
|
|
The result is that both hello.nix (exercise for the reader) and graphviz.nix are independent of the repository and customizable by passing specific inputs.
|
|
|
|
If you wanted to build graphviz with a specific version of gd, it would suffice to pass gd = ...;.
|
|
|
|
If you wanted to change the toolchain, you may pass a different mkDerivation function.
|
|
|
|
Clearing up the syntax:
|
|
* In the end we return an attribute set from default.nix. With "let" we define some local variables.
|
|
* We bring pkgs into the scope when defining the packages set, which is very convenient instead of typing everytime "pkgs".
|
|
* We import hello.nix and graphviz.nix, which will return a function, and call it with a set of inputs to get back the derivation.
|
|
* The "inherit x" syntax is equivalent to "x = x". So "inherit gd" here, combined to the above "with pkgs;" is equivalent to "gd = pkgs.gd".
|
|
|
|
You can find the whole repository at the pill 12 gist.
|
|
|
|
|
|
# 12.8. Conclusion
|
|
|
|
The "inputs" pattern allows our expressions to be easily customizable through a set of arguments.
|
|
These arguments could be flags, derivations, or whatever else.
|
|
Our package expressions are functions, don't think there's any magic in there.
|
|
|
|
It also makes the expressions independent of the repository.
|
|
Given that all the needed information is passed through arguments, it is possible to use that expression in any other context.
|