# Flakes

> Flakes are still an experimental feature in Nix. However, they are so widely used by the community that they almost became standard. Furthermore, *synix* uses Flakes.

Nix flakes are a reproducible way to define, build, and deploy Nix projects, making them reliable and portable.

Flakes accomplish that by:

## Standardized Input

They define a fixed, declarative input (the `flake.nix` file) that specifies all project dependencies, sources, and outputs. This eliminates implicit dependencies or environment variables that could cause builds to differ.

Example in `flake.nix`:

```nix
inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; # Declare we need nixpkgs, specifically this branch
};
```

## Reproducible "Lock File"

When you build or develop with a flake, Nix generates a `flake.lock` file. This file records the *exact* content-addressable hashes of *all* transitive inputs used for that specific build. This lock file can be committed to version control, ensuring that anyone else cloning the repository (or a CI system) will use precisely the same set of inputs and thus achieve the identical result.

Example `flake.lock` entry for `nixpkgs`:

```json
"nixpkgs": {
  "locked": {
    "lastModified": 1709259160,
    "narHash": "sha256-...",
    "owner": "NixOS",
    "repo": "nixpkgs",
    "rev": "b2f67f0b5d1a8e1b3c9f2d1e0f0e0c0b0a090807", // The exact commit!
    "type": "github"
  },
  "original": {
    "owner": "NixOS",
    "repo": "nixpkgs",
    "type": "github",
    "url": "github:NixOS/nixpkgs/nixos-23.11"
  }
}
```

## Flake Schema

The `flake.nix` has a well-defined structure for `inputs` (sources like Git repos, other flakes) and `outputs` (packages, applications, modules, etc.). This consistent schema makes flakes composable and predictable.

A `flake.nix` file typically looks like this:

```nix
# flake.nix
{
  description = "A simple example flake";

  inputs = {
    # Inputs are other flakes or external resources
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; # Locked to a specific branch/version
    # This is how you would add synix to your flake:
    # synix.url = "git+https://git.sid.ovh/sid/synix"
  };

  outputs = { self, nixpkgs, ... }@inputs: # 'self' refers to this flake, inputs are available
    let
      # Define common arguments for packages from nixpkgs
      # This ensures all packages use the same version of Nixpkgs on this system
      pkgs = import nixpkgs {
        system = "x86_64-linux"; # The target system architecture
      };
    in
    {
      # Outputs include packages, devShells, modules, etc.
      # Packages that can be built by `nix build .#<package-name>`
      packages.x86_64-linux.my-app = pkgs.callPackage ./pkgs/my-app { };
      packages.x86_64-linux.my-other-app = pkgs.hello; # From nixpkgs directly

      # Development shells that can be entered using `nix develop`
      devShells.x86_64-linux.default = pkgs.mkShell {
        name = "my-dev-env";
        buildInputs = [ pkgs.nodejs pkgs.python3 ];
        shellHook = "echo 'Welcome to my dev environment!'";
      };

      # NixOS modules (for system config)
      # nixosConfigurations.<hostname>.modules = [ ./nixos-modules/webserver.nix ];
      # (This is more advanced and will be covered in NixOS section)
    };
}
```

Key parts of a `flake.nix`:

- `description`: A human-readable description of your flake.
- `inputs`: Defines all dependencies of your flake. Each input has a `url` pointing to another flake (e.g., a GitHub repository, a local path, or a Git URL) and an optional `follows` attribute to link inputs.
- `outputs`: A function that takes `self` (this flake) and all `inputs` as arguments. It returns an attribute set defining what this flake provides. Common outputs are `packages`, `devShells`, `nixosConfigurations`, etc., usually segregated by system architecture. You can read more about flake outputs in the [NixOS & Flakes Book](https://nixos-and-flakes.thiscute.world/other-usage-of-flakes/outputs).

## `nix flake` Commands

The `nix flake` subcommand is your primary interface for interacting with flakes. Let's create a new flake to demonstrate them:

Initialize the flake:

```bash
mkdir my-flake && cd my-flake
nix flake init
```

This creates a minimal `flake.nix`.

Lock your flake:

```bash
nix flake lock
```

This creates `flake.lock`, a file that locks the exact versions of your inputs.

Update flake inputs:

```bash
nix flake update
``` 

This updates all inputs to their latest versions allowed by their `url` (e.g., the latest commit on `nixos-unstable` for `nixpkgs`) and then updates the `flake.lock` file. Since we just locked the flake for the first time, there probably won't be any updates available.

Print flake inputs:

```bash
nix flake metadata
```

Print flake outputs:

```bash
nix flake show
```

Build packages from a flake:

```bash
nix build .#hello # The '.' refers to the current directory's flake
./result/bin/hello
```

Run a package from a flake:

```bash
nix run .#hello
```

Since the `packages.<system>.default` output exists, you can just do `nix run`.

## `nix develop`

This command spins up a temporary shell environment with all the tools and dependencies specified in your flake's `devShells` output.

Let's expand your `flake.nix`:

```nix
# flake.nix
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs =
    { self, nixpkgs }:
    let
      # Define `pkgs` for the current system
      pkgs = import nixpkgs {
        system = "x86_64-linux";
      };
    in
    {
      packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
      # With `pkgs` defined, we could also do this:
      # packages.x86_64-linux.hello = pkgs.hello;

      packages.x86_64-linux.default = self.packages.x86_64-linux.hello;

      devShells.x86_64-linux.default = pkgs.mkShell {
        # Packages available in the shell
        packages = [
          pkgs.git
          pkgs.go
          pkgs.neovim
        ];
        # Environment variables for the shell
        GIT_COMMITTER_EMAIL = "your-email@example.com";
        # Commands to run when entering the shell
        shellHook = ''
          echo "Entering development shell for my project."
          echo "You have Git, Go, and Neovim available."
        '';
      };
    };
}
```

Now, from your project directory:

```bash
nix develop
```

You'll instantly find yourself in a shell where `git`, `go`, and `nvim` are available, and your `GIT_COMMITTER_EMAIL` is set. When you exit, your regular shell environment is restored – no lingering installations or modified global state. This makes it incredibly easy to switch between projects, each with its specific toolchain and dependencies, without conflicts.
