Files
nixpkgs/docs/guides/adding-packages.md

9.2 KiB

Adding Packages Guide

How to add new packages to m3ta-nixpkgs.

Overview

Packages in m3ta-nixpkgs are organized using a callPackage registry pattern. Each package lives in its own directory and is registered centrally.

Quick Start

Using Templates

Use the package template for quick setup:

nix flake init -t .#package my-new-package

This creates a template structure in templates/package/ that you can copy.

Automatic Updates

Important: This repository uses automated package updates via Gitea Actions and nix-update. When adding a new package:

  • Use any stable, working version for the initial package
  • You don't need to use the absolute latest version
  • The automation will keep the package updated automatically on a weekly basis
  • Review and merge automated update PRs as they come in

See the main README.md for more details on the automated update workflow.

Manual Setup

  1. Create directory: pkgs/your-package/
  2. Write default.nix with package definition
  3. Register in pkgs/default.nix

Package Structure

pkgs/your-package/
├── default.nix       # Package definition (required)
├── source.py         # Optional: additional source files
├── wrapper.sh        # Optional: wrapper scripts
└── README.md         # Optional: package documentation

Common Patterns

Rust Package

{
  lib,
  rustPlatform,
  fetchFromGitHub,
}:
rustPlatform.buildRustPackage rec {
  pname = "my-rust-app";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "author";
    repo = "my-rust-app";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  cargoLock.lockFile = src + "/Cargo.lock";

  buildInputs = [openssl];

  nativeBuildInputs = [pkg-config];

  meta = with lib; {
    description = "My Rust application";
    homepage = "https://github.com/author/my-rust-app";
    license = licenses.mit;
    platforms = platforms.linux;
    mainProgram = "my-rust-app";
  };
}

Python Package

{
  lib,
  python3,
  fetchFromGitHub,
}:
python3.pkgs.buildPythonPackage rec {
  pname = "my-python-app";
  version = "1.0.0";
  pyproject = true;

  src = fetchFromGitHub {
    owner = "author";
    repo = "my-python-app";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  build-system = with python3.pkgs; [setuptools];

  dependencies = with python3.pkgs; [
    requests
    click
  ];

  optional-dependencies = with python3.pkgs; {
    extras = [pyyaml];
  };

  doCheck = true;

  pythonImportsCheck = ["myapp"];

  meta = with lib; {
    description = "My Python application";
    homepage = "https://github.com/author/my-python-app";
    license = licenses.mit;
    platforms = platforms.linux;
    mainProgram = "my-app";
  };
}

Shell Script Package

{
  lib,
  writeShellScriptBin,
}:
writeShellScriptBin "my-script" ''
  #!/usr/bin/env bash
  set -euo pipefail

  echo "Hello from my script!"

  # Your script logic here
''

# If you need to add dependencies
{
  lib,
  writeShellApplication,
  bash,
  curl,
}:
writeShellApplication {
  name = "my-script";
  runtimeInputs = [bash curl];
  text = ''
    #!/usr/bin/env bash
    set -euo pipefail

    curl -s https://example.com
  '';
}

AppImage Package

{
  lib,
  appimageTools,
  fetchurl,
}:
appimageTools.wrapType2 rec {
  name = "my-app";
  version = "1.0.0";

  src = fetchurl {
    url = "https://github.com/author/my-app/releases/download/v${version}/My-App-${version}.AppImage";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  meta = with lib; {
    description = "My AppImage application";
    homepage = "https://github.com/author/my-app";
    license = licenses.unfree;
    platforms = platforms.linux;
    mainProgram = name;
  };
}

Custom Source with Patch

{
  lib,
  stdenv,
  fetchFromGitHub,
  fetchpatch,
  buildGoModule,
}:
buildGoModule rec {
  pname = "my-go-app";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "author";
    repo = "my-go-app";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  patches = [
    # Add local patch
    ./fix-build.patch

    # Add patch from URL
    (fetchpatch {
      url = "https://github.com/author/my-app/pull/123.patch";
      hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
    })
  ];

  vendorHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";

  meta = with lib; {
    description = "My Go application";
    homepage = "https://github.com/author/my-go-app";
    license = licenses.mit;
    platforms = platforms.linux;
    mainProgram = "my-app";
  };
}

Package with Custom Installation

{
  lib,
  stdenv,
  fetchFromGitHub,
  makeWrapper,
}:
stdenv.mkDerivation rec {
  pname = "my-app";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "author";
    repo = "my-app";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  nativeBuildInputs = [makeWrapper];

  buildPhase = ''
    make build
  '';

  installPhase = ''
    install -Dm755 my-app $out/bin/my-app

    # Wrap with runtime dependencies
    wrapProgram $out/bin/my-app \
      --prefix PATH : ${lib.makeBinPath [some-dep]}
  '';

  meta = with lib; {
    description = "My custom application";
    homepage = "https://github.com/author/my-app";
    license = licenses.mit;
    platforms = platforms.linux;
    mainProgram = "my-app";
  };
}

Registration

Register in pkgs/default.nix

Add your package to the registry:

{
  inherit (pkgs) callPackage;
} rec {
  # Existing packages
  code2prompt = callPackage ./code2prompt {};
  zellij-ps = callPackage ./zellij-ps {};

  # Your new package
  my-new-package = callPackage ./my-new-package {};
}

With Custom Arguments

If your package needs custom arguments:

# pkgs/default.nix
{
  inherit (pkgs) callPackage;
} rec {
  my-new-package = callPackage ./my-new-package {
    customArg = "value";
  };
}

# pkgs/my-new-package/default.nix
{
  lib,
  stdenv,
  fetchurl,
  customArg,  # This will be passed from the registry
}:
stdenv.mkDerivation {
  # ...
}

Getting Hashes

Using lib.fakeHash

During development, use lib.fakeHash to get the real hash:

src = fetchFromGitHub {
  owner = "author";
  repo = "my-app";
  rev = "v${version}";
  hash = lib.fakeHash;  # Temporary placeholder
};

Build the package:

nix build .#my-new-package

Copy the actual hash from the error message and update the package:

hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";  # Real hash

Important: Never commit lib.fakeHash to the repository.

Testing

Build Package

nix build .#my-new-package

Test Execution

nix run .#my-new-package -- --help

Run in Shell

nix shell .#my-new-package
my-new-app --version

Linting

nix develop
statix check pkgs/my-new-package/

Best Practices

Meta Fields

Always include complete meta information:

meta = with lib; {
  description = "Short one-line description";
  longDescription = ''
    Longer description explaining what the package does,
    its features, and use cases.
  '';
  homepage = "https://github.com/author/repo";
  changelog = "https://github.com/author/repo/releases/tag/v${version}";
  license = licenses.mit;
  platforms = platforms.linux;
  mainProgram = "program-name";
};

Dependencies

Explicitly declare all dependencies:

{
  lib,
  stdenv,
  fetchFromGitHub,
  # Runtime dependencies
  openssl,
  curl,
  # Native build dependencies
  pkg-config,
  cmake,
}:

Versioning

Use rec to reference version in multiple places:

rec {
  pname = "my-app";
  version = "1.0.0";

  src = fetchFromGitHub {
    rev = "v${version}";
    # ...
  };
}

Platform Restrictions

If package is platform-specific:

meta = with lib; {
  # Linux only
  platforms = platforms.linux;

  # Or specific platforms
  platforms = ["x86_64-linux" "aarch64-linux"];

  # Or exclude platforms
  broken = stdenv.isDarwin;
};

Troubleshooting

Hash Mismatch

Error: got: sha256-AAAAAAAA... expected: sha256-BBBBBB...

Solution: Copy got hash and update package definition.

Dependency Not Found

Error: error: undefined variable 'somedep'

Solution: Add dependency to function arguments and build inputs:

{
  lib,
  somedep,  # Add here
}:
stdenv.mkDerivation {
  buildInputs = [somedep];  # Add here
}

Import Check Failure

Error: error: Python module 'mymodule' not found

Solution: Disable or fix imports check:

pythonImportsCheck = ["mymodule"];  # Check this is correct
# Or if importing creates side effects:
pythonImportsCheck = [];

Examples

See existing packages in the repository:

  • pkgs/code2prompt/ - Rust package
  • pkgs/mem0/ - Python package
  • pkgs/hyprpaper-random/ - Shell script
  • pkgs/msty-studio/ - AppImage
  • pkgs/zellij-ps/ - Fetch from Gitea

Next Steps