mirror of
https://github.com/NixOS/nix-pills
synced 2024-09-19 04:00:13 -04:00
196 lines
9 KiB
XML
196 lines
9 KiB
XML
<chapter xmlns="http://docbook.org/ns/docbook"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
|
version="5.0"
|
|
xml:id="override-design-pattern">
|
|
|
|
<title>Override Design Pattern</title>
|
|
<para>
|
|
Welcome to the 14th Nix pill. In the previous <link linkend="callpackage-design-pattern">13th</link> pill, we introduced the
|
|
<literal>callPackage</literal> pattern and used it to simplify the composition
|
|
of software in a repository.
|
|
</para>
|
|
<para>
|
|
The next design pattern is less necessary, but is useful in many cases and is
|
|
a good exercise to learn more about Nix.
|
|
</para>
|
|
<section>
|
|
<title>About composability</title>
|
|
<para>
|
|
Functional languages are known for being able to compose functions. In particular,
|
|
these languages gain expressivity from functions that manipulate an original
|
|
value into a new value having the same structure. This allows us to compose
|
|
multiple functions to perform the desired modifications.
|
|
</para>
|
|
<para>
|
|
In Nix, we mostly talk about <emphasis role="bold">functions</emphasis>
|
|
that accept inputs in order to return <emphasis role="bold">derivations</emphasis>.
|
|
In our world, we want utility functions that are able to manipulate those structures.
|
|
These utilities add some useful properties to the original value, and we'd like to be
|
|
able to apply more utilities on top of the result.
|
|
</para>
|
|
<para>
|
|
For example, let's say we have an initial derivation <literal>drv</literal> and
|
|
we want to transform it into a <literal>drv</literal> with debugging information and
|
|
custom patches:
|
|
</para>
|
|
<screen>debugVersion (applyPatches [ ./patch1.patch ./patch2.patch ] drv)</screen>
|
|
<para>
|
|
The final result should be the original derivation with some changes.
|
|
This is both interesting and very different from other packaging approaches,
|
|
which is a consequence of using a functional language to describe packages.
|
|
</para>
|
|
<para>
|
|
Designing such utilities is not trivial in a functional language without static
|
|
typing, because understanding what can or cannot be composed is difficult.
|
|
But we try to do our best.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>The override pattern</title>
|
|
<para>
|
|
In <link linkend="inputs-design-pattern">pill 12</link> we introduced the inputs
|
|
design pattern. We do not return a derivation picking dependencies directly from the
|
|
repository; rather we declare the inputs and let the callers pass the necessary
|
|
arguments.
|
|
</para>
|
|
<para>
|
|
In our repository we have a set of attributes that import the expressions of the
|
|
packages and pass these arguments, getting back a derivation. Let's take for example
|
|
the <package>graphviz</package> attribute:
|
|
</para>
|
|
<screen>graphviz = import ./graphviz.nix { inherit mkDerivation gd fontconfig libjpeg bzip2; };</screen>
|
|
<para>
|
|
If we wanted to produce a derivation of <package>graphviz</package> with a customized
|
|
<package>gd</package> version, we would have to repeat most of the above plus
|
|
specifying an alternative <package>gd</package>:
|
|
</para>
|
|
<screen><xi:include href="./14/mygraphviz.txt" parse="text" /></screen>
|
|
<para>
|
|
That's hard to maintain. Using <code>callPackage</code> would be easier:
|
|
</para>
|
|
<screen>mygraphviz = callPackage ./graphviz.nix { gd = customgd; };</screen>
|
|
<para>
|
|
But we may still be diverging from the original <package>graphviz</package> in the repository.
|
|
</para>
|
|
<para>
|
|
We would like to avoid specifying the nix expression again. Instead, we would
|
|
like to reuse the original <package>graphviz</package> attribute in the
|
|
repository and add our overrides like so:
|
|
</para>
|
|
<screen>mygraphviz = graphviz.override { gd = customgd; };</screen>
|
|
<para>
|
|
The difference is obvious, as well as the advantages of this approach.
|
|
</para>
|
|
<para>
|
|
<emphasis role="underline">Note:</emphasis> that <literal>.override</literal> is
|
|
not a "method" in the OO sense as you may think. Nix is a functional language.
|
|
The<literal>.override</literal> is simply an attribute of a set.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>The override implementation</title>
|
|
<para>
|
|
Recall that the <package>graphviz</package> attribute in the repository is
|
|
the derivation returned by the function imported from
|
|
<filename>graphviz.nix</filename>. We would like to add a further attribute
|
|
named "<literal>override</literal>" to the returned set.
|
|
</para>
|
|
<para>
|
|
Let's start by first creating a function "<literal>makeOverridable</literal>".
|
|
This function will take two arguments: a function (that must return a set)
|
|
and the set of original arguments to be passed to the function.
|
|
</para>
|
|
<para>
|
|
We will put this function in a <filename>lib.nix</filename>:
|
|
</para>
|
|
<screen><xi:include href="./14/make-overridable-lib.txt" parse="text" /></screen>
|
|
<para>
|
|
<literal>makeOverridable</literal> takes a function and a set of original arguments.
|
|
It returns the original returned set, plus a new <literal>override</literal> attribute.
|
|
</para>
|
|
<para>
|
|
This <literal>override</literal> attribute is a function taking a set of new
|
|
arguments, and returns the result of the original function called with the
|
|
original arguments unified with the new arguments. This is admittedly somewhat
|
|
confusing, but the examples below should make it clear.
|
|
</para>
|
|
<para>
|
|
Let's try it with <literal>nix repl</literal>:
|
|
</para>
|
|
<screen><xi:include href="./14/nix-repl-make-overridable-test.txt" parse="text" /></screen>
|
|
<para>
|
|
Note that, as we specified above, the function <literal>f</literal> does not return
|
|
the plain sum. Instead, it returns a set with the sum bound to the name
|
|
<literal>result</literal>.
|
|
</para>
|
|
<para>
|
|
The variable <literal>res</literal> contains the result of the function call without
|
|
any override. It's easy to see in the definition of <literal>makeOverridable</literal>.
|
|
In addition, you can see that the new <literal>override</literal> attribute is a function.
|
|
</para>
|
|
<para>
|
|
Calling <literal>res.override</literal> with a set will invoke the original function
|
|
with the overrides, as expected.
|
|
</para>
|
|
<para>
|
|
This is a good start, but we can't override again! This is because the returned
|
|
set (with <literal>result = 15</literal>) does not have an <literal>override</literal>
|
|
attribute of its own. This is bad; it breaks further composition.
|
|
</para>
|
|
<para>
|
|
The solution is simple: the <literal>.override</literal> function should make the
|
|
result overridable again:
|
|
</para>
|
|
<screen><xi:include href="./14/rec-make-overridable.txt" parse="text" /></screen>
|
|
<para>
|
|
Please note the <literal>rec</literal> keyword. It's necessary so that we can refer
|
|
to <literal>makeOverridable</literal> from <literal>makeOverridable</literal> itself.
|
|
</para>
|
|
<para>
|
|
Now let's try overriding twice:
|
|
</para>
|
|
<screen><xi:include href="./14/nix-repl-make-overridable-twice.txt" parse="text" /></screen>
|
|
<para>
|
|
Success! The result is 30 (as expected) because <literal>a</literal> is overridden
|
|
to 10 in the first override, and <literal>b</literal> is overridden to 20 in the
|
|
second.
|
|
</para>
|
|
<para>
|
|
Now it would be nice if <literal>callPackage</literal> made our
|
|
derivations overridable. This is an exercise for the reader.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Conclusion</title>
|
|
<para>
|
|
The "<literal>override</literal>" pattern simplifies the way we customize packages
|
|
starting from an existing set of packages. This opens a world of possibilities for
|
|
using a central repository like <literal>nixpkgs</literal> and defining overrides
|
|
on our local machine without modifying the original package.
|
|
</para>
|
|
<para>
|
|
We can dream of a custom, isolated <command>nix-shell</command> environment for
|
|
testing <package>graphviz</package> with a custom <package>gd</package>:
|
|
</para>
|
|
<screen>debugVersion (graphviz.override { gd = customgd; })</screen>
|
|
<para>
|
|
Once a new version of the overridden package comes out in the repository, the
|
|
customized package will make use of it automatically.
|
|
</para>
|
|
<para>
|
|
The key in Nix is to find powerful yet simple abstractions in order to let the user
|
|
customize their environment with highest consistency and lowest maintenance time, by
|
|
using predefined composable components.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Next pill</title>
|
|
<para>
|
|
In the next pill, we will talk about Nix search paths. By "search path", we mean a
|
|
place in the file system where Nix looks for expressions. This answers the
|
|
question of where <literal><nixpkgs></literal> comes from.
|
|
</para>
|
|
</section>
|
|
</chapter>
|