File: 01-user-guide.adoc

package info (click to toggle)
pomegranate-clojure 1.2.24-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 636 kB
  • sloc: xml: 135; sh: 37; makefile: 17
file content (303 lines) | stat: -rw-r--r-- 12,926 bytes parent folder | download
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")}})
----