Skip to content

Language Toolchains

Overview

Nixpkgs ships language-specific builders and package sets so compilers, interpreters, and dependency fetchers live in the store—not on an impure host. For day-to-day work, put those tools in a dev shell (mkShell / flake devShells). For shipping packages, use the packaging leaves under 06-nixpkgs/packaging.

This page is a map: which entry points exist, how shells relate to packaging, and when community overlays are worth considering. It is not a packaging tutorial—builders, FODs, and set-specific knobs live on the packaging pages linked below.

Details

Packaging vs development

Goal Typical API What you get
Build a package buildPythonPackage, buildGoModule, rustPlatform.buildRustPackage, haskellPackages.mkDerivation, php.buildComposerProject2, … Derivation with locked deps and install layout
Work on a project pkgs.mkShell with packages / nativeBuildInputs Compilers, linters, language servers on PATH
Ad-hoc interpreter env e.g. python3.withPackages, haskellPackages.ghcWithPackages Scoped runtime without a full app derivation

Both paths sit on stdenv. Shells do not replace builders: a shell that only exposes cargo still needs buildRustPackage (or equivalent) for reproducible CI and nixpkgs-style packaging.

Nixpkgs language entry points

Canonical survey: Languages and frameworks in the Nixpkgs manual. Common builders (shell tools vs packaging):

Ecosystem Dev shell tools (examples) Package builders
Python python3, python3Packages.… buildPythonPackage / buildPythonApplication, python.withPackagesPython; packaging: Python / Node / Rust / Go
Rust rustc, cargo, rust-analyzer (from nixpkgs) rustPlatform.buildRustPackageRust; packaging: Python / Node / Rust / Go
Go go, gopls buildGoModuleGo; packaging: Python / Node / Rust / Go
Node / JS nodejs, npm / yarn tools as needed buildNpmPackage (and related hooks) — JavaScript; packaging: Python / Node / Rust / Go
Haskell haskellPackages.ghcWithPackages, cabal-install, HLS haskellPackages.mkDerivation / callPackageHaskell; packaging: Haskell packaging
JVM / Gradle jdk, gradle, tools from javaPackages Ant/jdk derivations; Gradle via mitmCache / deps.jsonJava, Gradle; packaging: JVM / PHP and others
PHP / Composer php, php.packages.composer, php.withExtensions php.buildComposerProject2 (vendorHash) — PHP; packaging: JVM / PHP and others

Nested scopes (python3Packages, haskellPackages, php.extensions, …) are the same package set pattern: pick one scope and stay in it for both shell and packaging.

Higher-level shell frameworks (devenv / other wrappers) still pull these same packages; they do not invent a separate language infra.

Failure modes

Failure What goes wrong
Shell vs packaging mismatch Dev shell has cargo / go / a JDK, but CI packages with a different builder or toolchain pin → “works in nix develop, fails in nix build”. Align shell tools with the builder’s platform (rustPlatform, versioned go_*, same haskellPackages / GHC).
Host rustup / Go / npm Tools from the impure host PATH drift across machines and diverge from store compilers packaging will use. Prefer nixpkgs (or a pinned overlay) for anything you expect to reproduce.
Wrong package set scope Mixing python3Packages with another interpreter’s set, or default haskellPackages with a different haskell.packages.ghc* compiler, yields missing attrs, ABI/version clashes, or Cabal bound failures. Stay inside one package set.

Boundaries

Community Rust overlays (optional)

Nixpkgs Rust is enough for many projects. When you need specific nightly/stable pins, rust-toolchain.toml fidelity, or rust-analyzer variants outside what your nixpkgs channel ships, community overlays are common:

Treat them as extra inputs, not as the default story: pin the overlay flake/input, document why nixpkgs Rust was insufficient, and keep packaging (buildRustPackage) on a clearly chosen rustPlatform or overridden toolchain. Overlay APIs change; follow their READMEs rather than copying stale blog snippets.

Examples

Minimal mkShell shapes—compilers from nixpkgs only. Expand with linters/LSPs as needed; see shells-and-direnv.md for direnv wiring.

# flake.nix (excerpt) — Python + tooling from one interpreter scope
{
  outputs = { nixpkgs, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      py = pkgs.python3.withPackages (ps: [ ps.requests ps.pytest ]);
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [ py pkgs.ruff ];
      };
    };
}
# flake.nix (excerpt) — Rust via nixpkgs
{
  outputs = { nixpkgs, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [
          pkgs.rustc
          pkgs.cargo
          pkgs.rustfmt
          pkgs.clippy
          pkgs.rust-analyzer
        ];
      };
    };
}
# flake.nix (excerpt) — Haskell GHC + libraries from haskellPackages
{
  outputs = { nixpkgs, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      ghc = pkgs.haskellPackages.ghcWithPackages (ps: [ ps.aeson ps.turtle ]);
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [ ghc pkgs.cabal-install ];
      };
    };
}

For actually building projects as derivations, use the packaging leaves—shells alone do not lock Cargo/npm/Go/Composer graphs the way those helpers do.

References

See also