зеркало из
				https://github.com/iharh/notes.git
				synced 2025-10-31 05: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. 
 | 
