Day 52 — nixpkgs layout & by-name
Day 52 — nixpkgs layout & by-name
Stage V · ~4h
Goal: Navigate modern nixpkgs package layout—especially pkgs/by-name—and shape a package so it could become an upstream PR (submission optional).
Why this day exists
Copy-pasting random default.nix from a blog creates private snowflakes. Knowing where packages live, how they are called, and what reviewers expect makes your flake packages portable and your future contributions reviewable.
Theory 1 — Mental map of nixpkgs (2026)
| Path | Role |
|---|---|
pkgs/by-name/<p>/<pname>/package.nix |
Preferred home for many new single-package expressions |
pkgs/top-level/all-packages.nix |
Historic wiring; less hand-editing for new pkgs |
pkgs/development/… |
Older category tree still holds many packages |
pkgs/applications/… |
Same—legacy category layout |
lib/ |
Nix library functions |
nixos/modules/ |
NixOS modules (not package expressions) |
maintainers/maintainer-list.nix |
Maintainer identities |
by-name rules of thumb
pkgs/by-name/he/hello/package.nix
││ │
││ └─ package directory = pname
└┴─ first two lowercase letters of pname
| Rule | Detail |
|---|---|
| File name | Usually package.nix (not always default.nix) |
| Shard | toLower (substring 0 2 pname) |
| Scope | One package per dir; keep it self-contained |
| Call | Auto-wired for eligible packages—follow current nixpkgs docs if adding upstream |
Your flake can still use callPackage ./pkgs/foo.nix { } without sharding; by-name is the upstream shape.
Theory 2 — Anatomy of a PR-shaped package.nix
{
lib,
stdenv,
fetchFromGitHub,
# only real deps
}:
stdenv.mkDerivation (finalAttrs: {
pname = "cooltool";
version = "1.2.3";
src = fetchFromGitHub {
owner = "acme";
repo = "cooltool";
rev = "v${finalAttrs.version}";
hash = "sha256-…";
};
nativeBuildInputs = [ /* … */ ];
buildInputs = [ /* … */ ];
meta = {
description = "One-line description ending without a period sometimes—match nixpkgs style you see nearby";
homepage = "https://github.com/acme/cooltool";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ /* your handle if upstream */ ];
platforms = lib.platforms.unix;
mainProgram = "cooltool";
};
})Modern habits
| Habit | Why |
|---|---|
finalAttrs: pattern |
Version/src self-reference without rec pitfalls |
| Explicit function head args | callPackage injects deps |
meta.mainProgram |
nix run nixpkgs#cooltool |
Real license |
Policy and binary caches |
| No unrelated formatting of huge trees | Reviewer happiness |
Theory 3 — callPackage and fixed-points
pkgs.callPackage ./package.nix { }
# equivalent spirit: package.nix is a function of deps# Override only one dep
pkgs.callPackage ./package.nix {
openssl = pkgs.openssl_3;
}Overlays often do:
final: prev: {
cooltool = final.callPackage ./cooltool/package.nix { };
}Use final.callPackage so deps see other overlay packages.
Theory 4 — Reading existing packages as literature
# If you have a nixpkgs checkout:
rg -n "pname = \"ripgrep\"" -S pkgs | head
# Or query without full clone:
nix edit nixpkgs#ripgrep
# opens the file in $EDITOR when configuredRead three packages in your Day 47 language:
- Tiny CLI
- One with patches
- One with multiple outputs
Note patterns: hooks, finalAttrs, meta, tests.
Theory 5 — Tests and passthru (awareness)
Many nixpkgs packages expose:
passthru.tests = { /* … */ };
passthru.updateScript = /* nix-update support */;Optional today; required mental model for “done upstream.” Your flake can mirror with checks.
Worked example — flake package laid out like by-name
day52/
flake.nix
pkgs/
by-name/
co/cooltool/package.nix
overlays/default.nix
NOTES.md
# overlays/default.nix
final: prev: {
cooltool = final.callPackage ../pkgs/by-name/co/cooltool/package.nix { };
}# flake.nix
{
description = "Day 52 by-name shaped package";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ (import ./overlays/default.nix) ];
};
in {
packages.${system}.default = pkgs.cooltool;
packages.${system}.cooltool = pkgs.cooltool;
# PR checklist as a check: evaluate meta
checks.${system}.meta-license =
assert pkgs.cooltool.meta ? license;
pkgs.runCommand "meta-ok" { } "touch $out";
};
}Lab — multi-step
Suggested workspace: ~/lab/90daysofx/02-nixos/day52
Step 1 — Locate packages
nix edit nixpkgs#hello || true
nix eval nixpkgs#hello.meta --json | jq .Record: description, license, platforms, maintainers shape.
Step 2 — Reshape your best Stage V package
Take Day 45–48 best package and move it to:
pkgs/by-name/<shard>/<pname>/package.nix
Use finalAttrs if it fits.
Step 3 — Overlay + flake export
callPackage through overlay; nix build .#cooltool.
Step 4 — Meta completeness pass
Ensure:
description
homepage(if any)
license
mainProgram
platformsor rely on sensible default
Step 5 — PR-shaped checklist (write even if not submitting)
PR-CHECKLIST.md:
- Builds on your system
nixpkgs-reviewawareness (optional run if you have checkout)
- No secrets in expression
- Hashes pinned
- Follows naming
- Commit message style (
cooltool: init at 1.2.3)
Step 6 — Optional: clone nixpkgs and dry-place the file
# shallow clone is large; optional
# place file in correct by-name path; do not push unless you intend a real PRStep 7 — Compare category tree vs by-name
Find one package still under pkgs/applications/… and one under by-name. Note one reviewer-facing difference (discovery, automation, structure).
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Wrong two-letter shard | Use first two chars of pname, lowercase |
default.nix in by-name |
Prefer package.nix per current convention |
Recursive rec version bugs |
Prefer finalAttrs |
| Giant unrelated deps in head | Only list what the function uses |
Meta missing mainProgram |
Multi-bin packages: set correctly or omit carefully |
| Assuming auto-discovery in your flake | Flakes need explicit callPackage/overlay |
Checkpoint
- Explained
by-namesharding
- Package lives in by-name-shaped path
- Overlay +
callPackageworks
- Meta filled to nixpkgs-ish bar
- PR checklist written (submit optional)
Commit
git add .
git commit -m "day52: by-name shaped package + meta"Write three personal gotchas before continuing.
Tomorrow
Day 53 — Binary caches: Cachix, Attic/Harmonia, trust, and push/pull workflows.