nix-playgraound/notes/005-multi-system.md

127 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

# Multi-System Outputs
This note covers `03-multi-system/`, which makes a flake's packages, dev shells, and checks available on multiple platforms from a single `flake.nix`.
---
## 1. The Problem
2026-04-21 14:19:00 +02:00
Flake outputs are keyed by system: `packages.x86_64-linux.default`, `packages.aarch64-darwin.default`, and so on. If you hard-code one system (as
`01-devshell` and `02-package` do), the flake only works on that platform.
To support multiple systems, you need to generate the per-system attrset for each platform you care about.
---
## 2. The `forAllSystems` Pattern
The `03-multi-system` example defines a local helper:
```nix
forAllSystems = f:
nixpkgs.lib.genAttrs supportedSystems (system:
f (import nixpkgs { inherit system; })
);
```
2026-04-21 14:19:00 +02:00
`genAttrs` takes a list of keys and a function, and returns `{ key1 = f key1; key2 = f key2; … }`. Wrapping it so the callback receives `pkgs` (
already imported for the right system) keeps the per-system output definitions clean:
```nix
packages = forAllSystems (pkgs: {
default = pkgs.writeShellScriptBin "hello-multi" ''
echo "hello from $(uname -s)/$(uname -m)"
'';
});
```
This produces:
```
packages.x86_64-linux.default
packages.aarch64-linux.default
packages.aarch64-darwin.default
packages.x86_64-darwin.default
```
No external dependencies. You control the system list explicitly.
---
## 3. The `flake-utils` Alternative
`flake-utils` is a community flake that provides `eachDefaultSystem`, which does the same thing with less boilerplate:
```nix
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; }; in {
packages.default = pkgs.hello;
});
```
Differences from the hand-rolled approach:
2026-04-21 14:19:00 +02:00
- `eachDefaultSystem` covers a predefined list of common systems (currently `x86_64-linux`, `aarch64-linux`, `x86_64-darwin`, `aarch64-darwin`). You
can use `eachSystem` to specify your own list.
- The callback returns a flat attrset (`{ packages.default = …; devShells.default = …; }`), and `eachDefaultSystem` nests it under each system
automatically.
- It adds one input to `flake.lock`. Use `follows` to keep its nixpkgs in sync with yours.
2026-04-21 14:19:00 +02:00
Both approaches are common. The hand-rolled helper is better when you want full control; `flake-utils` is better when you want less repetition.
`flake-parts` is a third option for larger projects that benefit from a module system on top of flakes.
---
## 4. The `checks` Output
`03-multi-system` also demonstrates a `checks` output:
```nix
checks = forAllSystems (pkgs: {
runs = pkgs.runCommand "hello-multi-runs" {} ''
${self.packages.${pkgs.system}.default}/bin/hello-multi > $out
'';
});
```
`nix flake check` evaluates and builds everything under `checks.<system>`. If any check derivation fails to build, the command exits non-zero.
2026-04-21 14:19:00 +02:00
`runCommand` is a convenience wrapper around `mkDerivation` that runs a single shell snippet. It needs to produce `$out` (a file or directory) to
succeed. Here it runs the built script and writes its output to `$out`, which proves the binary at least executes without error.
2026-04-21 14:19:00 +02:00
This is the simplest form of a flake-level test. More involved checks might run a test suite, validate config syntax, or compare outputs against
expected snapshots.
---
## 5. Commands to Try
```bash
cd 03-multi-system
nix flake show # see outputs for all four systems
nix build # build default package for your system
nix run # build + run
nix flake check # build all checks (proves the script runs)
# Inspect what other systems would get (eval only, no cross-build):
nix eval .#packages.aarch64-darwin.default.name
```
---
## 6. When Cross-System Matters
Multi-system outputs are useful when:
- You collaborate across macOS and Linux machines.
- CI runs on a different platform than your laptop.
- You publish a flake for others to consume (they expect their system to be present).
- You want `nix flake check` to evaluate outputs for every platform, catching typos that would only surface on the other OS.
2026-04-21 14:19:00 +02:00
If you only ever build on one machine, a single hard-coded system string is fine. You can always add `forAllSystems` later without changing any output
behavior on your own platform.