## title: My Nix exploration
       ## date: "2024-06-24"
       
       /nix_logo.svg
 (IMG) /nix_logo.svg
       
       Here I share some notes and other things I've learned about
       Nix that I find interesting. The content of this post is
       mainly about me learning Nix, it's not about understanding
       the whole tool and language.
       
       Also, it's important to note that I use Nix as a non-NixOS
       user.
       
       ## What is Nix?
       
       Nix is actually several things! It's a cross platform
       package manager. It would be a little more accurate to say
       that it's a deployment tool used as a package manager.
       
       And it's also a purely functional programming language,
       dynamically typed and lazily evaluated.
       
       ## Learning the programming language
       
       I started by learning the basics of the language and then
       went on to explore it in a bit more depth.
       
       ### The basics
       
       I read Nix language basics and to get used to the language I
       practised with A tour of Nix which has several levels of
       difficulty from "easy" to "hard".
 (HTM) Nix language basics
 (HTM) A tour of Nix
       
       One interesting thing about this language is that it has
       only one argument per function. To simulate several
       arguments, you can, for example, write a function with one
       argument that returns a function with one argument that
       returns a function with one argument, and so on. The syntax
       of the language makes it easy to do this.
       
       I was taught that it has a name, it's called Currying. It's
       the transformation of a function with several arguments into
       a function with one argument that returns a function on the
       rest of the arguments. Here's an example with arguments 3
       and 4.
 (HTM) Currying
       
       nix-repl> (a: b: a + b) 3 4
       7
       
       A Python equivalent might be something like the following.
       
       >>> (lambda a: lambda b: a + b)(3)(4)
       7
       
       Another solution that is often used, particularly in
       Nixpkgs, is to have an attribute set as a parameter to the
       function, and to use the attributes as arguments. For
       example, this might look like the expression below.
 (HTM) Nixpkgs
       
       nix-repl> ({a, b}: a + b){a = 3; b = 4;}
       7
       
       ### Fake dynamic binding
       
       Although the blog post How to Fake Dynamic Binding in Nix
       talks about this very well, I find it interesting to offer
       my own thoughts and approach.
 (HTM) How to Fake Dynamic Binding in Nix
       
       The language is statically scoped, i.e. binding decisions
       are made according to the scope at declaration time.
       
       Let's look at the rec keyword, which allows an attribute set
       to access its own attributes (recursive binding). Here's an
       example.
       
       nix-repl> rec { a = 1; b = a + 1;}
       {
         a = 1;
         b = 2;
       }
       
       This is an interesting feature, but it remains static
       because the binding is done before the runtime. This poses
       problems, particularly when it comes to overriding
       attributes, as shown in the example below.
       
       nix-repl> rec { a = 1; b = a + 1; } // { a = 10; }
       {
         a = 10;
         b = 2;
       }
       
       In this example, we would like b to be equal to 11, not 2.
       
       To solve this problem, we can look at the concept of a fixed
       point. A fixed point is a value of x that validates the
       equation x = f(x).
       
       We can therefore write the following function.
       
       nix-repl> fix = f: let
         result = f result;
       in
         result
       
       So here we have the function fix which takes a function f as
       a parameter and returns the fixed point result of the
       function f.
       
       You might be tempted to say that the f function calls itself
       ad infinitum (f(f(f(f(..))))), but Nix evaluates expressions
       lazily, so this isn't the case.
       
       We can literally see that the f function returns a fixed
       point (result), because result = f result, which respects
       the definition of a fixed point.
       
       The fix function will allow us to emulate the rec keyword,
       as shown in the example below.
       nix-repl> fix (self: { a = 3; b = 4; c = self.a + self.b; })
       {
         a = 3;
         b = 4;
         c = 7;
       }
       
       To better understand how it works, I've written the result
       of the fix function differently with the argument used
       previously.
       
       nix-repl> let
         result = { a = 3; b = 4; c = result.a + result.b;};
       in
         { a = 3; b = 4; c = result.a + result.b;}
       {
         a = 3;
         b = 4;
         c = 7;
       }
       
       Finally, I've written the following function, which will
       allow the attributes to be overridden dynamically as
       initially intended.
       
       nix-repl> fix = let
         fixWithOverride = f: overrides: let
             result = (f result) // overrides;
           in
             result // { override = x: fixWithOverride f x; };
       in
       f: fixWithOverride f {}
       
       attrFunction = self: { a = 3; b = 4; c = self.a+self.b; }
       
       attrFunctionFixedPoint = fix attrFunction
       
       nix-repl> attrFunctionFixedPoint
       {
         a = 3;
         b = 4;
         c = 7;
         override = «lambda override @ «string»:5:30»;
       }
       
       nix-repl> attrFunctionFixedPoint.override { b = 1; }
       {
         a = 3;
         b = 1;
         c = 4;
         override = «lambda override @ «string»:5:30»;
       }
       
       ## The essential Nix tool
       
       As already mentioned, the main use of Nix is cross platform
       package management. In this section I'm just trying to share
       and summarise some of the essential parts of my notes. If
       you want more details, I recommend you read the excellent
       Nix Pills. It's rather long but well worth the read!
 (HTM) Nix Pills
       
       ### How does it work ?
       
       To sum up, I'd say that the Nix language has a very
       interesting native function called derivation (see
       documentation) on which many Nix expressions are based. I'm
       not going to redefine the term because the documentation has
       a very comprehensible version, but the important thing to
       remember is that a derivation is a construction
       specification, it's an immutable Nix building block. With
       another package manager, you could see it as a literal
       package.
 (HTM) see documentation
       
       Nix technology will enable us to build these derivations, in
       the following stages.
       
       /nix_graph.png
 (IMG) /nix_graph.png
       
       The .drv files contain specifications on how to build the
       derivation, they are intermediate files comparable to .o
       files, and the .nix files are comparable to .c files.
       
       The construction result is immutable and will be stored in
       /nix/store/, a synchronisation with the SQLite database. I
       said it was immutable, in fact it is because Nix creates a
       hash for the path in the /nix/store/ from the input
       derivation (not from the construction result).
 (HTM) SQLite
       
       It's pretty hard to imagine all this, so I'll give you a
       concrete example. Let's imagine I want to create a
       derivation for the famous software GNU Hello. The Nix
       derivation could look something like this.
 (HTM) GNU Hello
       
       # default.nix
       
       let
         pkgs = import <nixpkgs> { };
       in
         {
           hello = pkgs.stdenv.mkDerivation {
             pname = "hello";
             version = "2.12.1";
       
             src = fetchTarball {
               url = "https://ftp.gnu.org/gnu/hello/hello-
       2.12.1.tar.gz";
               sha256 =
       "1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=";
             };
           };
         }
       
       “The mkDerivation function is based on the derivation
       builtin function.”
       
       It can be built with the following command.
       
       nix-build
       
       The build result has been created in
       /nix/store/x9cc4jsylk5q01iaxmxf941b59chws5h-hello-2.12.1 and a
       symbolic link named result pointing to this folder has been
       created in the current folder. We can then find the binary
       in ./result/bin/hello.
       
       Before the build, a .drv file was created, which can be
       found by running the following command.
       nix derivation show ./result | jq "keys[0]"
       
       The full path to the .drv file is found in the first key of
       the JSON object, so the path to the .drv file is
       /nix/store/dp5z62k3chf019biikg77p2acmz17phx-hello-2.12.1.drv.
       
       As it is in binary format we can use nix derivation show to
       display the construction information it contains with the
       following command.
       
       nix derivation show (nix derivation show ./result | jq
       "keys[0]" | tr -d "\"")
       # Or
       nix derivation show
       /nix/store/dp5z62k3chf019biikg77p2acmz17phx-hello-2.12.1.drv
       # ^
       # | Same output
       # v
       nix derivation show ./result
       
       ### Nixpkgs
       
       In the Nix expression used previously (the GNU Hello
       derivation), I used the mkDerivation function from stdenv.
 (HTM) GNU Hello
       
       This function is not builtin, it comes from the pkgs
       identifier which has the value import <nixpkgs> { };.
       
       Before explaining this import, I think it's very important
       to understand what Nixpkgs is. It's a Git repository that
       contains all the Nix expressions and modules. When this
       folder is evaluated, it produces an attribute set containing
       stdenv, which is itself an attribute set containing our
       mkDerivation function.
 (HTM) Nixpkgs
       
       Getting back to pkgs, <nixpkgs> is just a special Nix
       syntax, which, when evaluated, gives a path to a folder
       containing a collection of Nix expressions, i.e. Nixpkgs.
       
       Incidentally <nixpkgs> has an equivalence in Nix as shown
       below.
       
       nix-repl> <nixpkgs>
       /home/nagi/.nix-defexpr/channels/nixpkgs
       
       nix-repl> builtins.findFile builtins.nixPath "nixpkgs"
       /home/nagi/.nix-defexpr/channels/nixpkg
       
       nix-repl> :p builtins.nixPath
       [
         {
           path = "/home/nagi/.nix-defexpr/channels";
           prefix = "";
         }
       ]
       
       ### Managing multiple Python versions
       
       One of the advantages of Nix is that it naturally offers the
       possibility of managing several versions of the same
       application. Taking Python as an example, let's say I want a
       Nix shell with version 3.7 and version 3.13.
 (HTM) Python
       
       To do this, we can check for which version of Nixpkgs Python
       was built on version 3.7 and target a specific version of
       Nixpkgs in our Nix expression.
 (HTM) Nixpkgs
 (HTM) Nixpkgs
       
       To do this, there's the flox tool which works very well, but
       to make it easier to understand I prefer to use nixhub.io.
 (HTM) flox
 (HTM) nixhub.io
       
       So I'm looking for a version of the Nix packages that
       corresponds to Python version 3.7, and I find
       nixpkgs/aca0bbe791c220f8360bd0dd8e9dce161253b341#python37.
       
       # shell.nix
       
       let
         pkgs = import (fetchTarball
       "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11") { };
         nixpkgs-python = import (fetchTarball
       "https://github.com/NixOS/nixpkgs/archive/aca0bbe791c220f8360bd0dd8e9dce161253b341.tar.gz")
       { };
       in
         pkgs.mkShell {
           buildInputs = [
             nixpkgs-python.python37
             pkgs.python313
           ];
         }
       
       You can build Python derivations and enter a Nix shell with
       the following command.
       
       nix-shell
       
       And we see that we have access to the two versions requested
       with the commands python3.7 and python3.13 !
       
       ## A Virtual environment in Python with Nix flakes
       
       I've recently created a development environment with Nix
       flakes (see documentation), it's very handy as it provides a
       ready to use environment for Python 3.11 with the desired
       modules.
 (HTM) see documentation
       
       Below is a Nix expression I wrote for the Python module
       callviz, it has all the necessary dependencies and a virtual
       Python environment.
 (HTM) callviz
       
       # flake.nix
       
       {
         inputs = {
           nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
         };
       
         outputs =
           { self, nixpkgs }:
           let
             supportedSystems = [
               "x86_64-linux"
               "aarch64-linux"
               "x86_64-darwin"
               "aarch64-darwin"
             ];
       
             forEachSupportedSystem =
               f: nixpkgs.lib.genAttrs supportedSystems (system: f
       { pkgs = import nixpkgs { inherit system; }; });
           in
           {
             # ...
             # I usually also declare a default package, a code
       checker and formatter
             devShells = forEachSupportedSystem (
               { pkgs }:
               {
                 default = pkgs.mkShell {
                   venvDir = ".venv";
                   packages =
                     with pkgs;
                     [
                       python3
                       graphviz
                     ]
                     ++ (with pkgs.python3Packages; [
                       pip
                       venvShellHook
                       graphviz
                     ]);
                 };
               }
             );
           };
       }
       
       Note that the default package and the default development
       shell are compatible with all systems (supportedSystems)!
       
       To realise the derivations and enter the Nix shell, I can
       run the following command.
       nix develop
       
       ## Nixpkgs contribution
       
       Once I'd finished exploring and learning Nix, I wanted to
       make a package for Super Mario War and add it to Nixpkgs.
 (HTM) Super Mario War
 (HTM) Nixpkgs
       
       Here's what the package looks like.
       
       {
         lib,
         stdenv,
         fetchFromGitHub,
         cmake,
         pkg-config,
         enet,
         yaml-cpp,
         SDL2,
         SDL2_image,
         SDL2_mixer,
         zlib,
         unstableGitUpdater,
         makeWrapper,
       }:
       
       stdenv.mkDerivation (finalAttrs: {
         pname = "supermariowar";
         version = "2024-unstable-2025-06-18";
       
         src = fetchFromGitHub {
           owner = "mmatyas";
           repo = "supermariowar";
           rev = "71383b07b99a52b57be79cf371ab718337365019";
           hash = "sha256-
       PjweE8cGAp8V4LY0/6QzLekQ80Q1qbwDiiSzDirA29s=";
           fetchSubmodules = true;
         };
       
         nativeBuildInputs = [
           cmake
           pkg-config
           makeWrapper
         ];
       
         buildInputs = [
           enet
           yaml-cpp
           SDL2
           SDL2_image
           SDL2_mixer
           zlib
         ];
       
         cmakeFlags = [ "-DBUILD_STATIC_LIBS=OFF" ];
       
         postInstall = ''
           mkdir -p $out/bin
       
           for app in smw smw-leveledit smw-worldedit; do
             makeWrapper $out/games/$app $out/bin/$app 
               --add-flags "--datadir $out/share/games/smw"
           done
       
           ln -s $out/games/smw-server $out/bin/smw-server
         '';
       
         passthru.updateScript = unstableGitUpdater { };
       
         meta = {
           description = "Fan-made multiplayer Super Mario Bros.
       style deathmatch game";
           homepage = "https://github.com/mmatyas/supermariowar";
           changelog =
       "https://github.com/mmatyas/supermariowar/blob/${finalAttrs.src.rev}/CHANGELOG";
           license = lib.licenses.gpl2Plus;
           maintainers = with lib.maintainers; [ theobori ];
           mainProgram = "smw";
           platforms = lib.platforms.linux;
         };
       })