Create a new Ivy DependencyResolver which uses Aether.

* Here we wire Aether into the Ivy dependency chain
* Add hooks into Aether to use Ivy's http library (so credentials are configured the same)
* Create the actual Resolver which extracts metadata information from Aether
* Deprecate old Ivy-Maven integrations
* Create hooks in existing Resolver facilities to expose a flag to enable the new behavior.
* Create notes documenting the feature.
* Create a new resolver type `MavenCache` which denotes how to read/write local maven cache metadata
  correctly.  We use this type for publishM2 and mavenLocal.
* Update failing -SNAPSHOT related tests to use new Aether resolver
* Create specification for expected behavior from the new resolvers.

Known to fix #1322, #321, #647, #1616
This commit is contained in:
Josh Suereth 2014-12-16 13:14:14 -05:00
parent acf54b62a5
commit e3931dbfec
41 changed files with 2144 additions and 43 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),
libraryDependencies ++= Seq(ivy, jsch, json4sNative, jawnParser, jawnJson4s) ++ aetherLibs,
testExclusive)
// Runner for uniform test interface

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

@ -0,0 +1,656 @@
package org.apache.ivy.plugins.resolver
import java.io.{ File, IOException }
import java.text.ParseException
import java.util.Date
import org.apache.ivy.core.IvyContext
import org.apache.ivy.core.module.descriptor._
import org.apache.ivy.core.module.id.{ ModuleId, ArtifactId, ModuleRevisionId }
import org.apache.ivy.core.report.{ ArtifactDownloadReport, DownloadStatus, MetadataArtifactDownloadReport, DownloadReport }
import org.apache.ivy.core.resolve.{ ResolvedModuleRevision, ResolveData, DownloadOptions }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.matcher.ExactPatternMatcher
import org.apache.ivy.plugins.parser.m2.{ PomModuleDescriptorBuilder, ReplaceMavenConfigurationMappings }
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.{ SbtRepositoryLayout, SbtExtraProperties }
import org.eclipse.aether.{ RepositorySystemSession, RepositorySystem }
import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact }
import org.eclipse.aether.metadata.{ Metadata, DefaultMetadata }
import org.eclipse.aether.resolution.{
ArtifactDescriptorRequest => AetherDescriptorRequest,
ArtifactDescriptorResult => AetherDescriptorResult,
MetadataRequest => AetherMetadataRequest,
ArtifactRequest => AetherArtifactRequest,
ArtifactResolutionException
}
import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest }
import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest }
import org.apache.ivy.core.cache.{ ModuleDescriptorWriter, ArtifactOrigin }
import sbt.{ CustomPomParser, MavenCache, MavenRepository }
import scala.collection.JavaConverters._
object MavenRepositoryResolver {
val MAVEN_METADATA_XML = "maven-metadata.xml"
val CLASSIFIER_ATTRIBUTE = "e:classifier"
val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar", "jar", "bundle")
object JarPackaging {
def unapply(in: String): Boolean = JarPackagings.contains(in)
}
// Example: 2014 12 18 09 33 56
val LAST_UPDATE_FORMAT = new java.text.SimpleDateFormat("yyyyMMddhhmmss")
def parseTimeString(in: String): Option[Long] =
try Some(LAST_UPDATE_FORMAT.parse(in).getTime)
catch {
case _: java.text.ParseException => None
}
val DEFAULT_ARTIFACT_CONFIGURATION = "master"
}
/**
* A resolver instance which can resolve from a REMOTE maven repository.
*
* Note: This creates its *own* local cache directory for cache metadata. using its name.
*
*/
class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySettings) extends AbstractMavenRepositoryResolver(settings) {
setName(repo.name)
override def toString = s"${repo.name}: ${repo.root}"
protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl
// Note: All maven repository resolvers will use the SAME maven cache.
// We're not sure if we care whether or not this means that the wrong resolver may report finding an artifact.
// The key is not to duplicate files repeatedly across many caches.
private val localRepo = new java.io.File(settings.getDefaultIvyUserDir, s"maven-cache")
sbt.IO.createDirectory(localRepo)
protected val session = MavenRepositorySystemFactory.newSessionImpl(system, localRepo)
private val aetherRepository = {
new org.eclipse.aether.repository.RemoteRepository.Builder(repo.name, SbtRepositoryLayout.LAYOUT_NAME, repo.root).build()
}
// TODO - Check if isUseCacheOnly is used correctly.
private def isUseCacheOnly: Boolean =
Option(IvyContext.getContext).flatMap(x => Option(x.getResolveData)).flatMap(x => Option(x.getOptions)).map(_.isUseCacheOnly).getOrElse(false)
protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest =
if (isUseCacheOnly) request else request.addRepository(aetherRepository)
protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest =
if (isUseCacheOnly) request else request.addRepository(aetherRepository)
/** Actually publishes aether artifacts. */
protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = {
val request = new AetherDeployRequest()
request.setRepository(aetherRepository)
artifacts foreach request.addArtifact
system.deploy(session, request)
}
protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = {
val metadataRequest = new AetherMetadataRequest()
metadataRequest.setMetadata(
new DefaultMetadata(
mrid.getOrganisation,
mrid.getName,
mrid.getRevision,
MavenRepositoryResolver.MAVEN_METADATA_XML,
Metadata.Nature.RELEASE_OR_SNAPSHOT))
if (!isUseCacheOnly) metadataRequest.setRepository(aetherRepository)
val metadataResultOpt =
try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption
catch {
case e: org.eclipse.aether.resolution.ArtifactResolutionException => None
}
try metadataResultOpt match {
case Some(md) if md.isResolved =>
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader
import org.codehaus.plexus.util.ReaderFactory
val readMetadata = {
val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile)
try new MetadataXpp3Reader().read(reader, false)
finally reader.close()
}
val timestampOpt =
for {
v <- Option(readMetadata.getVersioning)
sp <- Option(v.getSnapshot)
ts <- Option(sp.getTimestamp)
t <- MavenRepositoryResolver.parseTimeString(ts)
} yield t
val lastUpdatedOpt =
for {
v <- Option(readMetadata.getVersioning)
lu <- Option(v.getLastUpdated)
d <- MavenRepositoryResolver.parseTimeString(lu)
} yield d
// TODO - Only look at timestamp *IF* the version is for a snapshot.
timestampOpt orElse lastUpdatedOpt
case _ => None
}
}
}
/**
* A resolver instance which can resolve from a maven CACHE.
*
* 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) {
setName(repo.name)
protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl
sbt.IO.createDirectory(repo.rootFile)
protected val session = MavenRepositorySystemFactory.newSessionImpl(system, repo.rootFile)
protected def setRepository(request: AetherMetadataRequest): AetherMetadataRequest = request
protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = request
protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = request
protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = {
val request = new AetherInstallRequest()
artifacts foreach request.addArtifact
system.install(session, request)
}
// TODO - Share this with non-local repository code, since it's MOSTLY the same.
protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = {
val metadataRequest = new AetherMetadataRequest()
metadataRequest.setMetadata(
new DefaultMetadata(
mrid.getOrganisation,
mrid.getName,
mrid.getRevision,
MavenRepositoryResolver.MAVEN_METADATA_XML,
Metadata.Nature.RELEASE_OR_SNAPSHOT))
val metadataResultOpt =
try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption
catch {
case e: org.eclipse.aether.resolution.ArtifactResolutionException => None
}
try metadataResultOpt match {
case Some(md) if md.isResolved =>
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader
import org.codehaus.plexus.util.ReaderFactory
val readMetadata = {
val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile)
try new MetadataXpp3Reader().read(reader, false)
finally reader.close()
}
val timestampOpt =
for {
v <- Option(readMetadata.getVersioning)
sp <- Option(v.getSnapshot)
ts <- Option(sp.getTimestamp)
t <- MavenRepositoryResolver.parseTimeString(ts)
} yield t
val lastUpdatedOpt =
for {
v <- Option(readMetadata.getVersioning)
lu <- Option(v.getLastUpdated)
d <- MavenRepositoryResolver.parseTimeString(lu)
} yield d
// TODO - Only look at timestamp *IF* the version is for a snapshot.
timestampOpt orElse lastUpdatedOpt
case _ => None
}
}
override def toString = s"${repo.name}: ${repo.root}"
}
/** An exception we can throw if we encounter issues. */
class MavenResolutionException(msg: String) extends RuntimeException(msg) {}
abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends AbstractResolver {
/** Our instance of the aether repository system. */
protected val system: RepositorySystem
/**
* Our instance of the aether repository system session.
*
* TODO - We may want to tie this into an IvyContext.
*/
protected val session: RepositorySystemSession
/** Determine the publication time of a module. The mechanism may differ if the repository is remote vs. local. */
protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long]
/** Inject necessary repositories into a descriptor request. */
protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest
protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest
/** Actually publishes aether artifacts. */
protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit
// TOOD - deal with packaging here.
private def aetherArtifactIdFromMrid(mrid: ModuleRevisionId): String =
getSbtVersion(mrid) match {
case Some(sbt) => s"${mrid.getName}_sbt_$sbt"
case None => mrid.getName
}
private def aetherCoordsFromMrid(mrid: ModuleRevisionId): String =
s"${mrid.getOrganisation}:${aetherArtifactIdFromMrid(mrid)}:${mrid.getRevision}"
private def aetherCoordsFromMrid(mrid: ModuleRevisionId, packaging: String): String =
s"${mrid.getOrganisation}:${aetherArtifactIdFromMrid(mrid)}:$packaging:${mrid.getRevision}"
private def aetherCoordsFromMrid(mrid: ModuleRevisionId, packaging: String, extension: String): String =
s"${mrid.getOrganisation}:${aetherArtifactIdFromMrid(mrid)}:$extension:$packaging:${mrid.getRevision}"
// Handles appending licenses to the module descriptor fromthe pom.
private def addLicenseInfo(md: DefaultModuleDescriptor, map: java.util.Map[String, AnyRef]) = {
val count = map.get(SbtExtraProperties.LICENSE_COUNT_KEY) match {
case null => 0
case x: java.lang.Integer => x.intValue
case x: String => x.toInt
case _ => 0
}
for {
i <- 0 until count
name <- Option(map.get(SbtExtraProperties.makeLicenseName(i))).map(_.toString)
url <- Option(map.get(SbtExtraProperties.makeLicenseUrl(i))).map(_.toString)
} md.addLicense(new License(name, url))
}
// This grabs the dependency for Ivy.
override def getDependency(dd: DependencyDescriptor, rd: ResolveData): ResolvedModuleRevision = {
val context = IvyContext.pushNewCopyContext
try {
// TODO - Check to see if we're asking for latest.* version, and if so, we should run a latest version query
// first and use that result to return the metadata/final module.
Message.debug(s"Requesting conf [${dd.getModuleConfigurations.mkString(",")}] from Aether module ${dd.getDependencyRevisionId} in resolver ${getName}")
val request = new AetherDescriptorRequest()
val coords = aetherCoordsFromMrid(dd.getDependencyRevisionId)
Message.debug(s"Aether about to resolve [$coords]...")
request.setArtifact(new AetherArtifact(coords, getArtifactProperties(dd.getDependencyRevisionId)))
addRepositories(request)
val result = system.readArtifactDescriptor(session, request)
val packaging = getPackagingFromPomProperties(result.getProperties)
Message.debug(s"Aether resolved ${dd.getDependencyId} w/ packaging ${packaging}")
// TODO - better pub date if we have no metadata.
val lastModifiedTime = getPublicationTime(dd.getDependencyRevisionId) getOrElse 0L
// Construct a new Ivy module descriptor
val desc: ModuleDescriptor = {
// TODO - Better detection of snapshot and handling latest.integration/latest.snapshot
val status =
if (dd.getDependencyRevisionId.getRevision.endsWith("-SNAPSHOT")) "integration"
else "release"
val md =
new DefaultModuleDescriptor(dd.getDependencyRevisionId, status, null /* pubDate */ , false)
//DefaultModuleDescriptor.newDefaultInstance(dd.getDependencyRevisionId)
// Here we add the standard configurations
for (config <- PomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS) {
md.addConfiguration(config)
}
// Here we look into the artifacts specified from the dependency descriptor *and* those that are defaulted,
// and append them to the appropriate configurations.
addArtifactsFromPom(dd, packaging, md, lastModifiedTime)
// Here we add dependencies.
addDependenciesFromAether(result, md)
// Here we use pom.xml Dependency management section to create Ivy dependency mediators.
addManagedDependenciesFromAether(result, md)
// TODO - Add excludes?
// Here we rip out license info.
addLicenseInfo(md, result.getProperties)
md.addExtraInfo(SbtExtraProperties.MAVEN_PACKAGING_KEY, packaging)
Message.debug(s"Setting publication date to ${new Date(lastModifiedTime)}")
// TODO - Figure out the differences between these items.
md.setPublicationDate(new Date(lastModifiedTime))
md.setLastModified(lastModifiedTime)
md.setResolvedPublicationDate(new Date(lastModifiedTime))
md.check()
// TODO - do we need the toSystem?
toSystem(md)
}
// Here we need to pretend we downloaded the pom.xml file
val pom = DefaultArtifact.newPomArtifact(dd.getDependencyRevisionId, new java.util.Date(lastModifiedTime))
val madr = new MetadataArtifactDownloadReport(pom)
madr.setSearched(true)
madr.setDownloadStatus(DownloadStatus.SUCCESSFUL) // TODO - Figure this things out for this report.
val rmr = new ResolvedModuleRevision(this, this, desc, madr, false /* Force */ )
// TODO - Here we cache the transformed pom.xml into an ivy.xml in the cache because ChainResolver will be looking at it.
// This doesn't appear to really work correctly.
// However, I think the chain resolver doesn't use this instance anyway. Ideally we don't put anything
// in the ivy cache, but this should be "ok".
getRepositoryCacheManager.originalToCachedModuleDescriptor(this,
null /* ivyRef. Just passed back to us. */ ,
pom,
rmr,
new ModuleDescriptorWriter() {
def write(originalMdResource: ResolvedResource, md: ModuleDescriptor, src: File, dest: File): Unit = {
// a basic ivy file is written containing default data
XmlModuleDescriptorWriter.write(md, dest);
}
}
)
rmr
} catch {
case e: org.eclipse.aether.resolution.ArtifactDescriptorException =>
Message.warn(s"Failed to read descriptor ${dd} from ${getName}, ${e.getMessage}")
e.printStackTrace(System.err)
rd.getCurrentResolvedModuleRevision
case e: MavenResolutionException =>
Message.debug(s"Resolution Exception from ${getName}, ${e.getMessage}, returning: ${rd.getCurrentResolvedModuleRevision}")
rd.getCurrentResolvedModuleRevision
} finally IvyContext.popContext()
}
def getSbtVersion(dd: ModuleRevisionId): Option[String] =
Option(dd.getExtraAttribute(CustomPomParser.SbtVersionKey))
def getArtifactProperties(dd: ModuleRevisionId): java.util.Map[String, String] = {
val m = new java.util.HashMap[String, String]
Option(dd.getExtraAttribute(CustomPomParser.ScalaVersionKey)) foreach { sv =>
m.put(SbtExtraProperties.POM_SCALA_VERSION, sv)
}
getSbtVersion(dd) foreach { sv =>
m.put(SbtExtraProperties.POM_SBT_VERSION, sv)
}
m
}
final def checkJarArtifactExists(dd: DependencyDescriptor): Boolean = {
// TODO - We really want this to be as fast/efficient as possible!
val request = new AetherArtifactRequest()
val art = new AetherArtifact(
aetherCoordsFromMrid(dd.getDependencyRevisionId, "jar"),
getArtifactProperties(dd.getDependencyRevisionId))
request.setArtifact(art)
addRepositories(request)
try {
val result = system.resolveArtifact(session, request)
result.isResolved && !result.isMissing
} catch {
case e: ArtifactResolutionException =>
// Ignore, as we're just working around issues with pom.xml's with no jars or POM packaging
Message.debug(s"Could not find $art in ${getName}")
false
}
}
/** Determines which artifacts are associated with this maven module and appends them to the descriptor. */
def addArtifactsFromPom(dd: DependencyDescriptor, packaging: String, md: DefaultModuleDescriptor, lastModifiedTime: Long): Unit = {
Message.debug(s"Calculating artifacts for ${dd.getDependencyId} w/ packaging $packaging")
// Here we add in additional artifact requests, which ALLWAYS have to be explicit since
// Maven/Aether doesn't include all known artifacts in a pom.xml
// TODO - This does not appear to be working correctly.
if (dd.getAllDependencyArtifacts.isEmpty) {
val artifactId = s"${dd.getDependencyId.getName}-${dd.getDependencyRevisionId.getRevision}"
// Add the artifacts we know about the module
packaging match {
case "pom" =>
// THere we have to attempt to download the JAR and see if it comes, if not, we can punt.
// This is because sometimes pom-packaging attaches a JAR.
if (checkJarArtifactExists(dd)) {
val defaultArt =
new DefaultArtifact(md.getModuleRevisionId, new Date(lastModifiedTime), artifactId, packaging, "jar")
md.addArtifact(MavenRepositoryResolver.DEFAULT_ARTIFACT_CONFIGURATION, defaultArt)
}
case JarPackaging() =>
// Here we fail the resolution. This is an issue when pom.xml files exist with no JAR, which happens
// on maven central for some reason on old artifacts.
if (!checkJarArtifactExists(dd))
throw new MavenResolutionException(s"Failed to find JAR file associated with $dd")
// Assume for now everything else is a jar.
val defaultArt =
new DefaultArtifact(md.getModuleRevisionId, new Date(lastModifiedTime), artifactId, packaging, "jar")
// TODO - Unfortunately we have to try to download the JAR file HERE and then fail resolution if we cannot find it.
// This is because sometime a pom.xml exists with no JARs.
md.addArtifact(MavenRepositoryResolver.DEFAULT_ARTIFACT_CONFIGURATION, defaultArt)
case _ => // Ignore, we have no idea what this artifact is.
Message.warn(s"Not adding artifacts for resolution because we don't understand packaging: $packaging")
}
} else {
// NOTE: this means that someone is requested specific artifacts from us. What we need to do is *only* download the
// requested artifacts rather than the default "jar". What's odd, is that pretty much this almost ALWAYS happens.
// but in some circumstances, the above logic is checked.
// Additionally, we may want to somehow merge the "defined" artifacts from maven with the requested ones here, rather
// than having completely separate logic. For now, this appears to work the same way it was before.
// Since we aren't accurately guessing what maven files are meant to be included as artifacts ANYWAY, this
// is probably the right way to go.
for (requestedArt <- dd.getAllDependencyArtifacts) {
getClassifier(requestedArt) match {
case None =>
// This is the default artifact. We do need to add this, and to the default configuration.
val defaultArt =
new DefaultArtifact(md.getModuleRevisionId, new Date(lastModifiedTime), requestedArt.getName, requestedArt.getType, requestedArt.getExt)
md.addArtifact(MavenRepositoryResolver.DEFAULT_ARTIFACT_CONFIGURATION, defaultArt)
case Some(scope) =>
Message.debug(s"Adding additional artifact in $scope, $requestedArt")
// TODO - more Extra attributes?
val mda =
new MDArtifact(
md,
requestedArt.getName,
requestedArt.getType,
requestedArt.getExt,
requestedArt.getUrl,
requestedArt.getExtraAttributes)
md.addArtifact(getConfiguration(scope), mda)
}
}
}
}
/** Adds the dependency mediators required based on the managed dependency instances from this pom. */
def addManagedDependenciesFromAether(result: AetherDescriptorResult, md: DefaultModuleDescriptor) {
for (d <- result.getManagedDependencies.asScala) {
if (d.getArtifact.getArtifactId == "stringtemplate") {
Message.warn(s"Found managed stringtemplate in $md !")
}
md.addDependencyDescriptorMediator(
ModuleId.newInstance(d.getArtifact.getGroupId, d.getArtifact.getArtifactId),
ExactPatternMatcher.INSTANCE,
new OverrideDependencyDescriptorMediator(null, d.getArtifact.getVersion) {
override def mediate(dd: DependencyDescriptor): DependencyDescriptor = {
super.mediate(dd)
}
})
}
}
/** Adds the list of dependencies this artifact has on other artifacts. */
def addDependenciesFromAether(result: AetherDescriptorResult, md: DefaultModuleDescriptor) {
for (d <- result.getDependencies.asScala) {
// TODO - Is this correct for changing detection. We should use the Ivy mechanism configured...
val isChanging = d.getArtifact.getVersion.endsWith("-SNAPSHOT")
val drid = ModuleRevisionId.newInstance(d.getArtifact.getGroupId, d.getArtifact.getArtifactId, d.getArtifact.getVersion)
// Note: The previous maven integration ALWAYS set force to true for dependnecies. If we do not do this, for some
// reason, Ivy will create dummy nodes when doing dependnecy mediation (e.g. dependencyManagement of one pom overrides version of a dependency)
// which was leading to "data not found" exceptions as Ivy would pick the correct IvyNode in the dependency tree but never load it with data....
val dd = new DefaultDependencyDescriptor(md, drid, /* force */ true, isChanging, true) {}
// TODO - Configuration mappings (are we grabbing scope correctly, or should the default not always be compile?)
val scope = Option(d.getScope).filterNot(_.isEmpty).getOrElse("compile")
val mapping = ReplaceMavenConfigurationMappings.addMappings(dd, scope, d.isOptional)
// TODO - include rules and exclude rules.
Message.debug(s"Adding maven transitive dependency ${md.getModuleRevisionId} -> ${dd}")
// TODO - Unify this borrowed Java code into something a bit friendlier.
// Now we add the artifact....
if ((d.getArtifact.getClassifier != null) || ((d.getArtifact.getExtension != null) && !("jar" == d.getArtifact.getExtension))) {
val tpe: String =
if (d.getArtifact.getExtension != null) d.getArtifact.getExtension
else "jar"
val ext: String = tpe match {
case "test-jar" => "jar"
case JarPackaging() => "jar"
case other => other
}
// Here we add the classifier, hopefully correctly...
val extraAtt = new java.util.HashMap[String, AnyRef]()
if (d.getArtifact.getClassifier != null) {
extraAtt.put("m:classifier", d.getArtifact.getClassifier)
}
val depArtifact: DefaultDependencyArtifactDescriptor =
new DefaultDependencyArtifactDescriptor(dd, dd.getDependencyId.getName, tpe, ext, null, extraAtt)
val optionalizedScope: String = if (d.isOptional) "optional" else scope
// TOOD - We may need to fix the configuration mappings here.
dd.addDependencyArtifact(optionalizedScope, depArtifact)
}
// TODO - is toSystem call correct?
md.addDependency(dd)
if (d.getArtifact.getArtifactId == "stringtemplate") {
Message.warn(s"Found stringtemplate dependency! $dd")
}
}
}
// This method appears to be deprecated/unused in all of Ivy so we do not implement it.
override def findIvyFileRef(dd: DependencyDescriptor, rd: ResolveData): ResolvedResource = {
Message.error(s"Looking for ivy file ref, method not implemented! MavenRepositoryResolver($getName) will always return null.")
null
}
private def getPackagingFromPomProperties(props: java.util.Map[String, AnyRef]): String =
if (props.containsKey(SbtExtraProperties.MAVEN_PACKAGING_KEY))
props.get(SbtExtraProperties.MAVEN_PACKAGING_KEY).toString
else "jar"
override def download(artifacts: Array[Artifact], dopts: DownloadOptions): DownloadReport = {
// TODO - Status reports on download and possibly parallel downloads
val report = new DownloadReport
val requests =
for (a <- artifacts) yield {
val request = new AetherArtifactRequest
val aetherArt =
getClassifier(a) match {
case None | Some("") =>
new AetherArtifact(
aetherCoordsFromMrid(a.getModuleRevisionId),
getArtifactProperties(a.getModuleRevisionId))
case Some(other) => new AetherArtifact(
aetherCoordsFromMrid(a.getModuleRevisionId, other, a.getExt),
getArtifactProperties(a.getModuleRevisionId))
}
Message.debug(s"Requesting download of [$aetherArt]")
request.setArtifact(aetherArt)
addRepositories(request)
request
}
val (aetherResults, failed) =
try {
(system.resolveArtifacts(session, requests.toList.asJava).asScala, false)
} catch {
case e: org.eclipse.aether.resolution.ArtifactResolutionException =>
Message.error(s"Failed to resolve artifacts from ${getName}, ${e.getMessage}")
(e.getResults.asScala, true)
}
for ((result, art) <- aetherResults zip artifacts) {
Message.debug(s"Aether resolved artifact result: $result")
val adr = new ArtifactDownloadReport(art)
adr.setDownloadDetails(result.toString)
// TODO - Fill this out with a real estimate on time...
adr.setDownloadTimeMillis(0L)
// TODO - what is artifact origin actuallyused for?
adr.setArtifactOrigin(new ArtifactOrigin(
art,
true,
getName))
if (result.isMissing) {
adr.setDownloadStatus(DownloadStatus.FAILED)
adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT)
} else if (!result.isResolved) {
adr.setDownloadStatus(DownloadStatus.FAILED)
adr.setDownloadDetails(result.toString)
// TODO - we should set download status to NO in the event we don't care about an artifact...
} else {
val file = result.getArtifact.getFile
Message.debug(s"Succesffully downloaded: $file")
adr.setLocalFile(file)
adr.setSize(file.length)
adr.setDownloadStatus(DownloadStatus.SUCCESSFUL)
}
report.addArtifactReport(adr)
}
report
}
case class PublishTransaction(module: ModuleRevisionId, artifacts: Seq[(Artifact, File)])
private var currentTransaction: Option[PublishTransaction] = None
override def beginPublishTransaction(module: ModuleRevisionId, overwrite: Boolean): Unit = {
currentTransaction match {
case Some(t) => throw new IllegalStateException(s"Publish Transaction already open for [$getName]")
case None => currentTransaction = Some(PublishTransaction(module, Nil))
}
}
override def abortPublishTransaction(): Unit = {
currentTransaction = None
}
def getClassifier(art: Artifact): Option[String] =
// TODO - Do we need to look anywere else?
Option(art.getExtraAttribute("classifier"))
def getClassifier(art: org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor): Option[String] =
art.getType match {
case "doc" | "javadoc" => Some("javadoc")
case "src" | "source" => Some("sources")
case "test-jar" | "test" => Some("tests")
case _ =>
// Look for extra attributes
art.getExtraAttribute(MavenRepositoryResolver.CLASSIFIER_ATTRIBUTE) match {
case null => None
case c => Some(c)
}
}
def getConfiguration(classifier: String): String =
classifier match {
// TODO - choice of configuration actually depends on whether or not the artifact is
// REQUESTED by the user, in which case it should be on master.
// Currently, we don't actually look for sources/javadoc/test artifacts at all,
// which means any artifact is in the master configuration, but we should
// fix this for better integration into the maven ecosystem from ivy.
//case "sources" => "sources"
//case "javadoc" => "javadoc"
case other => MavenRepositoryResolver.DEFAULT_ARTIFACT_CONFIGURATION
}
override def commitPublishTransaction(): Unit = {
// TODO - actually send all artifacts to aether
currentTransaction match {
case Some(t) =>
Message.debug(s"Publishing module ${t.module}, with artifact count = ${t.artifacts.size}")
val artifacts =
for ((art, file) <- t.artifacts) yield {
Message.debug(s" - Publishing $art (${art.getType})(${art.getExtraAttribute("classifier")}) in [${art.getConfigurations.mkString(",")}] from $file")
new AetherArtifact(
t.module.getOrganisation,
aetherArtifactIdFromMrid(t.module),
getClassifier(art).orNull,
art.getExt,
t.module.getRevision,
getArtifactProperties(t.module),
file
)
}
publishArtifacts(artifacts)
// TODO - Any kind of validity checking?
currentTransaction = None
case None => throw new IllegalStateException(s"Publish Transaction already open for [$getName]")
}
}
override def publish(art: Artifact, file: File, overwrite: Boolean): Unit = {
currentTransaction match {
case Some(t) =>
val allArts = t.artifacts ++ List(art -> file)
currentTransaction = Some(t.copy(artifacts = allArts))
case None =>
throw new IllegalStateException(("MavenRepositories require transactional publish"))
}
}
override def equals(a: Any): Boolean =
a match {
case x: AbstractMavenRepositoryResolver => x.getName == getName
case _ => false
}
}

View File

@ -0,0 +1,140 @@
package org.apache.ivy.plugins.resolver
import java.net.URI
import org.apache.ivy.plugins.repository.Resource
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.util.Message
import org.apache.ivy.util.url.URLHandlerRegistry
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.impl.{ MetadataGeneratorFactory, ArtifactDescriptorReader, RepositoryConnectorProvider, DefaultServiceLocator }
import org.eclipse.aether.metadata.Metadata
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.layout.{ RepositoryLayoutFactory, RepositoryLayoutProvider, RepositoryLayout }
import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum
import org.eclipse.aether.{
RepositorySystem,
RepositorySystemSession
}
import org.eclipse.aether.repository.{ RemoteRepository, LocalRepository }
import org.eclipse.aether.RepositorySystemSession
import org.apache.maven.repository.internal._
import org.eclipse.aether.spi.connector.transport._
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import java.io.File
/** Helper methods for dealing with starting up Aether. */
object MavenRepositorySystemFactory {
def newRepositorySystemImpl: RepositorySystem = {
// For now we just log Aether instantiation issues. These should probably cause fatal errors.
val locator = MavenRepositorySystemUtils.newServiceLocator()
locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler {
override def serviceCreationFailed(tpe: Class[_], impl: Class[_], exception: Throwable): Unit = {
Message.error(s"Failed to create $tpe, of class $impl")
}
})
// Here we register the Ivy <-> Aether transport bridge
locator.addService(classOf[TransporterFactory], classOf[MyTransportFactory])
// This connects the download mechanism to our transports. Why is it needed? no clue.
locator.addService(classOf[RepositoryConnectorFactory], classOf[BasicRepositoryConnectorFactory])
// Plugins cause issues here, as their layout is super odd. Here we inject a new plugin layout
locator.addService(classOf[RepositoryLayoutFactory], classOf[SbtPluginLayoutFactory])
// Here we add the metadata services so aether will automatically add maven-metadata.xml files.
locator.addService(classOf[MetadataGeneratorFactory], classOf[SnapshotMetadataGeneratorFactory])
locator.addService(classOf[MetadataGeneratorFactory], classOf[VersionsMetadataGeneratorFactory])
// Add our hook for parsing pom.xml files.
locator.setService(classOf[ArtifactDescriptorReader], classOf[SbtArtifactDescriptorReader])
// Finally, use the DI to create our repository system.
locator.getService(classOf[RepositorySystem])
}
def newSessionImpl(system: RepositorySystem, localRepoDir: File): RepositorySystemSession = {
val session = MavenRepositorySystemUtils.newSession()
val localRepo = new LocalRepository(localRepoDir)
session setLocalRepositoryManager (system.newLocalRepositoryManager(session, localRepo))
// Here we set a descriptor policy that FORCES the pom.xml to exist, otherwise Ivy's resolution
// algorithm freaks out. What we could do is also do the ivy lame-thing of checking for a JAR
// instead of a pom.xml, but let's see if this is actually a problem in practice.
val descriptorPolicy = new org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy(
/* ignoreMissing */ false, /* ignoreInvalid. */ true)
session.setArtifactDescriptorPolicy(descriptorPolicy)
session
}
def defaultLocalRepo: java.io.File = {
new java.io.File(s"${sys.props("user.home")}/.m2/repository")
}
}
/** Override aether's default transport with Ivy-ones. */
class MyTransportFactory extends TransporterFactory {
override def newInstance(session: RepositorySystemSession, repository: RemoteRepository): Transporter =
repository.getProtocol match {
case "http" | "https" => new HttpTransport(repository)
case "file" => new FileTransport(repository)
case other => throw new IllegalArgumentException(s"Unsupported transport protocol: $other")
}
override def getPriority: Float = 1.0f
}
/** Aether Http <-> Ivy Http adapter. Aether's is better, but Ivy's has configuration hooks in sbt. */
class HttpTransport(repository: RemoteRepository) extends AbstractTransporter {
class NotFoundException(msg: String) extends Exception(msg)
private def toURL(task: TransportTask): java.net.URL =
try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}")
catch {
case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.")
}
private def toResource(task: TransportTask): Resource = new URLResource(toURL(task))
override def implPeek(peek: PeekTask): Unit = {
if (!toResource(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}")
}
override def implClose(): Unit = ()
override def implGet(out: GetTask): Unit = {
if (!toResource(out).exists()) throw new NotFoundException(s"Could not find ${out.getLocation}")
URLHandlerRegistry.getDefault.download(toURL(out), out.getDataFile, null)
}
override def implPut(put: PutTask): Unit = {
URLHandlerRegistry.getDefault.upload(put.getDataFile, toURL(put), null)
}
override def classify(err: Throwable): Int =
err match {
// TODO - Implement
case _: NotFoundException => Transporter.ERROR_NOT_FOUND
case _ => Transporter.ERROR_OTHER
}
}
class FileTransport(repository: RemoteRepository) extends AbstractTransporter {
class NotFoundException(msg: String) extends Exception(msg)
private def toURL(task: TransportTask): java.net.URL =
try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}")
catch {
case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.")
}
private def toResource(task: TransportTask): Resource = new URLResource(toURL(task))
private def toFile(task: TransportTask): java.io.File =
new java.io.File(toURL(task).toURI)
override def implPeek(peek: PeekTask): Unit = {
if (!toFile(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}")
}
override def implClose(): Unit = ()
override def implGet(out: GetTask): Unit = {
val from = toFile(out)
if (!from.exists()) throw new NotFoundException(s"Could not find ${out.getLocation}")
sbt.IO.copyFile(from, out.getDataFile, true)
}
override def implPut(put: PutTask): Unit = {
val to = toFile(put)
val from = put.getDataFile
sbt.IO.copyFile(from, to, true)
}
override def classify(err: Throwable): Int =
err match {
// TODO - Implement
case _: NotFoundException => Transporter.ERROR_NOT_FOUND
case _ => Transporter.ERROR_OTHER
}
}

View File

@ -0,0 +1,556 @@
package org.apache.maven.repository.internal;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.License;
import org.apache.maven.model.Model;
import org.apache.maven.model.Prerequisites;
import org.apache.maven.model.Relocation;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.DefaultModelBuilderFactory;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.FileModelSource;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.resolution.UnresolvableModelException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositoryEvent.EventType;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.ArtifactProperties;
import org.eclipse.aether.artifact.ArtifactType;
import org.eclipse.aether.artifact.ArtifactTypeRegistry;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.artifact.DefaultArtifactType;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.impl.*;
import org.eclipse.aether.repository.WorkspaceRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
import org.eclipse.aether.resolution.ArtifactDescriptorPolicyRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRequest;
import org.eclipse.aether.resolution.VersionResolutionException;
import org.eclipse.aether.resolution.VersionResult;
import org.eclipse.aether.spi.locator.Service;
import org.eclipse.aether.spi.locator.ServiceLocator;
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;
/**
* A hacked version of maven's default artifact descriptor reader which we use in place of the standard aether adapter.
*
* This adds the following to the parsing of maven files:
*
* Additonal properties:
*
* - `sbt.pom.packaging` - The pom.packaging value.
*
*
* @author Benjamin Bentmann
* @author Josh Suereth - Adapted for sbt
*/
@Named
@Component( role = ArtifactDescriptorReader.class )
public class SbtArtifactDescriptorReader
implements ArtifactDescriptorReader, Service
{
@SuppressWarnings( "unused" )
@Requirement( role = LoggerFactory.class )
private Logger logger = NullLoggerFactory.LOGGER;
@Requirement
private RemoteRepositoryManager remoteRepositoryManager;
@Requirement
private VersionResolver versionResolver;
@Requirement
private VersionRangeResolver versionRangeResolver;
@Requirement
private ArtifactResolver artifactResolver;
@Requirement
private RepositoryEventDispatcher repositoryEventDispatcher;
@Requirement
private ModelBuilder modelBuilder;
public SbtArtifactDescriptorReader()
{
// enable no-arg constructor
}
@Inject
SbtArtifactDescriptorReader( RemoteRepositoryManager remoteRepositoryManager, VersionResolver versionResolver,
ArtifactResolver artifactResolver, ModelBuilder modelBuilder,
RepositoryEventDispatcher repositoryEventDispatcher, LoggerFactory loggerFactory )
{
setRemoteRepositoryManager( remoteRepositoryManager );
setVersionResolver( versionResolver );
setArtifactResolver( artifactResolver );
setModelBuilder( modelBuilder );
setLoggerFactory( loggerFactory );
setRepositoryEventDispatcher( repositoryEventDispatcher );
}
public void initService( ServiceLocator locator )
{
setLoggerFactory(locator.getService(LoggerFactory.class));
setRemoteRepositoryManager(locator.getService(RemoteRepositoryManager.class));
setVersionResolver(locator.getService(VersionResolver.class));
setArtifactResolver(locator.getService(ArtifactResolver.class));
setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class));
setVersionRangeResolver(locator.getService(VersionRangeResolver.class));
modelBuilder = locator.getService( ModelBuilder.class );
if ( modelBuilder == null )
{
setModelBuilder( new DefaultModelBuilderFactory().newInstance() );
}
}
public SbtArtifactDescriptorReader setLoggerFactory( LoggerFactory loggerFactory )
{
this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() );
return this;
}
void setLogger( LoggerFactory loggerFactory )
{
// plexus support
setLoggerFactory( loggerFactory );
}
public SbtArtifactDescriptorReader setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
{
if ( remoteRepositoryManager == null )
{
throw new IllegalArgumentException( "remote repository manager has not been specified" );
}
this.remoteRepositoryManager = remoteRepositoryManager;
return this;
}
public SbtArtifactDescriptorReader setVersionResolver( VersionResolver versionResolver )
{
if ( versionResolver == null )
{
throw new IllegalArgumentException( "version resolver has not been specified" );
}
this.versionResolver = versionResolver;
return this;
}
public SbtArtifactDescriptorReader setArtifactResolver( ArtifactResolver artifactResolver )
{
if ( artifactResolver == null )
{
throw new IllegalArgumentException( "artifact resolver has not been specified" );
}
this.artifactResolver = artifactResolver;
return this;
}
public SbtArtifactDescriptorReader setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
{
if ( repositoryEventDispatcher == null )
{
throw new IllegalArgumentException( "repository event dispatcher has not been specified" );
}
this.repositoryEventDispatcher = repositoryEventDispatcher;
return this;
}
public SbtArtifactDescriptorReader setModelBuilder( ModelBuilder modelBuilder )
{
if ( modelBuilder == null )
{
throw new IllegalArgumentException( "model builder has not been specified" );
}
this.modelBuilder = modelBuilder;
return this;
}
public ArtifactDescriptorResult readArtifactDescriptor( RepositorySystemSession session,
ArtifactDescriptorRequest request )
throws ArtifactDescriptorException
{
ArtifactDescriptorResult result = new ArtifactDescriptorResult( request );
Model model = loadPom( session, request, result );
if ( model != null )
{
ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
for ( Repository r : model.getRepositories() )
{
result.addRepository( ArtifactDescriptorUtils.toRemoteRepository( r ) );
}
for ( org.apache.maven.model.Dependency dependency : model.getDependencies() )
{
result.addDependency( convert( dependency, stereotypes ) );
}
DependencyManagement mngt = model.getDependencyManagement();
if ( mngt != null )
{
for ( org.apache.maven.model.Dependency dependency : mngt.getDependencies() )
{
result.addManagedDependency( convert( dependency, stereotypes ) );
}
}
Map<String, Object> properties = new LinkedHashMap<String, Object>();
Prerequisites prerequisites = model.getPrerequisites();
if ( prerequisites != null )
{
properties.put( "prerequisites.maven", prerequisites.getMaven() );
}
List<License> licenses = model.getLicenses();
properties.put( SbtExtraProperties.LICENSE_COUNT_KEY, licenses.size() );
for ( int i = 0; i < licenses.size(); i++ )
{
License license = licenses.get( i );
properties.put( SbtExtraProperties.makeLicenseName(i), license.getName() );
properties.put( SbtExtraProperties.makeLicenseUrl(i), license.getUrl() );
properties.put( "license." + i + ".comments", license.getComments() );
properties.put( "license." + i + ".distribution", license.getDistribution() );
}
// SBT ADDED - Here we push in the pom packaging type for Ivy expectations.
final String packaging =
(model.getPackaging() == null) ? "jar" : model.getPackaging();
properties.put(SbtExtraProperties.MAVEN_PACKAGING_KEY, packaging);
// SBT ADDED - Here we inject the sbt/scala version we parse out of the pom.
final Properties mprops = model.getProperties();
if(mprops.containsKey(SbtExtraProperties.POM_SBT_VERSION)) {
final String sbtVersion = mprops.getProperty(SbtExtraProperties.POM_SBT_VERSION);
properties.put(SbtExtraProperties.SBT_VERSION_KEY, sbtVersion);
}
if(mprops.containsKey(SbtExtraProperties.POM_SCALA_VERSION)) {
properties.put(SbtExtraProperties.SCALA_VERSION_KEY, mprops.getProperty(SbtExtraProperties.POM_SCALA_VERSION));
}
// TODO - Add extra attributes, e.g. sbtVersion + scalaVersion.
result.setProperties( properties);
setArtifactProperties( result, model );
}
return result;
}
// SBT FIX - We make sure that artifact properties are copied over here, so we can find sbt-plugin POM files.
public static Artifact toPomArtifact(Artifact artifact) {
Artifact pomArtifact = artifact;
if(artifact.getClassifier().length() > 0 || !"pom".equals(artifact.getExtension())) {
// TODO - only copy over sbt-important properties.
pomArtifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), "pom", artifact.getVersion()).setProperties(artifact.getProperties());
}
return pomArtifact;
}
private Model loadPom( RepositorySystemSession session, ArtifactDescriptorRequest request,
ArtifactDescriptorResult result )
throws ArtifactDescriptorException
{
RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
Set<String> visited = new LinkedHashSet<String>();
for ( Artifact artifact = request.getArtifact();; )
{
// SBT FIX - we need to use our own variant here to preserve extra attributes.
// Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact );
Artifact pomArtifact = toPomArtifact(artifact);
try
{
VersionRequest versionRequest =
new VersionRequest( artifact, request.getRepositories(), request.getRequestContext() );
versionRequest.setTrace( trace );
VersionResult versionResult = versionResolver.resolveVersion( session, versionRequest );
artifact = artifact.setVersion( versionResult.getVersion() );
versionRequest =
new VersionRequest( pomArtifact, request.getRepositories(), request.getRequestContext() );
versionRequest.setTrace( trace );
versionResult = versionResolver.resolveVersion( session, versionRequest );
pomArtifact = pomArtifact.setVersion( versionResult.getVersion() );
}
catch ( VersionResolutionException e )
{
result.addException( e );
throw new ArtifactDescriptorException( result );
}
if ( !visited.add( artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion() ) )
{
RepositoryException exception =
new RepositoryException( "Artifact relocations form a cycle: " + visited );
invalidDescriptor( session, trace, artifact, exception );
if ( ( getPolicy( session, artifact, request ) & ArtifactDescriptorPolicy.IGNORE_INVALID ) != 0 )
{
return null;
}
result.addException( exception );
throw new ArtifactDescriptorException( result );
}
ArtifactResult resolveResult;
try
{
ArtifactRequest resolveRequest =
new ArtifactRequest( pomArtifact, request.getRepositories(), request.getRequestContext() );
resolveRequest.setTrace( trace );
resolveResult = artifactResolver.resolveArtifact( session, resolveRequest );
pomArtifact = resolveResult.getArtifact();
result.setRepository( resolveResult.getRepository() );
}
catch ( ArtifactResolutionException e )
{
if ( e.getCause() instanceof ArtifactNotFoundException )
{
missingDescriptor( session, trace, artifact, (Exception) e.getCause() );
if ( ( getPolicy( session, artifact, request ) & ArtifactDescriptorPolicy.IGNORE_MISSING ) != 0 )
{
return null;
}
}
result.addException( e );
throw new ArtifactDescriptorException( result );
}
Model model;
try
{
ModelBuildingRequest modelRequest = new DefaultModelBuildingRequest();
modelRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
modelRequest.setProcessPlugins(false);
modelRequest.setTwoPhaseBuilding(false);
modelRequest.setSystemProperties(toProperties(session.getUserProperties(),
session.getSystemProperties()));
modelRequest.setModelCache(DefaultModelCache.newInstance(session));
modelRequest.setModelResolver(
new DefaultModelResolver(
session,
trace.newChild(modelRequest),
request.getRequestContext(),
artifactResolver,
versionRangeResolver,
remoteRepositoryManager,
request.getRepositories())
);
if ( resolveResult.getRepository() instanceof WorkspaceRepository )
{
modelRequest.setPomFile( pomArtifact.getFile() );
}
else
{
modelRequest.setModelSource( new FileModelSource( pomArtifact.getFile() ) );
}
model = modelBuilder.build( modelRequest ).getEffectiveModel();
}
catch ( ModelBuildingException e )
{
for ( ModelProblem problem : e.getProblems() )
{
if ( problem.getException() instanceof UnresolvableModelException )
{
result.addException( problem.getException() );
throw new ArtifactDescriptorException( result );
}
}
invalidDescriptor( session, trace, artifact, e );
if ( ( getPolicy( session, artifact, request ) & ArtifactDescriptorPolicy.IGNORE_INVALID ) != 0 )
{
return null;
}
result.addException( e );
throw new ArtifactDescriptorException( result );
}
Relocation relocation = getRelocation( model );
if ( relocation != null )
{
result.addRelocation( artifact );
artifact =
new RelocatedArtifact( artifact, relocation.getGroupId(), relocation.getArtifactId(),
relocation.getVersion() );
result.setArtifact( artifact );
}
else
{
return model;
}
}
}
private Properties toProperties( Map<String, String> dominant, Map<String, String> recessive )
{
Properties props = new Properties();
if ( recessive != null )
{
props.putAll( recessive );
}
if ( dominant != null )
{
props.putAll( dominant );
}
return props;
}
private Relocation getRelocation( Model model )
{
Relocation relocation = null;
DistributionManagement distMngt = model.getDistributionManagement();
if ( distMngt != null )
{
relocation = distMngt.getRelocation();
}
return relocation;
}
private void setArtifactProperties( ArtifactDescriptorResult result, Model model )
{
String downloadUrl = null;
DistributionManagement distMngt = model.getDistributionManagement();
if ( distMngt != null )
{
downloadUrl = distMngt.getDownloadUrl();
}
if ( downloadUrl != null && downloadUrl.length() > 0 )
{
Artifact artifact = result.getArtifact();
Map<String, String> props = new HashMap<String, String>( artifact.getProperties() );
props.put( ArtifactProperties.DOWNLOAD_URL, downloadUrl );
result.setArtifact( artifact.setProperties( props ) );
}
}
private Dependency convert( org.apache.maven.model.Dependency dependency, ArtifactTypeRegistry stereotypes )
{
ArtifactType stereotype = stereotypes.get( dependency.getType() );
if ( stereotype == null )
{
stereotype = new DefaultArtifactType( dependency.getType() );
}
boolean system = dependency.getSystemPath() != null && dependency.getSystemPath().length() > 0;
Map<String, String> props = null;
if ( system )
{
props = Collections.singletonMap( ArtifactProperties.LOCAL_PATH, dependency.getSystemPath() );
}
Artifact artifact =
new DefaultArtifact( dependency.getGroupId(), dependency.getArtifactId(), dependency.getClassifier(), null,
dependency.getVersion(), props, stereotype );
List<Exclusion> exclusions = new ArrayList<Exclusion>( dependency.getExclusions().size() );
for ( org.apache.maven.model.Exclusion exclusion : dependency.getExclusions() )
{
exclusions.add( convert( exclusion ) );
}
Dependency result = new Dependency( artifact, dependency.getScope(), dependency.isOptional(), exclusions );
return result;
}
private Exclusion convert( org.apache.maven.model.Exclusion exclusion )
{
return new Exclusion( exclusion.getGroupId(), exclusion.getArtifactId(), "*", "*" );
}
private void missingDescriptor( RepositorySystemSession session, RequestTrace trace, Artifact artifact,
Exception exception )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.ARTIFACT_DESCRIPTOR_MISSING );
event.setTrace( trace );
event.setArtifact( artifact );
event.setException( exception );
repositoryEventDispatcher.dispatch( event.build() );
}
private void invalidDescriptor( RepositorySystemSession session, RequestTrace trace, Artifact artifact,
Exception exception )
{
RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.ARTIFACT_DESCRIPTOR_INVALID );
event.setTrace( trace );
event.setArtifact( artifact );
event.setException( exception );
repositoryEventDispatcher.dispatch( event.build() );
}
private int getPolicy( RepositorySystemSession session, Artifact artifact, ArtifactDescriptorRequest request )
{
ArtifactDescriptorPolicy policy = session.getArtifactDescriptorPolicy();
if ( policy == null )
{
return ArtifactDescriptorPolicy.STRICT;
}
return policy.getPolicy( session, new ArtifactDescriptorPolicyRequest( artifact, request.getRequestContext() ) );
}
public void setVersionRangeResolver(final VersionRangeResolver versionRangeResolver) {
this.versionRangeResolver = versionRangeResolver;
}
}

View File

@ -0,0 +1,23 @@
package org.apache.maven.repository.internal;
/**
* Created by jsuereth on 12/20/14.
*/
public class SbtExtraProperties {
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_SCALA_VERSION = "scalaVersion";
public static final String POM_SBT_VERSION = "sbtVersion";
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

@ -0,0 +1,100 @@
package org.apache.maven.repository.internal
import org.apache.ivy.util.Message
import org.eclipse.aether.spi.connector.layout.{ RepositoryLayout, RepositoryLayoutFactory }
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.transfer.NoRepositoryLayoutException
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 scala.util.matching.Regex
/** A factory which knows how to create repository layouts which can find sbt plugins. */
class SbtPluginLayoutFactory extends RepositoryLayoutFactory {
def newInstance(session: RepositorySystemSession, repository: RemoteRepository): RepositoryLayout = {
repository.getContentType match {
case SbtRepositoryLayout.LAYOUT_NAME =>
SbtRepositoryLayout
case _ => throw new NoRepositoryLayoutException(repository, "Not an sbt-plugin repository")
}
}
def getPriority: Float = 100.0f
}
object SbtRepositoryLayout extends RepositoryLayout {
val LAYOUT_NAME = "sbt-plugin"
// get location is ALMOST the same for Metadata + artifact... but subtle differences are important.
def getLocation(artifact: Artifact, upload: Boolean): URI = {
import collection.JavaConverters._
val sbtVersion = Option(artifact.getProperties.get(SbtExtraProperties.POM_SBT_VERSION))
val scalaVersion = Option(artifact.getProperties.get(SbtExtraProperties.POM_SCALA_VERSION))
val path = new StringBuilder(128)
path.append(artifact.getGroupId.replace('.', '/')).append('/')
(sbtVersion zip scalaVersion).headOption match {
case Some((sbt, scala)) =>
if (artifact.getArtifactId contains "_sbt_") {
// TODO - Write this handler.
val SbtNameVersionSplit(name, sbt2) = artifact.getArtifactId
path.append(name).append('_').append(scala).append('_').append(sbt).append('/')
} else path.append(artifact.getArtifactId).append('_').append(scala).append('_').append(sbt).append('/')
case None =>
// TODO - Should we automatically append the _<scala-verison> here if it's not there? Probably not for now.
path.append(artifact.getArtifactId).append('/')
}
path.append(artifact.getBaseVersion).append('/')
sbtVersion match {
case Some(_) if artifact.getArtifactId contains "_sbt_" =>
val SbtNameVersionSplit(name, sbt2) = artifact.getArtifactId
path.append(name).append('-').append(artifact.getVersion)
case None => path.append(artifact.getArtifactId).append('-').append(artifact.getVersion)
}
if (artifact.getClassifier != null && !artifact.getClassifier.trim.isEmpty) {
path.append("-").append(artifact.getClassifier)
}
if (artifact.getExtension.length > 0) {
path.append('.').append(artifact.getExtension)
}
URI.create(path.toString())
}
// Trickery for disambiguating sbt plugins in maven repositories.
val SbtNameVersionSplit = new Regex("(.*)_sbt_(.*)")
def getLocation(metadata: Metadata, upload: Boolean): URI = {
val sbtVersion = Option(metadata.getProperties.get(SbtExtraProperties.POM_SBT_VERSION))
val scalaVersion = Option(metadata.getProperties.get(SbtExtraProperties.POM_SCALA_VERSION))
val path = new StringBuilder(128)
path.append(metadata.getGroupId.replace('.', '/')).append('/')
(sbtVersion zip scalaVersion).headOption match {
case Some((sbt, scala)) =>
if (metadata.getArtifactId contains "_sbt_") {
val SbtNameVersionSplit(name, sbt2) = metadata.getArtifactId
path.append(name).append('_').append(scala).append('_').append(sbt).append('/')
// TODO - Write this handler.
} else path.append(metadata.getArtifactId).append('_').append(scala).append('_').append(sbt).append('/')
case None =>
// TODO - Should we automatically append the _<scala-verison> here? Proabbly not for now.
path.append(metadata.getArtifactId).append('/')
}
if (metadata.getVersion.length > 0)
path.append(metadata.getVersion).append('/')
path.append(metadata.getType)
URI.create(path.toString)
}
// TODO - This should be the same as configured from Ivy...
def getChecksums(artifact: Artifact, upload: Boolean, location: URI): java.util.List[Checksum] =
getChecksums(location)
def getChecksums(metadata: Metadata, upload: Boolean, location: URI): java.util.List[Checksum] =
getChecksums(location)
private def getChecksums(location: URI): java.util.List[Checksum] =
java.util.Arrays.asList(Checksum.forLocation(location, "SHA-1"), Checksum.forLocation(location, "MD5"))
}

View File

@ -94,24 +94,37 @@ 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..*/
def apply(r: Resolver, settings: IvySettings, log: Logger) =
{
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.
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
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)
}
} 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.

View File

@ -13,6 +13,7 @@ import java.io.{ File, InputStream }
import java.net.URL
import java.util.regex.Pattern
@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,6 +27,7 @@ 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.

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,12 @@ 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
is.setVariable(ConvertResolver.USE_AETHER_PROPERTY, s"${configuration.updateOptions.aetherResolution}")
configuration match {
case e: ExternalIvyConfiguration =>
IvySbt.addResolvers(e.extraResolvers, is, configuration.log)
@ -104,6 +108,7 @@ final class IvySbt(val configuration: IvyConfiguration) {
super.bind()
}
}
i.setSettings(settings)
i.bind()
i.getLoggerEngine.pushLogger(new IvyLoggerInterface(configuration.log))

View File

@ -330,6 +330,8 @@ 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 =>
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

@ -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

@ -17,7 +17,13 @@ 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,
/** 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)
def withCircularDependencyLevel(circularDependencyLevel: CircularDependencyLevel): UpdateOptions =
copy(circularDependencyLevel = circularDependencyLevel)
@ -35,11 +41,13 @@ final class UpdateOptions private[sbt] (
circularDependencyLevel: CircularDependencyLevel = this.circularDependencyLevel,
latestSnapshots: Boolean = this.latestSnapshots,
consolidatedResolution: Boolean = this.consolidatedResolution,
cachedResolution: Boolean = this.cachedResolution): UpdateOptions =
cachedResolution: Boolean = this.cachedResolution,
aetherResolution: Boolean = this.aetherResolution): UpdateOptions =
new UpdateOptions(circularDependencyLevel,
latestSnapshots,
consolidatedResolution,
cachedResolution)
cachedResolution,
aetherResolution)
override def equals(o: Any): Boolean = o match {
case o: UpdateOptions =>
@ -65,5 +73,7 @@ object UpdateOptions {
circularDependencyLevel = CircularDependencyLevel.Warn,
latestSnapshots = false,
consolidatedResolution = false,
cachedResolution = false)
cachedResolution = false,
// TODO - Disable this before release, but make sure test suite passes with it on.
aetherResolution = true)
}

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[AbstractMavenRepositoryResolver] =>
// 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

@ -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)
}
}

View File

@ -0,0 +1,291 @@
package sbt
import java.io.FileInputStream
import org.specs2._
class MavenResolutionSpec extends BaseIvySpecification {
def is = args(sequential = true) ^ s2""".stripMargin
This is a specification to check the maven resolution
Resolving a maven dependency should
handle sbt plugins $resolveSbtPlugins
use ivy for conflict resolution $resolveMajorConflicts
handle cross configuration deps $resolveCrossConfigurations
publish with maven-metadata $publishMavenMetadata
resolve transitive maven dependencies $resolveTransitiveMavenDependency
resolve intransitive maven dependencies $resolveIntransitiveMavenDependency
handle transitive configuration shifts $resolveTransitiveConfigurationMavenDependency
resolve source and doc $resolveSourceAndJavadoc
resolve nonstandard (jdk5) classifier $resolveNonstandardClassifier
Resolve pom artifact dependencies $resolvePomArtifactAndDependencies
Fail if JAR artifact is not found w/ POM $failIfMainArtifactMissing
Fail if POM.xml is not found $failIfPomMissing
resolve publication date for -SNAPSHOT $resolveSnapshotPubDate
""" // */
// TODO - test latest.integration and .+
def akkaActor = ModuleID("com.typesafe.akka", "akka-actor_2.11", "2.3.8", Some("compile"))
def akkaActorTestkit = ModuleID("com.typesafe.akka", "akka-testkit_2.11", "2.3.8", Some("test"))
def testngJdk5 = ModuleID("org.testng", "testng", "5.7", Some("compile")).classifier("jdk15")
def jmxri = ModuleID("com.sun.jmx", "jmxri", "1.2.1", Some("compile"))
def scalaLibraryAll = ModuleID("org.scala-lang", "scala-library-all", "2.11.4", Some("compile"))
def scalaCompiler = ModuleID("org.scala-lang", "scala-compiler", "2.8.1", Some("scala-tool->default(compile)"))
def scalaContinuationPlugin = ModuleID("org.scala-lang.plugins", "continuations", "2.8.1", Some("plugin->default(compile)"))
def sbtPlugin =
ModuleID("com.github.mpeltonen", "sbt-idea", "1.6.0", Some("compile")).
extra(CustomPomParser.SbtVersionKey -> "0.13", CustomPomParser.ScalaVersionKey -> "2.10").
copy(crossVersion = CrossVersion.Disabled)
def oldSbtPlugin =
ModuleID("com.github.mpeltonen", "sbt-idea", "1.6.0", Some("compile")).
extra(CustomPomParser.SbtVersionKey -> "0.12", CustomPomParser.ScalaVersionKey -> "2.9.2").
copy(crossVersion = CrossVersion.Disabled)
def majorConflictLib = ModuleID("com.joestelmach", "natty", "0.3", Some("compile"))
// TODO - This snapshot and resolver should be something we own/control so it doesn't disappear on us.
def testSnapshot = ModuleID("com.typesafe", "config", "0.4.9-SNAPSHOT", Some("compile"))
val SnapshotResolver = MavenRepository("some-snapshots", "https://oss.sonatype.org/content/repositories/snapshots/")
override def resolvers = Seq(DefaultMavenRepository, SnapshotResolver, Resolver.publishMavenLocal)
import Configurations.{ Compile, Test, Runtime, CompilerPlugin, ScalaTool }
override def configurations = Seq(Compile, Test, Runtime, CompilerPlugin, ScalaTool)
import ShowLines._
def defaultUpdateOptions = UpdateOptions().withAetherResolution(true)
def resolveMajorConflicts = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")),
Seq(majorConflictLib), None, defaultUpdateOptions)
val report = ivyUpdate(m) // must not(throwAn[IllegalStateException])
val jars =
for {
conf <- report.configurations
if conf.configuration == Compile.name
m <- conf.modules
if (m.module.name contains "stringtemplate")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
jars must haveSize(1)
}
def resolveCrossConfigurations = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")),
Seq(scalaCompiler, scalaContinuationPlugin), None, defaultUpdateOptions)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
if conf.configuration == ScalaTool.name
m <- conf.modules
if (m.module.name contains "scala-compiler")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
jars must haveSize(1)
}
def resolveSbtPlugins = {
def sha(f: java.io.File): String = sbt.Hash.toHex(sbt.Hash(f))
def findSbtIdeaJars(dep: ModuleID, name: String) = {
val m = module(ModuleID("com.example", name, "0.1.0", Some("compile")), Seq(dep), None, defaultUpdateOptions)
val report = ivyUpdate(m)
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if (m.module.name contains "sbt-idea")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield (f, sha(f))
}
val oldJars = findSbtIdeaJars(oldSbtPlugin, "old")
System.err.println(s"${oldJars.mkString("\n")}")
val newJars = findSbtIdeaJars(sbtPlugin, "new")
System.err.println(s"${newJars.mkString("\n")}")
(newJars must haveSize(1)) and (oldJars must haveSize(1)) and (oldJars.map(_._2) must not(containTheSameElementsAs(newJars.map(_._2))))
}
def resolveSnapshotPubDate = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(testSnapshot), Some("2.10.2"), defaultUpdateOptions.withLatestSnapshots(true))
val report = ivyUpdate(m)
val pubTime =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if m.module.revision endsWith "-SNAPSHOT"
date <- m.publicationDate
} yield date
(pubTime must haveSize(1))
}
def resolvePomArtifactAndDependencies = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(scalaLibraryAll), Some("2.10.2"), defaultUpdateOptions)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if (m.module.name == "scala-library") || (m.module.name contains "parser")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
jars must haveSize(2)
}
def failIfPomMissing = {
// TODO - we need the jar to not exist too.
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(ModuleID("org.scala-sbt", "does-not-exist", "1.0", Some("compile"))), Some("2.10.2"), defaultUpdateOptions)
ivyUpdate(m) must throwAn[Exception]
}
def failIfMainArtifactMissing = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(jmxri), Some("2.10.2"), defaultUpdateOptions)
ivyUpdate(m) must throwAn[Exception]
}
def resolveNonstandardClassifier = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(testngJdk5), Some("2.10.2"), defaultUpdateOptions)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if m.module.name == "testng"
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
(report.configurations must haveSize(configurations.size)) and
(jars must haveSize(1))
(jars.forall(_.exists) must beTrue)
}
def resolveTransitiveMavenDependency = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(akkaActor), Some("2.10.2"), defaultUpdateOptions)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if m.module.name == "scala-library"
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
(report.configurations must haveSize(configurations.size)) and
(jars must not(beEmpty)) and
(jars.forall(_.exists) must beTrue)
}
def resolveIntransitiveMavenDependency = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(akkaActorTestkit.intransitive()), Some("2.10.2"), defaultUpdateOptions)
val report = ivyUpdate(m)
val transitiveJars =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if (m.module.name contains "akka-actor") && !(m.module.name contains "testkit")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
val directJars =
for {
conf <- report.configurations
if conf.configuration == "compile"
m <- conf.modules
if (m.module.name contains "akka-actor") && (m.module.name contains "testkit")
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
(report.configurations must haveSize(configurations.size)) and
(transitiveJars must beEmpty) and (directJars.forall(_.exists) must beTrue)
}
def resolveTransitiveConfigurationMavenDependency = {
val m = module(ModuleID("com.example", "foo", "0.1.0", Some("compile")), Seq(akkaActorTestkit), Some("2.10.2"), defaultUpdateOptions)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
if conf.configuration == "test"
m <- conf.modules
if m.module.name contains "akka-actor"
(a, f) <- m.artifacts
if a.extension == "jar"
} yield f
(report.configurations must haveSize(configurations.size)) and
(jars must not(beEmpty)) and
(jars.forall(_.exists) must beTrue)
}
def resolveSourceAndJavadoc = {
val m = module(
ModuleID("com.example", "foo", "0.1.0", Some("sources")),
Seq(akkaActor.artifacts(Artifact(akkaActor.name, "javadoc"), Artifact(akkaActor.name, "sources"))),
Some("2.10.2"),
defaultUpdateOptions
)
val report = ivyUpdate(m)
val jars =
for {
conf <- report.configurations
// We actually injected javadoc/sources into the compile scope, due to how we did the request.
// SO, we report that here.
if conf.configuration == "compile"
m <- conf.modules
(a, f) <- m.artifacts
if (f.getName contains "sources") || (f.getName contains "javadoc")
} yield f
(report.configurations must haveSize(configurations.size)) and
(jars must haveSize(2))
}
def publishMavenMetadata = {
val m = module(
ModuleID("com.example", "test-it", "1.0-SNAPSHOT", Some("compile")),
Seq(),
None,
defaultUpdateOptions.withLatestSnapshots(true)
)
sbt.IO.withTemporaryDirectory { dir =>
val pomFile = new java.io.File(dir, "pom.xml")
sbt.IO.write(pomFile,
"""
|<project>
| <groupId>com.example</groupId>
| <name>test-it</name>
| <version>1.0-SNAPSHOT</version>
|</project>
""".stripMargin)
val jarFile = new java.io.File(dir, "test-it-1.0-SNAPSHOT.jar")
sbt.IO.touch(jarFile)
System.err.println(s"DEBUGME - Publishing $m to ${Resolver.publishMavenLocal}")
ivyPublish(m, mkPublishConfiguration(
Resolver.publishMavenLocal,
Map(
Artifact("test-it-1.0-SNAPSHOT.jar") -> pomFile,
Artifact("test-it-1.0-SNAPSHOT.pom", "pom", "pom") -> jarFile
)))
}
val baseLocalMavenDir: java.io.File = Resolver.publishMavenLocal.rootFile
val allFiles: Seq[java.io.File] = sbt.PathFinder(new java.io.File(baseLocalMavenDir, "com/example/test-it")).***.get
val metadataFiles = allFiles.filter(_.getName contains "maven-metadata-local")
// TODO - maybe we check INSIDE the metadata, or make sure we can get a publication date on resolve...
// We end up with 4 files, two mavne-metadata files, and 2 maven-metadata-local files.
metadataFiles must haveSize(2)
}
}

View File

@ -156,6 +156,7 @@ object CacheIvy {
implicit def moduleConfIC: InputCache[ModuleConfiguration] = wrapIn
object L3 {
implicit def mavenCacheToHL = (m: MavenCache) => m.name :+: m.rootFile.getAbsolutePath :+: HNil
implicit def mavenRToHL = (m: MavenRepository) => m.name :+: m.root :+: HNil
implicit def fileRToHL = (r: FileRepository) => r.name :+: r.configuration :+: r.patterns :+: HNil
implicit def urlRToHL = (u: URLRepository) => u.name :+: u.patterns :+: HNil
@ -169,7 +170,7 @@ object CacheIvy {
implicit lazy val chainedIC: InputCache[ChainedResolver] = InputCache.lzy(wrapIn)
implicit lazy val resolverIC: InputCache[Resolver] =
unionInputCache[Resolver, ChainedResolver :+: JavaNet1Repository :+: MavenRepository :+: FileRepository :+: URLRepository :+: SshRepository :+: SftpRepository :+: RawRepository :+: HNil]
unionInputCache[Resolver, ChainedResolver :+: JavaNet1Repository :+: MavenRepository :+: MavenCache :+: FileRepository :+: URLRepository :+: SshRepository :+: SftpRepository :+: RawRepository :+: HNil]
implicit def moduleIC: InputCache[ModuleID] = wrapIn
implicitly[InputCache[Seq[Configuration]]]

View File

@ -0,0 +1,44 @@
[@jsuereth]: https://github.com/jsuereth
[1676]: https://github.com/sbt/sbt/issues/1676
[1322]: https://github.com/sbt/sbt/issues/1322
[679]: https://github.com/sbt/sbt/issues/679
[647]: https://github.com/sbt/sbt/issues/647
[1616]: https://github.com/sbt/sbt/issues/1616
### Fixes with compatibility implications
### Improvements
### Aether Resolution
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.
The key called `updateOptions` has been expanded to enable Aether resolutions via the following setting:
updateOptions := updateOptions.value.withAetherResolution(true)
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.
Known limitations:
* The current implementation does not support ivy-style version numbers, 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]
// Possibly fixed, need verification.
- 1676 - SNAPSHOT dependency not updated ???
- 679 - Incorrect Maven Snapshot file resolution ????

View File

@ -27,4 +27,41 @@ object Dependencies {
}
lazy val scalaXml = scala211Module("scala-xml", "1.0.1")
lazy val scalaParsers = scala211Module("scala-parser-combinators", "1.0.1")
// Maven related dependnecy craziness
//val mvnEmbedder = "org.apache.maven" % "maven-embedder" % mvnVersion
val mvnWagonVersion = "2.4"
val mvnVersion = "3.2.3"
val aetherVersion = "1.0.1.v20141111"
val mvnAether = "org.apache.maven" % "maven-aether-provider" % mvnVersion
val aether = "org.eclipse.aether" % "aether" % aetherVersion
val aetherImpl = "org.eclipse.aether" % "aether-impl" % aetherVersion
val aetherUtil = "org.eclipse.aether" % "aether-util" % aetherVersion
val aetherTransportFile = "org.eclipse.aether" % "aether-transport-file" % aetherVersion
val aetherTransportWagon = "org.eclipse.aether" % "aether-transport-wagon" % aetherVersion
val aetherTransportHttp = "org.eclipse.aether" % "aether-transport-http" % aetherVersion
val aetherConnectorBasic = "org.eclipse.aether" % "aether-connector-basic" % aetherVersion
val sisuPlexus = ("org.eclipse.sisu" % "org.eclipse.sisu.plexus" % "0.3.0.M1").exclude("javax.enterprise", "cdi-api").exclude("com.google.code.findbugs", "jsr305")
val guice = "com.google.inject" % "guice" % "3.0"
val guava = "com.google.guava" % "guava" % "18.0"
val javaxInject = "javax.inject" % "javax.inject" % "1"
//val sisuGuice = ("org.eclipse.sisu" % "sisu-guice" % "3.1.0").classifier("no_aop").exclude("javax.enterprise", "cdi-api", )
/*
val mvnWagon = "org.apache.maven.wagon" % "wagon-http" % mvnWagonVersion
val mvnWagonProviderApi = "org.apache.maven.wagon" % "wagon-provider-api" % mvnWagonVersion
val mvnWagonLwHttp = "org.apache.maven.wagon" % "wagon-http-lightweight" % mvnWagonVersion
val mvnWagonFile = "org.apache.maven.wagon" % "wagon-file" % mvnWagonVersion
*/
def aetherLibs =
Seq(
guava,
javaxInject,
sisuPlexus,
aetherImpl,
aetherConnectorBasic,
mvnAether)
}

View File

@ -135,6 +135,7 @@ object %s {
sourceGenerators <+= generateKeywords map (x => Seq(x))
))
}
object Licensed {
lazy val notice = SettingKey[File]("notice")
lazy val extractLicenses = TaskKey[Seq[File]]("extract-licenses")

View File

@ -6,4 +6,6 @@ version := "1.0.0-SNAPSHOT"
publishArtifact in (Test,packageBin) := true
publishTo := Some(Resolver.file("demo", (baseDirectory in ThisBuild).value / "demo-repo"))
publishTo := Some(MavenCache("demo", ((baseDirectory in ThisBuild).value / "demo-repo")))
//Resolver.file("demo", (baseDirectory in ThisBuild).value / "demo-repo"))

View File

@ -1,7 +1,7 @@
libraryDependencies += "org.example" %% "artifacta" % "1.0.0-SNAPSHOT" withSources() classifier("tests")
externalResolvers := Seq(
"demo" at ( (baseDirectory in ThisBuild).value / "demo-repo").toURI.toString,
MavenCache("demo", ((baseDirectory in ThisBuild).value / "demo-repo")),
DefaultMavenRepository
)

View File

@ -1,7 +1,7 @@
// the default, but make it explicit
publishMavenStyle := true
publishTo <<= baseDirectory(bd => Some( Resolver.file("test-repo", bd / "repo") ) )
publishTo <<= baseDirectory(bd => Some( MavenRepository("test-repo", (bd / "repo").toURI.toASCIIString )) )
name := "test"

View File

@ -3,6 +3,8 @@ val repoFile = file("mvn-repo")
resolvers += "bad-mvn-repo" at repoFile.toURI.toURL.toString
resolvers += Resolver.typesafeIvyRepo("releases")
libraryDependencies += "bad" % "mvn" % "1.0"
TaskKey[Unit]("check") := {

View File

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

View File

@ -19,6 +19,7 @@ $ copy-file changes/mainB1.scala main/B.scala
-> main/compile
$ copy-file changes/libA.scala library/A.scala
> debug
> library/publishM2
# should succeed even without 'update' because Ivy should use the jar from the origin and not copy it to its cache
> main/compile

View File

@ -1,9 +1,9 @@
# write the default pom. The only repositories should be Scala Tools Releases and Snapshots
> check-pom https://oss.sonatype.org/content/repositories/releases/ https://oss.sonatype.org/content/repositories/snapshots/
> check-pom https://oss.sonatype.org/content/repositories/releases https://oss.sonatype.org/content/repositories/snapshots
# include file:// repositories. The generated repositories section should include the local Maven repository as well
$ touch repo.all
> check-pom https://oss.sonatype.org/content/repositories/releases/ https://oss.sonatype.org/content/repositories/snapshots/ file://*.m2/repository/
> check-pom https://oss.sonatype.org/content/repositories/releases https://oss.sonatype.org/content/repositories/snapshots file://*.m2/repository
# include file:// repositories. The generated repositories section should include the local Maven repository as well
$ delete repo.all

View File

@ -0,0 +1,22 @@
name := "test-parent-pom"
val localMavenRepo = file("local-repo")
val cleanExampleCache = taskKey[Unit]("Cleans the example cache.")
resolvers +=
MavenRepository("Maven2 Local Test", localMavenRepo.toURI.toString)
libraryDependencies +=
"com.example" % "example-child" % "1.0-SNAPSHOT"
version := "1.0-SNAPSHOT"
cleanExampleCache := {
ivySbt.value.withIvy(streams.value.log) { ivy =>
val cacheDir = ivy.getSettings.getDefaultRepositoryCacheBasedir
// TODO - Is this actually ok?
IO.delete(cacheDir / "com.example")
}
}

View File

@ -0,0 +1,4 @@
#NOTE: This is an internal implementation file, its format can be changed without prior notice.
#Tue Dec 16 09:06:35 EST 2014
example-child-1.0-SNAPSHOT.jar>=
example-child-1.0-SNAPSHOT.pom>=

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<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>
<parent>
<groupId>com.example</groupId>
<artifactId>example-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>example-child</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
</project>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>com.example</groupId>
<artifactId>example-child</artifactId>
<version>1.0-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20141216140635</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>1.0-SNAPSHOT</value>
<updated>20141216140635</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>1.0-SNAPSHOT</value>
<updated>20141216140635</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.example</groupId>
<artifactId>example-child</artifactId>
<versioning>
<versions>
<version>1.0-SNAPSHOT</version>
</versions>
<lastUpdated>20141216140635</lastUpdated>
</versioning>
</metadata>

View File

@ -0,0 +1,3 @@
#NOTE: This is an internal implementation file, its format can be changed without prior notice.
#Tue Dec 16 09:01:47 EST 2014
example-parent-1.0-SNAPSHOT.pom>=

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<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.example</groupId>
<artifactId>example-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>com.example</groupId>
<artifactId>example-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20141216140147</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>pom</extension>
<value>1.0-SNAPSHOT</value>
<updated>20141216140147</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.example</groupId>
<artifactId>example-parent</artifactId>
<versioning>
<versions>
<version>1.0-SNAPSHOT</version>
</versions>
<lastUpdated>20141216140147</lastUpdated>
</versioning>
</metadata>

View File

@ -0,0 +1,2 @@
> cleanExampleCache
> update

View File

@ -21,6 +21,7 @@ lazy val common = project.
case repo: PatternsBasedRepository => repo.patterns.isMavenCompatible
case _: RawRepository => false // TODO - look deeper
case _: MavenRepository => true
case _: MavenCache => true
case _ => false // TODO - Handle chain repository?
}
case _ => true

View File

@ -0,0 +1,35 @@
# Validate that a bad dependency fails the compile
$ copy-file changes/BadCommon.scala common/src/main/scala/Common.scala
> common/publish
# Force dep resolution to be successful, then compilation to fail
> dependent/update
-> dependent/compile
# Push new good change to a DIFFERENT repository.
$ copy-file changes/GoodCommon.scala common/src/main/scala/Common.scala
# Sleep to ensure timestamp change
$ sleep 1000
> common/publishLocal
# This should compile now, because Ivy should look at each repository for the most up-to-date file.
> dependent/update
> dependent/compile
# Now let's try this on the opposite order: pubishLocal => publish
$ copy-file changes/BadCommon.scala common/src/main/scala/Common.scala
> common/publishLocal
# Force dep resolution to be successful, then compilation to fail
> dependent/update
-> dependent/compile
# Push new good change to a DIFFERENT repository.
$ copy-file changes/GoodCommon.scala common/src/main/scala/Common.scala
# Sleep to ensure timestamp change
$ sleep 1000
> common/publish
# This should compile now gain, because Ivy should look at each repository for the most up-to-date file.
> dependent/update
> dependent/compile

View File

@ -16,7 +16,8 @@ TaskKey[Unit]("check-update") <<= update map { report =>
TaskKey[Unit]("check-classpath") <<= dependencyClasspath in Compile map { cp =>
val jars = cp.files.map(_.getName).toSet
val expected = Set("org.sat4j.pb-2.3.1.jar", "org.sat4j.core-2.3.1.jar")
// Note: pb depends on tests artifact in core for no good reason. Previously this was not correctly added to the classpath.
val expected = Set("org.sat4j.pb-2.3.1.jar", "org.sat4j.core-2.3.1.jar", "org.sat4j.core-2.3.1-tests.jar")
if(jars != expected)
error("Expected jars " + expected + ", got: " + jars)
}