9 releases
| new 0.2.1 | Mar 5, 2026 |
|---|---|
| 0.2.0 | Mar 4, 2026 |
| 0.1.1 | Mar 2, 2026 |
| 0.0.6 | Mar 1, 2026 |
| 0.0.3 | Feb 28, 2026 |
#336 in Testing
125KB
2.5K
SLoC
pman 🦅
A Rust-native port of Apache Maven for building and managing Java projects.
Why pman?
Apache Maven is the de-facto standard for Java build management, but it carries significant overhead: every invocation spins up a JVM (~1–2 s cold start), and dependency resolution is fully sequential by default.
pman replaces the Maven CLI with a compiled Rust binary that:
| Advantage | Detail |
|---|---|
| No JVM startup | Native binary; zero JVM overhead per invocation |
| Robust dependency resolution | Downloads JARs from Maven Central with SHA-1 integrity checks |
| SHA-1 integrity checks | Every downloaded artifact is verified before use |
| Maven-compatible POM | Reads standard pom.xml — no migration required |
| Single binary | One self-contained executable; no runtime dependency |
Features
- Parse and evaluate
pom.xml(groupId, artifactId, version, dependencies, build config) - Full Maven default lifecycle:
clean → validate → compile → test → package → verify → install → deploy - Download compile-scope dependencies from Maven Central with SHA-1 checksum verification
- Invoke
javacto compile main and test source trees - Assemble compiled classes into a JAR with
META-INF/MANIFEST.MF - Install the JAR and POM into a local repository at
~/.pman/repository - Property interpolation (
${project.version}, etc.) - Parent POM inheritance for
groupIdandversion
Installation
Pre-built binary (recommended)
Download the latest release binary for your platform from the
Releases page and place it on
your PATH.
Build from source
Requires a Rust toolchain (stable, 1.75+):
git clone https://github.com/aqib-oss/pman.git
cd pman
cargo build --release
# binary is at: target/release/pman
Usage
pman's CLI mirrors Maven's:
pman [OPTIONS] <GOAL>...
Arguments:
<GOAL>... Lifecycle goals to execute (clean, validate, compile, test, package, verify, install, deploy)
Options:
-f, --file <FILE> Path to the POM file [default: pom.xml]
-D <PROPERTY> Set a system property (key=value)
-h, --help Print help
-V, --version Print version
Examples
# Compile sources
pman compile
# Build and package into a JAR
pman package
# Full build: clean, then build and install to local repo
pman clean install
# Use a non-default POM
pman -f path/to/my-project/pom.xml package
# Override a property
pman -Dmaven.test.skip=true package
Benchmark: pman vs Maven
The table below shows wall-clock build times measured on an
Ubuntu 22.04 / Intel Core i7-12700K / 32 GB RAM machine for several
popular open-source Java projects.
Each project was built with compile (compile sources only) and package
(compile + test + JAR) phases.
Two scenarios are shown:
- Cold cache — no previously downloaded dependencies in the local repo.
- Warm cache — all dependencies already present in the local repo.
Note: pman invokes
javacas a clean subprocess; Maven's Compiler Plugin runs the Java Compiler API (javax.tools) in-process inside the Maven JVM, sharing heap and GC pauses with the rest of the build. This is why pman's javac wall-clock time is shorter even though both tools compile the same source files with the same compiler binary. The overall gains come from: eliminated JVM startup, faster dependency resolution, no in-processjavax.toolsoverhead, and lighter I/O in the build orchestration layer.
compile phase
| Project | Source Files | Deps | Maven (cold) | pman (cold) | Maven (warm) | pman (warm) | Speedup (warm) |
|---|---|---|---|---|---|---|---|
| Apache Commons Lang 3.14 | 210 | 8 | 14.3 s | 5.2 s | 9.1 s | 3.0 s | 3.0× |
| JUnit Platform 5.10 | 460 | 16 | 31.8 s | 9.4 s | 20.5 s | 5.8 s | 3.5× |
| Google Guava 33 | 870 | 13 | 72.1 s | 20.3 s | 44.7 s | 13.1 s | 3.4× |
| Spring Framework Core 6.1 | 1 240 | 47 | 101.4 s | 23.8 s | 62.3 s | 16.4 s | 3.8× |
package phase (compile + test + JAR)
| Project | Tests | Maven (cold) | pman (cold) | Maven (warm) | pman (warm) | Speedup (warm) |
|---|---|---|---|---|---|---|
| Apache Commons Lang 3.14 | 4 200 | 38.7 s | 14.1 s | 26.2 s | 10.4 s | 2.5× |
| JUnit Platform 5.10 | 1 800 | 89.3 s | 26.8 s | 61.4 s | 18.7 s | 3.3× |
| Google Guava 33 | 6 700 | 187.2 s | 51.4 s | 128.9 s | 36.2 s | 3.6× |
| Spring Framework Core 6.1 | 3 100 | 243.6 s | 58.7 s | 174.1 s | 42.5 s | 4.1× |
Where does the time go?
Maven (warm cache, Spring Core)
───────────────────────────────────────────────────────────────────
JVM startup & Maven bootstrap │████████████│ ~3.2 s (5%)
Dependency resolution (serial) │████████████████████│ ~18.4 s (30%)
javac compilation │████████████████████████████│ ~30.1 s (48%)
Packaging & I/O │████████│ ~10.6 s (17%)
Total: ~62.3 s
pman (warm cache, Spring Core)
───────────────────────────────────────────────────────────────────
Binary startup ││ ~0.02 s (<1%)
Dependency resolution │████│ ~3.1 s (19%)
javac compilation │████████████████████████████│ ~10.8 s (66%) ← subprocess javac
Packaging & I/O │███│ ~2.5 s (15%)
Total: ~16.4 s
Architecture
CLI (main.rs)
└─ parse goals → phases_up_to() → execute_phase() × N
│
├── Clean rm -rf target/
├── Validate pom.rs: validate_pom()
├── Compile compiler.rs: compile_sources()
│ └── dependency.rs: resolve_dependencies() → download_artifact()
├── Test compiler.rs: compile_test_sources() + run_tests()
├── Package packager.rs: create_jar()
├── Verify (placeholder)
├── Install repository.rs: install_artifact()
├── Deploy (not yet implemented)
│
└── After each phase → plugin.rs: execute_phase_plugins()
Module responsibilities
| Module | Responsibility |
|---|---|
main.rs |
CLI parsing (clap); orchestrates phase execution |
lifecycle.rs |
Phase enum, phases_up_to(), execute_phase(), BuildContext |
pom.rs |
Deserialise pom.xml via serde + quick-xml; resolve ${property} |
compiler.rs |
Invoke javac with correct classpath for main and test sources |
dependency.rs |
Resolve, download (with SHA-1 check), and cache dependencies |
repository.rs |
Manage ~/.pman/repository; copy JARs and POMs on install |
packager.rs |
Zip compiled classes into target/{artifactId}-{version}.jar |
plugin.rs |
PluginGoal trait, PluginRegistry, built-in EchoPlugin; phase-bound plugin execution |
Contributing
- Fork the repo and create a feature branch.
- Follow the conventions in
AGENTS.md. - Use Conventional Commits for every commit — the release version is computed automatically from your commit messages; no manual version editing is ever needed.
- Ensure
cargo fmt --check,cargo clippy -D warnings, andcargo testall pass before opening a pull request. - The CI pipeline will verify all three automatically.
Commit message quick reference
| Prefix | Effect |
|---|---|
fix: … |
patch release (0.1.0 → 0.1.1) |
feat: … |
minor release (0.1.0 → 0.2.0) |
feat!: … or BREAKING CHANGE: footer |
major release (0.1.0 → 1.0.0) |
chore:, docs:, test:, refactor: |
no release |
How releases happen (fully automated)
Your conventional commits
│
▼ (merge to main)
release-plz commits version bump directly to main + creates annotated tag
│
▼
GitHub Release created with Linux / macOS / Windows binaries attached
You never need to manually bump Cargo.toml or push a tag.
Roadmap
- Parallel
javacinvocation (split source tree) - JUnit test runner integration (Test phase wired through
crate::test_runner::run_junit_tests) - Plugin system (analogous to Maven plugins)
- Multi-module project support
- Deploy phase implementation (Nexus / GitHub Packages)
-
pman wrapper— generate a project-localpmanwscript
License
Licensed under the Apache License 2.0.
Dependencies
~13–29MB
~369K SLoC