1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
= User Guide
:toclevels: 5
:toc:
// DO NOT EDIT: the lib-version parameter is automatically updated by bb publish
:lib-version: 1.2.24
== Audience
You want to learn more about how to use the Pomegranate library from your app or library.
== Introduction
Pomegranate is a library that provides:
1. A sane Clojure API for the https://maven.apache.org/resolver[Maven Artifact Resolver].
2. A re-implementation of Clojure core's deprecated https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/add-classpath[`add-classpath`] that:
** is a little more comprehensive, it should work as expected in more circumstances
** optionally uses the Maven Artifact Resolver to add a Maven artifact (and all of its transitive dependencies) to your Clojure runtime's classpath dynamically.
NOTE: When Pomegranate was created, the Maven Artifact Resolver was called Aether, so you'll see `aether` in our APIs and docs.
=== Interesting Alternatives
If Pomegranate is not your cup of tea, consider having a look at:
* https://github.com/clojure/tools.deps[clojure/tools.deps]
+
Like Pomegranate, Clojure's tools.deps resolves dependencies using the Maven Artifact Resolver.
Unlike Pomegranate, by design, it is focused on runtime dependencies only.
+
Clojure v1.12, in alpha at the time of this writing, includes support for https://clojure.org/news/2023/04/14/clojure-1-12-alpha2#_add_libraries_for_interactive_use[adding libraries at runtime].
* https://github.com/borkdude/deps.add-lib[borkdude/deps.add-lib]
+
Clojure v1.12's add-lib feature for leiningen and/or other environments without a specific version of the clojure CLI
* https://github.com/lambdaisland/classpath[com.lambdaisland.classpath]
+
Experimental utilities for dealing with "the classpath", and dynamically loading libraries.
=== History
* Oct 2011 - The first version of `cemerick/pomegranate` is released to Maven Central.
* Jan 2020 - Clj-commons adopts Pomegranate where it can get some ongoing love and care.
It makes its first release under `clj-commons/pomegranate` to Clojars.
== Installation
=== Leiningen `project.clj`
[source,clojure,subs="attributes+"]
----
[clj-commons/pomegranate "{lib-version}"]
----
=== Clojure CLI `deps.edn`
[source,clojure,subs="attributes+"]
----
clj-commons/pomegranate {:mvn/version "{lib-version}"}
----
=== As a Git Dependency
To get the latest changes that are not yet released to Clojars, you can use this library as a git dependency, for example:
[source,clojure]
----
$ cat deps.edn
{:deps {clj-commons/pomegranate {:git/url "https://github.com/clj-commons/pomegranate.git"
:git/sha "4db42b2091f363bff48cbb80bc5230c3afa598d9"}}}
----
Replace the `:git/sha` value as appropriate.
== Usage
=== Modifying the Classpath
To set the stage: you're at the REPL, and you've got some useful data that you'd like to munge and analyze in various ways.
Maybe it's something you've generated locally, maybe it's data on a production machine, and you're logged in via https://github.com/clojure/tools.nrepl[nREPL].
In any case, you'd like to work with the data, but realize that you don't have the libraries you need do what you want.
Your choices at this point are:
1. Dump the data to disk via `pr` (assuming it's just Clojure data structures!), and start up a new Clojure process with the appropriate libraries on the classpath.
This can really suck if the data is in a remote environment.
2. There is no second choice.
You _could_ use Clojure's deprecated `add-claspath`, but the libraries you want to add have 12 bajillion dependencies, and there's no way you're going to hunt them down manually.
Let's say we want to use https://github.com/liebke/incanter[Incanter].
Incanter has roughly 40 dependencies — far too many for us to reasonably locate and add via `add-classpath` manually:
[source,clojure]
----
user=> (require '[incanter core stats charts])
Execution error (FileNotFoundException) at user/eval1 (REPL:1).
Could not locate incanter/core__init.class, incanter/core.clj or incanter/core.cljc on classpath.
----
Looks bleak.
Assuming you've got Pomegranate on your classpath already, you can do this though:
[source,clojure]
----
user=> (require '[cemerick.pomegranate :as pom]
'[cemerick.pomegranate.aether :as aether])
nil
user=> (pom/add-dependencies :coordinates '[[incanter "1.9.2"]]
:repositories (merge aether/maven-central
{"clojars" "https://clojars.org/repo"}))
;...add-dependencies returns full dependency graph here...
user=> (require '[incanter core stats charts])
nil
----
Now you can analyze and chart away, Incanter having been added to your runtime.
Note that `add-dependencies` may crunch along for a while — it may need to download dependencies, so you're waiting on the network.
All resolved dependencies are stored in the default local maven repository (`~/.m2/repository`).
A dependency is only downloaded if it does not already exist in the local repository.
The arguments to `add-dependencies` look like Leiningen-style notation, and they are.
[TIP]
====
**There are a number of scenarios in which `add-dependencies` will not work, or will not work as you'd expect**.
Many of these are due to the nature of JVM classloaders (e.g. adding jars containing conflicting versions of a particular dependency will rarely end well), which Pomegranate does not currently attempt to hide.
Thus, `add-classpath` and `add-dependencies` should be considered escape hatches to be used when necessary, rather than a regular part of your development workflow.
====
=== Modifying the Classpath and JDK 9+
When Pomegranate was created, the JDK was amenable to inspecting and modifying class loaders.
This changed starting with JDK version 9.
Reflection API restrictions, modules, and encapsulation have given us less wiggle room.
Pomegranate `1.0.0` adapted to the new reality by no longer attempting to modify `java.net.URLClassLoader` instances via reflection.
Pomegranate now leans on the modifiability of `clojure.lang.DynamicClassLoader`.
As long as this classloader is available, we can modify the classpath.
If you find yourself in a situation where you want to use Pomegranate but have no dynamic classloader available, you might consider:
* creating your own modifiable classloader, per the https://github.com/tobias/dynapath#note-on-urlclassloader[dynapath README], https://github.com/boot-clj/boot/commit/a046a497a8bb7f3d1e7aa8d4db4a81c51beaef7d[like boot did].
* ensuring Clojure's dynamic classloader available like https://github.com/lambdaisland/kaocha/blob/7fb8134ecc2f282300c797efe83cd9fd105eb8b4/src/kaocha/classpath.clj#L11-L24[like kaocha did].
=== The Aether API
Here we go over some simple example usages to get your feet wet.
Please consult the API docs, they describe all available options.
==== Dependency Resolution
We'll do some setup in our REPL first:
[source,clojure]
----
(require '[cemerick.pomegranate.aether :as aether])
;; by default Pomegranate consults maven central, let's include clojars:
(alter-var-root #'aether/maven-central assoc "clojars" "https://repo.clojars.org")
;; => {"central" "https://repo1.maven.org/maven2/", "clojars" "https://repo.clojars.org"}
----
Let's try resolving an artifact:
[source,clojure]
----
(aether/resolve-artifacts :coordinates '[[metosin/malli "0.10.0"]])
;; => ([metosin/malli "0.10.0"])
----
Okay, not too exciting, maybe, but now let's resolve dependencies for that artifact:
[source,clojure]
----
(aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]])
;; => {[org.clojure/clojure "1.8.0"] nil,
;; [org.clojure/test.check "1.1.1"] nil,
;; [org.clojure/core.rrb-vector "0.1.2"] nil,
;; [fipp "0.6.26"] #{[org.clojure/clojure "1.8.0"] [org.clojure/core.rrb-vector "0.1.2"]},
;; [borkdude/edamame "1.0.0"] #{[org.clojure/tools.reader "1.3.4"]},
;; [metosin/malli "0.10.0"] #{[org.clojure/test.check "1.1.1"]
;; [fipp "0.6.26"]
;; [borkdude/edamame "1.0.0"]
;; [borkdude/dynaload "0.3.5"]
;; [mvxcvi/arrangement "2.0.0"]},
;; [org.clojure/tools.reader "1.3.4"] nil,
;; [borkdude/dynaload "0.3.5"] nil,
;; [mvxcvi/arrangement "2.0.0"] nil}
----
Interesting.
Also note that there are some details hiding in metadata:
[source,clojure]
----
(-> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]])
ffirst
((juxt identity meta)))
;; => [[org.clojure/clojure "1.8.0"]
;; {:dependency
;; #object[org.eclipse.aether.graph.Dependency 0x7e70e8a0 "org.clojure:clojure:jar:1.8.0 (compile)"],
;; :file
;; #object[java.io.File 0x501ed01a "/home/lee/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar"]}]
----
We can conveniently get to the `:file` info like so:
[source,clojure]
----
(->> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]])
aether/dependency-files
(map str))
;; => ("/home/lee/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar"
;; "/home/lee/.m2/repository/org/clojure/test.check/1.1.1/test.check-1.1.1.jar"
;; "/home/lee/.m2/repository/org/clojure/core.rrb-vector/0.1.2/core.rrb-vector-0.1.2.jar"
;; "/home/lee/.m2/repository/fipp/fipp/0.6.26/fipp-0.6.26.jar"
;; "/home/lee/.m2/repository/borkdude/edamame/1.0.0/edamame-1.0.0.jar"
;; "/home/lee/.m2/repository/metosin/malli/0.10.0/malli-0.10.0.jar"
;; "/home/lee/.m2/repository/org/clojure/tools.reader/1.3.4/tools.reader-1.3.4.jar"
;; "/home/lee/.m2/repository/borkdude/dynaload/0.3.5/dynaload-0.3.5.jar"
;; "/home/lee/.m2/repository/mvxcvi/arrangement/2.0.0/arrangement-2.0.0.jar")
----
Let's have Pomegranate express dependencies for malli using malli as the root dependency:
[source,clojure]
----
(->> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]])
(aether/dependency-hierarchy '[[metosin/malli "0.10.0"]]))
;; => {[metosin/malli "0.10.0"]
;; {[borkdude/dynaload "0.3.5"] nil,
;; [borkdude/edamame "1.0.0"] {[org.clojure/tools.reader "1.3.4"] nil},
;; [fipp "0.6.26"] {[org.clojure/clojure "1.8.0"] nil,
;; [org.clojure/core.rrb-vector "0.1.2"] nil},
;; [mvxcvi/arrangement "2.0.0"] nil,
;; [org.clojure/test.check "1.1.1"] nil}}
----
Cool!
==== Supporting other Protocols via Wagons
Out of the box, Pomegranate can communicate with maven repositories over HTTPS.
If you need to hit a maven repository that speaks some other protocol, you can do so via https://maven.apache.org/wagon/[Maven Wagon].
For example, by default, for security reasons, Pomegranate no longer has plain old unsecure HTTP support built available.
But, if you understand the risks (don't do this if you don't), and want to re-enable this support, you can do so by registering an HTTP wagon like so:
[source,clojure]
----
(aether/register-wagon-factory! "http" #(org.apache.maven.wagon.providers.http.HttpWagon.))
----
And now you can hit your unsecure HTTP maven repo too.
Maybe you are running a local instance for caching.
[source,clojure]
----
(aether/resolve-artifacts :coordinates '[[metosin/malli "0.10.0"]]
:repositories {"local-nexus" "http://localhost:8081/repository/maven-public"})
----
==== Deploying Artifacts
TIP: If you want a tool that does this well that uses the Pomegranate to do so, consider using https://github.com/slipset/deps-deploy[deps-deploy].
Fun fact: To deploy itself to clojars, Pomegranate uses deps-deploy, which uses Pomegranate.
===== To Local Maven Repo
Assuming `pom.xml` and `target/some-library.jar` files, exist:
[source,clojure]
----
(aether/install :coordinates '[lread/mucking-around "1.2.3"]
:jar-file (io/file "target" "some-library.jar")
:pom-file (io/file "pom.xml"))
----
After this completes, you'll see something like:
[source,shell]
----
$ tree ~/.m2/repository/lread/mucking-around
/home/lee/.m2/repository/lread/mucking-around
├── 1.2.3
│ ├── mucking-around-1.2.3.jar
│ ├── mucking-around-1.2.3.pom
│ └── _remote.repositories
└── maven-metadata-local.xml
1 directory, 4 files
----
===== To Remote Maven Repo
Assuming `pom.xml` and `target/some-library.jar`, exist, a deploy to clojars could look something like this:
[source,clojure]
----
(aether/deploy :coordinates '[lread/mucking-around "1.2.3"]
:jar-file (io/file "target" "some-library.jar")
:pom-file (io/file "pom.xml")
:repository {"clojars" {:url "https://repo.clojars.org"
:username (System/getenv "CLOJARS_USERNAME")
:password (System/getenv "CLOJARS_PASSWORD")}})
----
|