Day 52 — nixpkgs layout & by-name

Updated

July 30, 2026

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 configured

Read three packages in your Day 47 language:

  1. Tiny CLI
  2. One with patches
  3. 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
  • platforms or rely on sensible default

Step 5 — PR-shaped checklist (write even if not submitting)

PR-CHECKLIST.md:

  1. Builds on your system
  2. nixpkgs-review awareness (optional run if you have checkout)
  3. No secrets in expression
  4. Hashes pinned
  5. Follows naming
  6. 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 PR

Step 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-name sharding
  • Package lives in by-name-shaped path
  • Overlay + callPackage works
  • 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.