├── .gitignore ├── .travis.yml ├── README.md ├── ReleaseNotes.md ├── profiles.clj ├── project.clj ├── release.sh ├── src └── alembic │ └── still.clj └── test └── alembic └── still_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | /doc 6 | pom.xml 7 | pom.xml.asc 8 | *.jar 9 | *.class 10 | .lein-deps-sum 11 | .lein-failures 12 | .lein-plugins 13 | .lein-repl-history 14 | .nrepl-port 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | before_script: 4 | - lein2 version 5 | script: lein2 test 6 | after_success: 7 | - lein2 pallet-release push 8 | env: 9 | global: 10 | secure: VILXdiGc17ZWFyf+XUwGYNYbmQi/l+bTW3hzDIyxDxS+dv7coO6+Vbju2NPGAiprwnnNwNE/Ggg46kJ0AKkfbdGUKufdAptaY4RVWQ9zD6Yq8FTyTq48UBb1zQt9Sp2WIKB50Nh3GwPW0b7yw40NGk7SFT31zOEEPaatTuiWHUQ= 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alembic 2 | 3 | [Repository](https://github.com/pallet/alembic) · 4 | [Issues](https://github.com/pallet/alembic/issues) · 5 | [API docs](http://palletops.com/alembic/0.3/api) · 6 | [Annotated source](http://palletops.com/alembic/0.3/annotated/uberdoc.html) · 7 | [Release Notes](https://github.com/pallet/alembic/blob/develop/ReleaseNotes.md) 8 | 9 | Alembic is a clojure library that allows you to distill jars onto your 10 | classpath in a running JVM instance. You can use it to add 11 | dependencies to a running REPL, either in an ad-hoc fashion, or by 12 | reloading your `project.clj` file. 13 | 14 | You can also use it to reload your `project.clj` file and invoke 15 | leiningen tasks. 16 | 17 | It uses [leiningen][lein] and [pomegranate][pomegranate] to resolve 18 | the jars, [classlojure][classlojure] to isolate leiningen and its 19 | dependencies, and [dynapath][dynapath] to modify the classpath. 20 | 21 | This means you can use lein and pomegranate without their dependencies 22 | interfering with your project classpath. The only dependencies added 23 | are classlojure and dynapath - both small libraries with no transitive 24 | dependencies. 25 | 26 | ## Project Dependency 27 | 28 | To use Alembic with nREPL or any other clojure REPL, you will need to add 29 | Alembic to you development dependencies. For a leiningen based project, you can 30 | do this by adding it to the `:dependencies` vector of the `:dev` profile in 31 | `project.clj`. 32 | 33 | ```clj 34 | :profiles {:dev {:dependencies [[alembic "0.3.2"]]}} 35 | ``` 36 | 37 | You can enable Alembic on all you projects, by adding it to the `:dependencies` 38 | vector of the `:user` profile in `~/.lein/profiles.clj`. 39 | 40 | ## Usage 41 | 42 | ### Reloading project.clj 43 | 44 | If you modify the dependencies in your `project.clj` file, you can load the 45 | modified dependencies with `load-project`. 46 | 47 | This will add all non-conflicting dependency changes. Only new 48 | dependencies are considered non-conflicting. New versions of existing 49 | dependencies are not loaded. Removed dependencies are not unloaded. 50 | 51 | ### Adding Ad-Hoc Dependencies 52 | 53 | To add a dependency to the classpath, use the `distill` function, passing a 54 | leiningen style dependency vector. 55 | 56 | ```clj 57 | (alembic.still/distill '[org.clojure/tools.logging "0.3.2"]) 58 | ``` 59 | 60 | You can pass a sequence of dependencies to add, or just a single 61 | dependency as in the example above. 62 | 63 | `distill` prints the dependencies added to the classpath, and those 64 | not added due to conflicts. 65 | 66 | The distill function returns with no side-effects, if the dependency's 67 | jars are already on the classpath. 68 | 69 | By default, `distill` uses the repositories in the current lein project. You 70 | can override this by passing a map of lein style repository information to the 71 | `:repositories` option. The `project-repositories` function can be used to 72 | obtain the lein project repositories, should you want to adapt these to pass as 73 | an `:repositories` argument. 74 | 75 | 76 | For programmatic use, `distill*` returns a sequence of maps, where 77 | each map represents a dependent jar. Those jars without a current 78 | version on the classpath will be added to the classpath. The jars 79 | with a version already on the classpath are not added to the 80 | classpath, and the currently loaded version is reported on the 81 | :current-version key. 82 | 83 | You can query the dependencies that have been added with the 84 | `dependencies-added` function, which returns a sequence of leiningen style 85 | dependency vectors. 86 | 87 | You can lookup the dependency jars for the distilled dependencies, using the 88 | `dependency-jars` function. 89 | 90 | The `conflicting-versions` function returns a sequence of dependencies for a 91 | distilled dependency, where the dependency jar version doesn't match the version 92 | currently on the classpath. 93 | 94 | ### Invoking Leiningen Tasks 95 | 96 | The `lein` macro invokes [leiningen][lein]. For example, to 97 | show your project's dependency tree, you could run: 98 | 99 | ```clj 100 | (alembic.still/lein deps :tree) 101 | ``` 102 | 103 | The macro allows you to invoke tasks without passing string arguments. 104 | If you need to call lein functionally, use the `lein*` function 105 | instead. 106 | 107 | ## Configuring User Profile with Injections 108 | 109 | You can reduce the amount of typing you need to use alembic by using 110 | the [`lein-shorthand`][lein-shorthand] plugin and configuring your `:user` 111 | profile in `~/.lein/profiles.clj`. 112 | 113 | ```clj 114 | {:user 115 | {:dependencies [[alembic "0.3.2"]] 116 | :plugins [[com.palletops/lein-shorthand "0.4.0"]] 117 | :shorthand {. [alembic.still/distill alembic.still/lein]}}} 118 | ``` 119 | 120 | This will define the `.` namespace, so you can run `(./lein deps :tree)`. 121 | 122 | ## Support and Discussion 123 | 124 | Discussion of alembic, either on the 125 | [clojure-tools](https://groups.google.com/forum/?fromgroups#!forum/clojure-tools) 126 | google group, or on #clojure or #pallet on freenode IRC. 127 | 128 | ## License 129 | 130 | Copyright © 2013 Hugo Duncan 131 | 132 | Distributed under the Eclipse Public License. 133 | 134 | [lein]: http://leiningen.org "Leiningen" 135 | [lein-shorthand]: http://github.com/palletops/lein-shorthand "lein-shorthand plugin" 136 | [pomegranate]: https://github.com/cemerick/pomegranate#pomegranate-- "Pomegranate" 137 | [classlojure]: https://github.com/flatland/classlojure "Classlojure" 138 | [dynapath]: https://github.com/tobias/dynapath#dynapath- "Dynapath" 139 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | ## 0.3.2 2 | 3 | - Add function to reset a still classloader 4 | Reset the alembic classloader in the still. Can be used to reset the 5 | classpath of the still, in case of conflicts, e.g. between lein plugins. 6 | 7 | ## 0.3.1 8 | 9 | - Add a test for running a lein task 10 | 11 | - Move clojure dependency to the :provided profile 12 | 13 | ## 0.3.0 14 | 15 | - Add lein function to run lein tasks 16 | 17 | - Update to lein 2.5.0 18 | 19 | # 0.2.1 20 | 21 | - Add support for downloads via proxy 22 | Adds a :proxy option to load-project and distill. 23 | 24 | If no proxy is specified, the proxy settings are defaulted from 25 | environment variables, in the same way as leiningen does. 26 | 27 | - Respect verbose flag in distill 28 | 29 | # 0.2.0 30 | 31 | - Add load-project, and make distill print changes 32 | The load-project function will read the current project.clj file's 33 | dependencies and load any new ones. 34 | 35 | The distill function now prints the dependency coordinates that are 36 | loaded, and separately those that are not due to conflicts. It also now 37 | accepts a sequence of dependencies to load. 38 | 39 | The previous return behaviour of distill is available in distill*. 40 | 41 | # 0.1.4 42 | 43 | - Update to classlojure 0.7.0 44 | Removes transitive dependency on useful. 45 | 46 | # 0.1.3 47 | 48 | - Don't read project.clj when :repositories passed to distill 49 | 50 | # 0.1.2 51 | 52 | - Allow disabling version mismatch messages at the console. 53 | 54 | They can be turned off by passing `false` to the option `:verbose?` in 55 | `distill` and `add-dependency`. 56 | 57 | - Add no-arg arity to `project-dependencies` 58 | 59 | Describes the :repositories option for distill in the readme, mentioning 60 | project-dependencies. 61 | 62 | 63 | # 0.1.1 64 | 65 | - Add clojure 1.4 dependency 66 | 67 | - Normalise :repositories to be a map 68 | 69 | # 0.1.0 70 | 71 | - Initial version 72 | -------------------------------------------------------------------------------- /profiles.clj: -------------------------------------------------------------------------------- 1 | {:clojure-1.6.0 {:dependencies [[org.clojure/clojure "1.6.0"]]} 2 | :clojure-1.5.1 {:dependencies [[org.clojure/clojure "1.5.1"]]} 3 | :clojure-1.5.0 {:dependencies [[org.clojure/clojure "1.5.0"]]} 4 | :clojure-1.4.0 {:dependencies [[org.clojure/clojure "1.4.0"]]} 5 | :dev {:plugins [[lein-pallet-release "RELEASE"]]}} 6 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject alembic "0.3.3-SNAPSHOT" 2 | :description 3 | "A library for use In the REPL. Add dependencies to your classpath, 4 | reload your project.clj file, and invoke leiningen tasks." 5 | :url "https://github.com/pallet/alembic" 6 | :license {:name "Eclipse Public License" 7 | :url "http://www.eclipse.org/legal/epl-v10.html"} 8 | :dependencies [[lein-as-resource "2.5.0"] 9 | [org.flatland/classlojure "0.7.0"] 10 | [org.tcrawley/dynapath "0.2.3"]] 11 | :exclusions [[org.clojure/clojure]] 12 | :profiles {:provided 13 | {:dependencies [[org.clojure/clojure "1.4.0"]]}}) 14 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # release alembic 4 | 5 | if [[ $# -lt 3 ]]; then 6 | echo "usage: $(basename $0) previous-version new-version next-version" >&2 7 | exit 1 8 | fi 9 | 10 | previous_version=$1 11 | version=$2 12 | next_version=$3 13 | 14 | echo "" 15 | echo "Start release of $version, previous version is $previous_version" 16 | echo "" 17 | echo "" 18 | 19 | lein do clean, with-profile +no-checkouts test && \ 20 | git flow release start $version || exit 1 21 | 22 | lein with-profile +release set-version ${version} :previous-version ${previous_version} \ 23 | || { echo "set version failed" >2 ; exit 1; } 24 | 25 | echo "" 26 | echo "" 27 | echo "Changes since $previous_version" 28 | git --no-pager log --pretty=changelog $previous_version.. 29 | echo "" 30 | echo "" 31 | echo "Now edit project.clj, ReleaseNotes and README" 32 | 33 | $EDITOR project.clj 34 | $EDITOR ReleaseNotes.md 35 | $EDITOR README.md 36 | 37 | echo -n "commiting project.clj, release notes and readme. enter to continue:" \ 38 | && read x \ 39 | && git add project.clj ReleaseNotes.md README.md \ 40 | && git commit -m "Updated project.clj, release notes and readme for $version" \ 41 | && echo -n "Peform release. enter to continue:" && read x \ 42 | && lein do clean, with-profile +no-checkouts test, deploy clojars \ 43 | && rm -f pom.xml \ 44 | && git flow release finish $version \ 45 | && echo "Now push to github. Don't forget the tags!" \ 46 | && lein with-profile +doc doc \ 47 | && lein with-profile +release set-version ${next_version} \ 48 | && git add project.clj \ 49 | && git commit -m "Updated version for next release cycle" 50 | -------------------------------------------------------------------------------- /src/alembic/still.clj: -------------------------------------------------------------------------------- 1 | (ns alembic.still 2 | " 3 | 4 | Track the added dependencies, so that we can query for addition to 5 | project.clj 6 | 7 | Can not create a classloader with a jar inside a jar 8 | http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4388202 9 | " 10 | (:require 11 | [classlojure.core :refer [base-classloader classlojure ext-classloader] 12 | :as classlojure] 13 | [clojure.java.io :refer [copy file reader resource]] 14 | [clojure.pprint :refer [pprint]] 15 | [dynapath.util :as util]) 16 | (:import 17 | java.util.Properties)) 18 | 19 | ;;; ## Utilities 20 | (defn classpath-urls 21 | "Return the current classpath." 22 | [] 23 | (util/classpath-urls base-classloader)) 24 | 25 | (defn extract-jar 26 | "Extract a jar on the classpath to the filesystem, returning its URL." 27 | [^String jar-path] 28 | {:pre [(.endsWith jar-path ".jar")]} 29 | (let [jar-url (resource jar-path) 30 | f (java.io.File/createTempFile 31 | (subs jar-path 0 (- (count jar-path) 4)) ".jar")] 32 | (.deleteOnExit f) 33 | (with-open [is (.getContent jar-url)] 34 | (copy is f)) 35 | (.toURL f))) 36 | 37 | 38 | ;;; ## Still 39 | (defonce ^{:doc "Classpath URLs for a still"} 40 | alembic-cp 41 | (map extract-jar ["lein-standalone.jar"])) 42 | 43 | (def cl (apply classlojure alembic-cp)) 44 | 45 | (defn alembic-classloader 46 | "Return a classloader for alembic to use to resolve dependencies" 47 | [] 48 | (doto (apply classlojure alembic-cp) 49 | (classlojure/eval-in 50 | `(require '[cemerick.pomegranate.aether :as ~'aether])))) 51 | 52 | (defn make-still 53 | "Create an still that that can distill jars into the specified classloader." 54 | [classloader] 55 | (when-not (util/addable-classpath? classloader) 56 | (throw (ex-info 57 | "Alembic can not manipulate specified ClassLoader." 58 | {:classloader classloader 59 | :reason :not-addable-with-dynapath}))) 60 | {:dependencies [] 61 | :jars {} 62 | :classloader classloader 63 | :alembic-classloader (alembic-classloader)}) 64 | 65 | ;;; Our still 66 | (defonce the-still (atom (make-still base-classloader))) 67 | 68 | (defn reset-still 69 | "Reset the alembic classloader in the still. Can be used to reset 70 | the classpath of the still, in case of conflicts, e.g. between lein 71 | plugins." 72 | ([still] 73 | (swap! still assoc :alembic-classloader (alembic-classloader)) 74 | nil) 75 | ([] 76 | (reset-still the-still))) 77 | 78 | (defn project-repositories 79 | "Load project repositories from leiningen." 80 | ([still project-file] 81 | (classlojure/eval-in 82 | (:alembic-classloader @still) 83 | `(do 84 | (require '[leiningen.core.project :as ~'project]) 85 | (:repositories (leiningen.core.project/read ~project-file))))) 86 | ([still] 87 | (project-repositories still "project.clj")) 88 | ([] 89 | (project-repositories the-still))) 90 | 91 | (defn resolve-dependencies 92 | [still dependencies repositories proxy] 93 | (classlojure/eval-in 94 | (:alembic-classloader @still) 95 | `(do 96 | (require '[leiningen.core.classpath :as ~'cp]) 97 | (mapv 98 | (fn [dep#] 99 | (letfn [(apath# [^java.io.File f#] (.getAbsolutePath f#))] 100 | {:coords dep# 101 | :jar (-> dep# meta :file apath#)})) 102 | (keys 103 | (aether/resolve-dependencies 104 | :coordinates '~(vec dependencies) 105 | :repositories ~repositories 106 | :proxy (or ~proxy (cp/get-proxy-settings)))))))) 107 | 108 | (defn properties-path 109 | [group-id artifact-id] 110 | (str "META-INF/maven/" group-id "/" artifact-id "/pom.properties")) 111 | 112 | (defn coords->ids 113 | "Convert coords to a map of :group-id and :artifact-id." 114 | [[artifact version :as coords]] 115 | {:group-id (or (namespace artifact) artifact) 116 | :artifact-id (symbol (name artifact))}) 117 | 118 | (defn meta-inf-properties-url 119 | "Return a URL for the META-INF properties file for the given `coords`." 120 | [still coords] 121 | (let [{:keys [group-id artifact-id]} (coords->ids coords)] 122 | (classlojure/eval-in 123 | (:classloader @still) 124 | `(when-let [r# (resource ~(properties-path group-id artifact-id))] 125 | (.toString r#))))) 126 | 127 | (defn meta-inf-version 128 | "Return a version string for the currently loaded version of the given 129 | `coords`." 130 | [still coords] 131 | (let [{:keys [group-id artifact-id]} (coords->ids coords)] 132 | (classlojure/eval-in 133 | (:classloader @still) 134 | `(when-let [r# (resource ~(properties-path group-id artifact-id))] 135 | (with-open [rdr# (reader r#)] 136 | (let [properties# (doto (Properties.) (.load rdr#))] 137 | (.getProperty properties# "version"))))))) 138 | 139 | (defn current-dep-versions 140 | [still dep-jars] 141 | (for [{:keys [coords] :as dep} dep-jars] 142 | (if-let [current-version (meta-inf-version still coords)] 143 | (assoc dep :current-version current-version) 144 | dep))) 145 | 146 | (defn warn-mismatch-versions 147 | [dep-jars] 148 | (doseq [{:keys [coords current-version]} dep-jars 149 | :let [[artifact version] coords]] 150 | (when (and current-version (not= current-version version)) 151 | (println "WARN:" artifact "version" version "requested, but" 152 | current-version "already on classpath.")))) 153 | 154 | (defn conflicting-version? 155 | "Predicate to check for a conflicting version." 156 | [{:keys [coords current-version]}] 157 | (and current-version (not= current-version (second coords)))) 158 | 159 | (defn add-dep-jars 160 | "Add any non-conflicting dependency jars. Returns the sequence of 161 | dependency jar maps of the loaded jars." 162 | [still dep-jars] 163 | (let [{:keys [classloader]} @still 164 | deps (remove :current-version dep-jars)] 165 | (doseq [{:keys [jar]} deps] 166 | (util/add-classpath-url classloader (.toURL (file jar)))) 167 | deps)) 168 | 169 | (defn add-dependencies 170 | "Add dependencies to the classpath. Returns a sequence of maps, each 171 | containing a `:coords` vector, a `:jar` path and possibly a 172 | `:current-version` string. If the optional parameter :verbose is 173 | true (the default), then WARN messages will be printed to the console 174 | if a version of a library is requested and the classpath already 175 | contains a different version of the same library." 176 | [still dependencies repositories {:keys [verbose proxy] :as opts 177 | :or {verbose true}}] 178 | (let [dep-jars (->> (resolve-dependencies still dependencies repositories proxy) 179 | (current-dep-versions still))] 180 | (when verbose (warn-mismatch-versions dep-jars)) 181 | (add-dep-jars still dep-jars) 182 | (swap! still (fn [m] 183 | (-> m 184 | (update-in [:dependencies] 185 | #(distinct (concat % dependencies))) 186 | (update-in [:jars] 187 | #(distinct (concat % dep-jars)))))) 188 | dep-jars)) 189 | 190 | (defn print-coords 191 | "Pretty print the dependency coordinates of a sequence of dependencies." 192 | [deps] 193 | (pprint (vec (sort-by first (map :coords deps))))) 194 | 195 | (defn distill* 196 | "Add dependencies to the classpath. Returns a sequence of dependency maps. 197 | 198 | `dependencies` can be a coordinate vector, or a sequence of such 199 | vectors. 200 | 201 | `:repositories` 202 | : specify a map of leiningen style repository definitions to be used when 203 | resolving. Defaults to the repositories specified in the current lein 204 | project. 205 | 206 | `:still` 207 | : specifies an alembic still to use. This would be considered advanced 208 | usage (see the tests for an example). 209 | 210 | `:verbose` 211 | : specifies whether WARN messages should be printed to the console if 212 | a version of library is requests and there is already a different 213 | version of the same library in the classpath. Defaults to true 214 | 215 | `:proxy` 216 | : proxy configuration map (the host scheme and type must match). 217 | If not specified (or nil), the proxy configuration is read from 218 | environment variables (http_proxy, http_no_proxy, no_proxy). 219 | :host - proxy hostname 220 | :type - http (default) | http | https 221 | :port - proxy port 222 | :non-proxy-hosts - The list of hosts to exclude from proxying, may be null 223 | :username - username to log in with, may be null 224 | :password - password to log in with, may be null 225 | :passphrase - passphrase to log in wth, may be null 226 | :private-key-file - private key file to log in with, may be null" 227 | [dependencies {:keys [repositories still verbose proxy] 228 | :or {still the-still 229 | verbose true}}] 230 | (let [repositories (into {} (or repositories 231 | (project-repositories still)))] 232 | (add-dependencies 233 | still 234 | (if (every? vector? dependencies) dependencies [dependencies]) 235 | repositories 236 | {:verbose verbose :proxy proxy}))) 237 | 238 | (defn distill 239 | "Add dependencies to the classpath. 240 | 241 | `dependencies` can be a coordinate vector, or a sequence of such vectors. 242 | 243 | `:repositories` 244 | : specify a map of leiningen style repository definitions to be used when 245 | resolving. Defaults to the repositories specified in the current lein 246 | project. 247 | 248 | `:still` 249 | : specifies an alembic still to use. This would be considered advanced 250 | usage (see the tests for an example). 251 | 252 | `:verbose` 253 | : specifies whether WARN messages should be printed to the console if 254 | a version of library is requests and there is already a different 255 | version of the same library in the classpath. Defaults to true 256 | 257 | `:proxy` 258 | : proxy configuration map (the host scheme and type must match). 259 | If not specified (or nil), the proxy configuration is read from 260 | environment variables (http_proxy, http_no_proxy, no_proxy). 261 | :host - proxy hostname 262 | :type - http (default) | http | https 263 | :port - proxy port 264 | :non-proxy-hosts - The list of hosts to exclude from proxying, may be null 265 | :username - username to log in with, may be null 266 | :password - password to log in with, may be null 267 | :passphrase - passphrase to log in wth, may be null 268 | :private-key-file - private key file to log in with, may be null" 269 | [dependencies & {:keys [repositories still verbose proxy] 270 | :or {still the-still 271 | verbose true} 272 | :as options}] 273 | (let [dep-jars (distill* dependencies options) 274 | loaded (remove conflicting-version? dep-jars) 275 | conflicting (filter conflicting-version? dep-jars)] 276 | (when (and verbose (seq loaded)) 277 | (println "Loaded dependencies:") 278 | (print-coords loaded)) 279 | (when (and verbose (seq conflicting)) 280 | (println 281 | "Dependencies not loaded due to conflict with previous jars :") 282 | (print-coords conflicting)))) 283 | 284 | (defn load-project* 285 | "Load project.clj dependencies. Returns a vector of jars required 286 | for the dependencies. Loads any of the jars that are not conflicting 287 | with versions already on the classpath." 288 | [project-file {:keys [still verbose proxy] :as options}] 289 | (let [[dependencies repositories] 290 | (classlojure/eval-in 291 | (:alembic-classloader @still) 292 | `(do 293 | (require '[leiningen.core.project :as ~'project]) 294 | (let [project# (leiningen.core.project/read ~project-file)] 295 | [(:dependencies project#) 296 | (:repositories project#)])))] 297 | (add-dependencies still dependencies (into {} repositories) options))) 298 | 299 | (defn load-project 300 | "Load project.clj dependencies. Prints the dependency jars that are 301 | loaded, and those that were not loaded due to conflicts. 302 | 303 | `:proxy` 304 | : proxy configuration map (the host scheme and type must match). 305 | If not specified (or nil), the proxy configuration is read from 306 | environment variables (http_proxy, http_no_proxy, no_proxy). 307 | :host - proxy hostname 308 | :type - http (default) | http | https 309 | :port - proxy port 310 | :non-proxy-hosts - The list of hosts to exclude from proxying, may be null 311 | :username - username to log in with, may be null 312 | :password - password to log in with, may be null 313 | :passphrase - passphrase to log in wth, may be null 314 | :private-key-file - private key file to log in with, may be null" 315 | ([project-file & {:keys [still verbose proxy] 316 | :or {still the-still 317 | verbose true} 318 | :as options}] 319 | (let [dep-jars (load-project* project-file {:still still :verbose verbose :proxy proxy}) 320 | loaded (remove conflicting-version? dep-jars) 321 | conflicting (filter conflicting-version? dep-jars)] 322 | (when (seq loaded) 323 | (println "Loaded dependencies:") 324 | (print-coords loaded)) 325 | (when (seq conflicting) 326 | (println 327 | "Dependencies not loaded due to conflict with previous jars :") 328 | (print-coords conflicting)))) 329 | ([project] 330 | (load-project project :still the-still)) 331 | ([] 332 | (load-project "project.clj"))) 333 | 334 | (defn dependencies-added 335 | ([still] 336 | (:dependencies @still)) 337 | ([] (dependencies-added the-still))) 338 | 339 | (defn dependency-jars 340 | ([still] 341 | (:jars @still)) 342 | ([] (dependency-jars the-still))) 343 | 344 | (defn conflicting-versions 345 | "Return a sequence of possibly conflicting versions of jars required 346 | for dependencies by the still)." 347 | ([still] 348 | (filter conflicting-version? (dependency-jars still))) 349 | ([] (conflicting-versions the-still))) 350 | 351 | (defn lein-apply 352 | "Invoke lein" 353 | [args {:keys [still verbose] :as options}] 354 | (classlojure/eval-in 355 | (:alembic-classloader @still) 356 | `(require '[leiningen.core.main])) 357 | (classlojure/eval-in 358 | (:alembic-classloader @still) 359 | `(fn [out# err#] 360 | (binding [leiningen.core.main/*exit-process?* false 361 | ~'*out* out# 362 | ~'*err* err#] 363 | (try 364 | (leiningen.core.main/-main ~@(map str args)) 365 | (catch Exception e# 366 | (let [exit-code# (:exit-code (ex-data e#))] 367 | (when-not (and exit-code# (zero? exit-code#)) 368 | (binding [~'*out* ~'*err*] 369 | (println "Leiningen task failed")))))))) 370 | *out* *err*)) 371 | 372 | (defn lein* 373 | "Invoke lein" 374 | [& args] 375 | (lein-apply args {:still the-still})) 376 | 377 | (defmacro lein 378 | "Invoke a lein task" 379 | [& args] 380 | `(lein* ~@(map str args))) 381 | -------------------------------------------------------------------------------- /test/alembic/still_test.clj: -------------------------------------------------------------------------------- 1 | (ns alembic.still-test 2 | (:require 3 | [alembic.still :as still] 4 | [classlojure.core :refer [base-classloader classlojure ext-classloader] 5 | :as classlojure] 6 | [clojure.java.io :refer [file]] 7 | [clojure.test :refer :all] 8 | [dynapath.util :as util])) 9 | 10 | (defn clojure-path 11 | "Return the path of a clojure jar." 12 | [] 13 | (let [cl (still/alembic-classloader)] 14 | (classlojure/eval-in 15 | cl 16 | `(letfn [(apath# [^java.io.File f#] (.getAbsolutePath f#))] 17 | (-> 18 | (aether/resolve-dependencies 19 | :coordinates '[[org.clojure/clojure "1.4.0"]]) 20 | keys 21 | first 22 | meta 23 | :file 24 | apath#))))) 25 | 26 | (deftest clojure-path-test 27 | (is (string? (clojure-path)))) 28 | 29 | (def tools-logging '[org.clojure/tools.logging "0.2.0"]) 30 | (def clojure-dep '[org.clojure/clojure "1.5.1"]) 31 | 32 | (deftest distill-test 33 | (let [clojure-path (clojure-path) 34 | cl (classlojure (.toURL (file clojure-path))) 35 | still (atom (still/make-still cl))] 36 | (is (= [{:coords tools-logging}] 37 | (still/current-dep-versions still [{:coords tools-logging}]))) 38 | (is (some #(re-find #"org/clojure/clojure/1.4.0/clojure-1.4.0.jar" %) 39 | (map #(.toString %) (util/all-classpath-urls cl)))) 40 | (is (= [{:coords clojure-dep :current-version "1.4.0"}] 41 | (still/current-dep-versions still [{:coords clojure-dep}]))) 42 | (is (nil? (classlojure/eval-in 43 | cl 44 | `(do 45 | (try (require 'clojure.tools.logging) 46 | (catch Exception _#)) 47 | (find-ns 'clojure.tools.logging)))) 48 | "tools.logging not on the classpath") 49 | (let [n (count (util/all-classpath-urls cl)) 50 | deps (still/distill* tools-logging {:still still})] 51 | (is (= 2 (count deps)) "Two dependency jars") 52 | (is (= (inc n) 53 | (count (util/all-classpath-urls cl))) 54 | "One dependency jar added") 55 | (is (= 1 (count (filter :current-version deps))) 56 | "One possible dependency conflict") 57 | (is (classlojure/eval-in 58 | cl 59 | `(do 60 | (require 'clojure.tools.logging) 61 | (name (ns-name (find-ns 'clojure.tools.logging))))) 62 | "tools.logging on the classpath") 63 | (is (= [tools-logging] (still/dependencies-added still)) 64 | "distilled dependency listed") 65 | (is (= 1 (count (still/conflicting-versions still))) 66 | "possible distilled dependency conflict listed")) 67 | (is (nil? (still/distill tools-logging :still still)) 68 | "cursory distill check"))) 69 | 70 | (deftest load-project-test 71 | (is (nil? (still/load-project)) "we can load ourself")) 72 | 73 | (deftest lein-test 74 | (is (re-find #"org.clojure/clojure" (with-out-str (still/lein deps :tree))))) 75 | --------------------------------------------------------------------------------