Packaging Conventions
Package Organization
Packages in pkgs/ and pkgs-many/ are automatically added to the pkgs.* scope based on their directory name. No explicit entry in top-level.nix is needed.
When to add an explicit top-level.nix entry:
- Only when inputs deviate from those declared in the nix expression
- Or when overriding default arguments / providing additional configuration
# top-level.nix — only if inputs deviate
libxslt = callPackage ./pkgs/libxslt {
pythonSupport = false;
cryptoSupport = true;
};
Alternative Implementations
Keep alternative implementations in the same package directory using multiple files:
default.nix (short selector), generic.nix (usual implementation), darwin.nix (Apple's implementation).
Keep selection logic inside the package folder using ordinary if expressions and callPackage, forwarding explicit overrides. Avoid separate apple-<package> entries.
Expose libpcap.apple when callers need to choose an implementation. Preserve .override and .overrideAttrs on each implementation. For pkgs-many/ families, dispatch to separate implementation files from generic.nix. Use short variant names (real, darwin, v3_3); don't repeat the package name in package-local aliases.
Rules
- Never set
meta.maintainers or meta.teams — neither is a recognised meta key; setting one fails check-meta. Simply omit them.
__structuredAttrs = true is the default — never set it explicitly. If phases break, fix them instead of disabling. See structured-attrs.
doCheck = false and doInstallCheck = false are defaults — don't set them explicitly. Use passthru.tests for test coverage.
Package Structure
Use finalAttrs pattern for self-referencing:
stdenv.mkDerivation (finalAttrs: {
pname = "example";
version = "1.0.0";
src = fetchFromGitHub {
owner = "example";
repo = "example";
tag = "v${finalAttrs.version}";
hash = "sha256-...";
};
passthru.tests = {
unittests = runUnitTests finalAttrs.finalPackage;
};
})
Meta Attributes
Required fields:
meta = {
description = "Brief description";
license = lib.licenses.mit;
platforms = lib.platforms.linux;
};
Common licenses:
license = lib.licenses.mit;
license = lib.licenses.gpl3Plus;
license = with lib.licenses; [ gpl2Plus lgpl21Plus ];
Platform options:
platforms = lib.platforms.linux; # Linux only
platforms = lib.platforms.unix; # Unix-like
platforms = lib.platforms.darwin; # macOS only
platforms = lib.platforms.all; # All platforms
Testing
Goal: Keep builds as fast as possible by disabling in-build tests. Use passthru.tests to preserve test coverage without slowing every build.
Defaults
doCheck = false; and doInstallCheck = false; are the defaults across all build systems (stdenv.mkDerivation, buildPythonPackage, buildRustPackage, buildGoModule, buildPerlPackage). Don't set them explicitly.
Auto-Generated passthru.tests.build
buildRustPackage and buildGoModule automatically provide passthru.tests.build — a variant that re-runs the build with doCheck = true. User-supplied passthru.tests entries are merged alongside, not replaced.
For stdenv.mkDerivation packages, add passthru.tests manually:
passthru.tests = {
withChecks = finalAttrs.finalPackage.overrideAttrs { doCheck = true; };
};
Python: testPaths (Preferred)
Set testPaths to auto-generate passthru.tests.python and a test_src output:
nativeCheckInputs = [ pytestCheckHook ];
testPaths = [ "tests" ];
The test_src output bundles the listed paths plus config files (conftest.py, pyproject.toml, etc.). A standalone test derivation runs the suite against the installed package.
For suites that need extra files (helper modules, fixture data):
testPaths = [ "tests" "src/helpers" "README.rst" ];
Test configuration attributes are forwarded automatically:
disabledTests, disabledTestPaths, enabledTestPaths
pytestFlags, pytestFlagsArray, unittestFlagsArray
preCheck, postCheck, preInstallCheck, postInstallCheck
Python: Manual tests.nix (Fallback)
When testPaths is not suitable (e.g., circular dependencies with pytest), create a separate tests.nix:
# default.nix
passthru.tests = {
pytest = callPackage ./tests.nix { };
};
Acceptable In-Build Checks
doInstallCheck = true; is acceptable for:
versionCheckHook — verifies the built binary runs and prints expected version
pythonImportsCheck — runs automatically in buildPythonPackage, no need to enable explicitly
Dependencies
nativeBuildInputs = [ # Build tools (build platform)
pkg-config
cmake
];
buildInputs = [ # Libraries (host platform)
zlib
openssl
];
checkInputs = [ # Test dependencies
pytest
];
propagatedBuildInputs = [ # Runtime deps for dependents
python3
];
Cross-compilation:
strictDeps = true;
depsBuildBuild = [ pkg-config ]; # Build -> build tools
nativeBuildInputs = [ cmake ]; # Build -> host tools
buildInputs = [ zlib ]; # Host libraries
Fetching Sources
GitHub:
src = fetchFromGitHub {
owner = "example";
repo = "example";
tag = "v${finalAttrs.version}";
hash = "sha256-...";
};
GitLab:
src = fetchFromGitLab {
domain = "gitlab.com";
owner = "example";
repo = "example";
tag = finalAttrs.version;
hash = "sha256-...";
};
Tarball:
src = fetchurl {
url = "https://example.com/releases/example-${finalAttrs.version}.tar.gz";
hash = "sha256-...";
};
Patching
Substitutions:
postPatch = ''
substituteInPlace Makefile \
--replace '/usr/local' "$out" \
--replace 'python' '${python3}/bin/python3'
'';
Patch files:
patches = [
./fix-build.patch
(fetchpatch {
url = "https://github.com/example/example/commit/abc123.patch";
hash = "sha256-...";
})
];
Install Phase
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp example $out/bin/
runHook postInstall
'';
Post-install fixups:
postInstall = ''
wrapProgram $out/bin/example \
--prefix PATH : ${lib.makeBinPath [ coreutils ]}
'';
Multiple Outputs
outputs = [ "out" "dev" "doc" ];
postInstall = ''
moveToOutput include $dev
moveToOutput share/doc $doc
'';
Passthru
passthru = {
tests = {
unittests = runUnitTests finalAttrs.finalPackage;
};
plugins = {
foo = callPackage ./plugins/foo.nix { };
};
};
Porting from nixpkgs
Remove:
updateScript from passthru
- Maintainers list (set to
[ ])
- nixpkgs-specific test infrastructure
Add TODO for missing deps:
buildInputs = [
zlib
# TODO(corepkgs): Port openssl for TLS support
];
Complete Example
{
lib,
stdenv,
fetchFromGitHub,
cmake,
pkg-config,
zlib,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "example";
version = "2.1.0";
src = fetchFromGitHub {
owner = "example-org";
repo = "example";
tag = "v${finalAttrs.version}";
hash = "sha256-...";
};
nativeBuildInputs = [
cmake
cmake.configurePhaseHook
pkg-config
];
buildInputs = [ zlib ];
strictDeps = true;
cmakeBuildType = "Release";
passthru.tests = {
unittests = runUnitTests finalAttrs.finalPackage;
};
meta = {
description = "Example library";
homepage = "https://github.com/example-org/example";
license = lib.licenses.mit;
platforms = lib.platforms.unix;
};
})