Merge pull request #1793 from sbt/wip/aether-resolver

Use Aether for resolving maven dependencies
This commit is contained in:
eugene yokota 2015-01-14 15:17:50 -05:00
commit 8eabd82b32
13 changed files with 287 additions and 68 deletions

View File

@ -0,0 +1,25 @@
package sbt.mavenint;
/**
* Extra properties we dump from Aether into the properties list.
*/
public class SbtPomExtraProperties {
public static final String MAVEN_PACKAGING_KEY = "sbt.pom.packaging";
public static final String SCALA_VERSION_KEY = "sbt.pom.scalaversion";
public static final String SBT_VERSION_KEY = "sbt.pom.sbtversion";
public static final String POM_INFO_KEY_PREFIX = "info.";
public static final String POM_SCALA_VERSION = "scalaVersion";
public static final String POM_SBT_VERSION = "sbtVersion";
public static final String POM_API_KEY = "info.apiURL";
public static final String LICENSE_COUNT_KEY = "license.count";
public static String makeLicenseName(int i) {
return "license." + i + ".name";
}
public static String makeLicenseUrl(int i) {
return "license." + i + ".url";
}
}

View File

@ -26,6 +26,11 @@ import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
*/
object ReplaceMavenConfigurationMappings {
def addMappings(dd: DefaultDependencyDescriptor, scope: String, isOptional: Boolean) = {
val mapping = ReplaceMavenConfigurationMappings.REPLACEMENT_MAVEN_MAPPINGS.get(scope)
mapping.addMappingConfs(dd, isOptional)
}
val REPLACEMENT_MAVEN_MAPPINGS = {
// Here we copy paste from Ivy
val REPLACEMENT_MAPPINGS = new java.util.HashMap[String, PomModuleDescriptorBuilder.ConfMapper]

View File

@ -19,6 +19,8 @@ import org.apache.ivy.util.{ FileUtil, ChecksumHelper }
import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact }
private[sbt] object ConvertResolver {
import UpdateOptions.ResolverConverter
/**
* This class contains all the reflective lookups used in the
* checksum-friendly URL publishing shim.
@ -94,15 +96,25 @@ private[sbt] object ConvertResolver {
}
}
/** Converts the given sbt resolver into an Ivy resolver..*/
def apply(r: Resolver, settings: IvySettings, log: Logger) =
{
/** Converts the given sbt resolver into an Ivy resolver. */
@deprecated("0.13.8", "Use the variant with updateOptions")
def apply(r: Resolver, settings: IvySettings, log: Logger): DependencyResolver =
apply(r, settings, UpdateOptions(), log)
/** Converts the given sbt resolver into an Ivy resolver. */
def apply(r: Resolver, settings: IvySettings, updateOptions: UpdateOptions, log: Logger): DependencyResolver =
(updateOptions.resolverConverter orElse defaultConvert)((r, settings, log))
/** The default implementation of converter. */
lazy val defaultConvert: ResolverConverter = {
case (r, settings, log) =>
r match {
case repo: MavenRepository =>
{
val pattern = Collections.singletonList(Resolver.resolvePattern(repo.root, Resolver.mavenStyleBasePattern))
final class PluginCapableResolver extends IBiblioResolver with ChecksumFriendlyURLResolver with DescriptorRequired {
def setPatterns() { // done this way for access to protected methods.
def setPatterns() {
// done this way for access to protected methods.
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
}
@ -163,7 +175,7 @@ private[sbt] object ConvertResolver {
case repo: ChainedResolver => IvySbt.resolverChain(repo.name, repo.resolvers, false, settings, log)
case repo: RawRepository => repo.resolver
}
}
}
private sealed trait DescriptorRequired extends BasicResolver {
override def getDependency(dd: DependencyDescriptor, data: ResolveData) =

View File

@ -12,7 +12,9 @@ import org.apache.ivy.util.extendable.ExtendableItem
import java.io.{ File, InputStream }
import java.net.URL
import java.util.regex.Pattern
import sbt.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties }
@deprecated("0.13.8", "We now use an Aether-based pom parser.")
final class CustomPomParser(delegate: ModuleDescriptorParser, transform: (ModuleDescriptorParser, ModuleDescriptor) => ModuleDescriptor) extends ModuleDescriptorParser {
override def parseDescriptor(ivySettings: ParserSettings, descriptorURL: URL, validate: Boolean) =
transform(this, delegate.parseDescriptor(ivySettings, descriptorURL, validate))
@ -26,21 +28,23 @@ final class CustomPomParser(delegate: ModuleDescriptorParser, transform: (Module
override def getType() = delegate.getType()
override def getMetadataArtifact(mrid: ModuleRevisionId, res: Resource) = delegate.getMetadataArtifact(mrid, res)
}
@deprecated("0.13.8", "We now use an Aether-based pom parser.")
object CustomPomParser {
// Evil hackery to override the default maven pom mappings.
ReplaceMavenConfigurationMappings.init()
/** The key prefix that indicates that this is used only to store extra information and is not intended for dependency resolution.*/
val InfoKeyPrefix = "info."
val ApiURLKey = "info.apiURL"
val InfoKeyPrefix = SbtPomExtraProperties.POM_INFO_KEY_PREFIX
val ApiURLKey = SbtPomExtraProperties.POM_API_KEY
val SbtVersionKey = "sbtVersion"
val ScalaVersionKey = "scalaVersion"
val ExtraAttributesKey = "extraDependencyAttributes"
val SbtVersionKey = PomExtraDependencyAttributes.SbtVersionKey
val ScalaVersionKey = PomExtraDependencyAttributes.ScalaVersionKey
val ExtraAttributesKey = PomExtraDependencyAttributes.ExtraAttributesKey
private[this] val unqualifiedKeys = Set(SbtVersionKey, ScalaVersionKey, ExtraAttributesKey, ApiURLKey)
// packagings that should be jars, but that Ivy doesn't handle as jars
// TODO - move this elsewhere.
val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar")
val default = new CustomPomParser(PomModuleDescriptorParser.getInstance, defaultTransform)
@ -123,46 +127,24 @@ object CustomPomParser {
}
private[this] def getDependencyExtra(m: Map[String, String]): Map[ModuleRevisionId, Map[String, String]] =
(m get ExtraAttributesKey) match {
case None => Map.empty
case Some(str) =>
def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true))
readDependencyExtra(str).map(processDep).toMap
}
PomExtraDependencyAttributes.getDependencyExtra(m)
def qualifiedExtra(item: ExtendableItem): Map[String, String] =
{
import collection.JavaConverters._
item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap
}
def qualifiedExtra(item: ExtendableItem): Map[String, String] = PomExtraDependencyAttributes.qualifiedExtra(item)
def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] =
(qualifiedExtra(item) filterKeys { k => qualifiedIsExtra(k) == include })
def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] =
s.flatMap { dd =>
val revId = dd.getDependencyRevisionId
if (filterCustomExtra(revId, include = true).isEmpty)
Nil
else
revId.encodeToString :: Nil
}
PomExtraDependencyAttributes.writeDependencyExtra(s)
// parses the sequence of dependencies with extra attribute information, with one dependency per line
def readDependencyExtra(s: String): Seq[ModuleRevisionId] =
LinesP.split(s).map(_.trim).filter(!_.isEmpty).map(ModuleRevisionId.decode)
def readDependencyExtra(s: String): Seq[ModuleRevisionId] = PomExtraDependencyAttributes.readDependencyExtra(s)
private[this] val LinesP = Pattern.compile("(?m)^")
def qualifiedIsExtra(k: String): Boolean = k.endsWith(ScalaVersionKey) || k.endsWith(SbtVersionKey)
def qualifiedIsExtra(k: String): Boolean = PomExtraDependencyAttributes.qualifiedIsExtra(k)
// Reduces the id to exclude custom extra attributes
// This makes the id suitable as a key to associate a dependency parsed from a <dependency> element
// with the extra attributes from the <properties> section
def simplify(id: ModuleRevisionId): ModuleRevisionId =
{
import collection.JavaConverters._
ModuleRevisionId.newInstance(id.getOrganisation, id.getName, id.getBranch, id.getRevision, filterCustomExtra(id, include = false).asJava)
}
def simplify(id: ModuleRevisionId): ModuleRevisionId = PomExtraDependencyAttributes.simplify(id)
private[this] def addExtra(dep: DependencyDescriptor, extra: Map[ModuleRevisionId, Map[String, String]]): DependencyDescriptor =
{

View File

@ -14,19 +14,20 @@ import java.util.{ Collection, Collections => CS, Date }
import CS.singleton
import org.apache.ivy.Ivy
import org.apache.ivy.core.report.ResolveReport
import org.apache.ivy.core.{ IvyPatternHelper, LogOptions, IvyContext }
import org.apache.ivy.core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager, ModuleDescriptorWriter }
import org.apache.ivy.core.cache.{ ResolutionCacheManager, CacheMetadataOptions, DefaultRepositoryCacheManager, ModuleDescriptorWriter }
import org.apache.ivy.core.event.EventManager
import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact, DefaultArtifact, DefaultDependencyArtifactDescriptor, MDArtifact }
import org.apache.ivy.core.module.descriptor.{ DefaultDependencyDescriptor, DefaultModuleDescriptor, DependencyDescriptor, ModuleDescriptor, License }
import org.apache.ivy.core.module.descriptor.{ OverrideDependencyDescriptorMediator }
import org.apache.ivy.core.module.id.{ ArtifactId, ModuleId, ModuleRevisionId }
import org.apache.ivy.core.resolve.{ IvyNode, ResolveData, ResolvedModuleRevision, ResolveEngine }
import org.apache.ivy.core.resolve._
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.sort.SortEngine
import org.apache.ivy.plugins.latest.{ LatestStrategy, LatestRevisionStrategy, ArtifactInfo }
import org.apache.ivy.plugins.matcher.PatternMatcher
import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser
import org.apache.ivy.plugins.parser.m2.{ PomModuleDescriptorParser }
import org.apache.ivy.plugins.resolver.{ ChainResolver, DependencyResolver, BasicResolver }
import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource }
import org.apache.ivy.plugins.version.ExactVersionMatcher
@ -68,9 +69,11 @@ final class IvySbt(val configuration: IvyConfiguration) {
private lazy val settings: IvySettings =
{
val is = new IvySettings
is.setBaseDir(baseDirectory)
is.setCircularDependencyStrategy(configuration.updateOptions.circularDependencyLevel.ivyStrategy)
CustomPomParser.registerDefault
configuration match {
case e: ExternalIvyConfiguration =>
IvySbt.addResolvers(e.extraResolvers, is, configuration.log)
@ -104,6 +107,7 @@ final class IvySbt(val configuration: IvyConfiguration) {
super.bind()
}
}
i.setSettings(settings)
i.bind()
i.getLoggerEngine.pushLogger(new IvyLoggerInterface(configuration.log))
@ -284,7 +288,7 @@ private[sbt] object IvySbt {
def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, log: Logger): DependencyResolver =
resolverChain(name, resolvers, localOnly, settings, UpdateOptions(), log)
def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, updateOptions: UpdateOptions, log: Logger): DependencyResolver = {
def mapResolvers(rs: Seq[Resolver]) = rs.map(r => ConvertResolver(r, settings, log))
def mapResolvers(rs: Seq[Resolver]) = rs.map(r => ConvertResolver(r, settings, updateOptions, log))
val (projectResolvers, rest) = resolvers.partition(_.name == "inter-project")
if (projectResolvers.isEmpty) new ivyint.SbtChainResolver(name, mapResolvers(rest), settings, updateOptions, log)
else {

View File

@ -8,6 +8,9 @@
package sbt
import java.io.File
import sbt.mavenint.PomExtraDependencyAttributes
// Node needs to be renamed to XNode because the task subproject contains a Node type that will shadow
// scala.xml.Node when generating aggregated API documentation
import scala.xml.{ Elem, Node => XNode, NodeSeq, PrettyPrinter, PrefixedAttribute }
@ -17,6 +20,7 @@ import org.apache.ivy.Ivy
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.module.descriptor.{ DependencyArtifactDescriptor, DependencyDescriptor, License, ModuleDescriptor, ExcludeRule }
import org.apache.ivy.plugins.resolver.{ ChainResolver, DependencyResolver, IBiblioResolver }
import ivyint.CustomRemoteMavenResolver
class MakePom(val log: Logger) {
@deprecated("Use `write(Ivy, ModuleDescriptor, ModuleInfo, Option[Iterable[Configuration]], Set[String], NodeSeq, XNode => XNode, MavenRepository => Boolean, Boolean, File)` instead", "0.11.2")
@ -119,12 +123,12 @@ class MakePom(val log: Logger) {
def makeProperties(module: ModuleDescriptor, dependencies: Seq[DependencyDescriptor]): NodeSeq =
{
val extra = IvySbt.getExtraAttributes(module)
val depExtra = CustomPomParser.writeDependencyExtra(dependencies).mkString("\n")
val allExtra = if (depExtra.isEmpty) extra else extra.updated(CustomPomParser.ExtraAttributesKey, depExtra)
val depExtra = PomExtraDependencyAttributes.writeDependencyExtra(dependencies).mkString("\n")
val allExtra = if (depExtra.isEmpty) extra else extra.updated(PomExtraDependencyAttributes.ExtraAttributesKey, depExtra)
if (allExtra.isEmpty) NodeSeq.Empty else makeProperties(allExtra)
}
def makeProperties(extra: Map[String, String]): NodeSeq = {
def _extraAttributes(k: String) = if (k == CustomPomParser.ExtraAttributesKey) xmlSpacePreserve else scala.xml.Null
def _extraAttributes(k: String) = if (k == PomExtraDependencyAttributes.ExtraAttributesKey) xmlSpacePreserve else scala.xml.Null
<properties> {
for ((key, value) <- extra) yield (<x>{ value }</x>).copy(label = key, attributes = _extraAttributes(key))
} </properties>
@ -330,6 +334,8 @@ class MakePom(val log: Logger) {
val repositories = if (includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver)
val mavenRepositories =
repositories.flatMap {
case m: CustomRemoteMavenResolver if m.repo.root != DefaultMavenRepository.root =>
MavenRepository(m.repo.name, m.repo.root) :: Nil
case m: IBiblioResolver if m.isM2compatible && m.getRoot != DefaultMavenRepository.root =>
MavenRepository(m.getName, m.getRoot) :: Nil
case _ => Nil

View File

@ -5,6 +5,8 @@ package sbt
import java.net.URL
import sbt.mavenint.SbtPomExtraProperties
final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String] = None, isChanging: Boolean = false, isTransitive: Boolean = true, isForce: Boolean = false, explicitArtifacts: Seq[Artifact] = Nil, exclusions: Seq[ExclusionRule] = Nil, extraAttributes: Map[String, String] = Map.empty, crossVersion: CrossVersion = CrossVersion.Disabled) {
override def toString: String =
organization + ":" + name + ":" + revision +
@ -15,7 +17,7 @@ final case class ModuleID(organization: String, name: String, revision: String,
def extraString: String = extraDependencyAttributes.map { case (k, v) => k + "=" + v } mkString ("(", ", ", ")")
/** Returns the extra attributes except for ones marked as information only (ones that typically would not be used for dependency resolution). */
def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(CustomPomParser.InfoKeyPrefix))
def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(SbtPomExtraProperties.POM_INFO_KEY_PREFIX))
@deprecated("Use `cross(CrossVersion)`, the variant accepting a CrossVersion value constructed by a member of the CrossVersion object instead.", "0.12.0")
def cross(v: Boolean): ModuleID = cross(if (v) CrossVersion.binary else CrossVersion.Disabled)

View File

@ -30,8 +30,23 @@ final class RawRepository(val resolver: DependencyResolver) extends Resolver {
}
}
sealed case class ChainedResolver(name: String, resolvers: Seq[Resolver]) extends Resolver
/** An instance of a remote maven repository. Note: This will use Aether/Maven to resolve artifacts. */
sealed case class MavenRepository(name: String, root: String) extends Resolver {
override def toString = name + ": " + root
def isCache: Boolean = false
}
/**
* An instance of maven CACHE directory. You cannot treat a cache directory the same as a a remote repository because
* the metadata is different (see Aether ML discussion).
*/
final class MavenCache(name: String, val rootFile: File) extends MavenRepository(name, rootFile.toURI.toURL.toString) {
override val toString = "cache:" + name + ": " + rootFile.getAbsolutePath
override def isCache: Boolean = true
}
object MavenCache {
def apply(name: String, rootFile: File): MavenCache = new MavenCache(name, rootFile)
}
final class Patterns(val ivyPatterns: Seq[String], val artifactPatterns: Seq[String], val isMavenCompatible: Boolean, val descriptorOptional: Boolean, val skipConsistencyCheck: Boolean) {
@ -334,8 +349,9 @@ object Resolver {
loadHomeFromSettings(() => new File(new File(System.getenv("M2_HOME")), "conf/settings.xml")) getOrElse
new File(Path.userHome, ".m2/repository")
}
def publishMavenLocal = Resolver.file("publish-m2-local", mavenLocalDir)
def mavenLocal = MavenRepository("Maven2 Local", mavenLocalDir.toURI.toString)
// TODO - should this just be the *exact* same as mavenLocal? probably...
def publishMavenLocal: MavenCache = new MavenCache("publish-m2-local", mavenLocalDir)
def mavenLocal: MavenRepository = new MavenCache("Maven2 Local", mavenLocalDir)
def defaultLocal = defaultUserFileRepository("local")
def defaultShared = defaultUserFileRepository("shared")
def defaultUserFileRepository(id: String) =

View File

@ -1,6 +1,8 @@
package sbt
import java.io.File
import org.apache.ivy.plugins.resolver.DependencyResolver
import org.apache.ivy.core.settings.IvySettings
/**
* Represents configurable options for update task.
@ -17,8 +19,9 @@ final class UpdateOptions private[sbt] (
/** If set to true, use consolidated resolution. */
val consolidatedResolution: Boolean,
/** If set to true, use cached resolution. */
val cachedResolution: Boolean) {
val cachedResolution: Boolean,
/** Extention point for an alternative resolver converter. */
val resolverConverter: UpdateOptions.ResolverConverter) {
def withCircularDependencyLevel(circularDependencyLevel: CircularDependencyLevel): UpdateOptions =
copy(circularDependencyLevel = circularDependencyLevel)
def withLatestSnapshots(latestSnapshots: Boolean): UpdateOptions =
@ -30,22 +33,28 @@ final class UpdateOptions private[sbt] (
def withCachedResolution(cachedResoluton: Boolean): UpdateOptions =
copy(cachedResolution = cachedResoluton,
consolidatedResolution = cachedResolution)
/** Extention point for an alternative resolver converter. */
def withResolverConverter(resolverConverter: UpdateOptions.ResolverConverter): UpdateOptions =
copy(resolverConverter = resolverConverter)
private[sbt] def copy(
circularDependencyLevel: CircularDependencyLevel = this.circularDependencyLevel,
latestSnapshots: Boolean = this.latestSnapshots,
consolidatedResolution: Boolean = this.consolidatedResolution,
cachedResolution: Boolean = this.cachedResolution): UpdateOptions =
cachedResolution: Boolean = this.cachedResolution,
resolverConverter: UpdateOptions.ResolverConverter = this.resolverConverter): UpdateOptions =
new UpdateOptions(circularDependencyLevel,
latestSnapshots,
consolidatedResolution,
cachedResolution)
cachedResolution,
resolverConverter)
override def equals(o: Any): Boolean = o match {
case o: UpdateOptions =>
this.circularDependencyLevel == o.circularDependencyLevel &&
this.latestSnapshots == o.latestSnapshots &&
this.cachedResolution == o.cachedResolution
this.cachedResolution == o.cachedResolution &&
this.resolverConverter == o.resolverConverter
case _ => false
}
@ -55,15 +64,19 @@ final class UpdateOptions private[sbt] (
hash = hash * 31 + this.circularDependencyLevel.##
hash = hash * 31 + this.latestSnapshots.##
hash = hash * 31 + this.cachedResolution.##
hash = hash * 31 + this.resolverConverter.##
hash
}
}
object UpdateOptions {
type ResolverConverter = PartialFunction[(Resolver, IvySettings, Logger), DependencyResolver]
def apply(): UpdateOptions =
new UpdateOptions(
circularDependencyLevel = CircularDependencyLevel.Warn,
latestSnapshots = false,
consolidatedResolution = false,
cachedResolution = false)
cachedResolution = false,
resolverConverter = PartialFunction.empty)
}

View File

@ -0,0 +1,11 @@
package sbt
package ivyint
import org.apache.ivy.plugins.resolver.DependencyResolver
// These are placeholder traits for sbt-aether-resolver
trait CustomMavenResolver extends DependencyResolver {
}
trait CustomRemoteMavenResolver extends CustomMavenResolver {
def repo: MavenRepository
}

View File

@ -12,7 +12,7 @@ import org.apache.ivy.core.resolve.{ ResolvedModuleRevision, ResolveData }
import org.apache.ivy.plugins.latest.LatestStrategy
import org.apache.ivy.plugins.repository.file.{ FileRepository => IFileRepository, FileResource }
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.{ ChainResolver, BasicResolver, DependencyResolver }
import org.apache.ivy.plugins.resolver._
import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource }
import org.apache.ivy.util.{ Message, MessageLogger, StringUtils => IvyStringUtils }
@ -155,34 +155,39 @@ private[sbt] case class SbtChainResolver(
val sorted =
if (useLatest) (foundRevisions.sortBy {
case (rmr, resolver) =>
Message.warn(s"Sorrting results from $rmr, using ${rmr.getPublicationDate} and ${rmr.getDescriptor.getPublicationDate}")
// Just issue warning about issues with publication date, and fake one on it for now.
rmr.getDescriptor.getPublicationDate match {
case null =>
Option(rmr.getPublicationDate) orElse Option(rmr.getDescriptor.getPublicationDate) match {
case None =>
(resolver.findIvyFileRef(dd, data), rmr.getDescriptor) match {
case (null, _) =>
// In this instance, the dependency is specified by a direct URL or some other sort of "non-ivy" file
if (dd.isChanging)
Message.warn(s"Resolving a changing dependency (${rmr.getId}) with no ivy/pom file!, resolution order is undefined!")
0L
case (ivf, dmd: DefaultModuleDescriptor) =>
val lmd = new java.util.Date(ivf.getLastModified)
Message.debug(s"Getting no publication date from resolver: ${resolver} for ${rmr.getId}, setting to: ${lmd}")
dmd.setPublicationDate(lmd)
ivf.getLastModified
case _ =>
Message.warn(s"Getting null publication date from resolver: ${resolver} for ${rmr.getId}, resolution order is undefined!")
0L
}
case _ => // All other cases ok
}
rmr.getDescriptor.getPublicationDate match {
case null => 0L
case d => d.getTime
case Some(date) => // All other cases ok
date.getTime
}
}).reverse.headOption map {
case (rmr, resolver) =>
Message.warn(s"Choosing ${resolver} for ${rmr.getId}")
// Now that we know the real latest revision, let's force Ivy to use it
val artifactOpt = findFirstArtifactRef(rmr.getDescriptor, dd, data, resolver)
artifactOpt match {
case None if resolver.getName == "inter-project" => // do nothing
case None => throw new RuntimeException(s"\t${resolver.getName}: no ivy file nor artifact found for $rmr")
case None if resolver.isInstanceOf[CustomMavenResolver] =>
// do nothing for now....
// We want to see if the maven caching is sufficient and we do not need to duplicate within the ivy cache...
case None => throw new RuntimeException(s"\t${resolver.getName}: no ivy file nor artifact found for $rmr")
case Some(artifactRef) =>
val systemMd = toSystem(rmr.getDescriptor)
getRepositoryCacheManager.cacheModuleDescriptor(resolver, artifactRef,
@ -210,6 +215,9 @@ private[sbt] case class SbtChainResolver(
}
// Ivy seem to not want to use the module descriptor found at the latest resolver
private[this] def reparseModuleDescriptor(dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver, rmr: ResolvedModuleRevision): ResolvedModuleRevision =
// TODO - Redownloading/parsing the ivy file is not really the best way to make this correct.
// We should figure out a better alternative, or directly attack the resolvers Ivy uses to
// give them correct behavior around -SNAPSHOT.
Option(resolver.findIvyFileRef(dd, data)) flatMap { ivyFile =>
ivyFile.getResource match {
case r: FileResource =>
@ -222,7 +230,10 @@ private[sbt] case class SbtChainResolver(
}
case _ => None
}
} getOrElse rmr
} getOrElse {
Message.warn(s"Unable to reparse ${dd.getDependencyRevisionId} from $resolver, using ${rmr.getPublicationDate}")
rmr
}
/** Ported from BasicResolver#findFirstAirfactRef. */
private[this] def findFirstArtifactRef(md: ModuleDescriptor, dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver): Option[ResolvedResource] =
{

View File

@ -0,0 +1,111 @@
package sbt.mavenint
import java.util.Properties
import java.util.regex.Pattern
import org.apache.ivy.core.module.descriptor.DependencyDescriptor
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.util.extendable.ExtendableItem
/**
* This class contains all the logic for dealing with the extra attributes in pom files relating to extra attributes
* on dependency declarations.
*
* Specifically, if we have a dependency on an sbt plugin, there are two properties that need to propogate:
* - `sbtVersion`
* - `scalaVersion`
*
* These need to exist on the *dependency declaration*. Maven/Aether has no way to inject these into
* the <dependency> section of pom files, so we use Ivy's Extra attribute hackery to inject a lookup table
* of extra attributes by dependency id into POM files and later we read these back.
*/
object PomExtraDependencyAttributes {
val ExtraAttributesKey = "extraDependencyAttributes"
val SbtVersionKey = "sbtVersion"
val ScalaVersionKey = "scalaVersion"
/**
* Reads the extra dependency attributes out of a maven property.
* @param props The properties from an Aether resolution.
* @return
* A map of module id to extra dependency attributes associated with dependencies on that module.
*/
def readFromAether(props: java.util.Map[String, AnyRef]): Map[ModuleRevisionId, Map[String, String]] = {
import scala.collection.JavaConverters._
(props.asScala get ExtraAttributesKey) match {
case None => Map.empty
case Some(str) =>
def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true))
(for {
(id, props) <- readDependencyExtra(str.toString).map(processDep)
} yield id -> props).toMap
}
}
/**
* Mutates the to collection with the extra depdendency attributes from the incoming pom properties list.
*
* @param from The properties directly off a maven POM file
* @param to The aaether properties where we can write whatever we want.
*
* TODO - maybe we can just parse this directly here. Note the `readFromAether` method uses
* whatever we set here.
*/
def transferDependencyExtraAttributes(from: Properties, to: java.util.Map[String, AnyRef]): Unit = {
Option(from.getProperty(ExtraAttributesKey, null)) match {
case Some(str) => to.put(ExtraAttributesKey, str)
case None =>
}
}
/**
* Reads the extra dependency information out of Ivy's notion of POM properties and returns
* the map of ID -> Extra Properties.
*/
def getDependencyExtra(m: Map[String, String]): Map[ModuleRevisionId, Map[String, String]] =
(m get ExtraAttributesKey) match {
case None => Map.empty
case Some(str) =>
def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true))
readDependencyExtra(str).map(processDep).toMap
}
def qualifiedExtra(item: ExtendableItem): Map[String, String] = {
import scala.collection.JavaConverters._
item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap
}
def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] =
(qualifiedExtra(item) filterKeys { k => qualifiedIsExtra(k) == include })
def qualifiedIsExtra(k: String): Boolean = k.endsWith(ScalaVersionKey) || k.endsWith(SbtVersionKey)
// Reduces the id to exclude custom extra attributes
// This makes the id suitable as a key to associate a dependency parsed from a <dependency> element
// with the extra attributes from the <properties> section
def simplify(id: ModuleRevisionId): ModuleRevisionId = {
import scala.collection.JavaConverters._
ModuleRevisionId.newInstance(id.getOrganisation, id.getName, id.getBranch, id.getRevision, filterCustomExtra(id, include = false).asJava)
}
/** parses the sequence of dependencies with extra attribute information, with one dependency per line */
def readDependencyExtra(s: String): Seq[ModuleRevisionId] =
LinesP.split(s).map(_.trim).filter(!_.isEmpty).map(ModuleRevisionId.decode)
private[this] val LinesP = Pattern.compile("(?m)^")
/**
* Creates the "extra" property values for DependencyDescriptors that can be written into a maven pom
* so we don't loose the information.
* @param s
* @return
*/
def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] =
s.flatMap { dd =>
val revId = dd.getDependencyRevisionId
if (filterCustomExtra(revId, include = true).isEmpty)
Nil
else
revId.encodeToString :: Nil
}
}

View File

@ -4,6 +4,8 @@ import Path._, Configurations._
import java.io.File
import org.specs2._
import cross.CrossVersionUtil
import sbt.PublishConfiguration
import sbt.ivyint.SbtChainResolver
trait BaseIvySpecification extends Specification {
def currentBase: File = new File(".")
@ -12,6 +14,8 @@ trait BaseIvySpecification extends Specification {
def currentDependency: File = currentBase / "target" / "dependency"
def defaultModuleId: ModuleID = ModuleID("com.example", "foo", "0.1.0", Some("compile"))
lazy val log = ConsoleLogger()
def configurations = Seq(Compile, Test, Runtime)
def module(moduleId: ModuleID, deps: Seq[ModuleID], scalaFullVersion: Option[String],
uo: UpdateOptions = UpdateOptions()): IvySbt#Module = {
val ivyScala = scalaFullVersion map { fv =>
@ -28,21 +32,24 @@ trait BaseIvySpecification extends Specification {
module = moduleId,
moduleInfo = ModuleInfo("foo"),
dependencies = deps,
configurations = Seq(Compile, Test, Runtime),
configurations = configurations,
ivyScala = ivyScala)
val ivySbt = new IvySbt(mkIvyConfiguration(uo))
new ivySbt.Module(moduleSetting)
}
def resolvers: Seq[Resolver] = Seq(DefaultMavenRepository)
def chainResolver = ChainedResolver("sbt-chain", resolvers)
def mkIvyConfiguration(uo: UpdateOptions): IvyConfiguration = {
val paths = new IvyPaths(currentBase, Some(currentTarget))
val rs = Seq(DefaultMavenRepository)
val other = Nil
val moduleConfs = Seq(ModuleConfiguration("*", DefaultMavenRepository))
val moduleConfs = Seq(ModuleConfiguration("*", chainResolver))
val off = false
val check = Nil
val resCacheDir = currentTarget / "resolution-cache"
new InlineIvyConfiguration(paths, rs, other, moduleConfs, off, None, check, Some(resCacheDir), uo, log)
new InlineIvyConfiguration(paths, resolvers, other, moduleConfs, off, None, check, Some(resCacheDir), uo, log)
}
def ivyUpdateEither(module: IvySbt#Module): Either[UnresolvedWarning, UpdateReport] = {
@ -58,4 +65,18 @@ trait BaseIvySpecification extends Specification {
case Left(w) =>
throw w.resolveException
}
def mkPublishConfiguration(resolver: Resolver, artifacts: Map[Artifact, File]): PublishConfiguration = {
new PublishConfiguration(
ivyFile = None,
resolverName = resolver.name,
artifacts = artifacts,
checksums = Seq(),
logging = UpdateLogging.Full,
overwrite = true)
}
def ivyPublish(module: IvySbt#Module, config: PublishConfiguration) = {
IvyActions.publish(module, config, log)
}
}