asdf-mvn-module: enable use of MVN-MODULE in ASDF definitions

The MVN-MODULE component allows finer control over Maven's
behavior when the transitive dependency graph contains conflicting
requirements.

(:mvn-module NAME
   :dependencies DEPENDENCIES
   :managed-dependencies MANAGED-DEPENDENCIES
   :exclusions EXCLUSIONS)

where

DEPENDENCIES

   A list of Maven artifacts in the form of colon or slash separated
   components GROUPID:ARTIFACTID:VERSIONID.

MANAGED-DEPENDENCIES

   A List of maven artifacts. If an dependency with same GROUPID and
   ARTIFACTID are encountered, the version specified here overrides.

EXCLUSIONS

   A list of partial maven artifacts groupid:artifactid. Dependencies
   with same groupid and artifactid are exluded


An example of a MVN-MODULE ASDF declaration:

(defsystem foo
  :defsystem-depends-on (asdf-mvn-module)
  :components ((:mvn-module maven
		:dependencies
		("net.sourceforge.owlapi/pellet-cli-ignazio1977/2.4.0-ignazio1977"
		  "org.semanticweb.elk/elk-owlapi/0.4.3"
		  "net.sourceforge.owlapi/org.semanticweb.hermit/1.3.8.413"
		  "net.sourceforge.owlapi/owlapi-distribution/4.2.6"
		  "net.sourceforge.owlapi/owlexplanation/2.0.0"
		  "de.sciss/prefuse-core/1.0.1"
		  "de.sciss/prefuse-demos/1.0.1")
		:managed-dependencies
		("org.slf4j/slf4j-api/1.7.21"
		 "net.sourceforge.owlapi:owlapi-distribution:4.2.6")
		:exclusions
		("net.sourceforge.owlapi:owlapi-osgidistribution"
                 "edu.stanford.protege:org.protege.editor.owl")))

c.f. <https://mailman.common-lisp.net/pipermail/armedbear-devel/2017-April/003810.html>

Originally from <https://github.com/alanruttenberg/abcl/blob/stage/contrib/abcl-asdf/>.
parent d53b9c7f
(in-package :cl-user)
(defpackage mvn-module
(:use :cl))
(in-package :mvn-module)
(in-package :asdf)
;; dependencies: a list of maven artifacts. color or slash separated
;; components groupid:artifactid:versionid
;; managed-dependencies: a list of maven artifacts. If an dependency
;; with same groupid and artifactid are encountered, the version
;; specified here overrides.
;; exclusions: a list of partial maven artifacts
;; groupid:artifactid. Dependencies with same groupid and artifactid are
;; exluded
(defclass mvn-module (component)
((depends :initarg :dependencies :initform nil :accessor mvn-module-depends)
(excludes :initarg :exclusions :initform nil :accessor mvn-module-excludes)
(managed :initarg :managed-dependencies :initform nil :accessor mvn-module-managed)))
(defmethod component-children ((c mvn-module))
nil)
;;; TODO move to initarg
(defmethod source-file-type ((c mvn-module) (system parent-component)) nil)
(defmethod perform ((op compile-op) (c mvn-module))
)
(defmethod perform ((op prepare-op) (c mvn-module))
)
(defmethod perform ((operation load-op) (c mvn-module))
(loop for path
in (asdf-mvn-module:resolve-multiple-maven-dependencies
(mvn-module-depends c)
(mvn-module-managed c)
(mvn-module-excludes c))
do
(unless (member path abcl-asdf::*added-to-classpath* :test 'equalp)
(jss::add-to-classpath path))))
......@@ -3,7 +3,7 @@
:author "Mark Evenson"
:description "Extensions to ASDF for handling Java binary artifacts."
:long-description "<urn:abcl.org/release/1.5.0/contrib/abcl-asdf#>"
:version "2.0.0"
:version "2.1.0"
:depends-on (jss abcl-build)
:components
((:module package
......@@ -16,7 +16,8 @@
:depends-on (package))
(:module maven
:pathname ""
:components ((:file "maven"))
:components ((:file "maven")
(:file "mvn-module"))
:depends-on (base)))
:in-order-to ((test-op (test-op abcl-asdf-tests))))
;;;; -*- Mode: LISP -*-
(defsystem asdf-mvn-module-tests
:defsystem-depends-on (prove-asdf)
:depends-on (prove asdf-mvn-module)
:components ((:module tests :pathname "t/"
:components ((:file "mvn-module"))))
:perform (asdf:test-op (op c)
(uiop:symbol-call :prove-asdf 'run-test-system c)))
;;;; -*- Mode: LISP -*-
(defsystem asdf-mvn-module
:author "Alan Ruttenberg"
:version "1.0.0"
:depends-on (jss abcl-asdf)
:description "Handles Maven artifact exclusions via the ADSF:MVN-MODULE component."
:long-description "<urn:abcl.org/release/1.5.0/contrib/abcl-asdf/mvn-module#>"
:version "1.0.0"
:in-order-to ((test-op (test-op asdf-mvn-module-tests)))
:components ((:module source
:pathname ""
:components ((:file "asdf-mvn-module")))))
(in-package :asdf)
;; dependencies: a list of maven artifacts. color or slash separated
;; components groupid:artifactid:versionid
;; managed-dependencies: a list of maven artifacts. If an dependency
;; with same groupid and artifactid are encountered, the version
;; specified here overrides.
;; exclusions: a list of partial maven artifacts
;; groupid:artifactid. Dependencies with same groupid and artifactid are
;; exluded
(defclass mvn-module (component)
((depends :initarg :dependencies :initform nil :accessor mvn-module-depends)
(excludes :initarg :exclusions :initform nil :accessor mvn-module-excludes)
(managed :initarg :managed-dependencies :initform nil :accessor mvn-module-managed)))
(defmethod component-children ((c mvn-module))
nil)
;;; ASDF problems: method is deprecated. How do we define MVN-MODULE,
;;; a subclass of ASDF:COMPONENT, which has no corresponding pathname?
(defmethod source-file-type ((c mvn-module) (system parent-component)) nil)
(defmethod perform ((op compile-op) (c mvn-module)))
(defmethod perform ((op prepare-op) (c mvn-module)))
(defmethod perform ((operation load-op) (c mvn-module))
(loop for path in (abcl-asdf:resolve-multiple-maven-dependencies
(mvn-module-depends c) (mvn-module-managed c) (mvn-module-excludes c))
do
(unless (member path abcl-asdf::*added-to-classpath* :test 'equalp)
(jss::add-to-classpath path))))
......@@ -616,7 +616,7 @@ artifact and all of its transitive dependencies."
(first result) (second result)))
(t
(destructuring-bind (group-id artifact-id &optional version repository)
(split-string string "/")
(abcl-build:split-string string "/")
(setf result
(apply #'resolve-dependencies group-id artifact-id
(append (when version
......@@ -624,5 +624,59 @@ artifact and all of its transitive dependencies."
(when repository
`(:repository ,repository))))))))))
(defun resolve-multiple-maven-dependencies (dependencies &optional managed-dependencies exclusions (first-is-root nil))
"Return a list of jar file paths that satisfy dependencies
dependencies: a list of maven artifacts. color or slash separated
components groupid:artifactid:versionid
managed-dependencies: a list of maven artifacts. If an dependency
with same groupid and artifactid are encountered, the version
specified here overrides.
exclusions: a list of partial maven artifacts
groupid:artifactid. Dependencies with same groupid and artifactid are
exluded
first-is-root: If the first dependency should include optional
dependencies, set this to t. Usually not.
"
(let (aether)
(with-aether (aether)
(let ((collect-request (java:jnew (jss:find-java-class "CollectRequest")))
(exclusions-collection (jss:new 'hashset) )
(compile-scope (java:jfield (jss:find-java-class "JavaScopes") "COMPILE")))
(loop for e in exclusions
for (groupid artifactid) = (abcl-build:split-string e #\:)
;; If i have scope be compile-scope it doesn't get excluded!!
for exclusion = (jss:new 'aether.graph.Exclusion groupid artifactid "" "jar")
do (#"add" exclusions-collection exclusion))
(loop for a in dependencies
for artifact = (make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do
;; setExclusions returns a new dependency. We have to use that. That passed dependency i not modified!
;; http://grepcode.com/file/repo1.maven.org/maven2/org.eclipse.aether/aether-api/1.0.2.v0150114/org/eclipse/aether/graph/Dependency.java#Dependency.getOptional%28%29
;; Nice of them to clearly document that :-/
(setq dependency (#"setExclusions" dependency exclusions-collection))
(if first-is-root
(#"setRoot" collect-request dependency)
(#"addDependency" collect-request dependency))
(setq first-is-root nil))
(loop for a in managed-dependencies
for artifact = (make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do (setq dependency (#"setExclusions" dependency exclusions-collection))
(#"addManagedDependency" collect-request dependency))
(let ((dependencies (#"collectDependencies" (ensure-repository-system) (ensure-session) collect-request))
(nodelist-generator (jss:new 'PreorderNodeListGenerator))
(dependency-request (jss:new 'DependencyRequest)))
(#"setRoot" dependency-request (#"getRoot" dependencies))
(#"resolveDependencies" (ensure-repository-system) (ensure-session) dependency-request)
(#"accept" (#"getRoot" dependencies) nodelist-generator)
(abcl-build:split-string (#"getClassPath" nodelist-generator) #\:))))))
;;; Currently the last file listed in ASDF
(provide 'abcl-asdf)
(in-package :abcl-asdf)
;;; <https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html>
;;; If a artifact is root then its optional dependencies are
;; collected. If the same artifact is not root, then the optional
;;; dependencies are not collected. We don't need optionals since from
;;; our point of view we are the top pom and everything specified are
;;; dependencies
;;; Used by asdf-mvn-module.
(defun resolve-multiple-maven-dependencies
(dependencies &optional managed-dependencies exclusions (first-is-root nil))
"Return a list of jar file paths that satisfy dependencies
dependencies: a list of maven artifacts. color or slash separated
components groupid:artifactid:versionid
managed-dependencies: a list of maven artifacts. If an dependency
with same groupid and artifactid are encountered, the version
specified here overrides.
exclusions: a list of partial maven artifacts
groupid:artifactid. Dependencies with same groupid and artifactid are
exluded
first-is-root: If the first dependency should include optional
dependencies, set this to t. Usually not.
"
(let (aether)
(with-aether (aether)
(let ((collect-request (java:jnew (jss:find-java-class "CollectRequest")))
(exclusions-collection (jss:new 'hashset) )
(compile-scope (java:jfield (jss:find-java-class "JavaScopes") "COMPILE")))
(loop for e in exclusions
for (groupid artifactid) = (abcl-build:split-string e #\:)
;; If i have scope be compile-scope it doesn't get excluded!!
for exclusion = (jss:new 'aether.graph.Exclusion groupid artifactid "" "jar")
do (#"add" exclusions-collection exclusion))
(loop for a in dependencies
for artifact = (make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do
;; setExclusions returns a new dependency. We have to use
;; that. That passed dependency i not modified!
;; http://grepcode.com/file/repo1.maven.org/maven2/org.eclipse.aether/aether-api/1.0.2.v0150114/org/eclipse/aether/graph/Dependency.java#Dependency.getOptional%28%29
;; Nice of them to clearly document that :-/
(setq dependency (#"setExclusions" dependency exclusions-collection))
(if first-is-root
(#"setRoot" collect-request dependency)
(#"addDependency" collect-request dependency))
(setq first-is-root nil))
(loop for a in managed-dependencies
for artifact = (make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do (setq dependency (#"setExclusions" dependency exclusions-collection))
(#"addManagedDependency" collect-request dependency))
(let ((dependencies (#"collectDependencies" (ensure-repository-system) (ensure-session) collect-request))
(nodelist-generator (jss:new 'PreorderNodeListGenerator))
(dependency-request (jss:new 'DependencyRequest)))
(#"setRoot" dependency-request (#"getRoot" dependencies))
(#"resolveDependencies" (ensure-repository-system) (ensure-session) dependency-request)
(#"accept" (#"getRoot" dependencies) nodelist-generator)
(abcl-build:split-string (#"getClassPath" nodelist-generator) #\:))))))
......@@ -32,6 +32,7 @@
#:*maven-remote-repository*
#:resolve-artifact
#:resolve-dependencies
#:resolve-multiple-maven-dependencies
#:as-classpath
......
;;; From https://github.com/alanruttenberg/lsw2/blob/owlapiv4/owl2/owl2libs-mvn2.asd
(defsystem test-mvn-module
:description "Non-Lisp dependencies necessary for OWL to function."
:defsystem-depends-on (asdf-mvn-module)
:components
((:mvn-module maven
:dependencies
("net.sourceforge.owlapi/pellet-cli-ignazio1977/2.4.0-ignazio1977"
"org.semanticweb.elk/elk-owlapi/0.4.3"
"net.sourceforge.owlapi/org.semanticweb.hermit/1.3.8.413"
"net.sourceforge.owlapi/owlapi-distribution/4.2.6"
"net.sourceforge.owlapi/owlexplanation/2.0.0"
"de.sciss/prefuse-core/1.0.1"
"de.sciss/prefuse-demos/1.0.1")
:managed-dependencies
("org.slf4j/slf4j-api/1.7.21"
"net.sourceforge.owlapi:owlapi-distribution:4.2.6")
:exclusions
("net.sourceforge.owlapi:owlapi-osgidistribution"
"edu.stanford.protege:org.protege.editor.owl"))
#+(or)
(:module rest :pathname "lib" :components
((:bundle "uk.ac.manchester.cs.owl.factplusplus-1.6.5")
(:jar-file "LSWTreeview-1.0.0")
(:jar-file "QuotedStringAnnotationVisitor-1.0.0")))
(:module lib :pathname "lib"
:depends-on (maven #+(or) rest)))
:perform (load-op :after (o c)
(progn
(#"configure" 'org.apache.log4j.BasicConfigurator (jss::new 'NullAppender))
(print "configured log4j"))))
(in-package :cl-user)
;;; TODO: restore original ASDF configuration after running test
(defun asdf-add-test-mvn-module ()
(asdf:initialize-source-registry
`(:source-registry
(:directory ,(asdf:system-relative-pathname :mvn-module "t/eg/"))
:inherit-configuration)))
(unless (ignore-errors (asdf:find-system :test-mvn-module))
(asdf-add-test-mvn-module))
(prove:plan 1)
(prove:ok (asdf:load-system :test-mvn-module)
"Testing loading of ASDF:MVN-MODULE definition…")
(prove:finalize)
(in-package :cl-user)
(require :jss)
;; https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html
;; if a artifact is root then its optional dependencies are
;; collected. If the same artifact is not root, then the optional
;; dependencies are not collected. We don't need optionals since from
;; our point of view we are the top pom and everything specified are
;; dependencies
(defun resolve-multiple-maven-dependencies (dependencies &optional managed-dependencies exclusions (first-is-root nil))
"Return a list of jar file paths that satisfy dependencies
dependencies: a list of maven artifacts. color or slash separated
components groupid:artifactid:versionid
managed-dependencies: a list of maven artifacts. If an dependency
with same groupid and artifactid are encountered, the version
specified here overrides.
exclusions: a list of partial maven artifacts
groupid:artifactid. Dependencies with same groupid and artifactid are
exluded
first-is-root: If the first dependency should include optional
dependencies, set this to t. Usually not.
"
(let ((collect-request (java:jnew (jss:find-java-class "CollectRequest")))
(exclusions-collection (jss:new 'hashset) )
(compile-scope #"JavaScopes.COMPILE"))
(loop for e in exclusions
for (groupid artifactid) = (abcl/build:split-string e #\:)
;; If i have scope be compile-scope it doesn't get excluded!!
for exclusion = (jss:new 'aether.graph.Exclusion groupid artifactid "" "jar")
do (#"add" exclusions-collection exclusion))
(loop for a in dependencies
for artifact = (abcl-asdf::make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do
;; setExclusions returns a new dependency. We have to use that. That passed dependency is not modified!
;; http://grepcode.com/file/repo1.maven.org/maven2/org.eclipse.aether/aether-api/1.0.2.v20150114/org/eclipse/aether/graph/Dependency.java#Dependency.getOptional%28%29
;; Nice of them to clearly document that :-/
(setq dependency (#"setExclusions" dependency exclusions-collection))
(if first-is-root
(#"setRoot" collect-request dependency)
(#"addDependency" collect-request dependency))
(setq first-is-root nil))
(loop for a in managed-dependencies
for artifact = (abcl-asdf::make-artifact (#"replaceAll" a "/" ":"))
for dependency = (jss:new 'aether.graph.Dependency artifact compile-scope)
do (setq dependency (#"setExclusions" dependency exclusions-collection))
(#"addManagedDependency" collect-request dependency))
(let ((dependencies (#"collectDependencies"
(abcl-asdf::ensure-repository-system)
(abcl-asdf::ensure-session) collect-request))
(nodelist-generator (jss:new 'PreorderNodeListGenerator))
(dependency-request (jss:new 'DependencyRequest)))
(#"setRoot" dependency-request
(#"getRoot" dependencies))
(#"resolveDependencies" (abcl-asdf::ensure-repository-system)
(abcl-asdf::ensure-session)
dependency-request)
(#"accept" (#"getRoot" dependencies)
nodelist-generator)
(abcl/build:split-string (#"getClassPath" nodelist-generator) #\:))))
(prove:plan 3)
(let ((deps (resolve-multiple-maven-dependencies
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment