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

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

abcl-asdf: Enable the specification of an http(s) proxy in ABCL-ASDF:*maven-http-proxy*.

ABCL-ASDF:*MAVEN-VERBOSE* now controls the stream to which the Maven
Aether repository system reports progress in resolving dependencies.
The logging messages could be presented in a perhaps slightly less verbose method.

Refactor the setting of various subsystem in special variables with
associated ENSURE-* methods.

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