1;;;; Use the Aether system packaged as jar files in a locally
2;;;; installed Maven3 distribution to download and install JVM
3;;;; artifact dependencies.
4
5
6#|
7
8# Implementation
9
10Not necessarily multi-threaded safe, and unclear how much work that
11would be, as it is unknown how the Maven implementation behaves.
12
13## Installing Maven
14http://maven.apache.org/download.html
15
16## Current Javadoc for Maven Aether connector
17http://sonatype.github.com/sonatype-aether/apidocs/overview-summary.html
18
19## Incomplete, seemingly often wrong
20https://docs.sonatype.org/display/AETHER/Home
21
22Note that this is not an implementation of Maven per se, but the use
23of the Maven Aether connector infrastructure.  Among other things, this means
24that the Maven specific "~/.m2/settings.xml" file is NOT parsed for settings.
25
26|#
27
28#|
29
30We aim to be compatible with the "current" version of Maven back to
31maven-3.0.4.  The necessary internals of Maven are messy, and not very
32well abstracted, especially in the earlier releases.  In maintaining
33this code over the past decade, it has been the case that entire APIs
34will disappear during what are advertised as "patchlevel" upgrades of
35Maven.
36
37
38|#
39
40;;; N.b. evaluated *after* we load the ABCL specific modifications of
41;;;      ASDF in abcl-asdf.lisp
42
43(in-package :abcl-asdf)
44
45(require :abcl-contrib)
46(require :jss)
47
48#|
49Test:
50(abcl-asdf:resolve "org.slf4j:slf4j-api:1.6.1")
51
52(abcl-asdf:resolve "org.apache.maven:maven-aether-provider:3.0.4")
53
54(abcl-asdf:resolve "com.google.gwt:gwt-user")
55
56|#
57
58(defparameter *mavens*
59  (if (find :windows *features*)
60      '("mvn" "mvn.bat" "mvn.cmd" "mvn3.bat")
61      '("mvn" "mvn3"
62        ;; MacPorts
63        "/opt/local/bin/mvn" "/opt/local/bin/mvn3"))
64  "Locations to search for the Maven executable.")
65
66(defun find-mvn ()
67  "Attempt to find a suitable Maven ('mvn') executable on the hosting operating system.
68
69Returns the path of the Maven executable or nil if none are found.
70
71Returns the version of Maven found as the second value.
72
73Emits warnings if not able to find a suitable executable."
74
75  (let ((m2-home (ext:getenv "M2_HOME"))
76        (m2 (ext:getenv "M2"))
77        (mvn-executable (if (find :unix *features*)
78                            "mvn"
79                            "mvn.bat")))
80    (when (and m2-home (probe-file m2-home))
81      (let* ((m2-home (truename m2-home))
82             (mvn-path (merge-pathnames
83                        (format nil "bin/~A" mvn-executable)
84                        m2-home))
85             (mvn (truename mvn-path)))
86        (if mvn
87            (values (return-from find-mvn mvn)
88                    (ensure-mvn-version))
89            (warn "M2_HOME was set to '~A' in the process environment but '~A' doesn't exist."
90                  m2-home mvn-path))))
91    (when (and m2 (probe-file m2))
92      (let* ((m2 (truename m2))
93             (mvn-path (merge-pathnames mvn-executable m2))
94             (mvn (truename mvn-path)))
95        (if mvn
96            (values (return-from find-mvn mvn)
97                    (ensure-mvn-version))
98            (warn "M2 was set to '~A' in the process environment but '~A' doesn't exist."
99                  m2 mvn-path))))
100    (let ((which-cmd
101            (if (find :unix *features*)
102                "which"
103                ;; Starting with Windows Server 2003
104                "where.exe")))
105      (dolist (mvn-path *mavens*)
106	(let ((mvn
107	       (handler-case
108		   (truename
109		    (string-trim
110		     '(#\space #\newline #\return #\tab)
111		     (uiop:run-program
112		      (format nil "~a ~a" which-cmd mvn-path)
113		      :output :string)))
114		 (t (e)
115		   (format cl:*load-verbose*
116			   "~&; abcl-asdf; Failed to find Maven executable '~a' in PATH because~%~a"
117			   mvn-path e)))))
118	  (when mvn
119	    (return-from find-mvn mvn)))))
120  (warn "Unable to locate Maven executable to find Maven Aether adaptors.")))
121
122(defun find-mvn-libs ()
123  (unless (find-mvn)
124    (warn "Failed to find Maven executable to determine Aether library location.  Continuing anyways."))
125  (some
126   (lambda (d)
127     (when (and
128            (pathnamep d)
129            (directory (merge-pathnames "maven-core*.jar" d)))
130       (truename d)))
131   (list (ignore-errors
132           (make-pathname :defaults (merge-pathnames "../lib/" (find-mvn))
133                          :name nil :type nil))
134         (ignore-errors
135           (make-pathname :defaults (merge-pathnames "lib/" (mvn-home))
136                          :name nil :type nil))
137         ;; library location for homebrew maven package on OS X
138         (ignore-errors
139           (make-pathname :defaults (merge-pathnames "../libexec/lib/" (find-mvn))
140                          :name nil :type nil))
141         #p"/usr/local/share/java/maven3/lib/" ;; FreeBSD ports
142         #p"/usr/local/maven/lib/"))) ;; OpenBSD location suggested by Timo Myyrä
143
144(defparameter *mvn-libs-directory*
145  nil
146  "Location of 'maven-core-3.<m>.<p>.jar', 'maven-embedder-3.<m>.<p>.jar' etc.")
147
148(defun normalize-mvn-libs ()
149  "Ensure that any *mvn-libs-directory* is a both directory and a pathname"
150  (unless *mvn-libs-directory*
151    (return-from normalize-mvn-libs nil))
152  (when (not (pathnamep *mvn-libs-directory*))
153    (setf *mvn-libs-directory* (pathname *mvn-libs-directory*)))
154  (when (not (#"endsWith" (namestring *mvn-libs-directory*) "/"))
155    (setf *mvn-libs-directory*
156          (pathname (concatenate 'string *mvn-libs-directory* "/"))))
157  *mvn-libs-directory*)
158
159(defun mvn-version ()
160  "Return the version of Maven libaries in use"
161  (unless (normalize-mvn-libs)
162    (error "Need to specify a value of *mvn-libs-directory*"))
163  (let* ((pattern
164          "maven-core*.jar")
165         (maven-core-jars
166          (directory (merge-pathnames pattern
167                                      *mvn-libs-directory*)))
168         (maven-core-jar
169          (cond
170            ((= (length maven-core-jars) 0)
171             (error "No file matching '~a' found in '~a'." pattern *mvn-libs-directory*))
172            ((> (length maven-core-jars) 1)
173             (warn "More than one file matching '~a' found in '~a'."
174                   pattern *mvn-libs-directory*)
175             (first maven-core-jars))
176            (t
177             (first maven-core-jars)))))
178    (let* ((manifest
179            (#"getManifest" (jss:new 'java.util.jar.JarFile (namestring maven-core-jar))))
180           (attributes
181            (#"getMainAttributes" manifest))
182           (version
183            (#"getValue" attributes "Implementation-Version")))
184      (parse-mvn-version
185       version))))
186
187;;; deprecated, unused:  we now get the version directly from the JAR manifest
188(defun mvn-version-from-mvn-executable ()
189  "Return the Maven version used by the Aether connector located by
190  FIND-MVN as a list of (MAJOR MINOR PATHLEVEL) integers.
191
192Signals a simple-error with additional information if this attempt fails."
193  (handler-case
194      (let* ((mvn
195              (truename (find-mvn)))
196             (pattern (#"compile"
197                       'regex.Pattern
198                       "^Apache Maven ([0-9]+\\.[0-9]+\\.[0-9]+)")))
199        (multiple-value-bind (output error)
200            (uiop:run-program
201             (format nil "~a --version" mvn)
202             :output :string :error :string)
203          (let ((matcher (#"matcher" pattern output)))
204            (when (#"find" matcher)
205              (return-from mvn-version-from-mvn-executable
206                (parse-mvn-version (#"group" matcher 1)))))
207          (when output
208            (signal "No parseable Maven version found in ~a" output))
209          (signal "Invocation of Maven returned the error ~{~&  ~A~}" error)))
210    (t (e)
211      (error "Failed to determine Maven version: ~A." e))))
212
213(defun parse-mvn-version (version-string)
214  (let* ((pattern (#"compile"
215                   'regex.Pattern
216                   "([0-9]+)\\.([0-9]+)\\.([0-9]+)"))
217         (matcher (#"matcher" pattern version-string)))
218    (if (#"find" matcher)
219        (mapcar #'parse-integer
220                `(,(#"group" matcher 1)
221                   ,(#"group" matcher 2)
222                   ,(#"group" matcher 3)))
223        (error "Failed to parse a MAJOR.MINOR.PATCHLEVEL version from '~a'" version-string))))
224
225
226(defun mvn-home ()
227  "If the Maven executable can be invoked, introspect the value
228  reported as Maven home."
229  (handler-case
230      (multiple-value-bind (output error-output status)
231          (uiop:run-program
232           (format nil "~a --version" (truename (find-mvn)))
233           :output :string
234           :error-output :string)
235        (unless (zerop status)
236          (error "Failed to invoke Maven executable to introspect library locations: ~a." error-output))
237        (let ((pattern (#"compile"
238                        'regex.Pattern
239                        "Maven home: (.+)$")))
240          (with-input-from-string (s output)
241            (do ((line (read-line s nil :eof)
242                       (read-line s nil :eof)))
243                ((or (not line) (eq line :eof)) nil)
244              (let ((matcher (#"matcher" pattern line)))
245                (when (#"find" matcher)
246                  (return-from mvn-home
247                    (uiop/pathname:ensure-directory-pathname (#"group" matcher 1)))))))))
248    (subprocess-error (e)
249          (error "Failed to invoke Maven executable to introspect library locations: ~a." e))))
250
251(defun ensure-mvn-version ()
252  "Return t if Maven version is 3.0.3 or greater."
253  (let* ((version (mvn-version))
254         (major (first version))
255         (minor (second version))
256         (patch (third version)))
257    (values
258     (or
259      (and (>= major 3)
260           (>= minor 1))
261      (and (>= major 3)
262           (>= minor 0)
263           (>= patch 3)))
264     (list major minor patch))))
265
266(define-condition no-aether-maven-libs (error)
267  ((locations :initarg :locations
268              :initform nil
269              :reader locations))
270  (:report (lambda (condition stream)
271             (format stream "No Maven Aether libraries found locally in '~a'."
272                     (locations condition)))))
273
274(defparameter *init-p* nil
275  "Whether we have successfully located the necessary Maven libraries")
276
277(defun init (&optional &key (force nil))
278  "Run the initialization strategy to bootstrap a Maven dependency node
279
280Set *MVN-LIBS-DIRECTORY* to an explicit value before running this
281function in order to bypass the dynamic introspection of the location
282of the mvn executable with an explicit value."
283  (when force
284    (setf *session* nil
285          *repository-system* nil))
286  (unless (or force *mvn-libs-directory*)
287    (setf *mvn-libs-directory* (find-mvn-libs)))
288  (unless (and *mvn-libs-directory*
289               (probe-file *mvn-libs-directory*))
290    ;; FIXME Remove warning; put message in restart
291    (warn "Please obtain and install maven-3.0.3 or later locally from <http://maven.apache.org/download.html>, then set ABCL-ASDF:*MVN-LIBS-DIRECTORY* to the directory containing maven-core-3.*.jar et. al.")
292    (error (make-condition 'abcl-asdf::no-aether-maven-libs
293                           :locations (list *mvn-libs-directory*))))
294  (unless (ensure-mvn-version)
295    (error "We need maven-3.0.3 or later."))
296  (add-directory-jars-to-class-path *mvn-libs-directory* nil)
297  (setf *init-p* t))
298
299;;; The AETHER-DIRECTORY parameter is conceptually a little broken:
300;;; because we can't "unload" jar files, we can't easily switch
301;;; between Maven implementation at runtime.  Maybe this would be
302;;; possible with some sort of classloader chaining, but such effort
303;;; is not currently deemed as worthwhile.  Instead, to change Aether
304;;; libraries, you'll have to restart ABCL.
305(defmacro with-aether ((&optional aether-directory) &body body)
306  "Ensure that the code in BODY is executed with the Maven Aether libraries on the classpath"
307  (if aether-directory
308      `(let ((*mvn-libs-directory* ,aether-directory))
309         (init :force t)
310         ,@body)
311      `(progn (unless *init-p*
312                (init))
313              ,@body)))
314
315(defun find-http-wagon ()
316  "Find an implementation of the object that provides access to http and https resources.
317
318Supposedly configurable with the java.net.protocols (c.f. reference
319maso2000 in the Manual.)"
320  (handler-case
321      ;; maven-3.0.4
322      (java:jnew "org.apache.maven.wagon.providers.http.HttpWagon")
323    (error ()
324      ;; maven-3.0.3 reported as not working with all needed functionality
325      (java:jnew  "org.apache.maven.wagon.providers.http.LightweightHttpWagon"))))
326
327(defun make-wagon-provider ()
328  "Returns an implementation of the org.sonatype.aether.connector.wagon.WagonProvider contract
329
330The implementation is specified as Lisp closures.  Currently, it only
331specializes the lookup() method if passed an 'http' or an 'https' role
332hint."
333  (unless *init-p* (init))
334  (java:jinterface-implementation
335   (#"getName"
336    (or
337     (ignore-errors  ;; Maven 3.2.5+
338       (jss:find-java-class 'aether.transport.wagon.WagonProvider))
339     (ignore-errors  ;; Maven 3.1.0+
340       (jss:find-java-class 'aether.connector.wagon.WagonProvider))
341     (ignore-errors  ;; Maven 3.0.x
342       (jss:find-java-class 'org.sonatype.aether.connector.wagon.WagonProvider))))
343   "lookup"
344   (lambda (role-hint)
345     (cond
346       ((find role-hint '("http" "https") :test #'string-equal)
347        (find-http-wagon))
348       (t
349        (progn
350          (format cl:*load-verbose*
351                  "~&; abcl-asdf; WagonProvider stub passed '~A' as a hint it couldn't satisfy.~%"
352                  role-hint)
353          java:+null+))))
354   "release"
355   (lambda (wagon)
356     (declare (ignore wagon)))))
357
358(defun find-service-locator ()
359  (or
360   (ignore-errors
361     ;; maven-3.0.4
362     (jss:new "org.apache.maven.repository.internal.MavenServiceLocator"))
363   (ignore-errors
364     ;; maven-3.1.0 using org.eclipse.aether...
365     (jss:new "aether.impl.DefaultServiceLocator"))
366   (ignore-errors
367     (jss:new "org.apache.maven.repository.internal.DefaultServiceLocator"))
368   (ignore-errors
369     ;; maven-3.1.0
370     (#"newServiceLocator" 'org.apache.maven.repository.internal.MavenRepositorySystemUtils))))
371
372
373(defun make-repository-system ()
374  (unless *init-p* (init))
375  (let ((locator
376         (find-service-locator))
377        (wagon-provider-class
378         (or
379          (ignore-errors
380            (java:jclass "org.sonatype.aether.connector.wagon.WagonProvider"))
381          (ignore-errors ;; Maven-3.3.x
382            (jss:find-java-class 'connector.transport.TransporterFactory))
383          (ignore-errors ;; Maven-3.2.5
384            (jss:find-java-class 'org.eclipse.aether.transport.wagon.WagonProvider))
385          (ignore-errors  ;; Maven-3.1.x
386            (jss:find-java-class 'aether.connector.wagon.WagonProvider))))
387        (wagon-repository-connector-factory-class
388         (or
389          (ignore-errors
390            (jss:find-java-class 'org.sonatype.aether.connector.wagon.WagonRepositoryConnectorFactory))
391          (ignore-errors
392            (jss:find-java-class 'org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory))
393          (ignore-errors
394            (java:jclass "org.sonatype.aether.connector.wagon.WagonRepositoryConnectorFactory"))))
395        (repository-connector-factory-class
396         (or
397          (ignore-errors
398            (jss:find-java-class 'aether.spi.connector.RepositoryConnectorFactory))
399          (ignore-errors
400            (jss:find-java-class 'org.eclipse.aether.spi.connector.RepositoryConnectorFactory))
401          (ignore-errors
402            (java:jclass "org.sonatype.aether.spi.connector.RepositoryConnectorFactory"))))
403        (repository-system-class
404         (or
405          (ignore-errors
406            (java:jclass "org.sonatype.aether.RepositorySystem"))
407          (ignore-errors
408            (jss:find-java-class 'org.eclipse.aether.RepositorySystem))
409          (ignore-errors
410            (jss:find-java-class 'aether.RepositorySystem)))))
411    (if (equal wagon-provider-class (ignore-errors (jss:find-java-class 'TransporterFactory)))
412        ;;; Maven-3.3.3
413        (let ((wagon-transporter-factory (jss:new 'WagonTransporterFactory)))
414          (#"setWagonProvider" wagon-transporter-factory (make-wagon-provider))
415          (#"setServices" locator
416                          wagon-provider-class
417                          (java:jarray-from-list (list wagon-transporter-factory))))
418        (#"setServices" locator
419                        wagon-provider-class
420                        (java:jarray-from-list
421                         (list (make-wagon-provider)))))
422    (#"addService" locator
423                   repository-connector-factory-class
424                   wagon-repository-connector-factory-class)
425    (values (#"getService" locator
426                           repository-system-class)
427            locator)))
428
429(defun make-session (repository-system)
430  "Construct a new aether.RepositorySystemSession from the specified REPOSITORY-SYSTEM."
431  (with-aether ()
432    (let ((session
433           (or
434            (ignore-errors
435              (java:jnew
436               (jss:find-java-class "MavenRepositorySystemSession")))
437            (ignore-errors
438              (#"newSession"
439               'org.apache.maven.repository.internal.MavenRepositorySystemUtils))))
440          (local-repository
441           (make-local-repository)))
442      (#"setLocalRepositoryManager"
443       session
444       (make-local-repository-manager repository-system local-repository session)))))
445
446
447(defun make-local-repository-manager (repository-system local-repository session)
448  (or
449   (ignore-errors
450     (#"newLocalRepositoryManager"
451      repository-system local-repository))
452   (ignore-errors      ;; maven-3.1.0
453     (#"newLocalRepositoryManager"
454      repository-system session local-repository))))
455
456(defun make-local-repository ()
457  (java:jnew
458   (or
459    (ignore-errors
460      (jss:find-java-class "org.sonatype.aether.repository.LocalRepository"))
461    (ignore-errors
462      (jss:find-java-class "org.eclipse.aether.repository.LocalRepository")))
463   (namestring (merge-pathnames ".m2/repository/"
464                                (user-homedir-pathname)))))
465
466(defparameter *maven-http-proxy* nil
467  "A string containing the URI of an http proxy for Maven to use.")
468
469(defun make-proxy ()
470  "Return an aether.repository.Proxy instance initialized from *MAVEN-HTTP-PROXY*."
471  (unless *maven-http-proxy*
472    (warn "No proxy specified in *MAVEN-HTTP-PROXY*")
473    (return-from make-proxy nil))
474  (let* ((p (pathname *maven-http-proxy*))
475         (scheme (ext:url-pathname-scheme p))
476         (authority (ext:url-pathname-authority p))
477         (host (if (search ":" authority)
478                   (subseq authority 0 (search ":" authority))
479                   authority))
480         (port (when (search ":" authority)
481                 (parse-integer (subseq authority (1+ (search ":" authority))))))
482         ;; TODO allow specification of authentication
483         (authentication java:+null+))
484    (or
485     (ignore-errors
486       (jss:new 'org.eclipse.aether.repository.Proxy
487                scheme host port authentication))
488     (ignore-errors
489       (jss:new 'org.sonatype.aether.repository.Proxy
490                scheme host port authentication)))))
491
492(defparameter *repository-system*  nil
493  "The aether.RepositorySystem used by the Maeven Aether connector.")
494(defun ensure-repository-system (&key (force nil))
495  (when (or force (not *repository-system*))
496    (setf *repository-system* (make-repository-system)))
497  *repository-system*)
498
499(defparameter *session* nil
500  "Reference to the Maven RepositorySystemSession")
501(defun ensure-session (&key (force nil))
502  "Ensure that the RepositorySystemSession has been created.
503
504If *MAVEN-HTTP-PROXY* is non-nil, parse its value as the http proxy."
505  (when (or force (not *session*))
506    (ensure-repository-system :force force)
507    (setf *session* (make-session *repository-system*))
508    (#"setRepositoryListener" *session* (make-repository-listener))
509    (when *maven-http-proxy*
510      (let ((proxy (make-proxy)))
511        (#"add" (#"getProxySelector" *session*)
512                proxy
513                ;; A string specifying non proxy hosts, or null
514                java:+null+))))
515  *session*)
516
517(defun make-artifact (artifact-string)
518  "Return an instance of aether.artifact.DefaultArtifact initialized from ARTIFACT-STRING"
519  (or
520   (ignore-errors
521     (jss:new "org.sonatype.aether.util.artifact.DefaultArtifact" artifact-string))
522   (ignore-errors
523     (jss:new 'aether.artifact.DefaultArtifact artifact-string))))
524
525(defun make-artifact-request ()
526  "Construct a new aether.resolution.ArtifactRequest."
527  (or
528   (ignore-errors
529     (java:jnew (jss:find-java-class 'aether.resolution.ArtifactRequest)))
530   (ignore-errors
531     (java:jnew "org.sonatype.aether.resolution.ArtifactRequest"))))
532
533;;; TODO change this to work on artifact strings like log4j:log4j:jar:1.2.16
534(defun resolve-artifact (group-id artifact-id &key (version "LATEST" versionp))
535  "Resolve artifact to location on the local filesystem.
536
537Declared dependencies are not attempted to be located.
538
539If unspecified, the string \"LATEST\" will be used for the VERSION.
540
541Returns the Maven specific string for the artifact "
542  (unless versionp
543    (warn "Using LATEST for unspecified version."))
544  (unless *init-p* (init))
545  (let* ((artifact-string
546          (format nil "~A:~A:~A" group-id artifact-id version))
547         (artifact
548          (make-artifact artifact-string))
549         (artifact-request
550          (make-artifact-request)))
551    (#"setArtifact" artifact-request artifact)
552    (#"addRepository" artifact-request (ensure-remote-repository))
553    (#"toString" (#"getFile"
554                  (#"getArtifact" (#"resolveArtifact" (ensure-repository-system)
555                                                      (ensure-session) artifact-request))))))
556
557(defun make-remote-repository (id type url)
558  (or
559   (ignore-errors
560     (jss:new 'org.sonatype.aether.repository.RemoteRepository id type url))
561   (ignore-errors
562     (#"build" (jss:new "org.eclipse.aether.repository.RemoteRepository$Builder" id type url)))))
563
564(defvar *default-repository*
565  "https://repo1.maven.org/maven2/"
566  "URI of default remote Maven repository")
567
568(defun add-repository (repository)
569  (ensure-remote-repository :repository repository))
570
571(defparameter *maven-remote-repository*  nil
572  "Reference to remote repository used by the Maven Aether
573  embedder.")
574
575(defun ensure-remote-repository (&key
576                                   (force nil)
577                                   (repository *default-repository* repository-p))
578  (unless *init-p* (init))
579  (when (or force
580            repository-p
581            (not *maven-remote-repository*))
582    (let ((r (make-remote-repository "central" "default" repository)))
583      (when *maven-http-proxy*
584        (#"setProxy" r (make-proxy)))
585      (setf *maven-remote-repository* r)))
586  *maven-remote-repository*)
587
588(defun resolve-dependencies (group-id artifact-id
589                             &key
590                               (version "LATEST" versionp)
591                               (repository *maven-remote-repository* repository-p)
592                               (repositories NIL repositories-p))
593  "Dynamically resolve Maven dependencies for item with GROUP-ID and ARTIFACT-ID
594optionally with a VERSION and a REPOSITORY.
595
596All recursive dependencies will be visited before resolution is successful.
597
598If unspecified, the string \"LATEST\" will be used for the VERSION.
599
600Returns a string containing the necessary jvm classpath entries packed
601in Java CLASSPATH representation."
602  (unless *init-p* (init))
603  (unless versionp
604    (warn "Using LATEST for unspecified version."))
605  (let* ((coords
606          (format nil "~A:~A:~A" group-id artifact-id (if versionp version "LATEST")))
607         (artifact
608          (make-artifact coords))
609         (dependency
610          (make-dependency artifact))
611         (collect-request
612          (or
613           (ignore-errors
614             (java:jnew (jss:find-java-class "org.sonatype.aether.collection.CollectRequest")))
615           (ignore-errors
616             (java:jnew (jss:find-java-class "org.eclipse.aether.collection.CollectRequest"))))))
617    (#"setRoot" collect-request dependency)
618    (setf repositories-p (or repository-p repositories-p))
619    ;; Don't call addRepository if we explicitly specify a NIL repository
620    (cond
621      ((not repositories-p)
622       (#"addRepository" collect-request (ensure-remote-repository)))
623      (repository
624       (if (stringp repository)
625           (push repository repositories)
626           (#"addRepository" collect-request repository))))
627    (dolist (repository repositories)
628      (#"addRepository" collect-request
629                        (let ((r (make-remote-repository "central" "default" repository)))
630                          (when *maven-http-proxy*
631                            (#"setProxy" r (make-proxy)))
632                          r)))
633    (let* ((collect-result (#"collectDependencies" (ensure-repository-system)
634                                                   (ensure-session) collect-request))
635           (node
636            (#"getRoot" collect-result))
637           (dependency-request
638            (or
639             (ignore-errors
640            ;;; pre Maven-3.3.x
641               (java:jnew (jss:find-java-class "DependencyRequest")
642                          node java:+null+))
643             (ignore-errors
644               (jss:new 'DependencyRequest))))
645           (nlg
646            (java:jnew (jss:find-java-class "PreorderNodeListGenerator"))))
647      (#"setRoot" dependency-request node)
648      (#"resolveDependencies" (ensure-repository-system) (ensure-session) dependency-request)
649      (#"accept" node nlg)
650      (#"getClassPath" nlg))))
651
652(defun make-dependency (artifact)
653  (or
654   (ignore-errors
655     (java:jnew (jss:find-java-class 'org.sonatype.aether.graph.Dependency)
656                artifact
657                (java:jfield
658                 (jss:find-java-class "org.sonatype.aether.util.artifact.JavaScopes")
659                 "COMPILE")))
660   (ignore-errors
661     (java:jnew (jss:find-java-class 'org.eclipse.aether.graph.Dependency)
662                artifact
663                (java:jfield
664                 (jss:find-java-class "org.eclipse.aether.util.artifact.JavaScopes")
665                 "COMPILE")))))
666
667
668(defun make-repository-listener ()
669  (flet ((log (e)
670           (format cl:*load-verbose* "~&; abcl-asdf; ~A~%" (#"toString" e))))
671    (java:jinterface-implementation
672     (#"getName" (jss:find-java-class 'aether.RepositoryListener))
673     "artifactDeployed"
674     #'log
675     "artifactDeploying"
676     #'log
677     "artifactDescriptorInvalid"
678     #'log
679     "artifactDescriptorMissing"
680     #'log
681     "artifactDownloaded"
682     #'log
683     "artifactDownloading"
684     #'log
685     "artifactInstalled"
686     #'log
687     "artifactInstalling"
688     #'log
689     "artifactResolved"
690     #'log
691     "artifactResolving"
692     #'log
693     "metadataDeployed"
694     #'log
695     "metadataDeploying"
696     #'log
697     "metadataDownloaded"
698     #'log
699     "metadataDownloading"
700     #'log
701     "metadataInstalled"
702     #'log
703     "metadataInstalling"
704     #'log
705     "metadataInvalid"
706     #'log
707     "metadataResolved"
708     #'log
709     "metadataResolving"
710     #'log)))
711
712(defmethod resolve ((string string))
713  "Resolve a colon separated GROUP-ID:ARTIFACT-ID[:VERSION] reference to a Maven artifact.
714
715Examples of artifact references: \"log4j:log4j:1.2.14\" for
716'log4j-1.2.14.jar'.  Resolving \"log4j:log4j\" would return the latest
717version of the artifact known to the distributed Maven pom.xml graph.
718
719Returns a string containing the necessary classpath entries for this
720artifact and all of its transitive dependencies."
721  (let ((result (split-string string ":")))
722    (cond
723      ((= (length result) 3)
724       (resolve-dependencies
725        (first result) (second result) :version (third result)))
726      ((string= string "com.sun.jna:jna")
727       (warn "Replacing request for no longer available com.sun.jna:jna with net.java.dev.jna:jna")
728       (resolve-dependencies "net.java.dev.jna" "jna" :version "LATEST"))
729      ((= (length result) 2)
730       (resolve-dependencies
731        (first result) (second result)))
732      (t
733       (destructuring-bind (group-id artifact-id &optional version repository)
734           (abcl-build:split-string string "/")
735         (setf result
736               (apply #'resolve-dependencies group-id artifact-id
737                      (append (when version
738                                `(:version ,version))
739                              (when repository
740                                `(:repository ,repository))))))))))
741
742
743;;; Currently the last file listed in ASDF
744(provide 'abcl-asdf)
745
746
747