diff --git a/README.md b/README.md index 74f1aac..9c563df 100644 --- a/README.md +++ b/README.md @@ -32,21 +32,31 @@ vector of the `:user` profile in `~/.lein/profiles.clj`. ## Usage +### Reloading project.clj + +If you modify the dependencies in your `project.clj` file, you can load the +modified dependencies with `load-project`. + +This will add all non-conflicting dependency changes. Only new +dependencies are considered non-conflicting. New versions of existing +dependencies are not loaded. Removed dependencies are not unloaded. + +### Adding Ad-Hoc Dependencies + To add a dependency to the classpath, use the `distill` function, passing a leiningen style dependency vector. ```clj (alembic.still/distill '[org.clojure/tools.logging "0.2.0"]) ``` +You can pass a sequence of dependencies to add, or just a single +dependency as in the example above. -If the dependency is added to the classpath, `distill` returns a sequence of -maps, where each map represents a dependent jar. Those jars without a current -version on the classpath will be added to the classpath. The jars with a -version already on the classpath are not added to the classpath, and the -currently loaded version is reported on the :current-version key. +`distill` prints the dependencies added to the classpath, and those +not added due to conflicts. -The distill function returns nil, with no side-effects, if the dependency is -already on the classpath. +The distill function returns with no side-effects, if the dependency's +jars are already on the classpath. By default, `distill` uses the repositories in the current lein project. You can override this by passing a map of lein style repository information to the @@ -54,6 +64,14 @@ can override this by passing a map of lein style repository information to the obtain the lein project repositories, should you want to adapt these to pass as an `:repositories` argument. + +For programmatic use, `distill*` returns a sequence of maps, where +each map represents a dependent jar. Those jars without a current +version on the classpath will be added to the classpath. The jars +with a version already on the classpath are not added to the +classpath, and the currently loaded version is reported on the +:current-version key. + You can query the dependencies that have been added with the `dependencies-added` function, which returns a sequence of leiningen style dependency vectors. diff --git a/project.clj b/project.clj index 5ed05ea..18d9c8d 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject alembic "0.1.5-SNAPSHOT" +(defproject alembic "0.2.0-SNAPSHOT" :description "A jar distiller" :url "https://github.com/pallet/alembic" :license {:name "Eclipse Public License" diff --git a/src/alembic/still.clj b/src/alembic/still.clj index 2ef031c..bcd5ca7 100644 --- a/src/alembic/still.clj +++ b/src/alembic/still.clj @@ -11,6 +11,7 @@ http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 [classlojure.core :refer [base-classloader classlojure ext-classloader] :as classlojure] [clojure.java.io :refer [copy file reader resource]] + [clojure.pprint :refer [pprint]] [dynapath.util :as util]) (:import java.util.Properties)) @@ -27,8 +28,7 @@ http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 {:pre [(.endsWith jar-path ".jar")]} (let [jar-url (resource jar-path) f (java.io.File/createTempFile - (subs jar-path 0 (- (count jar-path) 4)) ".jar") - ] + (subs jar-path 0 (- (count jar-path) 4)) ".jar")] (.deleteOnExit f) (with-open [is (.getContent jar-url)] (copy is f)) @@ -88,8 +88,8 @@ http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 ([] (project-repositories the-still))) -(defn resolve-dependency - [still dependency repositories] +(defn resolve-dependencies + [still dependencies repositories] (classlojure/eval-in (:alembic-classloader @still) `(mapv @@ -99,7 +99,7 @@ http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 :jar (-> dep# meta :file apath#)})) (keys (aether/resolve-dependencies - :coordinates '[~dependency] + :coordinates '~(vec dependencies) :repositories ~repositories))))) (defn properties-path @@ -145,41 +145,55 @@ http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 (doseq [{:keys [coords current-version]} dep-jars :let [[artifact version] coords]] (when (and current-version (not= current-version version)) - (println "WARN:" artifact "version" version "requested, but " + (println "WARN:" artifact "version" version "requested, but" current-version "already on classpath.")))) +(defn conflicting-version? + "Predicate to check for a conflicting version." + [{:keys [coords current-version]}] + (and current-version (not= current-version (second coords)))) + (defn add-dep-jars + "Add any non-conflicting dependency jars. Returns the sequence of +dependency jar maps of the loaded jars." [still dep-jars] - (let [{:keys [classloader]} @still] - (doseq [{:keys [coords current-version jar]} dep-jars - :let [[artifact version] coords]] - (when-not current-version - (util/add-classpath-url classloader (.toURL (file jar))))))) - - -(defn add-dependency - "Add a dependency to the classpath. Returns a sequence of maps, each -containing a :coords vector, a :jar path and possibly -a :current-version string. If the dependency is already on the -classpath, returns nil. If the optional parameter :verbose is + (let [{:keys [classloader]} @still + deps (remove :current-version dep-jars)] + (doseq [{:keys [jar]} deps] + (util/add-classpath-url classloader (.toURL (file jar)))) + deps)) + +(defn add-dependencies + "Add dependencies to the classpath. Returns a sequence of maps, each +containing a `:coords` vector, a `:jar` path and possibly a +`:current-version` string. If the optional parameter :verbose is true (the default), then WARN messages will be printed to the console if a version of a library is requested and the classpath already -contains a different version of the same library" - [still dependency repositories - & {:keys [verbose] :as opts :or {verbose true}}] - (when-not (meta-inf-properties-url still dependency) - (let [dep-jars (->> (resolve-dependency still dependency repositories) - (current-dep-versions still))] - (when verbose (warn-mismatch-versions dep-jars)) - (add-dep-jars still dep-jars) - (swap! still (fn [m] - (-> m - (update-in [:dependencies] conj dependency) - (update-in [:jars] assoc dependency dep-jars)))) - dep-jars))) +contains a different version of the same library." + [still dependencies repositories {:keys [verbose] :as opts + :or {verbose true}}] + (let [dep-jars (->> (resolve-dependencies still dependencies repositories) + (current-dep-versions still))] + (when verbose (warn-mismatch-versions dep-jars)) + (add-dep-jars still dep-jars) + (swap! still (fn [m] + (-> m + (update-in [:dependencies] + #(distinct (concat % dependencies))) + (update-in [:jars] + #(distinct (concat % dep-jars)))))) + dep-jars)) -(defn distill - "Add a dependency to the classpath. +(defn print-coords + "Pretty print the dependency coordinates of a sequence of dependencies." + [deps] + (pprint (vec (sort-by first (map :coords deps))))) + +(defn distill* + "Add dependencies to the classpath. Returns a sequence of dependency maps. + +`dependencies` can be a coordinate vector, or a sequence of such +vectors. `:repositories` : specify a map of leiningen style repository definitions to be used when @@ -194,12 +208,83 @@ contains a different version of the same library" : specifies whether WARN messages should be printed to the console if a version of library is requests and there is already a different version of the same library in the classpath. Defaults to true" - [dependency & {:keys [repositories still verbose] + [dependencies {:keys [repositories still verbose] :or {still the-still verbose true}}] (let [repositories (into {} (or repositories (project-repositories still)))] - (add-dependency still dependency repositories :verbose verbose))) + (add-dependencies + still + (if (every? vector? dependencies) dependencies [dependencies]) + repositories + {:verbose verbose}))) + +(defn distill + "Add dependencies to the classpath. + +`dependencies` can be a coordinate vector, or a sequence of such vectors. + +`:repositories` +: specify a map of leiningen style repository definitions to be used when + resolving. Defaults to the repositories specified in the current lein + project. + +`:still` +: specifies an alembic still to use. This would be considered advanced + usage (see the tests for an example). + +`:verbose` +: specifies whether WARN messages should be printed to the console if + a version of library is requests and there is already a different + version of the same library in the classpath. Defaults to true" + [dependencies & {:keys [repositories still verbose] + :or {still the-still + verbose true} + :as options}] + (let [dep-jars (distill* dependencies options) + loaded (remove conflicting-version? dep-jars) + conflicting (filter conflicting-version? dep-jars)] + (when (seq loaded) + (println "Loaded dependencies:") + (print-coords loaded)) + (when (seq conflicting) + (println + "Dependencies not loaded due to conflict with previous jars :") + (print-coords conflicting)))) + +(defn load-project* + "Load project.clj dependencies. Returns a vector of jars required +for the dependencies. Loads any of the jars that are not conflicting +with versions already on the classpath." + [still project-file {:keys [verbose] :as options}] + (let [[dependencies repositories] + (classlojure/eval-in + (:alembic-classloader @still) + `(do + (require '[leiningen.core.project :as ~'project]) + (let [project# (leiningen.core.project/read ~project-file)] + [(:dependencies project#) + (:repositories project#)])))] + (add-dependencies still dependencies (into {} repositories) options))) + +(defn load-project + "Load project.clj dependencies. Prints the dependency jars that are +loaded, and those that were not loaded due to conflicts." + ([still project-file] + (let [dep-jars (load-project* still project-file {:verbose true}) + loaded (remove conflicting-version? dep-jars) + conflicting (filter conflicting-version? dep-jars)] + (when (seq loaded) + (println "Loaded dependencies:") + (print-coords loaded)) + (when (seq conflicting) + (println + "Dependencies not loaded due to conflict with previous jars :") + (print-coords conflicting)))) + ([still] + (load-project still "project.clj")) + ([] + (load-project the-still))) (defn dependencies-added ([still] @@ -209,12 +294,11 @@ contains a different version of the same library" (defn dependency-jars ([still] (:jars @still)) - ([] (dependencies-added the-still))) + ([] (dependency-jars the-still))) (defn conflicting-versions - "Return a sequence of possibly conflicting versions of jars required for the -specified dependency (dependency must have been added with `add-dependency`)." - ([dependency still] - (filter :current-version (get (dependency-jars still) dependency))) - ([dependency] - (conflicting-versions dependency the-still))) + "Return a sequence of possibly conflicting versions of jars required +for dependencies by the still)." + ([still] + (filter conflicting-version? (dependency-jars still))) + ([] (conflicting-versions the-still))) diff --git a/test/alembic/still_test.clj b/test/alembic/still_test.clj index a68a20f..d49e259 100644 --- a/test/alembic/still_test.clj +++ b/test/alembic/still_test.clj @@ -62,7 +62,5 @@ "tools.logging on the classpath") (is (= [tools-logging] (still/dependencies-added still)) "distilled dependency listed") - (is (= 1 (count (still/conflicting-versions tools-logging still))) - "possible distilled dependency conflict listed")) - (let [deps2 (still/distill tools-logging :still still)] - (is (nil? deps2) "Not loaded again")))) + (is (= 1 (count (still/conflicting-versions still))) + "possible distilled dependency conflict listed"))))