зеркало из
				https://github.com/iharh/notes.git
				synced 2025-11-04 15:46:08 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			160 строки
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			160 строки
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
https://nixos.org/guides/nix-pills/callpackage-design-pattern.html
 | 
						|
 | 
						|
The next design pattern worth noting is what I'd like to call the callPackage pattern.
 | 
						|
This technique is extensively used in nixpkgs, it's the current standard for importing packages in a repository.
 | 
						|
 | 
						|
 | 
						|
# 13.1. The callPackage convenience
 | 
						|
 | 
						|
In the previous pill, we underlined the fact that the inputs pattern is great to decouple packages from the repository,
 | 
						|
in that we can pass manually the inputs to the derivation.
 | 
						|
The derivation declares its inputs, and the caller passes the arguments.
 | 
						|
 | 
						|
However as with usual programming languages, we declare parameter names, and then we have to pass arguments.
 | 
						|
We do the job twice.
 | 
						|
With package management, we often see common patterns.
 | 
						|
In the case of nixpkgs it's the following.
 | 
						|
 | 
						|
Some package derivation:
 | 
						|
 | 
						|
{ input1, input2, ... }:
 | 
						|
...
 | 
						|
 | 
						|
Repository derivation:
 | 
						|
 | 
						|
rec {
 | 
						|
    lib1 = import package1.nix { inherit input1 input2 ...; };
 | 
						|
    program2 = import package1.nix { inherit inputX inputY lib1 ...; };
 | 
						|
}
 | 
						|
 | 
						|
Where inputs may even be packages in the repository itself (note the rec keyword).
 | 
						|
The pattern here is clear, often inputs have the same name of the attributes in the repository itself.
 | 
						|
Our desire is to pass those inputs from the repository automatically, and in case be able to specify a particular argument
 | 
						|
(that is, override the automatically passed default argument).
 | 
						|
 | 
						|
To achieve this, we will define a callPackage function with the following synopsis:
 | 
						|
 | 
						|
{
 | 
						|
    lib1 = callPackage package1.nix { };
 | 
						|
    program2 = callPackage package2.nix { someoverride = overriddenDerivation; };
 | 
						|
}
 | 
						|
 | 
						|
What should it do?
 | 
						|
    * Import the given expression, which in turn returns a function.
 | 
						|
    * Determine the name of its arguments.
 | 
						|
    * Pass default arguments from the repository set, and let us override those arguments.
 | 
						|
 | 
						|
 | 
						|
# 13.2. Implementing callPackage
 | 
						|
 | 
						|
First of all, we need a way to introspect (reflection or whatever) at runtime the argument names of a function.
 | 
						|
That's because we want to automatically pass such arguments.
 | 
						|
 | 
						|
Then callPackage requires access to the whole packages set, because it needs to find the packages to pass automatically.
 | 
						|
 | 
						|
We start off simple with :
 | 
						|
 | 
						|
nix-repl> add = { a ? 3, b }: a+b
 | 
						|
nix-repl> builtins.functionArgs add
 | 
						|
    { a = true; b = false; }
 | 
						|
 | 
						|
Nix provides a builtin function to introspect the names of the arguments of a function.
 | 
						|
In addition, for each argument, it tells whether the argument has a default value or not.
 | 
						|
We don't really care about default values in our case.
 | 
						|
We are only interested in the argument names.
 | 
						|
 | 
						|
Now we need a set with all the values, let's call it values.
 | 
						|
And a way to intersect the attributes of values with the function arguments:
 | 
						|
 | 
						|
nix-repl> values = { a = 3; b = 5; c = 10; }
 | 
						|
nix-repl> builtins.intersectAttrs values (builtins.functionArgs add)
 | 
						|
    { a = true; b = false; }
 | 
						|
nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values
 | 
						|
    { a = 3; b = 5; }
 | 
						|
 | 
						|
Perfect, note from the example above that the intersectAttrs returns a set whose names are the intersection,
 | 
						|
and the attribute values are taken from the second set.
 | 
						|
 | 
						|
We're done, we have a way to get argument names from a function, and match with an existing set of attributes.
 | 
						|
This is our simple implementation of callPackage:
 | 
						|
 | 
						|
nix-repl> callPackage = set: f: f (builtins.intersectAttrs (builtins.functionArgs f) set)
 | 
						|
nix-repl> callPackage values add
 | 
						|
    8
 | 
						|
nix-repl> with values; add { inherit a b; }
 | 
						|
    8
 | 
						|
 | 
						|
Clearing up the syntax:
 | 
						|
    * We define a callPackage variable which is a function.
 | 
						|
    * The second parameter is the function to "autocall".
 | 
						|
    * We take the argument names of the function and intersect with the set of all values.
 | 
						|
    * Finally we call the passed function f with the resulting intersection.
 | 
						|
 | 
						|
In the code above, I've also shown that the callPackage call is equivalent to directly calling add a b.
 | 
						|
 | 
						|
We achieved what we wanted.
 | 
						|
Automatically call functions given a set of possible arguments.
 | 
						|
If an argument is not found in the set, that's nothing special.
 | 
						|
It's a function call with a missing parameter, and that's an error (unless the function has varargs ... as explained in the 5th pill).
 | 
						|
 | 
						|
Or not. We missed something. Being able to override some of the parameters.
 | 
						|
We may not want to always call functions with values taken from the big set.
 | 
						|
Then we add a further parameter, which takes a set of overrides:
 | 
						|
 | 
						|
nix-repl> callPackage = set: f: overrides: f ((builtins.intersectAttrs (builtins.functionArgs f) set) // overrides)
 | 
						|
nix-repl> callPackage values add { }
 | 
						|
8
 | 
						|
nix-repl> callPackage values add { b = 12; }
 | 
						|
15
 | 
						|
 | 
						|
Apart from the increasing number of parenthesis, it should be clear that we simply do a set union between the default arguments, and the overriding set.
 | 
						|
 | 
						|
 | 
						|
# 13.3. Use callPackage to simplify the repository
 | 
						|
 | 
						|
Given our brand new tool, we can simplify the repository expression (default.nix).
 | 
						|
 | 
						|
Let me write it down first:
 | 
						|
 | 
						|
let
 | 
						|
    nixpkgs = import <nixpkgs> {};
 | 
						|
    allPkgs = nixpkgs // pkgs;
 | 
						|
    callPackage = path: overrides:
 | 
						|
        let f = import path;
 | 
						|
        in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
 | 
						|
    pkgs = with nixpkgs; {
 | 
						|
        mkDerivation = import ./autotools.nix nixpkgs;
 | 
						|
        hello = callPackage ./hello.nix { };
 | 
						|
        graphviz = callPackage ./graphviz.nix { };
 | 
						|
        graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
 | 
						|
    };
 | 
						|
in pkgs
 | 
						|
 | 
						|
Wow, there's a lot to say here:
 | 
						|
    * We renamed the old "pkgs" of the previous pill to "nixpkgs". Our package set is now instead named "pkgs". Sorry for the confusion.
 | 
						|
    *  We needed a way to pass pkgs to callPackage somehow. Instead of returning the set of packages directly from default.nix, we first assign it to a let variable and reuse it in callPackage.
 | 
						|
    *  For convenience, in callPackage we first import the file ("path"-param), instead of calling it directly. Otherwise for each package we would have to write the import.
 | 
						|
    * Since our expressions use packages from nixpkgs, in callPackage we use allPkgs, which is the union of nixpkgs and our packages.
 | 
						|
    * We moved mkDerivation into pkgs itself, so that it also gets passed automatically.
 | 
						|
 | 
						|
Note how easy is to override arguments in the case of graphviz without gd.
 | 
						|
But most importantly, how easy it was to merge two repositories: nixpkgs and our pkgs!
 | 
						|
 | 
						|
The reader should notice a magic thing happening.
 | 
						|
We're defining pkgs in terms of callPackage, and callPackage in terms of pkgs.
 | 
						|
That magic is possible thanks to lazy evaluation.
 | 
						|
 | 
						|
 | 
						|
# 13.4. Conclusion
 | 
						|
 | 
						|
The "callPackage" pattern has simplified a lot our repository.
 | 
						|
We're able to import packages that require some named arguments and call them automatically, given the set of all packages.
 | 
						|
 | 
						|
We've also introduced some useful builtin functions that allows us to introspect Nix functions and manipulate attributes.
 | 
						|
These builtin functions are not usually used when packaging software, rather to provide tools for packaging.
 | 
						|
That's why they are not documented in the nix manual.
 | 
						|
 | 
						|
Writing a repository in nix is an evolution of writing convenient functions for combining the packages.
 | 
						|
This demonstrates even more how nix is a generic tool to build and deploy something, and how suitable it is to create software repositories with your own conventions. 
 | 
						|
 |