The shardSplit flake module is a flake-parts module that automatically divides build outputs across multiple "shards" for distributed CI/CD evaluation. It's designed to work with tools like nix-eval-jobs for parallel building.
The module is defined in modules/shard-split/default.nix and uses the lib/shard-attrs.nix helper function.
The module reads from config.flake.mcl.shard-matrix with three configurable inputs:
| Option | Type | Default | Purpose |
|---|---|---|---|
| shardSize | positive number | 1 | Number of items per shard (chunk size for division) |
| systemsToBuild | list of strings | ["x86_64-linux", "aarch64-linux", "aarch64-darwin"] |
Systems to evaluate and shard |
| perSystemAttributePath | list of strings | ["legacyPackages", "checks"] |
Flake attribute path to extract packages from, with ${system} interpolated after first attribute |
The module performs this pipeline on the evaluated flake outputs:
1. For each system in systemsToBuild:
├─ Access flake outputs using perSystemAttributePath
│ └─ e.g., outputs.legacyPackages.${system}.checks
│
2. Flatten all systems' attributes into a single namespace:
│ └─ Each package name becomes: "${name}/${system}"
│
3. Split flattened list into shards using shardSize
│ └─ Shard indices are zero-padded (e.g., "shard-00", "shard-01")
│
4. Generate two output structures
The module generates four read-only output attributes under config.flake.mcl.shard-matrix.result:
{
"shard-0" = {
"hello-0.0.1/aarch64-darwin" = <derivation>;
"hello-0.0.1/x86_64-linux" = <derivation>;
"hello-0.0.2/aarch64-darwin" = <derivation>;
# ... more packages
};
"shard-1" = {
"bye-0.0.1/aarch64-darwin" = <derivation>;
"bye-0.0.1/x86_64-linux" = <derivation>;
# ...
};
}- Structure:
shards.{shardId}.{packageName}/{system} - Use case: Each shard can be built independently by nix-eval-jobs, with packages distributed across all systems in a single shard
{
"aarch64-darwin" = {
"shard-0" = {
"hello-0.0.1" = <derivation>;
"hello-0.0.2" = <derivation>;
};
"shard-1" = {
"bye-0.0.1" = <derivation>;
};
};
"x86_64-linux" = {
"shard-0" = { /* ... */ };
"shard-1" = { /* ... */ };
};
}- Structure:
shardsPerSystem.{system}.{shardId}.{packageName} - Use case: When you need system-specific shard organization (build per-system shards in parallel)
- Type: unsigned integer
- Value: Number of shards in the
shardsoutput - Calculated as:
ceil(totalPackageCount / shardSize)
- Type:
{system: shardCount, ...} - Value: For each system, the number of shards that system has
- Useful for job scheduling per-system
┌─────────────────────────────────────────────────────────────┐
│ Flake Outputs (evaluated for all systems) │
│ outputs.legacyPackages.x86_64-linux.checks │
│ outputs.legacyPackages.aarch64-darwin.checks │
└─────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┐
│ Extract attributes │
│ per system using │
│ perSystemAttributePath│
└───────────────────────┘
│
┌───────────────────┴───────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ For each system │ │ For each system │
│ gen attributes │ │ shard using │
│ as "{name}/{sys}"│ │ shardAttrs() │
└──────────────────┘ └──────────────────┘
│ │
└───────────────┬─────────────────────┘
▼
┌──────────────────────────────────┐
│ shardAttrs(attrs, shardSize) │
│ ─────────────────────────────── │
│ 1. Sort attribute names │
│ 2. Split into chunks │
│ 3. Create shards with padded ID │
│ 4. Return {shard-N: {...}} │
└──────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ shards (cross- │ │ shardsPerSystem │
│ system shards) │ │ (system-grouped) │
│ {shard-N: │ │ {system: {shard-N: │
│ {pkg/sys: drv}} │ │ {pkg: drv}}} │
└──────────────────┘ └────────────────────┘
│ │
└────────┬───────────┘
▼
┌───────────────────────────────────┐
│ shardCount & shardCountPerSystem │
│ (Metadata for scheduling) │
└───────────────────────────────────┘
The shardAttrs function implements the core sharding logic:
- Calculate shard count:
ceil(attrCount / shardSize) - Create fixed-width shard IDs: Pad IDs to match the width of the highest shard number (e.g., "00", "01", "02" for 100+ shards)
- Distribute attributes: Slice attribute names into consecutive chunks of
shardSize - Map to derivations: For each chunk, create a shard object mapping original attribute names to their derivations
If your flake has 25 packages and shardSize = 10:
- 3 shards are created: "shard-0", "shard-1", "shard-2"
- Shards contain ~10, ~10, and ~5 packages respectively
- Each shard can be evaluated independently by nix-eval-jobs in parallel
The module throws an error if systemsToBuild specifies systems that don't exist in the flake outputs at the configured perSystemAttributePath.