зеркало из
				https://github.com/iharh/notes.git
				synced 2025-11-04 07:36: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. 
 |