agentleFS
ekala-projectekala-project/ekala-claude-skills/skills/packaging/SKILL.md

packaging

Skill1 starsChanged 9 months agoMITView on GitHub

corepkgs packaging conventions. Use when writing or reviewing any pkgs/<name>/default.nix — covers package organization (auto-registration, top-level.nix, alternative implementations), the finalAttrs pattern, meta attributes, where each kind of dependency belongs, fetchers, patching, install phases, multiple outputs, and passthru.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 2 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Only when inputs deviate from those declared in the nix expression

    line 13
  2. Very specific:

    Keep alternative implementations in the same package directory using multiple files: default.nix (short selector), generic.nix (usual …

    line 26
  3. Very specific:

    Keep selection logic inside the package folder using ordinary if expressions and callPackage, forwarding explicit overrides.

    line 29
  4. Very specific:

    Use finalAttrs pattern for self-referencing:

    line 41
  5. Very specific:

    Goal: Keep builds as fast as possible by disabling in-build tests.

    line 89
  6. Distinctive:

    Test configuration attributes are forwarded automatically:

    line 123
  7. Very specific:

    When testPaths is not suitable (e.g., circular dependencies with pytest), create a separate tests.nix:

    line 130

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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;
  };
})

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.