Merge pull request #1454 from sbt/wip/resolve-consolidation

Consolidated resolution
This commit is contained in:
eugene yokota 2014-07-24 02:13:01 -04:00
commit 26f6e68b69
6 changed files with 219 additions and 9 deletions

View File

@ -4,6 +4,7 @@
package sbt
import Resolver.PluginPattern
import ivyint.{ ConsolidatedResolveEngine, ConsolidatedResolveCache }
import java.io.File
import java.net.URI
@ -14,12 +15,14 @@ import CS.singleton
import org.apache.ivy.Ivy
import org.apache.ivy.core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.core.cache.{ 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 }
import org.apache.ivy.core.resolve.{ IvyNode, ResolveData, ResolvedModuleRevision, ResolveEngine }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.sort.SortEngine
import org.apache.ivy.plugins.latest.LatestRevisionStrategy
import org.apache.ivy.plugins.matcher.PatternMatcher
import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser
@ -28,6 +31,7 @@ import org.apache.ivy.util.{ Message, MessageLogger }
import org.apache.ivy.util.extendable.ExtendableItem
import scala.xml.{ NodeSeq, Text }
import scala.collection.mutable
final class IvySbt(val configuration: IvyConfiguration) {
import configuration.baseDirectory
@ -76,7 +80,23 @@ final class IvySbt(val configuration: IvyConfiguration) {
}
private lazy val ivy: Ivy =
{
val i = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine }
val i = new Ivy() {
private val loggerEngine = new SbtMessageLoggerEngine
override def getLoggerEngine = loggerEngine
override def bind(): Unit = {
val prOpt = Option(getSettings.getResolver(ProjectResolver.InterProject)) map { case pr: ProjectResolver => pr }
// We inject the deps we need before we can hook our resolve engine.
setSortEngine(new SortEngine(getSettings))
setEventManager(new EventManager())
if (configuration.updateOptions.consolidatedResolution) {
setResolveEngine(new ResolveEngine(getSettings, getEventManager, getSortEngine) with ConsolidatedResolveEngine {
val consolidatedResolveCache = IvySbt.consolidatedResolveCache
val projectResolver = prOpt
})
} else setResolveEngine(new ResolveEngine(getSettings, getEventManager, getSortEngine))
super.bind()
}
}
i.setSettings(settings)
i.bind()
i.getLoggerEngine.pushLogger(new IvyLoggerInterface(configuration.log))
@ -103,6 +123,18 @@ final class IvySbt(val configuration: IvyConfiguration) {
}
}
/**
* Cleans consolidated resolution cache.
* @param md - module descriptor of the original Ivy graph.
*/
private[sbt] def cleanConsolidatedResolutionCache(md: ModuleDescriptor, log: Logger): Unit =
withIvy(log) { i =>
val prOpt = Option(i.getSettings.getResolver(ProjectResolver.InterProject)) map { case pr: ProjectResolver => pr }
if (configuration.updateOptions.consolidatedResolution) {
IvySbt.consolidatedResolveCache.clean(md, prOpt)
}
}
final class Module(rawModuleSettings: ModuleSettings) {
val moduleSettings: ModuleSettings = IvySbt.substituteCross(rawModuleSettings)
def owner = IvySbt.this
@ -204,6 +236,7 @@ private object IvySbt {
val DefaultIvyFilename = "ivy.xml"
val DefaultMavenFilename = "pom.xml"
val DefaultChecksums = Seq("sha1", "md5")
private[sbt] val consolidatedResolveCache: ConsolidatedResolveCache = new ConsolidatedResolveCache()
def defaultIvyFile(project: File) = new File(project, DefaultIvyFilename)
def defaultIvyConfiguration(project: File) = new File(project, DefaultIvyConfigFilename)

View File

@ -59,6 +59,15 @@ object IvyActions {
iv.getSettings.getRepositoryCacheManagers.foreach(_.clean())
}
/**
* Cleans the consolidated resolution cache, if any.
* This is called by clean.
*/
private[sbt] def cleanConsolidatedResolutionCache(module: IvySbt#Module, log: Logger): Unit =
module.withModule(log) { (ivy, md, default) =>
module.owner.cleanConsolidatedResolutionCache(md, log)
}
/** Creates a Maven pom from the given Ivy configuration*/
def makePom(module: IvySbt#Module, configuration: MakePomConfiguration, log: Logger) {
import configuration.{ allRepositories, moduleInfo, configurations, extra, file, filterRepositories, process, includeTypes }

View File

@ -16,27 +16,42 @@ sealed trait IvyConfiguration {
def baseDirectory: File
def log: Logger
def withBase(newBaseDirectory: File): This
def updateOptions: UpdateOptions
}
final class InlineIvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val otherResolvers: Seq[Resolver],
val moduleConfigurations: Seq[ModuleConfiguration], val localOnly: Boolean, val lock: Option[xsbti.GlobalLock],
val checksums: Seq[String], val resolutionCacheDir: Option[File], val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts the resolution cache location.", "0.13.0")
val checksums: Seq[String], val resolutionCacheDir: Option[File], val updateOptions: UpdateOptions,
val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts resolutionCacheDir and updateOptions.", "0.13.0")
def this(paths: IvyPaths, resolvers: Seq[Resolver], otherResolvers: Seq[Resolver],
moduleConfigurations: Seq[ModuleConfiguration], localOnly: Boolean, lock: Option[xsbti.GlobalLock],
checksums: Seq[String], log: Logger) =
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, None, log)
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, None, UpdateOptions(), log)
@deprecated("Use the variant that accepts updateOptions.", "0.13.6")
def this(paths: IvyPaths, resolvers: Seq[Resolver], otherResolvers: Seq[Resolver],
moduleConfigurations: Seq[ModuleConfiguration], localOnly: Boolean, lock: Option[xsbti.GlobalLock],
checksums: Seq[String], resolutionCacheDir: Option[File], log: Logger) =
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, UpdateOptions(), log)
type This = InlineIvyConfiguration
def baseDirectory = paths.baseDirectory
def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, log)
def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, log)
def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums,
resolutionCacheDir, updateOptions, log)
def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums,
resolutionCacheDir, updateOptions, log)
}
final class ExternalIvyConfiguration(val baseDirectory: File, val uri: URI, val lock: Option[xsbti.GlobalLock], val extraResolvers: Seq[Resolver], val log: Logger) extends IvyConfiguration {
final class ExternalIvyConfiguration(val baseDirectory: File, val uri: URI, val lock: Option[xsbti.GlobalLock],
val extraResolvers: Seq[Resolver], val updateOptions: UpdateOptions, val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts updateOptions.", "0.13.6")
def this(baseDirectory: File, uri: URI, lock: Option[xsbti.GlobalLock], extraResolvers: Seq[Resolver], log: Logger) =
this(baseDirectory, uri, lock, extraResolvers, UpdateOptions(), log)
type This = ExternalIvyConfiguration
def withBase(newBase: File) = new ExternalIvyConfiguration(newBase, uri, lock, extraResolvers, log)
}
object ExternalIvyConfiguration {
def apply(baseDirectory: File, file: File, lock: Option[xsbti.GlobalLock], log: Logger) = new ExternalIvyConfiguration(baseDirectory, file.toURI, lock, Nil, log)
def apply(baseDirectory: File, file: File, lock: Option[xsbti.GlobalLock], log: Logger) = new ExternalIvyConfiguration(baseDirectory, file.toURI, lock, Nil, UpdateOptions(), log)
}
object IvyConfiguration {

View File

@ -34,6 +34,8 @@ class ProjectResolver(name: String, map: Map[ModuleRevisionId, ModuleDescriptor]
map get revisionId map constructResult
}
private[sbt] def getModuleDescriptor(revisionId: ModuleRevisionId): Option[ModuleDescriptor] = map.get(revisionId)
def report(revisionId: ModuleRevisionId): MetadataArtifactDownloadReport =
{
val artifact = DefaultArtifact.newIvyArtifact(revisionId, new Date)
@ -87,3 +89,7 @@ class ProjectResolver(name: String, map: Map[ModuleRevisionId, ModuleDescriptor]
def setSettings(settings: ResolverSettings) { this.settings = Some(settings) }
def getRepositoryCacheManager = settings match { case Some(s) => s.getDefaultRepositoryCacheManager; case None => sys.error("No settings defined for ProjectResolver") }
}
object ProjectResolver {
private[sbt] val InterProject = "inter-project"
}

View File

@ -0,0 +1,27 @@
package sbt
import java.io.File
/**
* Represents configurable options for update task.
* While UpdateConfiguration is passed into update at runtime,
* UpdateOption is intended to be used while setting up the Ivy object.
*
* See also UpdateConfiguration in IvyActions.scala.
*/
final class UpdateOptions(
/** If set to true, use consolidated resolution. */
val consolidatedResolution: Boolean) {
def withConsolidatedResolution(consolidatedResolution: Boolean): UpdateOptions =
copy(consolidatedResolution = consolidatedResolution)
private[sbt] def copy(
consolidatedResolution: Boolean = this.consolidatedResolution): UpdateOptions =
new UpdateOptions(consolidatedResolution)
}
object UpdateOptions {
def apply(): UpdateOptions =
new UpdateOptions(false)
}

View File

@ -0,0 +1,120 @@
package sbt
package ivyint
import java.io.File
import collection.concurrent
import org.apache.ivy.core
import core.resolve._
import core.module.id.ModuleRevisionId
import core.report.ResolveReport
import core.module.descriptor.{ DefaultModuleDescriptor, ModuleDescriptor, DependencyDescriptor }
import core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.util.Message
private[sbt] object ConsolidatedResolveCache {
def createID(organization: String, name: String, revision: String) =
ModuleRevisionId.newInstance(organization, name, revision)
def sbtOrgTemp = "org.scala-sbt.temp"
}
private[sbt] class ConsolidatedResolveCache() {
import ConsolidatedResolveCache._
val resolveReportCache: concurrent.Map[ModuleRevisionId, ResolveReport] = concurrent.TrieMap()
val resolvePropertiesCache: concurrent.Map[ModuleRevisionId, String] = concurrent.TrieMap()
val directDependencyCache: concurrent.Map[ModuleDescriptor, Vector[DependencyDescriptor]] = concurrent.TrieMap()
def clean(md0: ModuleDescriptor, prOpt: Option[ProjectResolver]): Unit = {
val mrid0 = md0.getModuleRevisionId
val md1 = if (mrid0.getOrganisation == sbtOrgTemp) md0
else buildConsolidatedModuleDescriptor(md0, prOpt)
val mrid1 = md1.getModuleRevisionId
resolveReportCache.remove(mrid1)
resolvePropertiesCache.remove(mrid1)
}
def directDependencies(md0: ModuleDescriptor): Vector[DependencyDescriptor] =
directDependencyCache.getOrElseUpdate(md0, md0.getDependencies.toVector)
def buildConsolidatedModuleDescriptor(md0: ModuleDescriptor, prOpt: Option[ProjectResolver]): DefaultModuleDescriptor = {
def expandInternalDeps(dep: DependencyDescriptor): Vector[DependencyDescriptor] =
prOpt map {
_.getModuleDescriptor(dep.getDependencyRevisionId) match {
case Some(internal) => directDependencies(internal) flatMap expandInternalDeps
case _ => Vector(dep)
}
} getOrElse Vector(dep)
val expanded = directDependencies(md0) flatMap expandInternalDeps
val depStrings = expanded map { dep =>
val mrid = dep.getDependencyRevisionId
val confMap = (dep.getModuleConfigurations map { conf =>
conf + "->(" + dep.getDependencyConfigurations(conf).mkString(",") + ")"
})
mrid.toString + ";" + confMap.mkString(";")
}
val depsString = depStrings.distinct.sorted.mkString("\n")
val sha1 = Hash.toHex(Hash(depsString))
// println("sha1: " + sha1)
val md1 = new DefaultModuleDescriptor(createID(sbtOrgTemp, "temp-resolve-" + sha1, "1.0"), "release", null, false)
md1
}
}
private[sbt] trait ConsolidatedResolveEngine extends ResolveEngine {
import ConsolidatedResolveCache._
private[sbt] def consolidatedResolveCache: ConsolidatedResolveCache
private[sbt] def projectResolver: Option[ProjectResolver]
/**
* Resolve dependencies of a module described by a module descriptor.
*/
override def resolve(md0: ModuleDescriptor, options0: ResolveOptions): ResolveReport = {
val cache = consolidatedResolveCache
val cacheManager = getSettings.getResolutionCacheManager
val md1 = cache.buildConsolidatedModuleDescriptor(md0, projectResolver)
val md1mrid = md1.getModuleRevisionId
def doWork: (ResolveReport, String) = {
if (options0.getLog != LogOptions.LOG_QUIET) {
Message.info("Consolidating managed dependencies to " + md1mrid.toString + " ...")
}
md1.setLastModified(System.currentTimeMillis)
for {
x <- md0.getConfigurations
} yield md1.addConfiguration(x)
for {
x <- md0.getDependencies
} yield md1.addDependency(x)
val options1 = new ResolveOptions(options0)
options1.setOutputReport(false)
val report0 = super.resolve(md1, options1)
val ivyPropertiesInCache1 = cacheManager.getResolvedIvyPropertiesInCache(md1.getResolvedModuleRevisionId)
val prop0 =
if (ivyPropertiesInCache1.exists) IO.read(ivyPropertiesInCache1)
else ""
if (options0.isOutputReport) {
this.outputReport(report0, cacheManager, options0)
}
cache.resolveReportCache(md1mrid) = report0
cache.resolvePropertiesCache(md1mrid) = prop0
(report0, prop0)
}
val (report0, prop0) = (cache.resolveReportCache.get(md1mrid), cache.resolvePropertiesCache.get(md1mrid)) match {
case (Some(report), Some(prop)) =>
if (options0.getLog != LogOptions.LOG_QUIET) {
Message.info("Found consolidated dependency " + md1mrid.toString + " ...")
}
(report, prop)
case _ => doWork
}
cacheManager.saveResolvedModuleDescriptor(md0)
if (prop0 != "") {
val ivyPropertiesInCache0 = cacheManager.getResolvedIvyPropertiesInCache(md0.getResolvedModuleRevisionId)
IO.write(ivyPropertiesInCache0, prop0)
}
report0
}
}