Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

264 lines (242 sloc) 12.935 kb
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
import java.io.File
import scala.xml.{Node => XNode, NodeSeq}
import org.apache.ivy.{core, plugins, Ivy}
import core.{IvyPatternHelper, LogOptions}
import core.deliver.DeliverOptions
import core.install.InstallOptions
import core.module.descriptor.{Artifact => IArtifact, MDArtifact, ModuleDescriptor, DefaultModuleDescriptor}
import core.report.ResolveReport
import core.resolve.ResolveOptions
import plugins.resolver.{BasicResolver, DependencyResolver}
final class DeliverConfiguration(val deliverIvyPattern: String, val status: String, val configurations: Option[Seq[Configuration]], val logging: UpdateLogging.Value)
final class PublishConfiguration(val ivyFile: Option[File], val resolverName: String, val artifacts: Map[Artifact, File], val checksums: Seq[String], val logging: UpdateLogging.Value)
final class UpdateConfiguration(val retrieve: Option[RetrieveConfiguration], val missingOk: Boolean, val logging: UpdateLogging.Value)
final class RetrieveConfiguration(val retrieveDirectory: File, val outputPattern: String)
final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, configurations: Option[Seq[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean, includeTypes: Set[String] = Set(Artifact.DefaultType))
// exclude is a map on a restricted ModuleID
final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala])
final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String])
/** Configures logging during an 'update'. `level` determines the amount of other information logged.
* `Full` is the default and logs the most.
* `DownloadOnly` only logs what is downloaded.
* `Quiet` only displays errors.*/
object UpdateLogging extends Enumeration
{
val Full, DownloadOnly, Quiet = Value
}
object IvyActions
{
/** Installs the dependencies of the given 'module' from the resolver named 'from' to the resolver named 'to'.*/
def install(module: IvySbt#Module, from: String, to: String, log: Logger)
{
module.withModule(log) { (ivy, md, default) =>
for(dependency <- md.getDependencies)
{
log.info("Installing " + dependency)
val options = new InstallOptions
options.setValidate(module.moduleSettings.validate)
options.setTransitive(dependency.isTransitive)
ivy.install(dependency.getDependencyRevisionId, from, to, options)
}
}
}
/** Clears the Ivy cache, as configured by 'config'. */
def cleanCache(ivy: IvySbt, log: Logger) = ivy.withIvy(log) { iv =>
iv.getSettings.getResolutionCacheManager.clean()
iv.getSettings.getRepositoryCacheManagers.foreach(_.clean())
}
/** 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}
module.withModule(log) { (ivy, md, default) =>
(new MakePom(log)).write(ivy, md, moduleInfo, configurations, includeTypes, extra, process, filterRepositories, allRepositories, file)
log.info("Wrote " + file.getAbsolutePath)
}
}
def deliver(module: IvySbt#Module, configuration: DeliverConfiguration, log: Logger): File =
{
import configuration._
module.withModule(log) { case (ivy, md, default) =>
val revID = md.getModuleRevisionId
val options = DeliverOptions.newInstance(ivy.getSettings).setStatus(status)
options.setConfs(IvySbt.getConfigurations(md, configurations))
ivy.deliver(revID, revID.getRevision, deliverIvyPattern, options)
deliveredFile(ivy, deliverIvyPattern, md)
}
}
def deliveredFile(ivy: Ivy, pattern: String, md: ModuleDescriptor): File =
ivy.getSettings.resolveFile(IvyPatternHelper.substitute(pattern, md.getResolvedModuleRevisionId))
def publish(module: IvySbt#Module, configuration: PublishConfiguration, log: Logger)
{
import configuration._
module.withModule(log) { case (ivy, md, default) =>
val resolver = ivy.getSettings.getResolver(resolverName)
if(resolver eq null) error("Undefined resolver '" + resolverName + "'")
val ivyArtifact = ivyFile map { file => (MDArtifact.newIvyArtifact(md), file) }
val cross = crossVersionMap(module.moduleSettings)
val as = mapArtifacts(md, cross, artifacts) ++ ivyArtifact.toList
withChecksums(resolver, checksums) { publish(md, as, resolver, overwrite = true) }
}
}
private[this] def withChecksums[T](resolver: DependencyResolver, checksums: Seq[String])(act: => T): T =
resolver match { case br: BasicResolver => withChecksums(br, checksums)(act); case _ => act }
private[this] def withChecksums[T](resolver: BasicResolver, checksums: Seq[String])(act: => T): T =
{
val previous = resolver.getChecksumAlgorithms
resolver.setChecksums(checksums mkString ",")
try { act }
finally { resolver.setChecksums(previous mkString ",") }
}
private def crossVersionMap(moduleSettings: ModuleSettings): Option[String => String] =
moduleSettings match {
case i: InlineConfiguration => CrossVersion(i.module, i.ivyScala)
case e: EmptyConfiguration => CrossVersion(e.module, e.ivyScala)
case _ => None
}
def mapArtifacts(module: ModuleDescriptor, cross: Option[String => String], artifacts: Map[Artifact, File]): Seq[(IArtifact, File)] =
{
val rawa = artifacts.keys.toSeq
val seqa = CrossVersion.substituteCross(rawa, cross)
val zipped = rawa zip IvySbt.mapArtifacts(module, seqa)
zipped map { case (a, ivyA) => (ivyA, artifacts(a)) }
}
/** Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration.
* 'updateConfig' configures the actual resolution and retrieval process. */
def update(module: IvySbt#Module, configuration: UpdateConfiguration, log: Logger): UpdateReport =
module.withModule(log) { case (ivy, md, default) =>
val (report, err) = resolve(configuration.logging)(ivy, md, default)
err match
{
case Some(x) if !configuration.missingOk =>
processUnresolved(x, log)
throw x
case _ =>
val cachedDescriptor = ivy.getSettings.getResolutionCacheManager.getResolvedIvyFileInCache(md.getModuleRevisionId)
val uReport = IvyRetrieve.updateReport(report, cachedDescriptor)
configuration.retrieve match
{
case Some(rConf) => retrieve(ivy, uReport, rConf)
case None => uReport
}
}
}
def processUnresolved(err: ResolveException, log: Logger)
{
val withExtra = err.failed.filter(!_.extraAttributes.isEmpty)
if(!withExtra.isEmpty)
{
log.warn("\n\tNote: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes.")
withExtra foreach { id => log.warn("\t\t" + id) }
log.warn("")
}
}
def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)(report: UpdateReport): Map[T, Set[String]] =
report.configurations.flatMap { confReport =>
val evicted = confReport.evicted.filter(moduleFilter)
val evictedSet = evicted.map( m => (m.organization, m.name) ).toSet
val conflicted = confReport.allModules.filter( mod => evictedSet( (mod.organization, mod.name) ) )
grouped(grouping)(conflicted ++ evicted)
} toMap;
def grouped[T](grouping: ModuleID => T)(mods: Seq[ModuleID]): Map[T, Set[String]] =
mods groupBy(grouping) mapValues(_.map(_.revision).toSet)
def transitiveScratch(ivySbt: IvySbt, label: String, config: GetClassifiersConfiguration, log: Logger): UpdateReport =
{
import config.{configuration => c, ivyScala, module => mod}
import mod.{id, modules => deps}
val base = restrictedCopy(id, true).copy(name = id.name + "$" + label)
val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala))
val report = update(module, c, log)
val newConfig = config.copy(module = mod.copy(modules = report.allModules))
updateClassifiers(ivySbt, newConfig, log)
}
def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration, log: Logger): UpdateReport =
{
import config.{configuration => c, module => mod, _}
import mod.{configurations => confs, _}
assert(!classifiers.isEmpty, "classifiers cannot be empty")
val baseModules = modules map { m => restrictedCopy(m, true) }
val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude)
val base = restrictedCopy(id, true).copy(name = id.name + classifiers.mkString("$","_",""))
val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs))
val upConf = new UpdateConfiguration(c.retrieve, true, c.logging)
update(module, upConf, log)
}
def classifiedArtifacts(classifiers: Seq[String], exclude: Map[ModuleID, Set[String]])(m: ModuleID): Option[ModuleID] =
{
val excluded = exclude getOrElse(restrictedCopy(m, false), Set.empty)
val included = classifiers filterNot excluded
if(included.isEmpty) None else Some(m.copy(isTransitive = false, explicitArtifacts = classifiedArtifacts(m.name, included) ))
}
def addExcluded(report: UpdateReport, classifiers: Seq[String], exclude: Map[ModuleID, Set[String]]): UpdateReport =
report.addMissing { id => classifiedArtifacts(id.name, classifiers filter getExcluded(id, exclude)) }
def classifiedArtifacts(name: String, classifiers: Seq[String]): Seq[Artifact] =
classifiers map { c => Artifact.classified(name, c) }
private[this] def getExcluded(id: ModuleID, exclude: Map[ModuleID, Set[String]]): Set[String] =
exclude.getOrElse(restrictedCopy(id, false), Set.empty[String])
def extractExcludes(report: UpdateReport): Map[ModuleID, Set[String]] =
report.allMissing flatMap { case (_, mod, art) => art.classifier.map { c => (restrictedCopy(mod, false), c) } } groupBy(_._1) map { case (mod, pairs) => (mod, pairs.map(_._2).toSet) }
private[this] def restrictedCopy(m: ModuleID, confs: Boolean) =
ModuleID(m.organization, m.name, m.revision, crossVersion = m.crossVersion, extraAttributes = m.extraAttributes, configurations = if(confs) m.configurations else None)
private[this] def resolve(logging: UpdateLogging.Value)(ivy: Ivy, module: DefaultModuleDescriptor, defaultConf: String): (ResolveReport, Option[ResolveException]) =
{
val resolveOptions = new ResolveOptions
resolveOptions.setLog(ivyLogLevel(logging))
val resolveReport = ivy.resolve(module, resolveOptions)
val err =
if(resolveReport.hasError)
{
val messages = resolveReport.getAllProblemMessages.toArray.map(_.toString).distinct
val failed = resolveReport.getUnresolvedDependencies.map(node => IvyRetrieve.toModuleID(node.getId))
Some(new ResolveException(messages, failed))
}
else None
(resolveReport, err)
}
private def retrieve(ivy: Ivy, report: UpdateReport, config: RetrieveConfiguration): UpdateReport =
retrieve(ivy, report, config.retrieveDirectory, config.outputPattern)
private def retrieve(ivy: Ivy, report: UpdateReport, base: File, pattern: String): UpdateReport =
{
val toCopy = new collection.mutable.HashSet[(File,File)]
val retReport = report retrieve { (conf, mid, art, cached) =>
val to = retrieveTarget(conf, mid, art, base, pattern)
toCopy += ((cached, to))
to
}
IO.copy( toCopy )
retReport
}
private def retrieveTarget(conf: String, mid: ModuleID, art: Artifact, base: File, pattern: String): File =
new File(base, substitute(conf, mid, art, pattern))
private def substitute(conf: String, mid: ModuleID, art: Artifact, pattern: String): String =
{
val mextra = IvySbt.javaMap(mid.extraAttributes, true)
val aextra = IvySbt.extra(art, true)
IvyPatternHelper.substitute(pattern, mid.organization, mid.name, mid.revision, art.name, art.`type`, art.extension, conf, mextra, aextra)
}
import UpdateLogging.{Quiet, Full, DownloadOnly}
import LogOptions.{LOG_QUIET, LOG_DEFAULT, LOG_DOWNLOAD_ONLY}
private def ivyLogLevel(level: UpdateLogging.Value) =
level match
{
case Quiet => LOG_QUIET
case DownloadOnly => LOG_DOWNLOAD_ONLY
case Full => LOG_DEFAULT
}
def publish(module: ModuleDescriptor, artifacts: Seq[(IArtifact, File)], resolver: DependencyResolver, overwrite: Boolean): Unit =
try {
resolver.beginPublishTransaction(module.getModuleRevisionId(), overwrite);
for( (artifact, file) <- artifacts) if(file.exists)
resolver.publish(artifact, file, overwrite)
resolver.commitPublishTransaction()
} catch {
case e =>
try { resolver.abortPublishTransaction() }
finally { throw e }
}
}
final class ResolveException(val messages: Seq[String], val failed: Seq[ModuleID]) extends RuntimeException(messages.mkString("\n"))
Jump to Line
Something went wrong with that request. Please try again.