зеркало из
				https://github.com/iharh/notes.git
				synced 2025-10-31 05:36:08 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			308 строки
		
	
	
		
			10 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			308 строки
		
	
	
		
			10 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| https://nixos.org/guides/nix-pills/basic-dependencies-and-hooks.html
 | |
| 
 | |
| This time, we'll focus on the interaction of packages built with stdenv.mkDerivation.
 | |
| Packages need to depend on each other, of course.
 | |
| For this we have buildInputs and propagatedBuildInputs attributes.
 | |
| We've also found that dependencies sometimes need to influence their dependents in ways the dependents can't or shouldn't predict.
 | |
| For this we have setup hooks and env hooks.
 | |
| Together, these 4 concepts support almost all build-time package interactions.
 | |
| 
 | |
| Note: The complexity of the dependencies and hooks infrastructure has increased, over time, to support cross compilation.
 | |
|     Once you learn the core concepts, you will be able to understand the extra complexity.
 | |
|     As a starting point, you might want to refer to nixpkgs commit 6675f0a5, the last version of stdenv without cross-compilation complexity.
 | |
| 
 | |
| 
 | |
| # 20.1. The buildInputs Attribute
 | |
| 
 | |
| For the simplest dependencies where the current package directly needs another, we use the buildInputs attribute.
 | |
| This is exactly the pattern in taught with our builder in Pill 8.
 | |
| To demo this, lets build GNU Hello, and then another package which provides a shell script that execs it.
 | |
| 
 | |
| ```
 | |
| let
 | |
|     nixpkgs = import <nixpkgs> {};
 | |
| 
 | |
|     inherit (nixpkgs) stdenv fetchurl which;
 | |
| 
 | |
|     actualHello = stdenv.mkDerivation {
 | |
|         name = "hello-2.3";
 | |
| 
 | |
|         src = fetchurl {
 | |
|             url = mirror://gnu/hello/hello-2.3.tar.bz2;
 | |
|             sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1";
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     wrappedHello = stdenv.mkDerivation {
 | |
|         name = "hello-wrapper";
 | |
| 
 | |
|         buildInputs = [ actualHello which ];
 | |
| 
 | |
|         unpackPhase = "true";
 | |
| 
 | |
|         installPhase = ''
 | |
|             mkdir -p "$out/bin"
 | |
|             echo "#! ${stdenv.shell}" >> "$out/bin/hello"
 | |
|             echo "exec $(which hello)" >> "$out/bin/hello"
 | |
|         '';
 | |
|     };
 | |
| 
 | |
| in wrappedHello
 | |
| ```
 | |
| 
 | |
| Notice that the wrappedHello derivation finds the hello binary from the PATH.
 | |
| This works because stdenv contains something like:
 | |
| 
 | |
| ```
 | |
| pkgs=""
 | |
| for i in $buildInputs; do
 | |
|     findInputs $i
 | |
| done
 | |
| ```
 | |
| 
 | |
| where findInputs is defined like:
 | |
| 
 | |
| ```
 | |
| findInputs() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     ## Don't need to repeat already processed package
 | |
|     case $pkgs in
 | |
|         *\ $pkg\ *)
 | |
|             return 0
 | |
|             ;;
 | |
|     esac
 | |
| 
 | |
|     pkgs="$pkgs $pkg "
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| }
 | |
| ```
 | |
| 
 | |
| then after this is run:
 | |
| 
 | |
| ```
 | |
| for i in $pkgs; do
 | |
|     addToEnv $i
 | |
| done
 | |
| ```
 | |
| 
 | |
| where addToEnv is defined like:
 | |
| 
 | |
| ```
 | |
| addToEnv() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     if test -d $1/bin; then
 | |
|         addToSearchPath _PATH $1/bin
 | |
|     fi
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| }
 | |
| ```
 | |
| 
 | |
| The addToSearchPath call adds $1/bin to _PATH if the former exists (code here).
 | |
|     https://github.com/NixOS/nixpkgs/blob/6675f0a52c0962042a1000c7f20e887d0d26ae25/pkgs/stdenv/generic/setup.sh#L60-L73
 | |
| Once all the packages in buildInputs have been processed, then content of _PATH is added to PATH, as follows:
 | |
| 
 | |
| ```
 | |
| PATH="${_PATH-}${_PATH:+${PATH:+:}}$PATH"
 | |
| ```
 | |
| 
 | |
| With the real hello on the PATH, the installPhase should hopefully make sense.
 | |
| 
 | |
| 
 | |
| # 20.2. The propagatedBuildInputs Attribute
 | |
| 
 | |
| The buildInputs covers direct dependencies, but what about indirect dependencies where one package needs a second package which needs a third?
 | |
| Nix itself handles this just fine, understanding various dependency closures as covered in previous builds.
 | |
| But what about the conveniences that buildInputs provides, namely accumulating in "pkgs" environment variable and inclusion of "pkg/bin" directories on the PATH?
 | |
| For this, stdenv provides the propagatedBuildInputs:
 | |
| 
 | |
| ```
 | |
| let
 | |
|     nixpkgs = import <nixpkgs> {};
 | |
| 
 | |
|     inherit (nixpkgs) stdenv fetchurl which;
 | |
| 
 | |
|     actualHello = stdenv.mkDerivation {
 | |
|         name = "hello-2.3";
 | |
| 
 | |
|         src = fetchurl {
 | |
|             url = mirror://gnu/hello/hello-2.3.tar.bz2;
 | |
|             sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1";
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     intermediary = stdenv.mkDerivation {
 | |
|         name = "middle-man";
 | |
| 
 | |
|         propagatedBuildInputs = [ actualHello ];
 | |
| 
 | |
|         unpackPhase = "true";
 | |
| 
 | |
|         installPhase = ''
 | |
|             mkdir -p "$out"
 | |
|         '';
 | |
|     };
 | |
| 
 | |
|     wrappedHello = stdenv.mkDerivation {
 | |
|         name = "hello-wrapper";
 | |
| 
 | |
|         buildInputs = [ intermediary which ];
 | |
| 
 | |
|         unpackPhase = "true";
 | |
| 
 | |
|         installPhase = ''
 | |
|             mkdir -p "$out/bin"
 | |
|             echo "#! ${stdenv.shell}" >> "$out/bin/hello"
 | |
|             echo "exec $(which hello)" >> "$out/bin/hello"
 | |
|         '';
 | |
|     };
 | |
| 
 | |
| in wrappedHello
 | |
| ```
 | |
| 
 | |
| See how the intermediate package has a propagatedBuildInputs dependency, but the wrapper only needs a buildInputs dependency on the intermediary.
 | |
| 
 | |
| How does this work?
 | |
| You might think we do something in Nix, but actually its done not at eval time but at build time in bash.
 | |
| lets look at part of the fixupPhase of stdenv:
 | |
| 
 | |
| ```
 | |
| fixupPhase() {
 | |
|     ## Elided
 | |
| 
 | |
|     if test -n "$propagatedBuildInputs"; then
 | |
|         mkdir -p "$out/nix-support"
 | |
|         echo "$propagatedBuildInputs" > "$out/nix-support/propagated-build-inputs"
 | |
|     fi
 | |
| 
 | |
|     ## Elided
 | |
| }
 | |
| ```
 | |
| 
 | |
| This dumps the propagated build inputs in a so-named file in $out/nix-support/.
 | |
| Then, back in findInputs look at the lines at the bottom we elided before:
 | |
| 
 | |
| ```
 | |
| findInputs() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| 
 | |
|     if test -f $pkg/nix-support/propagated-build-inputs; then
 | |
|         for i in $(cat $pkg/nix-support/propagated-build-inputs); do
 | |
|             findInputs $i
 | |
|         done
 | |
|     fi
 | |
| }
 | |
| ```
 | |
| 
 | |
| See how findInputs is actually recursive, looking at the propagated build inputs of each dependency, and those dependencies' propagated build inputs, etc.
 | |
| 
 | |
| We actually simplified the findInputs call site from before; propagatedBuildInputs is also looped over in reality:
 | |
| 
 | |
| ```
 | |
| pkgs=""
 | |
| for i in $buildInputs $propagatedBuildInputs; do
 | |
|     findInputs $i
 | |
| done
 | |
| ```
 | |
| 
 | |
| This demonstrates an important point.
 | |
| For the current package alone, it doesn't matter whether a dependency is propagated or not.
 | |
| It will be processed the same way: called with findInputs and addToEnv.
 | |
| (The packages discovered by findInputs, which are also accumulated in pkgs and passed to addToEnv, are also the same in both cases.)
 | |
| Downstream however, it certainly does matter because only the propagated immediate dependencies are put in the $out/nix-support/propagated-build-inputs.
 | |
| 
 | |
| 
 | |
| # 20.3. Setup Hooks
 | |
| 
 | |
| As we mentioned above, sometimes dependencies need to influence the packages that use them in ways other than just being a dependency. [1]
 | |
|     [1] We can now be precise and consider what addToEnv does alone the minimal treatment of a dependency:
 | |
|         i.e. a package that is just a dependency would only have addToEnv applied to it. 
 | |
| 
 | |
| propagatedBuildInputs can actually be seen as an example of this:
 | |
|     packages using that are effectively "injecting" those dependencies as extra buildInputs in their downstream dependents.
 | |
| But in general, a dependency might affect the packages it depends on in arbitrary ways.
 | |
| Arbitrary is the key word here.
 | |
| We could teach setup.sh things about upstream packages like "pkg/nix-support/propagated-build-inputs", but not arbitrary interactions.
 | |
| 
 | |
| Setup hooks are the basic building block we have for this.
 | |
| In nixpkgs, a "hook" is basically a bash callback, and a setup hook is no exception.
 | |
| Let's look at the last part of findInputs we haven't covered:
 | |
| 
 | |
| ```
 | |
| findInputs() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| 
 | |
|     if test -f $pkg/nix-support/setup-hook; then
 | |
|         source $pkg/nix-support/setup-hook
 | |
|     fi
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| }
 | |
| ```
 | |
| 
 | |
| If a package includes the path pkg/nix-support/setup-hook, it will be sourced by any stdenv-based build including that as a dependency.
 | |
| 
 | |
| This is strictly more general than any of the other mechanisms introduced in this chapter.
 | |
| For example, try writing a setup hook that has the same effect as a propagatedBuildInputs entry.
 | |
| One can almost think of this as an escape hatch around Nix's normal isolation guarantees, and the principle that dependencies are immutable and inert.
 | |
| We're not actually doing something unsafe or modifying dependencies, but we are allowing arbitrary ad-hoc behavior.
 | |
| For this reason, setup-hooks should only be used as a last resort.
 | |
| 
 | |
| 
 | |
| # 20.4. Environment Hooks
 | |
| 
 | |
| As a final convenience, we have environment hooks.
 | |
| Recall in Pill 12 how we created NIX_CFLAGS_COMPILE for -I flags and NIX_LDFLAGS for -L flags, in a similar manner to how we prepared the PATH.
 | |
| One point of ugliness was how anti-modular this was.
 | |
| It makes sense to build the PATH in generic builder, because the PATH is used by the shell, and the generic builder is intrinsically tied to the shell.
 | |
| But -I and -L flags are only relevant to the C compiler.
 | |
| The stdenv isn't wedded to including a C compiler (though it does by default), and there are other compilers too which may take completely different flags.
 | |
| 
 | |
| As a first step, we can move that logic to a setup hook on the C compiler;
 | |
| indeed that's just what we do in CC Wrapper. [2]
 | |
|     [2] It was called GCC Wrapper in the version of nixpkgs suggested for following along in this pill;
 | |
|         https://github.com/NixOS/nixpkgs/tree/6675f0a52c0962042a1000c7f20e887d0d26ae25/pkgs/build-support/gcc-wrapper
 | |
|         note: this is no longer available in this form, most probably
 | |
|             https://github.com/NixOS/nixpkgs/tree/master/pkgs/build-support/cc-wrapper
 | |
|     Darwin and Clang support hadn't yet motivated the rename. 
 | |
| 
 | |
| But this pattern comes up fairly often, so somebody decided to add some helper support to reduce boilerplate.
 | |
| 
 | |
| The other half of addToEnv is:
 | |
| 
 | |
| ```
 | |
| addToEnv() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     ## More goes here in reality that we can ignore for now.
 | |
| 
 | |
|     # Run the package-specific hooks set by the setup-hook scripts.
 | |
|     for i in "${envHooks[@]}"; do
 | |
|         $i $pkg
 | |
|     done
 | |
| }
 | |
| ```
 | |
| 
 | |
| Functions listed in envHooks are applied to every package passed to addToEnv.
 | |
| One can write a setup hook like:
 | |
| 
 | |
| ```
 | |
| anEnvHook() {
 | |
|     local pkg=$1
 | |
| 
 | |
|     echo "I'm depending on \"$pkg\""
 | |
| }
 | |
| 
 | |
| envHooks+=(anEnvHook)
 | |
| ```
 | |
| 
 | |
| and if one dependency has that setup hook then all of them will be so echoed.
 | |
| Allowing dependencies to learn about their sibling dependencies is exactly what compilers need. 
 | 
