Build Tools & Package Managers — Cross-Language Comparison
Build Tools & Package Managers — Cross-Language Comparison
- Type: cross-cutting comparison
- Languages covered: 51 (see _index)
- Last updated: 2026-05-07
TL;DR
- Modern tooling bundles: version manager + package manager + dependency resolver + build orchestrator + test runner + linter + formatter. Best-in-class languages unify these (Cargo, .NET CLI, Mix, Go toolchain). Worst-in-class fragment them across 5+ tools.
- Cargo (Rust) is the de-facto template for “what good looks like”: single binary, lockfile-based reproducibility, integrated test/bench/doc, no global state pollution.
- Python’s tool churn has finally stabilized:
uv(Astral) won the 2025 race against poetry/pdm/pip-tools/hatch as the dominant resolver. Pair withruff(formatter+linter) andpytest(testing). - Java fragmentation persists: Maven (XML), Gradle (Groovy/Kotlin DSL), Mill, sbt all coexist. Pick by ecosystem, not preference.
- Compiler-as-build-system is a 2020s trend: Zig’s
build.zig, Odin’s build via Odin code, V’sv build. The build system speaks the same language as your code. - No first-class package manager is still common in C/C++/Fortran;
vcpkg/conan/spackare converging answers.
What “build tooling” means in 2026
A complete toolchain provides:
- Version manager: install + switch between language versions (rustup, nvm, pyenv, sdkman).
- Package manager: download + install dependencies (cargo, npm, pip, maven).
- Dependency resolver: solve compatible-version graph (uv resolver, npm/pnpm resolver, cargo resolver).
- Lockfile: pin exact versions for reproducibility (Cargo.lock, package-lock.json, requirements.txt or uv.lock, Gemfile.lock).
- Build orchestrator: compile, link, package (cargo build, mvn package, npm run build).
- Test runner: discover + execute tests (cargo test, pytest, jest).
- Linter: catch likely bugs (clippy, ruff, eslint).
- Formatter: enforce style (rustfmt, ruff format, prettier, gofmt).
- Type checker (if external): mypy/pyright, tsc, dialyzer.
- Doc generator: rustdoc, sphinx, javadoc.
- Publish path: cargo publish, npm publish, pypi twine, mvn deploy.
Languages with single-tool stories (one binary does ~all of this): Rust (cargo), Go, .NET (dotnet CLI), Dart, Deno, Bun, Elixir (mix), Crystal (shards-driven).
Languages with stacked stories (assemble from N tools): Python (uv + ruff + pytest + mypy), Ruby (bundler + rake + rspec + rubocop), C/C++ (CMake + vcpkg + ctest + clang-format + clang-tidy), Java (Maven OR Gradle + JUnit + Checkstyle/SpotBugs + google-java-format).
Categories
1. Single unified tool (the modern gold standard)
- Rust — Cargo: install via rustup (toolchain manager).
cargo build/test/run/bench/doc/publish/install.Cargo.toml+Cargo.lock. crates.io. The reference design. - Go — go toolchain (built into the
gobinary).go build/test/run/install/get/mod. Modules +go.mod+go.sum. No separate package manager. Workspaces (1.18+) for multi-module repos. - [[Languages/csharp|C#]] / [[Languages/fsharp|F#]] / VB.NET — dotnet CLI.
dotnet new/build/test/run/publish/pack. NuGet for packages. Solution + project files (.csproj/.fsproj/.sln) — XML but tooled. - Dart — dart pub.
dart create/run/test;pub get/upgrade/publish. Lockfile. Flutter has its own CLI on top. - Elixir — Mix.
mix new/compile/test/deps.get/format/release. Hex.pm for packages.mix.exs+mix.lock. - Crystal — Shards (package mgr) + Crystal compiler.
shards install/build.shard.yml+shard.lock. Slightly less unified than Rust/Go. - Swift — Swift Package Manager (SPM).
swift build/test/run/package.Package.swiftis Swift code. Apple frameworks via Xcode (separate UX). - Zig —
zig build(usesbuild.zigwritten in Zig). Native package manager (zon files) since 0.11.zig fetchfor dependencies. Compiler-as-build-system. - Odin — Odin compiler itself; build scripts written in Odin. No first-party package manager (community: pkg). Dependencies typically vendored.
- V —
vCLI.v new/run/build/test.vpmpackage manager.v.modfiles. - Nim — Nimble.
nimble install/build/test..nimblefile. - Julia — Pkg (built-in REPL mode).
]add/rm/update/test/precompile.Project.toml+Manifest.toml. Julia’s package mgmt is excellent — sysimage caching, env stacking. - Roc —
roc dev/build/test/check/format. Packages by URL/hash (no central registry yet). - Gleam —
gleam new/build/test/format/run/publish. Hex.pm reused (BEAM ecosystem).gleam.toml. - Pony —
ponyccompiler +corralpackage manager. Less unified than Cargo.
2. Package manager + separate build system
- Python — installer/resolver + build backend stack. 2026 winning combo:
uv(Astral) +ruff(Astral) +pytest+mypy(or pyright). PEP 518pyproject.tomlis canonical metadata. Build backends: hatch, setuptools, poetry, pdm, flit. Version manager: pyenv (legacy) oruv python(winning). PEP 723 inline script metadata. - Ruby — Bundler (deps) + Rake (build) + RSpec/Minitest (test) + RuboCop (lint).
Gemfile+Gemfile.lock. Version manager: rbenv, rvm, asdf, mise. - PHP — Composer (deps) + Make/Phing (build, optional) + PHPUnit/Pest (test) + PHP_CodeSniffer/PHPStan (lint/static analysis).
composer.json+composer.lock. Packagist registry. - Java — Maven (XML, declarative) OR Gradle (Groovy/Kotlin DSL, imperative) OR Mill (Scala-based, newer challenger). Maven Central registry. Test: JUnit 5/TestNG. JaCoCo coverage.
- Scala — sbt (default, Scala DSL) OR Mill (Scala-based) OR Maven OR Gradle. Both reach into the JVM ecosystem.
build.sbtis Scala source. - Kotlin — Gradle (default; Kotlin DSL recommended) OR Maven. Same JVM packaging story.
- Groovy — Gradle (built in Groovy) OR Maven. Grape (
@Grab) for ad-hoc script deps. - Clojure — deps.edn / Clojure CLI tools (
clj/clojure, modern) OR Leiningen (lein, legacy but widely used).deps.ednis the official future. Test: clojure.test, kaocha. Maven/Clojars registries. - JavaScript / TypeScript — npm (default) / pnpm (best — content-addressed store) / yarn (declining) + Node.js as runtime. Bundlers: Vite (winning), webpack (legacy), Rollup, esbuild, Bun (also a runtime).
package.json+ lockfile per tool. TS adds tsc + tsup/tsdown. Test: vitest (winning), jest, node:test built-in. - Perl — CPAN (centralized 30+ year archive) + cpanm / carton (modern).
cpanfilefor declarative deps. Build: ExtUtils::MakeMaker, Module::Build, Dist::Zilla. - Lua — LuaRocks (de facto).
*.rockspecfiles. LuaJIT pairs with it. Embedded contexts (Roblox, Defold, Love2D) bypass entirely. - R — CRAN (centralized, vetted) + install.packages(); renv for reproducibility; devtools for development. Bioconductor for bio packages.
3. OS package manager as the package manager
- C — historically apt/yum/brew + manual
./configure && make install. Modern: vcpkg (Microsoft), Conan (JFrog), Spack (HPC), pkg-config + CMake for build. CMake (CMake DSL), Meson (Python DSL), Bazel (Starlark DSL), Make (the original) all live here. - C++ — same as C, plus Conan 2.x (most polished) + CMake. Bazel for monorepos. Build2 as a unified attempt.
- Fortran — FPM (Fortran Package Manager) is gaining traction; otherwise CMake + Make. Spack for scientific computing.
- COBOL — none; the source IS the deliverable. JCL on mainframes. GCC’s gcobol uses standard GCC tooling.
- Ada — Alire (modern, 2020+) + GPRBuild for builds. AdaCore tools. Crates.io equivalent at alire.ada.dev.
- Pascal — Free Pascal Package Manager (FPMake) + Lazarus packages. Less centralized.
- Tcl — TEA (Tcl Extension Architecture), Teapot, Tcllib. Less centralized than modern languages.
4. Compiler-as-build-system (write your build in your language)
- Zig —
build.zigis a Zig program. The compiler interprets it. - Odin — build scripts in Odin.
- V —
v buildintrospects the project;v.mod. - sbt —
build.sbtis Scala source. - SCons — Python-as-build (cross-language; not in 51).
- Julia —
Project.tomlis declarative, but build customization is done in Julia code (build.jl scripts).
5. Image-based / interactive
- Smalltalk (Pharo) — Iceberg for git integration; Metacello for package management; the image IS the deployment artifact. Tonel format for source on disk.
- Common Lisp — Quicklisp (community registry, ~3000 systems) + ASDF (build/system definition). Image saved via
save-lisp-and-die(SBCL). - Racket —
raco pkg(built-in) + raco build/test. PLT/community catalogs. - Scheme — implementation-specific. Chicken: chicken-install + eggs. Racket has its own. Guile uses guile-utils + Guix.
6. Theorem-prover toolchains
- Lean — Lake (Lean’s native build/package manager).
lakefile.lean(orlakefile.toml). mathlib4 is the reference project. - Coq (Rocq) — dune (cross-language OCaml build) + opam (OCaml package manager) for source-level Coq packages. OPAM Coq Archive.
- Agda —
agda --build+--library; libraries viaagda-libfiles. Less polished than Lean’s Lake. - Idris —
packpackage manager (de facto for Idris 2).idris2 --buildfor builds.
7. Shell / scripting / “just run it”
- Bash — none; copy script,
chmod +x, run.shellcheckfor linting,shfmtfor formatting. Versioning is OS package version of bash itself. - PowerShell — PowerShellGet / PSResourceGet (modern, 2024+) for modules.
Install-Module. PSGallery is the registry.
8. Database / declarative
- SQL — schema-versioning tools (Flyway, Liquibase, sqlx-migrate, dbmate, atlas) play this role; ORM/query builders (Prisma, Drizzle, Diesel, ActiveRecord, SQLAlchemy, Ecto) on top.
9. Logic programming
- Prolog — SWI-Prolog Pack manager (
pack_install/1) + community packs. SICStus has its own. - Erlang — rebar3 (modern) + Hex.pm (shared with Elixir).
rebar.config. mix from Elixir is interoperable.
Per-language quick reference
| Language | Version mgr | Package mgr | Build/test | Linter | Formatter |
|---|---|---|---|---|---|
| Python | uv (or pyenv) | uv / pip | pytest, hatch, setuptools | ruff, pylint, mypy | ruff format, black |
| JavaScript | nvm, fnm, volta, mise | pnpm / npm / yarn / bun | vitest, jest, node:test | eslint, biome | prettier, biome |
| TypeScript | (inherits JS) | (inherits JS) + tsc | vitest, jest, tsx | tsc, eslint, biome | prettier, biome |
| Java | sdkman, asdf, jenv | Maven / Gradle / Mill | JUnit 5, TestNG | Checkstyle, SpotBugs, ErrorProne | google-java-format, palantir |
| C | os pkg | vcpkg / Conan / Spack | CMake, Meson, Make | clang-tidy | clang-format |
| C++ | os pkg | Conan / vcpkg | CMake, Meson, Bazel, Build2 | clang-tidy, cppcheck | clang-format |
| [[Languages/csharp|C#]] | dotnet | NuGet (via dotnet) | dotnet test (xUnit/NUnit/MSTest) | Roslyn analyzers | dotnet format |
| Go | go install (or asdf) | go modules | go test, gotestsum | go vet, staticcheck, golangci-lint | gofmt, goimports |
| Rust | rustup | Cargo + crates.io | cargo test, criterion (bench) | clippy | rustfmt |
| Swift | swiftly, asdf | SPM | swift test, XCTest | swift-lint, SwiftLint | swift-format, SwiftFormat |
| Kotlin | sdkman, kotlinc | Gradle / Maven | kotlin-test, JUnit | detekt, ktlint | ktfmt, ktlint |
| Ruby | rbenv, rvm, mise | Bundler + RubyGems | RSpec, Minitest | RuboCop, Standard | RuboCop, Standard |
| PHP | phpenv, asdf | Composer + Packagist | PHPUnit, Pest | PHPStan, Psalm, PHP_CodeSniffer | PHP-CS-Fixer |
| Scala | sdkman, coursier | sbt / Mill / Maven / Gradle | ScalaTest, MUnit, ZIO Test | Scalafix | Scalafmt |
| Dart | dart pub global | dart pub + pub.dev | dart test, flutter test | dart analyze | dart format |
| Elixir | asdf, mise | Mix + Hex | ExUnit | Credo, Dialyzer | mix format |
| Haskell | ghcup | Cabal / Stack + Hackage / Stackage | hspec, tasty, QuickCheck | HLint | ormolu, fourmolu, brittany |
| Clojure | (jvm) | deps.edn / Leiningen + Clojars | clojure.test, kaocha | clj-kondo | cljfmt, zprint |
| [[Languages/fsharp|F#]] | dotnet | NuGet | dotnet test (xUnit), Expecto | F# analyzers | Fantomas |
| Lua | (luajit/lua install) | LuaRocks | busted | luacheck | StyLua |
| R | renv, rig | install.packages + CRAN/Bioconductor | testthat | lintr | styler |
| Julia | juliaup | Pkg + General registry | Test stdlib, Aqua.jl | JET.jl, ExplicitImports.jl | JuliaFormatter.jl |
| Zig | (zig install) | build.zig + zon files | zig build test | (built into compiler) | zig fmt |
| Nim | choosenim | Nimble | unittest, testament | nimble check | nimpretty |
| Crystal | asdf, mise | Shards | spec | Ameba | crystal tool format |
| OCaml | opam | opam + dune | alcotest, ppx_inline_test | merlin (LSP) | ocamlformat |
| Perl | plenv, perlbrew | cpanm + CPAN, carton | Test::More, Test2 | Perl::Critic | perltidy |
| Erlang | asdf, kerl | rebar3 + Hex.pm | EUnit, CommonTest | dialyzer, elvis | erlfmt |
| Racket | (racket install) | raco pkg | rackunit | (built-in) | (built-in) |
| Common Lisp | (impl install) | Quicklisp + ASDF | FiveAM, Parachute, Rove | SBCL warnings | (none std) |
| Scheme | impl-specific | impl-specific (eggs etc.) | impl-specific | impl-specific | impl-specific |
| Fortran | os pkg | FPM + Spack | FPM test, pFUnit | (compiler warnings) | findent, fprettify |
| COBOL | os pkg | none | (build script) | (compiler) | none |
| Ada | Alire | Alire (alr) | AUnit, GNATtest | GNATcheck, GNATprove | gnatpp |
| Pascal | os pkg | FPMake, Lazarus packages | fpcunit | (compiler warnings) | jcfsettings |
| Prolog | os pkg | SWI pack_install | plunit | (built-in) | (impl-specific) |
| Tcl | os pkg | TEA / Teapot / Tcllib | tcltest | tclchecker | indent (style/) |
| Groovy | sdkman | Grape (@Grab) / Gradle / Maven | Spock, JUnit | CodeNarc | (Gradle/Maven plugins) |
| Bash | os pkg | none | bats, shunit2 | shellcheck | shfmt |
| PowerShell | os installer | PSResourceGet (modern) / PSGet | Pester | PSScriptAnalyzer | PSScriptAnalyzer |
| SQL | (engine-specific) | none for the language; migration tools (Flyway, Liquibase, atlas) | (engine + dbunit/pgTAP) | sqlfluff | sqlfluff, pg_format |
| V | v up | vpm | v test | v vet | v fmt |
| Odin | git pull | (none/community pkg) | build via Odin code | (compiler warnings) | (community) |
| Roc | (roc install) | URL/hash imports (no central registry yet) | roc test | roc check | roc format |
| Gleam | asdf, mise | Mix-compatible Hex.pm | gleam test | (compiler) | gleam format |
| Pony | ponyup | corral | ponytest (built-in) | (compiler) | (none official) |
| Idris | (manual) | pack | idris2 --check | (compiler) | (none) |
| Lean | elan | Lake + Reservoir registry | Lake test targets | (kernel checks) | (none std) |
| Coq (Rocq) | opam | opam + Coq archive | dune | (kernel checks) | (none std) |
| Agda | (cabal/stack) | agda libraries | (none std) | (kernel checks) | (none std) |
| Smalltalk (Pharo) | (image installer) | Metacello + Iceberg | SUnit | (image lints) | (image-based) |
Decision rubric
What makes good tooling?
- Single source of truth for dependencies (one file, not five).
- Reproducible builds via lockfile + content-addressed cache.
- Fast incremental rebuilds (Cargo, esbuild, ts-go, mold linker).
- No global state pollution — projects are isolated (uv venvs, npm node_modules, cargo target).
- Easy publish path — one command, predictable URL.
- Cross-platform out of box (Cargo on Win/Mac/Linux/Wasm; Zig cross-compiles everywhere).
- Speaks your team’s other tools (git, CI, IDE, container builds).
Best-in-class examples to imitate:
- Cargo (Rust) — the gold standard for unification.
- Mix (Elixir) — the unsung gold standard. Same DX as Cargo for BEAM.
- uv (Python) — Astral’s resolver wrote the chapter on “10× faster” in the resolver space.
- Pkg (Julia) — sysimage caching is unique; project envs work cleanly.
gotoolchain — minimal-flag philosophy, but underpowered for some monorepo cases.- dotnet CLI — comprehensive; the XML project files are the wart.
Avoidable failure modes:
- Mixing build systems (Maven + Gradle) without a clear ownership rule.
- Per-developer global state (legacy CL Quicklisp, Python pre-pyenv).
- Missing lockfiles (Common Lisp, classic Lua, Bash).
- Network-dependent builds (no offline cache; many CI horror stories).
- “Vendor everything by copying source” without a tool to update it (early Odin, classic C).
Edge cases & nuances
- Python’s “10 ways to install” problem — finally won by
uv(Astral, written in Rust). pip remains the install backend; pyproject.toml + uv.lock the metadata. PEP 723 inline metadata gives single-file scripts pinning. - Java’s three-way split — Maven (declarative XML, plugin-rich, slow), Gradle (imperative, fast incremental, debugging hell), Mill (Scala-based challenger). Pick by team familiarity; Maven for libraries, Gradle for apps is the conventional choice.
- JS bundler turnover — esbuild (Go, fast) + Vite (uses esbuild dev, Rollup prod, winning), Rollup (libraries), webpack (legacy), Turbopack (Next.js), Bun (runtime+bundler). 2026 status: Vite + Vitest is the default for new projects.
- Rust’s
Cargo.lockrule — commit it for binaries, don’t commit it for libraries. Almost every other language gets this wrong. - Java’s Reproducible Builds — JEP 392 made javac builds reproducible. Few teams use it.
- Common Lisp’s Quicklisp model — single curated dist, monthly snapshots. Less Cathedral than CRAN, less Bazaar than crates.io. Ages well; not innovating.
- Lean’s Lake + Reservoir is the most modern of the theorem-prover toolchains.
- Zig’s
zig ccis a drop-in C/C++ compiler with cross-compilation built in. Effectively makes Zig a build tool for C.
Cross-references
For per-language detail, see each note’s Getting started (install + project layout) and Ecosystem (testing/doc tools) sections. The 51 individual notes are linked from _index.
Citations
- The Cargo Book: https://doc.rust-lang.org/cargo/
- Astral uv: https://docs.astral.sh/uv/
- Astral ruff: https://docs.astral.sh/ruff/
- PEP 518 (
pyproject.toml): https://peps.python.org/pep-0518/ - PEP 723 (single-file metadata): https://peps.python.org/pep-0723/
- Bundler / RubyGems: https://bundler.io/
- Composer: https://getcomposer.org/
- Maven Central: https://central.sonatype.com/
- Hex.pm: https://hex.pm/
- Lake (Lean): https://github.com/leanprover/lean4/tree/master/src/lake
- The 51 per-language notes in _index are the underlying source.