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))
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])
object UpdateLogging extends Enumeration
{
val Full, DownloadOnly, Quiet = Value
}
object IvyActions
{
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)
}
}
}
def cleanCache(ivy: IvySbt, log: Logger) = ivy.withIvy(log) { iv =>
iv.getSettings.getResolutionCacheManager.clean()
iv.getSettings.getRepositoryCacheManagers.foreach(_.clean())
}
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)) }
}
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"))