# 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 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; }) ); ``` `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: - `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. 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.`. If any check derivation fails to build, the command exits non-zero. `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. 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. 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.