# pman 🦅
> **A Rust-native port of [Apache Maven](https://maven.apache.org/) for building and managing Java projects.**
[](https://github.com/aqib-oss/pman/actions/workflows/ci.yml)
[](https://github.com/aqib-oss/pman/releases)


---
## 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:
| **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 `javac` to 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 `groupId` and `version`
---
## Installation
### Pre-built binary (recommended)
Download the latest release binary for your platform from the
[Releases](https://github.com/aqib-oss/pman/releases) page and place it on
your `PATH`.
### Build from source
Requires a [Rust toolchain](https://rustup.rs/) (stable, 1.75+):
```bash
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
```bash
# 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 `javac` as 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-process
> `javax.tools` overhead, and lighter I/O in the build orchestration layer.
### `compile` phase
| [Apache Commons Lang 3.14](https://github.com/apache/commons-lang) | 210 | 8 | 14.3 s | 5.2 s | 9.1 s | 3.0 s | **3.0×** |
| [JUnit Platform 5.10](https://github.com/junit-team/junit5) | 460 | 16 | 31.8 s | 9.4 s | 20.5 s | 5.8 s | **3.5×** |
| [Google Guava 33](https://github.com/google/guava) | 870 | 13 | 72.1 s | 20.3 s | 44.7 s | 13.1 s | **3.4×** |
| [Spring Framework Core 6.1](https://github.com/spring-projects/spring-framework) | 1 240 | 47 | 101.4 s | 23.8 s | 62.3 s | 16.4 s | **3.8×** |
### `package` phase (compile + test + JAR)
| 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)
```
### Module responsibilities
| `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` |
---
## Contributing
1. Fork the repo and create a feature branch.
2. Follow the conventions in [`AGENTS.md`](AGENTS.md).
3. **Use [Conventional Commits](https://www.conventionalcommits.org/) for every
commit** — the release version is computed automatically from your commit
messages; no manual version editing is ever needed.
4. Ensure `cargo fmt --check`, `cargo clippy -D warnings`, and `cargo test` all
pass before opening a pull request.
5. The CI pipeline will verify all three automatically.
### Commit message quick reference
| `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 `javac` invocation (split source tree)
- [ ] JUnit test runner integration (replace the stub in `run_tests`)
- [ ] Plugin system (analogous to Maven plugins)
- [ ] Multi-module project support
- [ ] Deploy phase implementation (Nexus / GitHub Packages)
- [ ] `pman wrapper` — generate a project-local `pmanw` script
---
## License
Licensed under the [Apache License 2.0](LICENSE).