Flakes

Nix Flakes are an essential part of the Nix ecosystem, offering a more reproducible, composable, and declarative approach to package management. Here's a comprehensive overview with examples:

Overview

  • Purpose: Flakes provide a hermetic, reproducible, and declarative way to manage Nix packages, projects, and configurations.
  • Components:
    • Flake.nix: Central to any flake, this file declares inputs (dependencies), outputs (packages, applications), and how to build them.
    • Lock File: Ensures reproducibility by pinning the exact version of each input. This file is called flake.lock

Basic Structure

A simple flake.nix might look like this:

{
  description = "A simple Nix flake example";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  };

  outputs = { self, nixpkgs }: {
    defaultPackage.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.hello;
  };
}

This is the same example from the previous chapter

Using Flakes

  1. Creating a New Flake: Initialize a new flake in a directory with nix flake init.
  2. Building a Flake: Use nix build to build the flake's default package.
  3. Updating Flake Inputs: Run nix flake update to update the inputs according to the lock file.

Example: Packaging an Application

Here's an example of packaging a simple application with a flake:

{
  description = "My Application";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.myapp = nixpkgs.stdenv.mkDerivation {
      name = "myapp";
      src = ./.;
      buildInputs = [ nixpkgs.gcc nixpkgs.make ];
      buildPhase = ''
        make
      '';
      installPhase = ''
        mkdir -p $out/bin
        cp myapp $out/bin/
      '';
    };
  };
}

Advanced Usage

  • Overlays: Customize packages from nixpkgs using overlays.
  • Custom Inputs: Include other flakes as inputs.
  • Cross-Compilation: Support for cross-compiling to different platforms.

Best Practices

  • Version Control: Keep your flake.nix and flake.lock under version control.
  • Modularity: Create modular and reusable components.
  • Documentation: Document the purpose and usage of your flakes.

Limitations

  • Compatibility: Not all Nix packages, libraries, or configurations have been converted to flakes.
  • Not all features related to flakes are stable yet

Examples of derivations

Derivations are Nix's counterpart to packages (and also sometimes intermediary build steps leading to packages!). Many language-specific libraries, such as crane provide wrappers which construct derivations for you. The simplest way to create a derivation is to use the mkDerivation function:

Example 1: Basic C Application

{
  description = "A simple C application";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.myCApp = nixpkgs.stdenv.mkDerivation {
      pname = "myCApp";
      version = "1.0";
      src = ./.;
      buildInputs = [ nixpkgs.gcc ];
    };
  };
}

Example 2: Python Application with Custom Phases

{
  description = "A Python application";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.myPythonApp = nixpkgs.stdenv.mkDerivation {
      pname = "myPythonApp";
      version = "1.0";
      src = ./.;
      buildInputs = [ nixpkgs.python3 ];
      buildPhase = ''
        python3 setup.py build
      '';
      installPhase = ''
        python3 setup.py install --prefix=$out
      '';
    };
  };
}

Example 3: Application with Custom Patches and Post-Install

{
  description = "An application with custom patches";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.myPatchedApp = nixpkgs.stdenv.mkDerivation {
      pname = "myPatchedApp";
      version = "1.0";
      src = ./.;
      patches = [ ./fix-bug.patch ./add-feature.patch ];
      postInstall = ''
        echo "Post-install actions here"
      '';
    };
  };
}

Example 4: Application with Pre-Configured and Post-Build Steps

{
  description = "Application with pre- and post-build steps";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.myComplexApp = nixpkgs.stdenv.mkDerivation {
      pname = "myComplexApp";
      version = "1.0";
      src = ./.;
      preConfigure = ''
        echo "Pre-configuration actions here"
      '';
      postBuild = ''
        echo "Post-build actions here"
      '';
    };
  };
}

Some important fields in mkDerivation:

  • pname: Package name.
  • version: Package version.
  • src: Source of the package, often the current directory (./.).
  • buildInputs: List of dependencies required to build the package.
  • buildPhase, installPhase, preConfigure, postInstall, postBuild: Custom phases to control different stages of the build process.
  • patches: List of patches to be applied to the source.