Commit 6049ac30 authored by Eugenio Cano-Manuel's avatar Eugenio Cano-Manuel

Imported Upstream version 0.2.0

parents
# emacs + vi backup files
*~
.*.sw*
# various IDE junk
*.ipr
*.iml
*.iws
.project
.classpath
.settings
target
classes
it-repo
tmp
.externalToolBuilders
language: clojure
install: mvn install --quiet -DskipTests=true
script: mvn test
jdk:
- openjdk7
- openjdk6
- oraclejdk7
## [Pomegranate](http://github.com/cemerick/pomegranate) changelog
### `0.2.0`
_This release contains breaking changes from `0.1.3`_, though it's expected
that the affected APIs are rarely used.
**Dynamic classpath support (`cemerick.pomegranate`)**
* The `URLClasspath` protocol has been removed in favor of using
[dynapath](https://github.com/tobias/dynapath/)'s `DynamicClasspath`
protocol. (gh-43) *breaking change*
* `classloader-hierarchy` now starts at the current thread context classloader
instead of Clojure's "baseLoader". *breaking change*
* New `resources` and `classloader-resources` fns for determining from which
classloader(s) a given resource is available (gh-48)
**Aether API (`cemerick.pomegranate.aether` and friends)**
* `install-artifacts` and `deploy-artifacts` are now generalized to operate
over multiple files (vs. the prior assumptions re: an artifact + a POM)
(gh-52)
* `resolve-dependencies*` now available that returns the bare Aether results of
dependency resolution, _sans_ Clojure-friendly graphification (gh-50)
* `resolve-dependencies`, `install-artifacts`, and `deploy-artifacts` now
accept an optional `:repository-session-fn` to potentially modify the Aether
`RespositorySystemSession` prior to its use (gh-56)
# Pomegranate [![Travis CI status](https://secure.travis-ci.org/cemerick/pomegranate.png)](http://travis-ci.org/#!/cemerick/pomegranate/builds)
[Pomegranate](http://github.com/cemerick/pomegranate) is a library that provides:
1. A sane Clojure API for Sonatype [Aether](https://github.com/sonatype/sonatype-aether).
2. A re-implementation of [`add-classpath`](http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/add-classpath) (deprecated in Clojure core) that:
* is a little more comprehensive than core's `add-classpath` — it should work as expected in more circumstances, and
* optionally uses Aether to add a Maven artifact (and all of its transitive dependencies) to your Clojure runtime's classpath dynamically.
Insofar as most useful Clojure libraries have dependencies, any reasonable implementation of the `add-classpath` concept must seamlessly support resolving those dependencies IMO.
## "Installation"
Pomegranate is available in Maven central. Add it to your Leiningen `project.clj`:
```clojure
[com.cemerick/pomegranate "0.2.0"]
```
or to your Maven project's `pom.xml`:
```xml
<dependency>
<groupId>com.cemerick</groupId>
<artifactId>pomegranate</artifactId>
<version>0.2.0</version>
</dependency>
```
## `add-classpath` usage
Just to set a stage: you're at the REPL, and you've got some useful data that you'd like to munge and analyze in various ways. Maybe it's something you've generated locally, maybe it's data on a production machine and you're logged in via [nREPL](http://github.com/clojure/tools.nrepl). In any case, you'd like to work with the data, but realize that you don't have the libraries you need do what you want. Your choices at this point are:
1. Dump the data to disk via `pr` (assuming it's just Clojure data structures!), and start up a new Clojure process with the appropriate libraries on the classpath. This can really suck if the data is in a remote environment.
2. There is no second choice. You _could_ use `add-claspath`, but the library you want has 12 bajillion dependencies, and there's no way you're going to hunt them down manually.
Let's say we want to use [Incanter](https://github.com/liebke/incanter) (which has roughly 40 dependencies — far too many for us to reasonably locate and add via `add-classpath` manually):
```clojure
=> (require '(incanter core stats charts))
#<CompilerException java.io.FileNotFoundException:
Could not locate incanter/core__init.class or incanter/core.clj on classpath: (NO_SOURCE_FILE:0)>
```
Looks bleak. Assuming you've got Pomegranate on your classpath already, you can do this though:
```clojure
=> (use '[cemerick.pomegranate :only (add-dependencies)])
nil
=> (add-dependencies :coordinates '[[incanter "1.2.3"]]
:repositories (merge cemerick.pomegranate.aether/maven-central
{"clojars" "http://clojars.org/repo"}))
;...add-dependencies returns full dependency graph...
=> (require '(incanter core stats charts))
nil
```
Now you can analyze and chart away, Incanter having been added to your runtime. Note that `add-dependencies` may crunch along for a while — it may need to download dependencies, so you're waiting on the network. All resolved dependencies are stored in the default local repository (`~/.m2/repository`), and if they are found there, then they are not downloaded.
The arguments to `add-dependencies` look like Leiningen-style notation, and they are.
Please note that **there are a number of scenarios in which `add-dependencies` will not work, or
will not work as you'd expect**. Many of these are due to the nature of JVM classloaders
(e.g. adding jars containing conflicting versions of a particular dependency will rarely
end well), which Pomegranate does not currently attempt to hide. Thus, `add-classpath` and
`add-dependencies` should be considered escape hatches to be used when necessary, rather than
a regular part of your development workflow.
## Status of Aether support
Pomegranate is being used by [Leiningen v2.x](http://leiningen.org) as
its sole dependency resolution library. This has prompted rapid
maturation of the scope and quality of Aether support. That said,
specific API points remain subject to change as we find the right
abstractions and conventions.
#### Supported features
* dependency resolution
** common dependency graph/hierarchy manipulation ops
* local installation
* remote deployment
* repository authentication for all of the above
* HTTP proxy configuration
* offline mode
* transfer listeners (with a sane Clojure fn veneer)
#### Not there yet
* repository listeners
* mirror support
* options to retrieve a single artifact (e.g. for obtaining
source/javadoc)
* tests; there's halfway decent coverage, but nowhere near the kind of comprehensive combinatorial testing that maven dependency resolution demands
## Changelog
See the `CHANGES.md` file at the top level of the repo.
## Need Help?
Ping `cemerick` on freenode irc or twitter if you have questions
or would like to contribute patches.
## License
Copyright © 2011-2012 [Chas Emerick](http://cemerick.com) and all other
contributors.
Licensed under the EPL. (See the file epl-v10.html.)
This diff is collapsed.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cemerick</groupId>
<artifactId>pomegranate</artifactId>
<version>0.2.0</version>
<name>pomegranate</name>
<description />
<url>http://github.com/cemerick/pomegranate</url>
<parent>
<groupId>org.clojure</groupId>
<artifactId>pom.contrib</artifactId>
<version>0.0.20</version>
</parent>
<developers>
<developer>
<name>Chas Emerick</name>
<url>http://cemerick.com</url>
<email>cemerick@snowtide.com</email>
<timezone>-5</timezone>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:cemerick/pomegranate.git</connection>
<developerConnection>scm:git:git@github.com:cemerick/pomegranate.git</developerConnection>
<url>git@github.com:cemerick/pomegranate.git</url>
</scm>
<properties>
<clojure.version>1.3.0</clojure.version>
<aetherVersion>1.13.1</aetherVersion>
<mavenVersion>3.0.4</mavenVersion>
<wagonVersion>2.2</wagonVersion>
</properties>
<dependencies>
<dependency>
<groupId>org.sonatype.aether</groupId>
<artifactId>aether-api</artifactId>
<version>${aetherVersion}</version>
</dependency>
<dependency>
<groupId>org.sonatype.aether</groupId>
<artifactId>aether-util</artifactId>
<version>${aetherVersion}</version>
</dependency>
<dependency>
<groupId>org.sonatype.aether</groupId>
<artifactId>aether-impl</artifactId>
<version>${aetherVersion}</version>
</dependency>
<dependency>
<groupId>org.sonatype.aether</groupId>
<artifactId>aether-connector-file</artifactId>
<version>${aetherVersion}</version>
</dependency>
<dependency>
<groupId>org.sonatype.aether</groupId>
<artifactId>aether-connector-wagon</artifactId>
<version>${aetherVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-aether-provider</artifactId>
<version>${mavenVersion}</version>
</dependency>
<dependency>
<groupId>org.tcrawley</groupId>
<artifactId>dynapath</artifactId>
<version>0.2.3</version>
</dependency>
<!-- wagons for dependency resolution -->
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-provider-api</artifactId>
<version>${wagonVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http</artifactId>
<version>${wagonVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>${wagonVersion}</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/clojure</directory>
</resource>
</resources>
</build>
</project>
(ns cemerick.pomegranate
(:import (clojure.lang DynamicClassLoader)
(java.net URL URLClassLoader))
(:require [clojure.java.io :as io]
[cemerick.pomegranate.aether :as aether]
[dynapath.util :as dp])
(:refer-clojure :exclude (add-classpath)))
;; call-method pulled from clojure.contrib.reflect, (c) 2010 Stuart Halloway & Contributors
(defn- call-method
"Calls a private or protected method.
params is a vector of classes which correspond to the arguments to
the method e
obj is nil for static methods, the instance object otherwise.
The method-name is given a symbol or a keyword (something Named)."
[klass method-name params obj & args]
(-> klass (.getDeclaredMethod (name method-name)
(into-array Class params))
(doto (.setAccessible true))
(.invoke obj (into-array Object args))))
(defn classloader-hierarchy
"Returns a seq of classloaders, with the tip of the hierarchy first.
Uses the current thread context ClassLoader as the tip ClassLoader
if one is not provided."
([] (classloader-hierarchy (.. Thread currentThread getContextClassLoader)))
([tip]
(->> tip
(iterate #(.getParent %))
(take-while boolean))))
(defn modifiable-classloader?
"Returns true iff the given ClassLoader is of a type that satisfies
the dynapath.dynamic-classpath/DynamicClasspath protocol, and it can
be modified."
[cl]
(dp/addable-classpath? cl))
(defn add-classpath
"A corollary to the (deprecated) `add-classpath` in clojure.core. This implementation
requires a java.io.File or String path to a jar file or directory, and will attempt
to add that path to the right classloader (with the search rooted at the current
thread's context classloader)."
([jar-or-dir classloader]
(if-not (dp/add-classpath-url classloader (.toURL (io/file jar-or-dir)))
(throw (IllegalStateException. (str classloader " is not a modifiable classloader")))))
([jar-or-dir]
(let [classloaders (classloader-hierarchy)]
(if-let [cl (last (filter modifiable-classloader? classloaders))]
(add-classpath jar-or-dir cl)
(throw (IllegalStateException. (str "Could not find a suitable classloader to modify from "
classloaders)))))))
(defn add-dependencies
"Resolves a set of dependencies, optionally against a set of additional Maven repositories,
and adds all of the resulting artifacts (jar files) to the current runtime via
`add-classpath`:
(add-dependencies :coordinates '[[incanter \"1.2.3\"]]
:repositories (merge cemerick.pomegranate.aether/maven-central
{\"clojars\" \"http://clojars.org/repo\"}))
Note that Maven central is used as the sole repository if none are specified.
If :repositories are provided, then you must merge in the `maven-central` map from
the cemerick.pomegranate.aether namespace yourself.
Acceptable arguments are the same as those for
`cemerick.pomegranate.aether/resolve-dependencies`; returns the dependency graph
returned from that function."
[& args]
(let [deps (apply aether/resolve-dependencies args)]
(doseq [artifact-file (aether/dependency-files deps)]
(add-classpath artifact-file))
deps))
(defn get-classpath
"Returns the effective classpath (i.e. _not_ the value of
(System/getProperty \"java.class.path\") as a seq of URL strings.
Produces the classpath from all classloaders by default, or from a
collection of classloaders if provided. This allows you to easily look
at subsets of the current classloader hierarchy, e.g.:
(get-classpath (drop 2 (classloader-hierarchy)))"
([classloaders]
(->> (reverse classloaders)
(mapcat #(dp/classpath-urls %))
(map str)))
([] (get-classpath (classloader-hierarchy))))
(defn classloader-resources
"Returns a sequence of [classloader url-seq] pairs representing all
of the resources of the specified name on the classpath of each
classloader. If no classloaders are given, uses the
classloader-heirarchy, in which case the order of pairs will be
such that the first url mentioned will in most circumstances match
what clojure.java.io/resource returns."
([classloaders resource-name]
(for [classloader (reverse classloaders)]
[classloader (enumeration-seq
(.getResources ^ClassLoader classloader resource-name))]))
([resource-name] (classloader-resources (classloader-hierarchy) resource-name)))
(defn resources
"Returns a sequence of URLs representing all of the resources of the
specified name on the effective classpath. This can be useful for
finding name collisions among items on the classpath. In most
circumstances, the first of the returned sequence will be the same
as what clojure.java.io/resource returns."
([classloaders resource-name]
(distinct (mapcat second (classloader-resources classloaders resource-name))))
([resource-name] (resources (classloader-hierarchy) resource-name)))
This diff is collapsed.
This diff is collapsed.
(ns cemerick.pomegranate-test
(:require [cemerick.pomegranate :as p]
clojure.java.io)
(:use clojure.test))
(deftest resources
(is (= (first (p/resources "META-INF/MANIFEST.MF"))
(clojure.java.io/resource "META-INF/MANIFEST.MF")))
; the last classloader should be ext, for e.g. $JAVA_HOME/lib/ext/*
(is (->> (p/resources [(last (p/classloader-hierarchy))] "META-INF/MANIFEST.MF")
(map str)
(filter #(.contains % "clojure"))
empty?))
(is (->> (p/resources (butlast (p/classloader-hierarchy)) "META-INF/MANIFEST.MF")
(map str)
(filter #(.contains % "clojure"))
seq)))
\ No newline at end of file
This file here only to test deployment operations; the fact that this isn't a signature file shouldn't cause any issues.
d41d8cd98f00b204e9800998ecf8427e
\ No newline at end of file
da39a3ee5e6b4b0d3255bfef95601890afd80709
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>demo</artifactId>
<version>1.0.0</version>
<description>POM was created from install:install-file</description>
</project>
This file here only to test deployment operations; the fact that this isn't a signature file shouldn't cause any issues.
5689f1eaf926240885eb9976e5269e5d
\ No newline at end of file
eb4ff44542c69c5271390dad76a96bfd15b4858a
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>demo</groupId>
<artifactId>demo</artifactId>
<versioning>
<release>1.0.0</release>
<versions>
<version>1.0.0</version>
</versions>
<lastUpdated>20120403044818</lastUpdated>
</versioning>
</metadata>
eba3d5db3eae4a0d224883479f0af51a
\ No newline at end of file
e135fe7f1f28125dc0ed279af92e112af16bcf64
\ No newline at end of file
d41d8cd98f00b204e9800998ecf8427e
\ No newline at end of file
da39a3ee5e6b4b0d3255bfef95601890afd80709
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>demo2</artifactId>
<version>1.0.0</version>
<description>POM was created from install:install-file</description>
<dependencies>
<dependency>
<groupId>demo</groupId>
<artifactId>demo</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
79893caa8e0888232ed9dfe56e396172
\ No newline at end of file
d2a795ccaccf9ade57f7938c390368eeb2e842e7
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>demo</groupId>
<artifactId>demo2</artifactId>
<versioning>
<release>1.0.0</release>
<versions>
<version>1.0.0</version>
</versions>
<lastUpdated>20120403045648</lastUpdated>
</versioning>
</metadata>
8550a6918ed8fe0cf9ff6e08e61c4d8c
\ No newline at end of file
c38f519e44c887aa563201af58779379cbee4b18
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>tester</groupId>
<artifactId>tester</artifactId>
<version>0.1.0-SNAPSHOT</version>
<versioning>
<snapshot>
<timestamp>20120403.012847</timestamp>
<buildNumber>1</buildNumber>
</snapshot>
<lastUpdated>20120403012847</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>0.1.0-20120403.012847-1</value>
<updated>20120403012847</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>0.1.0-20120403.012847-1</value>
<updated>20120403012847</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>
32f77845f3013e9010130dd3c5056d35
\ No newline at end of file
34f980dd6a45ef4ad423c27315015580f5e8c996
\ No newline at end of file
b959415ced8425a03218a51430d8a7e1
\ No newline at end of file
ecddbf891119b76b09e0f888f8cf5764932c811b
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><modelVersion>4.0.0</modelVersion><groupId>tester</groupId><artifactId>tester</artifactId><version>0.1.0-SNAPSHOT</version><name>tester</name><description>FIXME: write description</description><url>http://example.com/FIXME</url><licenses><license><name>Eclipse Public License</name><url>http://www.eclipse.org/legal/epl-v10.html</url></license></licenses><build><sourceDirectory>/workspace/tester/src</sourceDirectory><testSourceDirectory>/workspace/tester/test</testSourceDirectory><resources><resource><directory>/workspace/tester/resources</directory></resource></resources><testResources><testResource><directory>/workspace/tester/resources</directory></testResource></testResources></build><repositories><repository><id>central</id><url>http://repo1.maven.org/maven2</url></repository><repository><id>clojars</id><url>http://clojars.org/repo/</url></repository><repository><id>snapshots</id><url>file:///tmp/snapshots</url></repository></repositories><dependencies></dependencies></project>
<!-- This file was autogenerated by Leiningen.
Please do not edit it directly; instead edit project.clj and regenerate it.
It should not be considered canonical data. For more information see
https://github.com/technomancy/leiningen -->
f5550306172f01752d6483dd88528271
\ No newline at end of file
eb42a597bcb2ec429e5ba36ed9b16100a1e9a336
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>tester</groupId>
<artifactId>tester</artifactId>
<versioning>
<versions>
<version>0.1.0-SNAPSHOT</version>
</versions>
<lastUpdated>20120403012847</lastUpdated>
</versioning>
</metadata>
0748c7d4b7b9b6dc951840d798be8fda
\ No newline at end of file
23c0ae6c73dc4c080e18d7664a88e9d6936b9521
\ No newline at end of file
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