/
classpath.clj
604 lines (546 loc) · 27.5 KB
/
classpath.clj
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
(ns leiningen.core.classpath
"Calculate project classpaths by resolving dependencies via Aether."
(:require [cemerick.pomegranate.aether :as aether]
[cemerick.pomegranate :as pomegranate]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.set :as set]
[leiningen.core.user :as user]
[leiningen.core.utils :as utils]
[leiningen.core.pedantic :as pedantic])
(:import (java.util.jar JarFile)
(org.eclipse.aether.resolution DependencyResolutionException)))
(defn- warn [& args]
;; TODO: remove me once #1227 is merged
(require 'leiningen.core.main)
(apply (resolve 'leiningen.core.main/warn) args))
(def ^:private warn-once (memoize warn))
(defn ^:deprecated extract-native-deps [files native-path native-prefixes]
(doseq [file files
:let [native-prefix (get native-prefixes file "native/")
jar (try (JarFile. file)
(catch Exception e
(throw (Exception. (format "Problem opening jar %s" file) e))))]
entry (enumeration-seq (.entries jar))
:when (.startsWith (.getName entry) native-prefix)]
(let [f (io/file native-path (subs (.getName entry) (count native-prefix)))]
(if (.isDirectory entry)
(utils/mkdirs f)
(do (utils/mkdirs (.getParentFile f))
(io/copy (.getInputStream jar entry) f))))))
(defn extract-native-dep!
"Extracts native content into the native path. Returns true if at least one
file was extracted."
[native-path file native-prefix]
(let [native? (volatile! false)
native-prefix (or native-prefix "native/")
jar (try (JarFile. file)
(catch Exception e
(throw (Exception. (format "Problem opening jar %s" file) e))))]
(doseq [entry (enumeration-seq (.entries jar))
:when (.startsWith (.getName entry) native-prefix)]
(vreset! native? true)
(let [f (io/file native-path (subs (.getName entry) (count native-prefix)))]
(if (.isDirectory entry)
(utils/mkdirs f)
(do (utils/mkdirs (.getParentFile f))
(io/copy (.getInputStream jar entry) f)))))
@native?))
(defn- stale-extract-native-deps
"Extract native dependencies by comparing what has already been extracted to
avoid redoing work. If stale files end up in some native path, new or old, the
user will receive a warning -- we cannot delete files as the native path may
be used for other things as well (or stuff generated earlier may be put in
there)."
[{old-deps :dependencies
old-native-path :native-path} new-raw-deps relative-native-path native-path]
(let [renamed-old-deps (utils/map-vals
old-deps
#(set/rename-keys % {:vsn :old-vsn
:native-prefix :old-native-prefix
:native? :old-native?}))
renamed-new-raw-deps (utils/map-vals
new-raw-deps
#(set/rename-keys % {:vsn :new-vsn
:native-prefix :new-native-prefix
:file :new-file}))
join (merge-with merge renamed-old-deps renamed-new-raw-deps)
new-native-path? (and old-native-path
(not= (io/file old-native-path) (io/file relative-native-path)))
maybe-stale (volatile! false)]
;; Why all the warnings? Well, we cannot really delete a directory, as it
;; may be populated by things created by others, either in a prep-step
;; before us OR as manual work (adding some self-made native stuff into
;; said). :native-path does not have to be in :target, which makes this
;; stuff kind of hard to avoid. However, it is likely that this path is in
;; the :target-path, which makes life easier for us. TODO: Fix this up for
;; Lein 3.0 by enforcing native to be inside :target-path and stating that
;; it should only be used by native deps.
(when new-native-path?
(warn-once "Warning: You changed :native-path to" (pr-str relative-native-path)
", but old native data is still available at" (pr-str old-native-path))
(vreset! maybe-stale true)
(doseq [[_ {:keys [native-prefix file]}] new-raw-deps]
(extract-native-dep! native-path file native-prefix)))
(let [newly-extracted-deps
(->>
(for [[dep {:keys [old-vsn old-native-prefix old-native?
new-vsn new-native-prefix new-file]}] join]
(cond (and (= old-vsn new-vsn) ;; no change, stuff already in directory
(= old-native-prefix new-native-prefix))
[dep {:vsn old-vsn
:native-prefix old-native-prefix
:native? old-native?}]
(nil? old-vsn) ;; no old version, attempt to extract
(let [native? (extract-native-dep! native-path new-file new-native-prefix)]
[dep {:vsn new-vsn
:native-prefix new-native-prefix
:native? native?}])
(nil? new-vsn) ;; dependency was removed
(when (and (not new-native-path?) old-native?)
(vreset! maybe-stale true)
(warn-once "Warning:" dep old-vsn "will still have its native content in :native-path"))
;; prefix changed (possibly version as well)
(not= old-native-prefix new-native-prefix)
(let [native? (extract-native-dep! native-path new-file new-native-prefix)]
(when (and (not new-native-path?) old-native?)
(vreset! maybe-stale true)
(warn-once "Warning:" dep "had its native prefix changed, but content"
"from" (pr-str old-native-prefix) "is still in :native-path"))
[dep {:vsn new-vsn
:native-prefix new-native-prefix
:native? native?}])
;; version changed (all options are now exhausted)
(not= old-vsn new-vsn)
(let [native? (extract-native-dep! native-path new-file new-native-prefix)]
(when (and (not new-native-path?) old-native?)
(vreset! maybe-stale true)
(warn-once "Warning: Native dependencies from the old version of"
dep (str "(" old-vsn ") is still in :native-path")))
[dep {:vsn new-vsn
:native-prefix new-native-prefix
:native? native?}])))
(filter identity)
(into {}))]
(when @maybe-stale
(warn-once " Consider doing `lein clean` to remove potentially stale native files"))
{:native-path relative-native-path
:dependencies newly-extracted-deps})))
(defn- read-string-or-error
"Like read-string, but will return ::error if the reader threw an error."
[s]
(try
(read-string s)
(catch Exception e
::error)))
(defn outdated-swap!
"Performs f if cmp-val is not equal to the old compare value. f is
then called with (f outdated-val args...). If no previous cached
result is found, then outdated-val is set to nil.
The comparison value and cached value is stored
in :target-path/stale/`identifier`. Make sure your identifier is
unique, e.g. by providing your namespace and function name. The
values will be read through read-string and printed with pr-str.
outdated-swap! will not run outside of projects."
[project identifier cmp-val f & args]
(when (and (:root project) (:target-path project))
(let [file (io/file (:target-path project) "stale" identifier)
file-content (if (.exists file)
(read-string-or-error (slurp file)))
[old-cmp-val outdated-val] (if (not= ::error file-content)
file-content)]
(when (= ::error file-content)
(warn-once "Could not read the old stale value for" identifier ", rerunning stale task"))
(when (or (= ::error file-content)
(not= old-cmp-val cmp-val))
(utils/mkdirs (.getParentFile file))
(let [result (apply f outdated-val args)]
(spit file (pr-str [cmp-val result]))
result)))))
(defn ^:deprecated when-stale
"DEPRECATED: Use outdated-swap! instead.
Call f with args when keys in project.clj have changed since the last
run. Stores value of project keys in stale directory inside :target-path.
Because multiple callers may check the same keys, you must also provide a
token to keep your stale value separate. Returns true if the code was executed
and nil otherwise."
[token keys project f & args]
(warn-once "leiningen.core.classpath/when-stale is deprecated, use outdated-swap! instead.")
(let [file (io/file (:target-path project) "stale"
(str (name token) "." (str/join "+" (map name keys))))
current-value (pr-str (map (juxt identity project) keys))
old-value (and (.exists file) (slurp file))]
(when (and (:name project) (:target-path project)
(not= current-value old-value))
(apply f args)
(utils/mkdirs (.getParentFile file))
(spit file (doall current-value))
true)))
;; The new version of aether (lein 2.8.0+) is more strict about authentication
;; settings; it will ignore :passphrase if :private-key-file is not set.
;; s3-wagon-private uses :passphrase, so if you've got a private s3 repo, we'll
;; set :private-key-file to an empty string here so that aether will allow the
;; wagon to see it instead of silently discarding it.
(defn- hack-private-key [repo]
(if-not (re-find #"^s3p" (:url repo ""))
repo
(update repo :private-key-file #(or % ""))))
(defn add-repo-auth
"Repository credentials (a map containing some of
#{:username :password :passphrase :private-key-file}) are discovered
from:
1. Looking up the repository URL in the ~/.lein/credentials.clj.gpg map
2. Scanning that map for regular expression keys that match the
repository URL.
So, a credentials map that contains an entry:
{#\"http://maven.company.com/.*\" {:username \"abc\" :password \"xyz\"}}
would be applied to all repositories with URLs matching the regex key
that didn't have an explicit entry."
[[id repo]]
[id (-> repo user/profile-auth user/resolve-credentials hack-private-key)])
(defn get-non-proxy-hosts []
(let [system-no-proxy (System/getenv "no_proxy")
lein-no-proxy (System/getenv "http_no_proxy")]
(if (and (empty? lein-no-proxy) (not-empty system-no-proxy))
(->> (str/split system-no-proxy #",")
(map #(str "*" %))
(str/join "|"))
(System/getenv "http_no_proxy"))))
(defn get-proxy-settings
"Returns a map of the JVM proxy settings"
([] (get-proxy-settings "http_proxy"))
([key]
(let [proxy (System/getenv key)]
(when-not (str/blank? proxy)
(let [url (utils/build-url proxy)
user-info (.getUserInfo url)
[username password] (and user-info (.split user-info ":"))]
{:host (.getHost url)
:port (.getPort url)
:username username
:password password
:non-proxy-hosts (get-non-proxy-hosts)})))))
(defn- update-policies [update checksum [repo-name opts]]
(let [project-policies (cond-> {}
update (assoc :update update)
checksum (assoc :checksum checksum))]
[repo-name (merge project-policies opts)]))
(defn ^:internal default-aether-args
"Returns a map of keyword arguments to be used with Pomegranate Aether
dependency resolution for the given project."
[{:keys [repositories local-repo offline? update checksum mirrors] :as project}]
{:local-repo local-repo
:offline? offline?
:repositories (->> repositories
(map add-repo-auth)
(map (partial update-policies update checksum)))
:mirrors (->> mirrors
(map add-repo-auth)
(map (partial update-policies update checksum)))
:proxy (get-proxy-settings)})
(defn- print-failures [e]
(doseq [result (.getArtifactResults (.getResult e))
:when (not (.isResolved result))
exception (.getExceptions result)]
(warn (.getMessage exception)))
(doseq [ex (.getCollectExceptions (.getResult e))]
(warn (.getMessage ex))))
(defn- root-cause [e]
(last (take-while identity (iterate (memfn getCause) e))))
(def ^:private ^:dynamic *dependencies-session*
"This is dynamic in order to avoid memoization issues.")
(defn- get-dependencies*
[dependencies-key managed-dependencies-key
{:keys [offline?] :as project}
{:keys [add-classpath?] :as args}]
{:pre [(every? vector? (get project dependencies-key))
(every? vector? (get project managed-dependencies-key))]}
(try
(apply
(if add-classpath?
pomegranate/add-dependencies
aether/resolve-dependencies)
(apply concat
(merge
(default-aether-args project)
{:managed-coordinates (get project managed-dependencies-key)
:coordinates (get project dependencies-key)
:repository-session-fn *dependencies-session*
:transfer-listener
(bound-fn [e]
(let [{:keys [type resource error]} e
{:keys [repository name size trace]} resource
aether-repos (if trace (.getRepositories (.getData trace)))
find-repo #(or (= (.getUrl %) repository)
;; sometimes the "base" url
;; doesn't have a slash on it
(= (str (.getUrl %) "/") repository))]
(when-let [repo (and (= type :started)
(first (filter find-repo aether-repos)))]
(locking *err*
(warn "Retrieving" name "from" (.getId repo))))))})))
(catch DependencyResolutionException e
;; Cannot recur from catch/finally so have to put this in its own defn
(print-failures e)
(warn "This could be due to a typo in :dependencies, file system permissions, or network issues.")
(warn "If you are behind a proxy, try setting the 'http_proxy' environment variable.")
(throw (ex-info "Could not resolve dependencies" {:suppress-msg true
:exit-code 1} e)))
(catch Exception e
(let [exception-cause (root-cause e)]
(if (and (or (instance? java.net.UnknownHostException exception-cause)
(instance? java.net.NoRouteToHostException exception-cause))
(not offline?))
(get-dependencies* dependencies-key managed-dependencies-key
(assoc project :offline? true) args)
(throw e))))))
(def ^:private get-dependencies-memoized (memoize get-dependencies*))
(defn ^:internal get-dependencies [dependencies-key managed-dependencies-key
project & args]
(let [ranges (atom []), overrides (atom [])
trimmed (select-keys project [dependencies-key managed-dependencies-key
:repositories :checksum :local-repo
:offline? :update :mirrors :memoize-buster])
deps-result (binding [*dependencies-session* (pedantic/session
project ranges overrides)]
(get-dependencies-memoized dependencies-key
managed-dependencies-key
trimmed (apply hash-map args)))]
(pedantic/do (:pedantic? project) @ranges @overrides)
deps-result))
(defn- get-original-dependency
"Return a match to dep (a single dependency vector) in
dependencies (a dependencies vector, such as :dependencies in
project.clj). Matching is done on the basis of the group/artifact id
and version."
[dep dependencies]
(some (fn [v] ; not certain if this is the best matching fn
(when (= (subvec dep 0 2) (subvec v 0 2 )) v))
dependencies))
(defn get-native-prefix
"Return the :native-prefix of a dependency vector, or nil."
[[id version & {:as opts}]]
(get opts :native-prefix))
(defn native-dependency-info
"Returns the dependency information about a dependency on the form
[id version native-prefix] if the dependency is not nil."
[dependency]
(if dependency
(let [[id version & {:as opts}] dependency]
[id version (get opts :native-prefix)])))
(defn- native-dependency-map
"Given a dependencies vector (such as :dependencies in project.clj) and a
dependencies tree, as returned by get-dependencies, return a map from
dependency identifier to :vsn, :file and :native-prefix (may be nil) for ALL
dependencies this project depends on -- including transitive ones."
[dependencies dependencies-tree]
(let [native-dep-info (->> (map #(or (get-original-dependency % dependencies) %)
(keys dependencies-tree))
(map native-dependency-info))]
(->> (aether/dependency-files dependencies-tree)
(#(map vector % native-dep-info))
(filter #(re-find #"\.(jar|zip)$" (.getName (first %))))
(map (fn [[file [id version native-prefix]]]
[id {:file file :vsn version :native-prefix native-prefix}]))
(into {}))))
(defn- extract-native-dependencies
"extract-native-dependencies calculates the native dependency map for all
dependencies, including transitive ones. It then extracts new native content
from native dependencies,"
[{:keys [native-path dependencies] :as project} jars dependencies-tree]
;; FIXME: This is a hack for a bug I noticed (issue #2077): Sometimes the
;; project comes through without having an init-profiles call ran on it. This
;; means that native-path is on an uninitialised form. The sane way to fix
;; this up is to check if this is the case, and if so, just ignore it. (Don't
;; worry, this call is done a ton of times)
;; To check this, just check if the path is absolute:
(when (and native-path (.isAbsolute (io/file native-path)))
(let [relative-native-path (utils/relativize (:root project) native-path)
native-dep-map (native-dependency-map dependencies dependencies-tree)
snap-deps (utils/filter-vals native-dep-map
#(.endsWith (:vsn %) "SNAPSHOT"))
stale-check {:dependencies (utils/map-vals native-dep-map
#(select-keys % [:vsn :native-prefix]))
:native-path relative-native-path}]
(or (outdated-swap!
project "leiningen.core.classpath.extract-native-dependencies"
stale-check
stale-extract-native-deps
native-dep-map
relative-native-path
native-path)
;; Always extract native deps from SNAPSHOT deps.
(doseq [[_ {:keys [native-prefix file]}] snap-deps]
(extract-native-dep! native-path file native-prefix))))))
(def ^:private bootclasspath-deps
(if-let [deps-file (io/resource "leiningen/bootclasspath-deps.clj")]
(read-string (slurp deps-file))
{}))
(defn- warn-conflicts
"When using the bootclasspath (for boot speed), resources already on the
bootclasspath cannot be overridden by plugins, so notify the user about it."
[project dependencies]
(when (#{:warn :abort} (:pedantic? project))
(let [warned (atom false)]
(doseq [[artifact version] dependencies
:when (and (bootclasspath-deps artifact)
(= :leiningen (:eval-in project))
(not= (bootclasspath-deps artifact) version))]
(reset! warned true)
(warn-once "Tried to load" artifact "version" version "but"
(bootclasspath-deps artifact) "was already loaded."))
(when (and @warned
(not (:root project))
(not (:suppress-conflict-warnings project)))
(warn-once "You can set :eval-in :subprocess in your :user profile;"
"however this will increase repl load time.")))))
(defn resolve-managed-dependencies
"Delegate dependencies to pomegranate. This will ensure they are
downloaded into ~/.m2/repository and that native components of
dependencies have been extracted to :native-path. If :add-classpath?
is logically true, will add the resolved dependencies to Leiningen's
classpath.
Supports inheriting 'managed' dependencies, e.g. to allow common dependency
versions to be specified from an alternate location in the project file, or
from a parent project file.
Returns a seq of the dependencies' files."
[dependencies-key managed-dependencies-key project & rest]
(let [dependencies-tree (apply get-dependencies dependencies-key
managed-dependencies-key project rest)
jars (->> dependencies-tree
(aether/dependency-files)
(filter #(re-find #"\.(jar|zip)$" (.getName %))))]
(when (some #{:add-classpath?} rest)
(warn-conflicts project (concat (keys dependencies-tree)
(reduce into (vals dependencies-tree)))))
(when (and (= :dependencies dependencies-key)
(:root project))
(extract-native-dependencies project jars dependencies-tree))
jars))
(defn ^:deprecated resolve-dependencies
"Delegate dependencies to pomegranate. This will ensure they are
downloaded into ~/.m2/repository and that native components of
dependencies have been extracted to :native-path. If :add-classpath?
is logically true, will add the resolved dependencies to Leiningen's
classpath.
Returns a seq of the dependencies' files.
NOTE: deprecated in favor of `resolve-managed-dependencies`."
[dependencies-key project & rest]
(let [managed-dependencies-key (if (= dependencies-key :dependencies)
:managed-dependencies)]
(apply resolve-managed-dependencies dependencies-key managed-dependencies-key project rest)))
(defn normalize-dep-vector
"Normalize the vector for a single dependency, to ensure it is compatible with
the format expected by pomegranate. The main purpose of this function is to
to detect the case where the version string for a dependency has been omitted,
due to the use of `:managed-dependencies`, and to inject a `nil` into the
vector in the place where the version string should be."
[dep]
;; Some plugins may replace a keyword with a version string later on, so
;; assume that even length vectors are alright. If not, then they will blow up
;; at a later stage.
(if (even? (count dep))
dep
(let [id (first dep)
opts (rest dep)]
;; it's important to preserve the metadata, because it is used for
;; profile merging, etc.
(with-meta
(into [id nil] opts)
(meta dep)))))
(defn normalize-dep-vectors
"Normalize the vectors for the `:dependencies` section of the project. This
ensures that they are compatible with the format expected by pomegranate.
The main purpose of this function is to to detect the case where the version
string for a dependency has been omitted, due to the use of `:managed-dependencies`,
and to inject a `nil` into the vector in the place where the version string
should be."
[deps]
(map normalize-dep-vector deps))
(defn merge-versions-from-managed-coords
[deps managed-deps]
(aether/merge-versions-from-managed-coords
(normalize-dep-vectors deps)
managed-deps))
(defn managed-dependency-hierarchy
"Returns a graph of the project's dependencies.
Supports inheriting 'managed' dependencies, e.g. to allow common dependency
versions to be specified from an alternate location in the project file, or
from a parent project file."
[dependencies-key managed-dependencies-key project & options]
(if-let [deps-list (merge-versions-from-managed-coords
(get project dependencies-key)
(get project managed-dependencies-key))]
(aether/dependency-hierarchy deps-list
(apply get-dependencies dependencies-key
managed-dependencies-key
project options))))
(defn dependency-hierarchy
"Returns a graph of the project's dependencies."
[dependencies-key project & options]
(apply managed-dependency-hierarchy dependencies-key nil project options))
(defn- normalize-path [root path]
(let [f (io/file path) ; http://tinyurl.com/ab5vtqf
abs (.getAbsolutePath (if (or (.isAbsolute f)
(.startsWith (.getPath f) "\\"))
f (io/file root path)))
sep (System/getProperty "path.separator")]
(str/replace abs sep (str "\\" sep))))
(defn ext-dependency?
"Should the given dependency be loaded in the extensions classloader?"
[dep]
(second
(some #(if (= :ext (first %)) dep)
(partition 2 dep))))
(defn ext-classpath
"Classpath of the extensions dependencies in project as a list of strings."
[project]
(seq
(->> (filter ext-dependency? (:dependencies project))
(assoc project :dependencies)
(resolve-managed-dependencies :dependencies :managed-dependencies)
(map (memfn getAbsolutePath)))))
(defn- visit-project!
"Records a visit to a project into the volatile `seen`, returning nil if the project has already been visited."
[seen {:keys [root] :as project}]
(when-not (@seen root)
(vswap! seen conj root)
project))
(defn- project-paths
"Returns a function that applies each function in checkout-paths to a given project and returns a flattened list of
classpath entries."
[checkout-paths]
(if (seq checkout-paths)
(comp flatten (apply juxt checkout-paths))
(constantly nil)))
(def ^:internal ^:dynamic *seen* nil)
(defn ^:internal checkout-deps-paths
"Checkout dependencies are used to place source for a dependency
project directly on the classpath rather than having to install the
dependency and restart the dependent project."
[{:keys [checkout-deps-shares root] :as project}]
(require 'leiningen.core.project)
(try
;; This function needs to be re-entrant as it is one of the default members of `:checkout-deps-shares`.
;; Use *seen* to communicate visit state between invocations.
(binding [*seen* (or *seen* (volatile! #{root}))]
;; Visit each project and accumulate classpaths into a vector. This cannot be lazy as *seen* must be bound.
(into []
(comp (keep (partial visit-project! *seen*))
(mapcat (project-paths checkout-deps-shares)))
((resolve 'leiningen.core.project/read-checkouts) project)))
(catch Exception e
(throw (Exception. (format "Problem loading %s checkouts" project) e)))))
(defn get-classpath
"Return the classpath for project as a list of strings."
[project]
(for [path (concat (:test-paths project)
(:source-paths project)
(:resource-paths project)
[(:compile-path project)]
(checkout-deps-paths project)
(for [dep (resolve-managed-dependencies
:dependencies :managed-dependencies project)]
(.getAbsolutePath dep)))
:when path]
(normalize-path (:root project) path)))