Merge pull request #1520 from sbt/fix/1514

Fixes #1514, #321. Fixes -SNAPSHOT issue by re-implemeting ChainResolver
This commit is contained in:
Josh Suereth 2014-08-12 10:04:10 -04:00
commit e49e6b46f9
2 changed files with 208 additions and 13 deletions

View File

@ -8,12 +8,13 @@ import ivyint.{ ConsolidatedResolveEngine, ConsolidatedResolveCache }
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.text.ParseException
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.{ Collection, Collections => CS } import java.util.{ Collection, Collections => CS, Date }
import CS.singleton import CS.singleton
import org.apache.ivy.Ivy import org.apache.ivy.Ivy
import org.apache.ivy.core.{ IvyPatternHelper, LogOptions } import org.apache.ivy.core.{ IvyPatternHelper, LogOptions, IvyContext }
import org.apache.ivy.core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager, ModuleDescriptorWriter } import org.apache.ivy.core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager, ModuleDescriptorWriter }
import org.apache.ivy.core.event.EventManager 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.{ Artifact => IArtifact, DefaultArtifact, DefaultDependencyArtifactDescriptor, MDArtifact }
@ -23,11 +24,15 @@ 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.{ IvyNode, ResolveData, ResolvedModuleRevision, ResolveEngine }
import org.apache.ivy.core.settings.IvySettings import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.sort.SortEngine import org.apache.ivy.core.sort.SortEngine
import org.apache.ivy.plugins.latest.LatestRevisionStrategy import org.apache.ivy.plugins.latest.{ LatestStrategy, LatestRevisionStrategy, ArtifactInfo }
import org.apache.ivy.plugins.matcher.PatternMatcher 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 } import org.apache.ivy.plugins.resolver.{ ChainResolver, DependencyResolver, BasicResolver }
import org.apache.ivy.util.{ Message, MessageLogger } import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource }
import org.apache.ivy.plugins.version.ExactVersionMatcher
import org.apache.ivy.plugins.repository.file.{ FileResource, FileRepository => IFileRepository }
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.util.{ Message, MessageLogger, StringUtils => IvyStringUtils }
import org.apache.ivy.util.extendable.ExtendableItem import org.apache.ivy.util.extendable.ExtendableItem
import scala.xml.{ NodeSeq, Text } import scala.xml.{ NodeSeq, Text }
@ -73,7 +78,7 @@ final class IvySbt(val configuration: IvyConfiguration) {
is.setVariable("ivy.checksums", i.checksums mkString ",") is.setVariable("ivy.checksums", i.checksums mkString ",")
i.paths.ivyHome foreach is.setDefaultIvyUserDir i.paths.ivyHome foreach is.setDefaultIvyUserDir
IvySbt.configureCache(is, i.localOnly, i.resolutionCacheDir) IvySbt.configureCache(is, i.localOnly, i.resolutionCacheDir)
IvySbt.setResolvers(is, i.resolvers, i.otherResolvers, i.localOnly, configuration.log) IvySbt.setResolvers(is, i.resolvers, i.otherResolvers, i.localOnly, configuration.updateOptions, configuration.log)
IvySbt.setModuleConfigurations(is, i.moduleConfigurations, configuration.log) IvySbt.setModuleConfigurations(is, i.moduleConfigurations, configuration.log)
} }
is is
@ -253,10 +258,10 @@ private object IvySbt {
* Sets the resolvers for 'settings' to 'resolvers'. This is done by creating a new chain and making it the default. * Sets the resolvers for 'settings' to 'resolvers'. This is done by creating a new chain and making it the default.
* 'other' is for resolvers that should be in a different chain. These are typically used for publishing or other actions. * 'other' is for resolvers that should be in a different chain. These are typically used for publishing or other actions.
*/ */
private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, log: Logger) { private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, updateOptions: UpdateOptions, log: Logger) {
def makeChain(label: String, name: String, rs: Seq[Resolver]) = { def makeChain(label: String, name: String, rs: Seq[Resolver]) = {
log.debug(label + " repositories:") log.debug(label + " repositories:")
val chain = resolverChain(name, rs, localOnly, settings, log) val chain = resolverChain(name, rs, localOnly, settings, updateOptions, log)
settings.addResolver(chain) settings.addResolver(chain)
chain chain
} }
@ -264,7 +269,11 @@ private object IvySbt {
val mainChain = makeChain("Default", "sbt-chain", resolvers) val mainChain = makeChain("Default", "sbt-chain", resolvers)
settings.setDefaultResolver(mainChain.getName) settings.setDefaultResolver(mainChain.getName)
} }
private[sbt] def isChanging(mrid: ModuleRevisionId): Boolean =
mrid.getRevision endsWith "-SNAPSHOT"
def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, log: Logger): DependencyResolver = def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, log: Logger): DependencyResolver =
resolverChain(name, resolvers, localOnly, settings, UpdateOptions(), log)
def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, updateOptions: UpdateOptions, log: Logger): DependencyResolver =
{ {
val newDefault = new ChainResolver { val newDefault = new ChainResolver {
// Technically, this should be applied to module configurations. // Technically, this should be applied to module configurations.
@ -285,8 +294,187 @@ private object IvySbt {
{ {
if (data.getOptions.getLog != LogOptions.LOG_QUIET) if (data.getOptions.getLog != LogOptions.LOG_QUIET)
Message.info("Resolving " + dd.getDependencyRevisionId + " ...") Message.info("Resolving " + dd.getDependencyRevisionId + " ...")
val gd = super.getDependency(dd, data) val gd = doGetDependency(dd, data)
resetArtifactResolver(gd) val mod = resetArtifactResolver(gd)
mod
}
// Modified implementation of ChainResolver#getDependency.
// When the dependency is changing, it will check all resolvers on the chain
// regardless of what the "latest strategy" is set, and look for the published date
// or the module descriptor to sort them.
// This implementation also skips resolution if "return first" is set to true,
// and if a previously resolved or cached revision has been found.
def doGetDependency(dd: DependencyDescriptor, data0: ResolveData): ResolvedModuleRevision =
{
val useLatest = (dd.isChanging || (isChanging(dd.getDependencyRevisionId))) && updateOptions.latestSnapshots
if (useLatest) {
Message.verbose(s"${getName} is changing. Checking all resolvers on the chain")
}
val data = new ResolveData(data0, doValidate(data0))
val resolved = Option(data.getCurrentResolvedModuleRevision)
val resolvedOrCached =
resolved orElse {
Message.verbose(getName + ": Checking cache for: " + dd)
Option(findModuleInCache(dd, data, true)) map { mr =>
Message.verbose(getName + ": module revision found in cache: " + mr.getId)
forcedRevision(mr)
}
}
var temp: Option[ResolvedModuleRevision] =
if (useLatest) None
else resolvedOrCached
val resolvers = getResolvers.toArray.toVector collect { case x: DependencyResolver => x }
val results = resolvers map { x =>
// if the revision is cached and isReturnFirst is set, don't bother hitting any resolvers
if (isReturnFirst && temp.isDefined && !useLatest) Right(None)
else {
val resolver = x
val oldLatest: Option[LatestStrategy] = setLatestIfRequired(resolver, Option(getLatestStrategy))
try {
val previouslyResolved = temp
// if the module qualifies as changing, then resolve all resolvers
if (useLatest) data.setCurrentResolvedModuleRevision(None.orNull)
else data.setCurrentResolvedModuleRevision(temp.orNull)
temp = Option(resolver.getDependency(dd, data))
val retval = Right(
if (temp eq previouslyResolved) None
else if (useLatest) temp map { x =>
(reparseModuleDescriptor(dd, data, resolver, x), resolver)
}
else temp map { x => (forcedRevision(x), resolver) }
)
retval
} catch {
case ex: Exception =>
Message.verbose("problem occurred while resolving " + dd + " with " + resolver
+ ": " + IvyStringUtils.getStackTrace(ex))
Left(ex)
} finally {
oldLatest map { _ => doSetLatestStrategy(resolver, oldLatest) }
checkInterrupted
}
}
}
val errors = results collect { case Left(e) => e }
val foundRevisions: Vector[(ResolvedModuleRevision, DependencyResolver)] = results collect { case Right(Some(x)) => x }
val sorted =
if (useLatest) (foundRevisions.sortBy {
case (rmr, _) =>
rmr.getDescriptor.getPublicationDate.getTime
}).reverse.headOption map {
case (rmr, resolver) =>
// 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("\t" + resolver.getName
+ ": no ivy file nor artifact found for " + rmr)
case Some(artifactRef) =>
val systemMd = toSystem(rmr.getDescriptor)
getRepositoryCacheManager.cacheModuleDescriptor(resolver, artifactRef,
toSystem(dd), systemMd.getAllArtifacts().head, None.orNull, getCacheOptions(data))
}
rmr
}
else foundRevisions.reverse.headOption map { _._1 }
val mrOpt: Option[ResolvedModuleRevision] = sorted orElse resolvedOrCached
mrOpt match {
case None if errors.size == 1 =>
errors.head match {
case e: RuntimeException => throw e
case e: ParseException => throw e
case e: Throwable => throw new RuntimeException(e.toString, e)
}
case None if errors.size > 1 =>
val err = (errors.toList map { IvyStringUtils.getErrorMessage }).mkString("\n\t", "\n\t", "\n")
throw new RuntimeException(s"several problems occurred while resolving $dd:$err")
case _ =>
if (resolved == mrOpt) resolved.orNull
else (mrOpt map { resolvedRevision }).orNull
}
}
// 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 =
Option(resolver.findIvyFileRef(dd, data)) flatMap { ivyFile =>
ivyFile.getResource match {
case r: FileResource =>
try {
val parser = rmr.getDescriptor.getParser
val md = parser.parseDescriptor(settings, r.getFile.toURL, r, false)
Some(new ResolvedModuleRevision(resolver, resolver, md, rmr.getReport, true))
} catch {
case _: ParseException => None
}
case _ => None
}
} getOrElse rmr
/** Ported from BasicResolver#findFirstAirfactRef. */
private[this] def findFirstArtifactRef(md: ModuleDescriptor, dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver): Option[ResolvedResource] =
{
def artifactRef(artifact: IArtifact, date: Date): Option[ResolvedResource] =
resolver match {
case resolver: BasicResolver =>
IvyContext.getContext.set(resolver.getName + ".artifact", artifact)
try {
Option(resolver.doFindArtifactRef(artifact, date)) orElse {
Option(artifact.getUrl) map { url =>
Message.verbose("\tusing url for " + artifact + ": " + url)
val resource =
if ("file" == url.getProtocol) new FileResource(new IFileRepository(), new File(url.getPath()))
else new URLResource(url)
new ResolvedResource(resource, artifact.getModuleRevisionId.getRevision)
}
}
} finally {
IvyContext.getContext.set(resolver.getName + ".artifact", null)
}
case _ =>
None
}
val artifactRefs = md.getConfigurations.toIterator flatMap { conf =>
md.getArtifacts(conf.getName).toIterator flatMap { af =>
artifactRef(af, data.getDate).toIterator
}
}
if (artifactRefs.hasNext) Some(artifactRefs.next)
else None
}
/** Ported from ChainResolver#forcedRevision. */
private[this] def forcedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision =
new ResolvedModuleRevision(rmr.getResolver, rmr.getArtifactResolver, rmr.getDescriptor, rmr.getReport, true)
/** Ported from ChainResolver#resolvedRevision. */
private[this] def resolvedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision =
if (isDual) new ResolvedModuleRevision(rmr.getResolver, this, rmr.getDescriptor, rmr.getReport, rmr.isForce)
else rmr
/** Ported from ChainResolver#setLatestIfRequired. */
private[this] def setLatestIfRequired(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] =
latestStrategyName(resolver) match {
case Some(latestName) if latestName != "default" =>
val oldLatest = latestStrategy(resolver)
doSetLatestStrategy(resolver, latest)
oldLatest
case _ => None
}
/** Ported from ChainResolver#getLatestStrategyName. */
private[this] def latestStrategyName(resolver: DependencyResolver): Option[String] =
resolver match {
case r: HasLatestStrategy => Some(r.getLatest)
case _ => None
}
/** Ported from ChainResolver#getLatest. */
private[this] def latestStrategy(resolver: DependencyResolver): Option[LatestStrategy] =
resolver match {
case r: HasLatestStrategy => Some(r.getLatestStrategy)
case _ => None
}
/** Ported from ChainResolver#setLatest. */
private[this] def doSetLatestStrategy(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] =
resolver match {
case r: HasLatestStrategy =>
val oldLatest = latestStrategy(resolver)
r.setLatestStrategy(latest.orNull)
oldLatest
case _ => None
} }
} }
newDefault.setName(name) newDefault.setName(name)

View File

@ -9,19 +9,26 @@ import java.io.File
* *
* See also UpdateConfiguration in IvyActions.scala. * See also UpdateConfiguration in IvyActions.scala.
*/ */
final class UpdateOptions( final class UpdateOptions private[sbt] (
/** If set to true, check all resolvers for snapshots. */
val latestSnapshots: Boolean,
/** If set to true, use consolidated resolution. */ /** If set to true, use consolidated resolution. */
val consolidatedResolution: Boolean) { val consolidatedResolution: Boolean) {
def withLatestSnapshots(latestSnapshots: Boolean): UpdateOptions =
copy(latestSnapshots = latestSnapshots)
def withConsolidatedResolution(consolidatedResolution: Boolean): UpdateOptions = def withConsolidatedResolution(consolidatedResolution: Boolean): UpdateOptions =
copy(consolidatedResolution = consolidatedResolution) copy(consolidatedResolution = consolidatedResolution)
private[sbt] def copy( private[sbt] def copy(
latestSnapshots: Boolean = this.latestSnapshots,
consolidatedResolution: Boolean = this.consolidatedResolution): UpdateOptions = consolidatedResolution: Boolean = this.consolidatedResolution): UpdateOptions =
new UpdateOptions(consolidatedResolution) new UpdateOptions(latestSnapshots, consolidatedResolution)
} }
object UpdateOptions { object UpdateOptions {
def apply(): UpdateOptions = def apply(): UpdateOptions =
new UpdateOptions(false) new UpdateOptions(
latestSnapshots = true,
consolidatedResolution = false)
} }