Nix basic concepts and patterns

Here are some common patterns and concepts you'll often encounter in Nix:

  1. Attribute Sets: These are similar to dictionaries or maps in other languages. They are a key-value store, where the keys are strings and the values can be of any type.

    {
      packageName = "example";
      version = "1.2.3";
      dependencies = [ "dep1" "dep2" ];
    }
    
  2. Functions: Functions are first-class citizens in Nix. They are often used to parameterize package descriptions.

    { stdenv, fetchurl }:
    
    stdenv.mkDerivation {
      name = "example-1.2.3";
      src = fetchurl {
        url = "http://example.com/example-1.2.3.tar.gz";
        sha256 = "0l6m5...";
      };
    }
    
  3. Derivations: The most important concept in Nix is the derivation. A derivation describes everything needed to build a package (source, dependencies, build script, etc.).

    derivation {
      name = "example-1.2.3";
      builder = "${bash}/bin/bash";
      args = [ ./builder.sh ];
      env = {
        source = fetchurl {
          url = "http://example.com/example-1.2.3.tar.gz";
          sha256 = "0l6m5...";
        };
      };
    }
    
  4. String Interpolation: Nix supports string interpolation, making it easy to compose strings.

    let
      name = "example";
      version = "1.2.3";
    in
    "${name}-${version}"
    
  5. Let-In Blocks: Used for defining local variables. The let block allows you to define a set of local variables that can be used in the expression following the in.

    let
      x = 10;
      y = 20;
    in
    x + y  # 30
    
  6. Importing Other Nix Files: Nix allows modularization by letting you import other Nix files.

    let
      myPackage = import ./my-package.nix;
    in
    myPackage
    
  7. Conditional Expressions: Nix supports if...then...else expressions for conditional logic.

    if lib.versionAtLeast lib.version "2.0" then
      { enableFeatureX = true; }
    else
      { enableFeatureX = false; }
    
  8. Lists: Just like in other languages, Nix supports lists. They are often used for dependencies.

    [ "pkg1" "pkg2" "pkg3" ]
    
  9. Overrides and Customization: One powerful feature of Nix is the ability to override or customize packages. This is often done using the overrideAttrs function.

    pkgs.hello.overrideAttrs (oldAttrs: {
      buildInputs = oldAttrs.buildInputs ++ [ pkgs.gnutls ];
    })
    

    This example takes the existing hello package and adds gnutls to its buildInputs.

  10. Builtins and Language Constructs: Nix has several built-in functions and language constructs that are frequently used.

    • builtins.fetchGit: Fetches sources from a Git repository.
    • builtins.readFile: Reads the contents of a file into a string.
    let
      version = builtins.readFile ./version.txt;
    in
    "Package version: ${version}"
    
  11. Lazy Evaluation: Nix employs lazy evaluation, meaning expressions are only evaluated when they are needed. Keep that in mind, since your code may not execute when you expect it to. (Particularly when debugging)

  12. Path Expressions: Nix has a unique way of handling file paths, using the / operator to construct paths in a manner that is aware of the Nix store.

    let
      src = ././source;
    in
    "${src}/subdir/file"
    
  13. Platform Specifics: Nix allows you to specify platform-specific dependencies or configurations using conditional expressions.

    { stdenv }:
    
    stdenv.mkDerivation {
      name = "example";
    
      buildInputs = stdenv.lib.optionals stdenv.isLinux [ pkgs.glibc ]
        ++ stdenv.lib.optionals stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.CoreFoundation ];
    }
    
  14. Using with Statement: The with statement allows for importing all attributes from a given set into the local scope, reducing the need for prefixing them.

with pkgs;
let
  myPackage = stdenv.mkDerivation {
    name = "my-package";
    buildInputs = [ git cmake gcc ];
  };
in
myPackage
  1. Conditional Dependencies with lib.optionals: You can conditionally include dependencies based on certain conditions using lib.optionals.
{ lib, stdenv, fetchurl, openssl, libevent }:

stdenv.mkDerivation {
  name = "example";

  src = fetchurl {
    url = "http://example.com/example.tar.gz";
    sha256 = "0l6m5...";
  };

  buildInputs = [ openssl libevent ]
    ++ lib.optionals stdenv.isLinux [ pkgs.glibc ];
}
  1. Dynamic Attribute Set with mapAttrs: The mapAttrs function is used to transform each attribute in a set, which is useful for dynamically creating attribute sets.
let
  inputSet = { a = 1; b = 2; c = 3; };
  outputSet = lib.mapAttrs (name: value: value * 2) inputSet;
in
outputSet  # { a = 2; b = 4; c = 6; }
  1. Environment Variables in Derivations: You can set environment variables in package derivations, which are used during the build process.
stdenv.mkDerivation {
  name = "example";

  buildInputs = [ openssl ];

  preBuild = ''
    export OPENSSL_DIR=${openssl.dev}
  '';
}