Package design research

Reviewed 2026-09-07 against the official sources linked below. These recommendations apply to nixpkgs-personal, including its package definitions, bundled programs, and update tools. They describe the intended standard; this document alone is not evidence that every build or interactive application was tested.

Package boundaries and file layout

Keep pkgs/by-name/<first-two-letters>/<attribute-name>/package.nix. Nixpkgs uses this structure for top-level packages and requires a function that returns a derivation. Additional files belong inside the package directory. Its by-name checks prohibit references to files outside that directory. Nixpkgs by-name rules

Use this shape when a package needs the corresponding files. Small packages do not need empty subdirectories.

pkgs/by-name/fo/foo/
  package.nix
  source.nix
  update.py
  README.md
  tests.nix
  patches/
  <package-local helpers and application sources>

The names source.nix, update.py, and patches/ are project conventions. Preserve Swift's Package.swift, Sources/, and Tests/ layout and Rust's Cargo.toml, Cargo.lock, and src/ layout instead of renaming application files to force uniformity. Swift Package Manager models executable, library, and test targets within a package. Swift Package Manager reference

Our stronger project rule is that a package can be copied out with its own directory and instantiated against the supported upstream Nixpkgs pin. Build, runtime, tests, and package update tools must not require a sibling personal package or repository-root Python module. Upstream Nixpkgs normally allows package dependencies. The additional restriction comes from this project's independence requirement.

Keep repository-wide CI, formatting, discovery, and batch-update orchestration outside package directories. These tools may invoke packages; packages must not import them. Small duplicated support files are an acceptable cost of independent copying. Keep duplicates narrow and use repository checks to detect unintended divergence.

Use lib.packagesFromDirectoryRecursive with an upstream-only callPackage to discover the package directories, then flatten the two-letter prefix groups. Omit newScope, which would let discovered packages resolve each other as dependencies. The helper stops at a directory's package.nix, keeping internal Nix files private. Nixpkgs directory discovery implementation

Keep the registry lazy, as in Nixpkgs. Declare supported platforms inside each package and select flake packages.<system> with lib.meta.availableOn, which respects both meta.platforms and meta.badPlatforms. Metadata does not automatically filter flake outputs. The overlay uses the same selection to choose names, then instantiates those packages against its incoming scope. Reading platform metadata from those overlay values would force dependencies while Nixpkgs is still constructing its recursive package set. Selecting names against the flake's plain Nixpkgs input avoids that cycle and keeps unsupported overrides from replacing upstream packages, such as Steam on Linux. Packages must let callers read platform metadata without first throwing on an unsupported host. OS-specific build recipes may still use a conditional inside the package. Nixpkgs platform predicate

Dependency injection and overlays

List dependencies as function arguments and instantiate with callPackage. This exposes dependencies and preserves .override without importing Nixpkgs or a flake from inside a package. nix.dev callPackage tutorial

For this collection, explicitly resolve package arguments against the incoming upstream scope. The ordinary overlay model resolves dependencies through the final scope so other overlays can replace them. That can accidentally connect personal packages when names overlap, such as steam. Independence therefore deliberately trades some overlay-wide replacement behavior for predictable upstream dependencies. Document this choice and preserve explicit package overrides. Nixpkgs overlays

Use lib.callPackageWith pkgs when a fixed incoming scope is required. Merely using an inherited callPackage is not sufficient evidence that its automatic argument scope excludes final overlay values. Avoid a recursive scope that combines upstream and personal outputs.

Aliases may remain for compatibility, but any output promised as an independently copyable package needs its own package directory. A package may build internal components or expose passthru information without creating a dependency on another public personal package.

Build and metadata standards

Use immutable revisions or versioned releases with real content hashes. Prefer fetchers appropriate to the source. A hash detects changed bytes; it cannot prevent an upstream server removing an old release. Keep network discovery in update tools and network downloads in fixed-output fetchers. Nixpkgs fetcher documentation

Use stdenvNoCC for copying assets or installing prebuilt applications. Use the language-specific builder when compilation needs it. Put build tools in nativeBuildInputs and target libraries in buildInputs. Preserve standard phases and invoke the corresponding pre/post hooks in custom phases. Document patches, unusual flags, and disabled checks. Nixpkgs package review checklist

Each package should provide a factual one-line description, upstream homepage, accurate license and platforms. Set mainProgram only when an executable exists. Mark downloaded native binaries with lib.sourceTypes.binaryNativeCode. An empty maintainer list is not meaningful ownership; use a real agreed maintainer identity, never an invented one. Preserve upstream attribution and license files in outputs where required. Nixpkgs metadata reference

Builds should succeed in the sandbox with declared inputs, without reading a checkout, user home, or installed application. Some macOS frameworks require system resources; document those narrow requirements and test on macOS. Sandbox isolation does not itself prove bit-for-bit reproducibility. Nix sandbox configuration

For prebuilt signed macOS applications, preserve the vendor bundle rather than running generic fixup over it. Apple documents that post-signing changes invalidate signatures and that signed bundles should be treated as read-only. Locally compiled or patched applications need their explicit signing step after modifications. Apple code signing tasks

Language choices

There is no universal best language for this collection. These are project choices based on what the programs do and the existing code.

Work Default Reason and practice
Derivations and dependency wiring Nix Keep declarative package logic in the package expression
Short command wrappers and build phases Bash Appropriate for invoking tools; use ShellCheck and quote expansions
Update discovery, manifests, archive inspection, font data transformations Python Use structured parsers and explicit errors; call subprocesses with argument lists, checked exit status, and suitable timeouts
Existing native macOS utilities Swift Keep direct use of macOS APIs and the existing Swift package and test structure
Existing narrow C ABI helpers C Keep the small interposition library, explicit exported symbols and integration tests
Existing systems programs or new services needing tight resource control Rust Ownership checks provide memory safety without a garbage collector; the compiler and dependency maintenance cost needs a concrete benefit

Google's shell guide recommends shell for small wrappers and a structured language for complicated control flow. Its length threshold is a Google convention, not a Nixpkgs requirement. Google shell guide

Prefer writeShellApplication for installed wrappers. It supplies runtime dependencies, Bash syntax checks, ShellCheck, and strict Bash options. Nixpkgs shell application helper

Python's subprocess API supports argument lists, check=True, and timeouts. Avoid shell=True when invoking tools with discovered filenames or release metadata. Python subprocess reference

Rust's ownership model is a reason to choose it for suitable systems work, not evidence that rewriting working Python or Swift improves this repository. Retain upstream implementation languages when packaging third-party software. Rust ownership chapter

Verification and update policy

Run upstream unit tests during checkPhase where supported. Use installation checks for basic executable or asset validation. Put consumer-facing checks in passthru.tests, then explicitly expose or run them in CI. Merely defining passthru tests does not guarantee they run with a normal package build. Nixpkgs passthru test documentation

For this project, verify independence by copying each directory and evaluating it with a clean upstream package scope. Inspect required function arguments, symlink escapes, and runtime references as well. Source-text scanning alone cannot prove dependency isolation. Validate output metadata for every supported system; build applicable packages on actual compatible builders. Record unavailable builders and GUI checks as validation limits.

Package update tools should locate files relative to themselves, accept --help without network access, validate upstream metadata before writing, and change only their own pinned files. Expose conventional passthru.updateScript where practical. Keep reviewed historical versions explicitly excluded from automatic updates. The standard updater entrypoint is documented by Nixpkgs; the stricter write boundary is this project's policy. Nixpkgs automatic update conventions