Metadata-Version: 2.4
Name: pants-backend-clojure
Version: 0.1.1
Summary: A Clojure backend for the Pants build system
Home-page: https://github.com/enragedginger/pants_backend_clojure
Author: Stephen Hopper
Author-email: stephenmhopper@gmail.com
License: Apache-2.0
Keywords: pantsbuild,pants,clojure,jvm,build-system
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: requires-python
Dynamic: summary

# pants-backend-clojure

A Clojure backend for the [Pants build system](https://www.pantsbuild.org/).

This plugin brings first-class Clojure support to Pants, enabling REPL-driven development, automatic dependency inference, linting, formatting, testing, and uberjar packaging within a monorepo-friendly build system.

## A handcrafted word from the author

Hello, yes, it's me, a real person. I'm writing this section to you, another real person.
Most markdown docs these days are generated by AI. This has its pros and cons. The advantages are obvious.
But among the disadvantages is an explosion of useless, overly verbose, impersonal information.

So I'm writing this section by myself to add a personal touch and help you, the human reviewing this project,
to gain immediate insight into what's going on with this project instead of having to clone it down and ask Claude to review it.

AI coding tools are great when used properly. I've found them particularly useful in situations where I want to tackle a project
in a domain that is largely unfamiliar to me, but I don't have the time to gain deep mastery of everything.
That's basically what's happened here. I've written a lot of Clojure code in my life. I've been working with the Pants build tool
for a few years now and I've written many internal plugins for it. This is my first attempt at creating something that has use
and value to the outside world. I've used Claude to write all of the code.

I'm "dogfooding" this project though and using that experience as my main method of verifying this project and driving it forward.
However, I'd very much welcome input from others who see value in this. Feel free to try it out and let me know what you think.
I have a project which is 35k lines of Clojure. It outgrew Leiningen sometime ago. I moved it to poly as that seemed to be the best
option for multi-module, multi-build Clojure project. However, I never really loved poly. The commands always felt foreign to me.
The errors were unhelpful. I ran into situations where it messed with my test classpath in unexplainable ways. The distinction
between "base" and "component" felt arbitrary. And the patterns around "interfaces" didn't sit well with me.

I've used the Pants build tool for a few years now with Python. I've loved the way it provides "resolves".
Every large software repo I've ever worked in has two problems:
1. Upgrading parts of the codebase either to new language runtime versions or new third party libraries results in hard or impossible to resolve
conflicts with other older, unrelated parts of the codebase. Resolving these issues is usually non-trivial. You typically either avoid upgrading
because the amount of resources required to upgrade isn't worth the hassle. Or you put hacks in place to test or compile your code against one version,
but in production you end up using a different version. This always leads to bugs and distrust in the codebase.
2. Unrelated projects end up depending on each other because dependencies must be specified explicitly and at the project level. A relatively minor, unrelated
changes in a common project at the bottom of your stack can result in cache misses for everything else in the repo.

Pants addresses both of these issues. Pants registers each source file as a target. It understands dependencies in your codebase at the target level. So changes in one file only mean that the other files / tests / builds with a transitive dependency on that file need cache misses.
It also provides "resolves" which you can think of as a combination of your target runtime (i.e. Java 21) and a lockfile of third party dependencies.
Every source target belongs to one or more resolves. Suppose you have some common source that needs to work with other first party sources in both Java 8 and Java 21 and against different versions of some way-too-common library like Guava. You'd assign this hypothetical source to both resolves. Its unit tests would run in both resolves. Bugs that might exist in one runtime, but not the other would be caught and surfaced quickly. Also, Pants has the ability to detect first and third party dependencies automatically. So you no longer have to look at a long list of project dependency lines and wonder which ones you can safely remove. Just specify nothing and let Pants figure it out for you.

This is all wonderful and I've felt for awhile now that it was the missing piece for large Clojure projects. So I went out and built it.
I'm using it now. I'm incredibly happy with the results.

Both first party and third party dependency inference work. After you run `pants generate-lockfiles`, Pants automatically analyzes the JARs in your lockfiles and infers third-party Clojure dependencies — no extra steps needed.

Anyhow, back to your regularly scheduled AI generated readme doc:

## Features

- **Dependency Inference**: Automatically discovers dependencies from `require` and `import` forms
- **REPL**: Interactive development with nREPL support and rebel-readline
- **Testing**: Run tests with `clojure.test` via `pants test`
- **Linting**: Static analysis with [clj-kondo](https://github.com/clj-kondo/clj-kondo)
- **Formatting**: Code formatting with [cljfmt](https://github.com/weavejester/cljfmt)
- **Packaging**: Build uberjars with AOT compilation and direct linking
- **JVM Integration**: Works with Pants' JVM support for mixed Clojure/Java projects
- **Provided Dependencies**: Maven-style provided scope for excluding runtime dependencies

## Installation

Add the plugin to your `pants.toml`:

```toml
[GLOBAL]
pants_version = "2.30.0"
plugins = ["pants-backend-clojure==0.1.0"]
backend_packages = [
    "pants.backend.experimental.java",
    "pants_backend_clojure",
]

[coursier]
repos = [
    "https://repo.clojars.org/",
    "https://repo1.maven.org/maven2",
]

[jvm.resolves]
jvm-default = "locks/jvm-default.lock"
```

## Quick Start

### 1. Define your dependencies

Create a `BUILD` file with your Clojure dependencies:

```python
jvm_artifact(
    name="clojure",
    group="org.clojure",
    artifact="clojure",
    version="1.12.0",
)

jvm_artifact(
    name="core-async",
    group="org.clojure",
    artifact="core.async",
    version="1.7.701",
)
```

### 2. Add source targets

```python
clojure_sources(
    name="lib",
    dependencies=["//:clojure"],
)

clojure_tests(
    name="tests",
    dependencies=["//:clojure"],
)
```

### 3. Generate the lockfile

```bash
pants generate-lockfiles
```

### 4. Run common commands

```bash
# Start a REPL
pants repl src/myapp:lib

# Run tests
pants test ::

# Lint code
pants lint ::

# Format code
pants fmt ::

# Check for errors
pants check ::

# Build an uberjar
pants package src/myapp:deploy
```

## Target Types

### `clojure_source` / `clojure_sources`

Source files containing application or library code.

```python
clojure_sources(
    name="lib",
    sources=["*.clj", "*.cljc"],
    dependencies=["//:clojure"],
    resolve="jvm-default",
)
```

### `clojure_test` / `clojure_tests`

Test files using `clojure.test`.

```python
clojure_tests(
    name="tests",
    sources=["*_test.clj"],
    dependencies=[":lib", "//:clojure"],
    timeout=120,
)
```

### `clojure_deploy_jar`

An executable uberjar with AOT compilation.

```python
clojure_deploy_jar(
    name="deploy",
    main="myapp.core",  # Namespace with -main and (:gen-class)
    dependencies=[":lib", "//:clojure"],
    provided=[":servlet-api"],  # Excluded from JAR
)
```

The main namespace must include `(:gen-class)` and define a `-main` function:

```clojure
(ns myapp.core
  (:gen-class))

(defn -main [& args]
  (println "Hello, World!"))
```

## Goals

| Goal | Command | Description |
|------|---------|-------------|
| REPL | `pants repl path/to:target` | Start an interactive nREPL session |
| Test | `pants test ::` | Run clojure.test tests |
| Lint | `pants lint ::` | Static analysis with clj-kondo |
| Format | `pants fmt ::` | Format code with cljfmt |
| Check | `pants check ::` | Verify code compiles |
| Package | `pants package path/to:jar` | Build an uberjar |

## Configuration

### Tool versions

Override default tool versions in `pants.toml`:

```toml
[clj-kondo]
version = "2025.10.23"

[cljfmt]
version = "0.14.0"

[nrepl]
version = "1.4.0"
```

### Skip linting/formatting per target

```python
clojure_source(
    name="generated",
    source="generated.clj",
    skip_cljfmt=True,
    skip_clj_kondo=True,
)
```

## System Requirements

- **Python**: 3.9+
- **Pants**: 2.30.0+
- **JDK**: 11+ (17 or 21 recommended)
- **zip/unzip**: Required for clj-kondo
  - macOS: Pre-installed
  - Debian/Ubuntu: `apt-get install zip unzip`
  - RHEL/Fedora: `dnf install zip unzip`
  - Alpine: `apk add zip unzip`

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

Apache License 2.0 - see [LICENSE](LICENSE) for details.
