# pman 🦅

> **A Rust-native port of [Apache Maven](https://maven.apache.org/) for building and managing Java projects.**

[![CI](https://github.com/aqib-oss/pman/actions/workflows/ci.yml/badge.svg)](https://github.com/aqib-oss/pman/actions/workflows/ci.yml)
[![Release](https://github.com/aqib-oss/pman/actions/workflows/main.yml/badge.svg)](https://github.com/aqib-oss/pman/releases)
![Rust](https://img.shields.io/badge/language-Rust-orange)
![License](https://img.shields.io/github/license/aqib-oss/pman)

---

## 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 `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

| Project | Source Files | Deps | Maven (cold) | pman (cold) | Maven (warm) | pman (warm) | Speedup (warm) |
|---------|:------------:|:----:|:------------:|:------------:|:------------:|:------------:|:--------------:|
| [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)

| 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

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

| 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

- [x] Parallel `javac` invocation (split source tree)
- [x] JUnit test runner integration (Test phase wired through `crate::test_runner::run_junit_tests`)
- [x] 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).
