source: trunk/abcl/contrib/abcl-asdf/maven-embedder.lisp @ 13841

Last change on this file since 13841 was 13841, checked in by Mark Evenson, 10 years ago

abcl-asdf: Use *maven-http-proxy* for all Maven Aether resolutions.

Increment abcl-asdf ASDF version to 0.6.0 to denote the ability to
specify an http proxy for Maven.

File size: 11.6 KB
Line 
1;;; Use the Aether system in a default Maven3 distribution to download
2;;; and install dependencies.
3;;;
4;;; References:
5;;; -- javadoc
6;;; http://sonatype.github.com/sonatype-aether/apidocs/overview-summary.html
7;;; -- incomplete, seemingly often wrong
8;;; https://docs.sonatype.org/display/AETHER/Home
9
10(in-package :abcl-asdf)
11
12(require :abcl-contrib)
13(require :jss)
14
15#|
16Test:
17(resolve-dependencies "org.slf4j" "slf4j-api" "1.6.1")
18
19(resolve-dependencies "org.apache.maven" "maven-aether-provider" "3.0.4")
20|#
21
22(defparameter *maven-verbose* t
23  "Stream to send output from the Maven Aether subsystem to, or NIL to muffle output")
24
25(defvar *mavens* '("/opt/local/bin/mvn3" "mvn3" "mvn" "mvn3.bat" "mvn.bat")
26  "Locations to search for the Maven executable.")
27
28(defun find-mvn () 
29  "Attempt to find a suitable Maven ('mvn') executable on the hosting operating system."
30  (dolist (mvn-path *mavens*)
31    (let ((mvn 
32           (handler-case 
33               (truename (read-line (sys::process-output 
34                                     (sys::run-program "which" `(,mvn-path))))) ;; TODO equivalent for MSDOS
35             (end-of-file () nil))))
36      (when mvn
37        (return-from find-mvn mvn)))))
38
39(defun find-mvn-libs ()
40  (let ((mvn (find-mvn)))
41    (unless mvn
42      (warn "Failed to find Maven3 libraries.")
43      (return-from find-mvn-libs nil))
44    (truename (make-pathname 
45               :defaults (merge-pathnames "../lib/" mvn)
46               :name nil :type nil))))
47
48(defparameter *mvn-libs-directory*
49  nil
50  "Location of 'maven-core-3.<m>.<p>.jar', 'maven-embedder-3.<m>.<p>.jar' etc.")
51
52(defun mvn-version ()
53  "Return the Maven version used by the Aether connector."
54  (let* ((line
55         (read-line (sys::process-output 
56                     (sys::run-program 
57                      (namestring (find-mvn)) '("-version")))))
58         (pattern (#"compile" 
59                   'regex.Pattern
60                   "Apache Maven ([0-9]+)\\.([0-9]+)\\.([0-9]+)"))
61         (matcher (#"matcher" pattern line))
62         (found (#"find" matcher)))
63    (unless found 
64      (return-from mvn-version nil))
65    (mapcar #'parse-integer
66            `(,(#"group" matcher 1)
67              ,(#"group" matcher 2)
68              ,(#"group" matcher 3)))))
69
70(defun ensure-mvn-version ()
71  "Return t if Maven version is 3.0.3 or greater."
72  (let* ((version (mvn-version))
73         (major (first version))
74         (minor (second version))
75         (patch (third version)))
76    (or 
77     (and (>= major 3)
78          (>= minor 1))
79     (and (>= major 3)
80          (>= major 0)
81          (>= patch 3)))))
82
83(defparameter *init* nil)
84
85(defun init (&optional &key (force nil))
86  "Run the initialization strategy to bootstrap a Maven dependency node."
87  (unless (or force *mvn-libs-directory*)
88    (setf *mvn-libs-directory* (find-mvn-libs)))
89  (unless (probe-file *mvn-libs-directory*)
90    (error "You must download maven-3.0.3 or later from http://maven.apache.org/download.html, then set ABCL-ASDF:*MVN-DIRECTORY* appropiately."))
91  (unless (ensure-mvn-version)
92    (error "We need maven-3.0.3 or later."))
93  (add-directory-jars-to-class-path *mvn-libs-directory* nil)
94  (setf *init* t))
95
96(defparameter *http-wagon-implementations*
97  `("org.apache.maven.wagon.providers.http.HttpWagon" ;; introduced as default with maven-3.0.4
98    "org.apache.maven.wagon.providers.http.LightweightHttpWagon")
99  "A list of possible candidate implementations that provide access to http and https resources.
100
101Supposedly configurable with the java.net.protocols (c.f. reference maso2000 in the Manual.")
102
103(defun make-wagon-provider ()
104  "Returns an implementation of the org.sonatype.aether.connector.wagon.WagonProvider contract.
105
106The implementation is specified as Lisp closures.  Currently, it only
107specializes the lookup() method if passed an 'http' role hint."
108  (unless *init* (init))
109  (java:jinterface-implementation 
110   "org.sonatype.aether.connector.wagon.WagonProvider"
111   "lookup"
112   (lambda (role-hint)
113     (if (string-equal "http" role-hint)
114         (some (lambda (provider) (java:jnew provider)) *http-wagon-implementations*)
115       java:+null+))
116   "release"
117   (lambda (wagon)
118     (declare (ignore wagon)))))
119
120(defun make-repository-system ()
121  (unless *init* (init))
122  (let ((locator 
123         (java:jnew "org.apache.maven.repository.internal.DefaultServiceLocator"))
124        (repository-connector-factory-class 
125         (java:jclass "org.sonatype.aether.spi.connector.RepositoryConnectorFactory"))
126        (wagon-repository-connector-factory-class
127         (java:jclass "org.sonatype.aether.connector.wagon.WagonRepositoryConnectorFactory"))
128        (wagon-provider-class 
129         (java:jclass "org.sonatype.aether.connector.wagon.WagonProvider"))
130        (repository-system-class
131         (java:jclass "org.sonatype.aether.RepositorySystem")))
132    (#"addService" locator
133                   repository-connector-factory-class 
134                   wagon-repository-connector-factory-class)
135    (#"setServices" locator
136                    wagon-provider-class
137                    (java:jnew-array-from-list 
138                     "org.sonatype.aether.connector.wagon.WagonProvider"
139                     (list 
140                      (make-wagon-provider))))
141    (#"getService" locator
142                   repository-system-class)))
143       
144(defun make-session (repository-system)
145  "Construct a new org.sonatype.aether.RepositorySystemSession from REPOSITORY-SYSTEM"
146  (let ((session 
147         (java:jnew (jss:find-java-class "MavenRepositorySystemSession")))
148        (local-repository 
149         (java:jnew (jss:find-java-class "LocalRepository")
150                  (namestring (merge-pathnames ".m2/repository/"
151                                               (user-homedir-pathname))))))
152    (#"setLocalRepositoryManager" 
153     session
154     (#"newLocalRepositoryManager" repository-system local-repository))))
155
156(defparameter *maven-http-proxy* nil
157  "A string containing the URI of an http proxy for Maven to use.")
158
159(defun make-proxy ()
160  "Return an org.sonatype.aether.repository.Proxy instance initialized form *MAVEN-HTTP-PROXY*."
161  (unless *maven-http-proxy*
162    (warn "No proxy specified in *MAVEN-HTTP-PROXY*")
163    (return-from make-proxy nil))
164  (let* ((p (pathname *maven-http-proxy*))
165         (scheme (sys::url-pathname-scheme p))
166         (authority (sys::url-pathname-authority p))
167         (host (if (search ":" authority)
168                   (subseq authority 0 (search ":" authority))
169                   authority))
170         (port (when (search ":" authority)
171                 (parse-integer (subseq authority (1+ (search ":" authority))))))
172         ;; TODO allow specification of authentication
173         (authentication java:+null+))
174    (jss:new 'org.sonatype.aether.repository.Proxy
175             scheme host port authentication)))
176
177(defparameter *repository-system*  nil
178  "The org.sonatype.aether.RepositorySystem used by the Maeven Aether connector.")
179(defun ensure-repository-system ()
180  (unless *repository-system*
181    (setf *repository-system* (make-repository-system)))
182  *repository-system*)
183
184(defparameter *session* nil
185  "Reference to the Maven RepositorySystemSession")
186(defun ensure-session ()
187  "Ensure that the RepositorySystemSession has been created.
188
189If *MAVEN-HTTP-PROXY* is non-nil, parse its value as the http proxy."
190  (unless *session*
191    (ensure-repository-system)
192    (setf *session* (make-session *repository-system*))
193    (#"setRepositoryListener" *session* (make-repository-listener))
194    (when *maven-http-proxy*
195      (let ((proxy (make-proxy)))
196        (#"add" (#"getProxySelector" *session*)
197                proxy 
198                ;; A string specifying non proxy hosts, or null
199                java:+null+))))
200    *session*)
201
202;;; TODO change this to work on artifact strings like log4j:log4j:jar:1.2.16
203(defun resolve-artifact (group-id artifact-id &optional (version "LATEST" versionp))
204  "Directly resolve Maven dependencies for item with GROUP-ID and ARTIFACT-ID at VERSION, ignoring dependencies.
205
206Declared dependencies are not attempted to be located.
207
208If unspecified, the string \"LATEST\" will be used for the VERSION.
209
210Returns the Maven specific string for the artifact "
211  (unless versionp
212    (warn "Using LATEST for unspecified version."))
213  (unless *init* (init))
214  (let* ((artifact-string (format nil "~A:~A:~A" group-id artifact-id version))
215         (artifact 
216          (jss:new "org.sonatype.aether.util.artifact.DefaultArtifact" artifact-string))
217         (artifact-request 
218          (java:jnew "org.sonatype.aether.resolution.ArtifactRequest")))
219    (#"setArtifact" artifact-request artifact)
220    (#"addRepository" artifact-request (ensure-remote-repository))
221    (#"toString" (#"resolveArtifact" (ensure-repository-system) (ensure-session) artifact-request))))
222
223(defun make-remote-repository (id type url) 
224  (jss:new 'aether.repository.RemoteRepository id type url))
225
226(defparameter *maven-remote-repository*  nil
227    "The remote repository used by the Maven Aether embedder.")
228(defun ensure-remote-repository () 
229  (unless *init* (init))
230  (unless *maven-remote-repository*
231    (let ((r (make-remote-repository "central" "default" "http://repo1.maven.org/maven2/")))
232      (when *maven-http-proxy*
233        (#"setProxy" r (make-proxy)))
234      (setf *maven-remote-repository* r)))
235  *maven-remote-repository*)
236
237(defun resolve-dependencies (group-id artifact-id &optional (version "LATEST" versionp))
238  "Dynamically resolve Maven dependencies for item with GROUP-ID and ARTIFACT-ID at VERSION.
239
240All recursive dependencies will be visited before resolution is successful.
241
242If unspecified, the string \"LATEST\" will be used for the VERSION.
243
244Returns a string containing the necessary jvm classpath entries packed
245in Java CLASSPATH representation."
246  (unless *init* (init))
247  (unless versionp
248    (warn "Using LATEST for unspecified version."))
249  (let* ((artifact
250          (java:jnew (jss:find-java-class "aether.util.artifact.DefaultArtifact")
251                     (format nil "~A:~A:~A"
252                             group-id artifact-id version)))
253         (dependency 
254          (java:jnew (jss:find-java-class "aether.graph.Dependency")
255                     artifact "compile"))
256         (collect-request (java:jnew (jss:find-java-class "CollectRequest"))))
257    (#"setRoot" collect-request dependency)
258    (#"addRepository" collect-request (ensure-remote-repository))
259    (let* ((node 
260            (#"getRoot" (#"collectDependencies" (ensure-repository-system) (ensure-session) collect-request)))
261           (dependency-request 
262            (java:jnew (jss:find-java-class "DependencyRequest")
263                       node java:+null+))
264           (nlg 
265            (java:jnew (jss:find-java-class "PreorderNodeListGenerator"))))
266      (#"resolveDependencies" (ensure-repository-system) (ensure-session) dependency-request)
267      (#"accept" node nlg)
268      (#"getClassPath" nlg))))
269
270(defun make-repository-listener ()
271  (flet ((log (e) 
272           (format *maven-verbose* "~&~A~%" (#"toString" e))))
273    (java:jinterface-implementation 
274     "org.sonatype.aether.RepositoryListener"
275     "artifactDeployed" 
276     #'log
277     "artifactDeploying" 
278     #'log
279     "artifactDescriptorInvalid" 
280     #'log
281     "artifactDescriptorMissing" 
282     #'log
283     "artifactDownloaded" 
284     #'log
285     "artifactDownloading" 
286     #'log
287     "artifactInstalled" 
288     #'log
289     "artifactInstalling" 
290     #'log
291     "artifactResolved" 
292     #'log
293     "artifactResolving" 
294     #'log
295     "metadataDeployed" 
296     #'log
297     "metadataDeploying" 
298     #'log
299     "metadataDownloaded" 
300     #'log
301     "metadataDownloading" 
302     #'log
303     "metadataInstalled"
304     #'log
305     "metadataInstalling" 
306     #'log
307     "metadataInvalid" 
308     #'log
309     "metadataResolved" 
310     #'log
311     "metadataResolving"
312     #'log)))
313
314         
315
Note: See TracBrowser for help on using the repository browser.