Merge pull request #1795 from sbt/wip/aether-resolver-plugin

Turn Aether integration into sbt-maven-resolver
This commit is contained in:
Josh Suereth 2015-01-12 13:41:59 -05:00
commit cacc454af5
25 changed files with 133 additions and 79 deletions

View File

@ -215,7 +215,7 @@ lazy val ivyProj = (project in file("ivy")).
settings(baseSettings: _*).
settings(
name := "Ivy",
libraryDependencies ++= Seq(ivy, jsch, json4sNative, jawnParser, jawnJson4s) ++ aetherLibs,
libraryDependencies ++= Seq(ivy, jsch, json4sNative, jawnParser, jawnJson4s),
testExclusive)
// Runner for uniform test interface
@ -412,6 +412,15 @@ lazy val sbtProj = (project in sbtPath).
normalizedName := "sbt"
)
lazy val mavenResolverPluginProj = (project in file("sbt-maven-resolver")).
dependsOn(sbtProj, ivyProj % "test->test").
settings(baseSettings: _*).
settings(
name := "sbt-maven-resolver",
libraryDependencies ++= aetherLibs,
sbtPlugin := true
)
def scriptedTask: Initialize[InputTask[Unit]] = InputTask(scriptedSource(dir => (s: State) => scriptedParser(dir))) { result =>
(proguard in Proguard, fullClasspath in scriptedSbtProj in Test, scalaInstance in scriptedSbtProj, publishAll, scriptedSource, result) map {
(launcher, scriptedSbtClasspath, scriptedSbtInstance, _, sourcePath, args) =>
@ -435,7 +444,7 @@ def allProjects = Seq(launchInterfaceProj, launchProj, testSamples, interfacePro
compileInterfaceProj, compileIncrementalProj, compilePersistProj, compilerProj,
compilerIntegrationProj, compilerIvyProj,
scriptedBaseProj, scriptedSbtProj, scriptedPluginProj,
actionsProj, commandProj, mainSettingsProj, mainProj, sbtProj)
actionsProj, commandProj, mainSettingsProj, mainProj, sbtProj, mavenResolverPluginProj)
def projectsWithMyProvided = allProjects.map(p => p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided))
lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id))

View File

@ -1,6 +1,4 @@
package org.apache.maven.repository.internal;
package sbt;
/**
* Extra properties we dump from Aether into the properties list.

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,37 +96,34 @@ private[sbt] object ConvertResolver {
}
}
private[sbt] val USE_AETHER_PROPERTY = "sbt.use.aether"
private def isUseAetherForResolution(settings: IvySettings): Boolean =
settings.getVariable(USE_AETHER_PROPERTY) == "true"
/** 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, log: Logger) =
{
/** 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 =>
{
if (isUseAetherForResolution(settings)) {
repo match {
case cache: MavenCache => new org.apache.ivy.plugins.resolver.MavenCacheRepositoryResolver(cache, settings)
case _ => new org.apache.ivy.plugins.resolver.MavenRemoteRepositoryResolver(repo, settings)
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.
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
}
} else {
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.
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
}
}
val resolver = new PluginCapableResolver
resolver.setRepository(new LocalIfFileRepo)
initializeMavenStyle(resolver, repo.name, repo.root)
resolver.setPatterns() // has to be done after initializeMavenStyle, which calls methods that overwrite the patterns
resolver
}
val resolver = new PluginCapableResolver
resolver.setRepository(new LocalIfFileRepo)
initializeMavenStyle(resolver, repo.name, repo.root)
resolver.setPatterns() // has to be done after initializeMavenStyle, which calls methods that overwrite the patterns
resolver
}
// TODO: HTTP repository is no longer recommended. #1541
// Remove `JavaNet1Repository` when we bump up the API.
@ -176,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

@ -13,7 +13,7 @@ import java.io.{ File, InputStream }
import java.net.URL
import java.util.regex.Pattern
import org.apache.maven.repository.internal.{ SbtExtraProperties, PomExtraDependencyAttributes }
import org.apache.maven.repository.internal.{ PomExtraDependencyAttributes }
@deprecated("0.13.8", "We now use an Aether-based pom parser.")
final class CustomPomParser(delegate: ModuleDescriptorParser, transform: (ModuleDescriptorParser, ModuleDescriptor) => ModuleDescriptor) extends ModuleDescriptorParser {

View File

@ -73,7 +73,6 @@ final class IvySbt(val configuration: IvyConfiguration) {
is.setBaseDir(baseDirectory)
is.setCircularDependencyStrategy(configuration.updateOptions.circularDependencyLevel.ivyStrategy)
CustomPomParser.registerDefault
is.setVariable(ConvertResolver.USE_AETHER_PROPERTY, s"${configuration.updateOptions.aetherResolution}")
configuration match {
case e: ExternalIvyConfiguration =>
@ -289,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

@ -20,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")
@ -333,7 +334,7 @@ class MakePom(val log: Logger) {
val repositories = if (includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver)
val mavenRepositories =
repositories.flatMap {
case m: org.apache.ivy.plugins.resolver.MavenRemoteRepositoryResolver if m.repo.root != DefaultMavenRepository.root =>
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

View File

@ -5,8 +5,6 @@ package sbt
import java.net.URL
import org.apache.maven.repository.internal.SbtExtraProperties
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 +

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.
@ -18,13 +20,8 @@ final class UpdateOptions private[sbt] (
val consolidatedResolution: Boolean,
/** If set to true, use cached resolution. */
val cachedResolution: Boolean,
/** If set to true, use aether for resolving maven artifacts. */
val aetherResolution: Boolean) {
/** Enables Aether for dependency resolution. */
def withAetherResolution(aetherResolution: Boolean): UpdateOptions =
copy(aetherResolution = aetherResolution)
/** Extention point for an alternative resolver converter. */
val resolverConverter: UpdateOptions.ResolverConverter) {
def withCircularDependencyLevel(circularDependencyLevel: CircularDependencyLevel): UpdateOptions =
copy(circularDependencyLevel = circularDependencyLevel)
def withLatestSnapshots(latestSnapshots: Boolean): UpdateOptions =
@ -36,24 +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,
aetherResolution: Boolean = this.aetherResolution): UpdateOptions =
resolverConverter: UpdateOptions.ResolverConverter = this.resolverConverter): UpdateOptions =
new UpdateOptions(circularDependencyLevel,
latestSnapshots,
consolidatedResolution,
cachedResolution,
aetherResolution)
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
}
@ -63,17 +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,
// TODO - Disable this before release, but make sure test suite passes with it on.
aetherResolution = true)
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

@ -184,7 +184,7 @@ private[sbt] case class SbtChainResolver(
val artifactOpt = findFirstArtifactRef(rmr.getDescriptor, dd, data, resolver)
artifactOpt match {
case None if resolver.getName == "inter-project" => // do nothing
case None if resolver.isInstanceOf[AbstractMavenRepositoryResolver] =>
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")

View File

@ -3,8 +3,6 @@ package sbt
import java.io.File
import java.net.{ MalformedURLException, URL }
import org.apache.maven.repository.internal.SbtExtraProperties
private[sbt] object APIMappings {
def extract(cp: Seq[Attributed[File]], log: Logger): Seq[(File, URL)] =
cp.flatMap(entry => extractFromEntry(entry, log))

View File

@ -5,7 +5,7 @@ package sbt
import Attributed.data
import Scope.{ fillTaskAxis, GlobalScope, ThisScope }
import org.apache.maven.repository.internal.{ SbtExtraProperties, PomExtraDependencyAttributes }
import org.apache.maven.repository.internal.PomExtraDependencyAttributes
import sbt.Compiler.InputsWithPrevious
import xsbt.api.Discovery
import xsbti.compile.CompileOrder

View File

@ -9,36 +9,36 @@
### Improvements
### Aether Resolution
### Maven resolver plugin
sbt 0.13.8 adds the ability to use Eclipse Aether to resolve maven dependencies. This is designed to work within Ivy
so that both Aether + Ivy dependencies cohesively depend on each other.
sbt 0.13.8 adds an extention point in the dependency resolution to customize Maven resolvers.
This allows us to write sbt-maven-resolver auto plugin, which internally uses Eclipse Aether
to resolve Maven dependencies instead of Apache Ivy.
The key called `updateOptions` has been expanded to enable Aether resolutions via the following setting:
To enable this plugin, add the following to `project/maven.sbt` (or `project/plugin.sbt` the file name doesn't matter):
updateOptions := updateOptions.value.withAetherResolution(true)
libraryDependencies += Defaults.sbtPluginExtra("org.scala-sbt" % "sbt-maven-resolver" % sbtVersion.value,
sbtBinaryVersion.value, scalaBinaryVersion.value)
This will create a new `~/.ivy2/maven-cache` directory which contains the Aether cache of files. You may notice some
file will be re-downloaded for the new cache layout. Additionally, sbt will now be able to fully construct
This will create a new `~/.ivy2/maven-cache` directory, which contains the Aether cache of files.
You may notice some file will be re-downloaded for the new cache layout.
Additionally, sbt will now be able to fully construct
`maven-metadata.xml` files when publishing to remote repositories or when publishing to the local `~/.m2/repository`.
This should help erase many of the deficiencies encountered when using Maven and sbt together.
Note: The setting must be places on EVERY subproject within a build if you wish to fully use Aether for all projects.
**Notes and known limitations**:
Known limitations:
* The current implementation does not support ivy-style version numbers, such as "2.10.+" or "latest.snapshot". This
- sbt-maven-resolver requires sbt 0.13.8 and above.
- The current implementation does not support Ivy-style dynamic revisions, such as "2.10.+" or "latest.snapshot". This
is a fixable situation, but the version range query and Ivy -> Maven version range translation code has not been migrated.
### Bug fixes
- sbt doens't honor Maven's uniqueVersions (use aether resolver to fix). [#1322][1322] by [@jsuereth][@jsuereth]
- sbt doens't see new SNAPSHOT dependency versions in local maven repos (use withLatestSnapshots + aether resolver to fix) [#321][321] by [@jsuereth][@jsuereth]
- Property in pom's version field results to wrong dependency resolution (use aether resolver to fix). [#647][647] by [@jsuereth][@jsuereth]
- Maven local resolver with parent POM (use aether resolver). [#1616][1616] by [@jsuereth][@jsuereth]
- sbt doens't honor Maven's uniqueVersions (use sbt-maven-resolver to fix). [#1322][1322] by [@jsuereth][@jsuereth]
- sbt doens't see new SNAPSHOT dependency versions in local maven repos (use withLatestSnapshots + sbt-maven-resolver to fix) [#321][321] by [@jsuereth][@jsuereth]
- Property in pom's version field results to wrong dependency resolution (use sbt-maven-resolver to fix). [#647][647] by [@jsuereth][@jsuereth]
- Maven local resolver with parent POM (use sbt-maven-resolver). [#1616][1616] by [@jsuereth][@jsuereth]
// Possibly fixed, need verification.
- 1676 - SNAPSHOT dependency not updated ???
- 679 - Incorrect Maven Snapshot file resolution ????
- 679 - Incorrect Maven Snapshot file resolution ????

View File

@ -81,6 +81,7 @@ import org.eclipse.aether.spi.log.Logger;
import org.eclipse.aether.spi.log.LoggerFactory;
import org.eclipse.aether.spi.log.NullLoggerFactory;
import org.eclipse.aether.transfer.ArtifactNotFoundException;
import sbt.SbtExtraProperties;
/**
* A hacked version of maven's default artifact descriptor reader which we use in place of the standard aether adapter.

View File

@ -15,7 +15,7 @@ import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter
import org.apache.ivy.plugins.resolver.MavenRepositoryResolver.JarPackaging
import org.apache.ivy.plugins.resolver.util.ResolvedResource
import org.apache.ivy.util.Message
import org.apache.maven.repository.internal.{ PomExtraDependencyAttributes, SbtRepositoryLayout, SbtExtraProperties }
import org.apache.maven.repository.internal.{ PomExtraDependencyAttributes, SbtRepositoryLayout }
import org.eclipse.aether.{ RepositorySystemSession, RepositorySystem }
import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact }
import org.eclipse.aether.metadata.{ Metadata, DefaultMetadata }
@ -29,7 +29,8 @@ import org.eclipse.aether.resolution.{
import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest }
import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest }
import org.apache.ivy.core.cache.{ ModuleDescriptorWriter, ArtifactOrigin }
import sbt.{ MavenCache, MavenRepository }
import sbt.{ MavenCache, MavenRepository, SbtExtraProperties }
import sbt.ivyint.{ CustomMavenResolver, CustomRemoteMavenResolver }
import scala.collection.JavaConverters._
object MavenRepositoryResolver {
@ -60,7 +61,8 @@ object MavenRepositoryResolver {
* Note: This creates its *own* local cache directory for cache metadata. using its name.
*
*/
class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySettings) extends AbstractMavenRepositoryResolver(settings) {
class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySettings)
extends AbstractMavenRepositoryResolver(settings) with CustomRemoteMavenResolver {
setName(repo.name)
override def toString = s"${repo.name}: ${repo.root}"
protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl
@ -136,7 +138,8 @@ class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySett
*
* Note: This should never hit somethign remote, as it just looks in the maven cache for things already resolved.
*/
class MavenCacheRepositoryResolver(val repo: MavenCache, settings: IvySettings) extends AbstractMavenRepositoryResolver(settings) {
class MavenCacheRepositoryResolver(val repo: MavenCache, settings: IvySettings)
extends AbstractMavenRepositoryResolver(settings) with CustomMavenResolver {
setName(repo.name)
protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl
sbt.IO.createDirectory(repo.rootFile)

View File

@ -9,6 +9,7 @@ import org.eclipse.aether.metadata.Metadata
import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum
import org.eclipse.aether.artifact.Artifact
import java.net.URI
import sbt.SbtExtraProperties
import scala.util.matching.Regex

View File

@ -0,0 +1,13 @@
package sbt
import UpdateOptions.ResolverConverter
import org.apache.ivy.plugins.resolver.{ MavenCacheRepositoryResolver, MavenRemoteRepositoryResolver }
object MavenResolverConverter {
val converter: ResolverConverter = {
case (cache: MavenCache, settings, log) =>
new MavenCacheRepositoryResolver(cache, settings)
case (repo: MavenRepository, settings, log) =>
new MavenRemoteRepositoryResolver(repo, settings)
}
}

View File

@ -0,0 +1,13 @@
package sbt
package plugins
import Keys._
object MavenResolverPlugin extends AutoPlugin {
override def requires = IvyPlugin
override def trigger = allRequirements
override lazy val projectSettings: Seq[Setting[_]] = Seq(
updateOptions := updateOptions.value.withResolverConverter(MavenResolverConverter.converter)
)
}

View File

@ -55,7 +55,7 @@ class MavenResolutionSpec extends BaseIvySpecification {
import ShowLines._
def defaultUpdateOptions = UpdateOptions().withAetherResolution(true)
def defaultUpdateOptions = UpdateOptions().withResolverConverter(MavenResolverConverter.converter)
def resolveMajorConflicts = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")),

View File

@ -0,0 +1,2 @@
libraryDependencies += Defaults.sbtPluginExtra("org.scala-sbt" % "sbt-maven-resolver" % sbtVersion.value,
sbtBinaryVersion.value, scalaBinaryVersion.value)

View File

@ -14,14 +14,13 @@ lazy val main = project.
libraryDependencies += (projectID in library).value,
fullResolvers := fullResolvers.value.filterNot(_.name == "inter-project"),
// TODO - should this not be needed?
updateOptions := updateOptions.value.withLatestSnapshots(true).withAetherResolution(true)
updateOptions := updateOptions.value.withLatestSnapshots(true)
)
lazy val library = project.
settings(commonSettings: _*).
settings(
uniqueName,
updateOptions := updateOptions.value.withAetherResolution(true)
uniqueName
)
def uniqueName =

View File

@ -0,0 +1,2 @@
libraryDependencies += Defaults.sbtPluginExtra("org.scala-sbt" % "sbt-maven-resolver" % sbtVersion.value,
sbtBinaryVersion.value, scalaBinaryVersion.value)

View File

@ -0,0 +1,2 @@
libraryDependencies += Defaults.sbtPluginExtra("org.scala-sbt" % "sbt-maven-resolver" % sbtVersion.value,
sbtBinaryVersion.value, scalaBinaryVersion.value)

View File

@ -0,0 +1,2 @@
libraryDependencies += Defaults.sbtPluginExtra("org.scala-sbt" % "sbt-maven-resolver" % sbtVersion.value,
sbtBinaryVersion.value, scalaBinaryVersion.value)