From 2dee7f2938d3067682ae709ad01413aff4c2e7d4 Mon Sep 17 00:00:00 2001 From: Hugo Duncan Date: Wed, 28 Aug 2013 08:45:57 -0400 Subject: [PATCH] Add load-project, and make distill print changes The load-project function will read the current project.clj file's dependencies and load any new ones. The distill function now prints the dependency coordinates that are loaded, and separately those that are not due to conflicts. It also now accepts a sequence of dependencies to load. The previous return behaviour of distill is available in distill*. --- README.md | 32 +++++-- project.clj | 2 +- src/alembic/still.clj | 168 +++++++++++++++++++++++++++--------- test/alembic/still_test.clj | 6 +- 4 files changed, 154 insertions(+), 54 deletions(-) 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"))))