diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index 0e29ba740..eed985a03 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -27,6 +27,8 @@ final class PublishConfiguration(val ivyFile: Option[File], val resolverName: St 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, configurations: Option[Iterable[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean) + // exclude is a map on a restricted ModuleID +final case class GetClassifiersConfiguration(id: ModuleID, modules: Seq[ModuleID], classifiers: Seq[String], exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala]) /** Configures logging during an 'update'. `level` determines the amount of other information logged. * `Full` is the default and logs the most. @@ -129,24 +131,40 @@ object IvyActions } } - def transitiveScratch(ivySbt: IvySbt, id: ModuleID, label: String, deps: Seq[ModuleID], classifiers: Seq[String], c: UpdateConfiguration, ivyScala: Option[IvyScala], log: Logger): UpdateReport = + def transitiveScratch(ivySbt: IvySbt, label: String, config: GetClassifiersConfiguration, log: Logger): UpdateReport = { + import config.{configuration => c, id, ivyScala, modules => deps} val base = restrictedCopy(id).copy(name = id.name + "$" + label) val module = new ivySbt.Module(InlineConfiguration(base, deps).copy(ivyScala = ivyScala)) val report = update(module, c, log) - transitive(ivySbt, id, report, classifiers, c, ivyScala, log) + val newConfig = config.copy(modules = report.allModules) + updateClassifiers(ivySbt, newConfig, log) } - def transitive(ivySbt: IvySbt, module: ModuleID, report: UpdateReport, classifiers: Seq[String], c: UpdateConfiguration, ivyScala: Option[IvyScala], log: Logger): UpdateReport = - updateClassifiers(ivySbt, module, report.allModules, classifiers, new UpdateConfiguration(c.retrieve, true, c.logging), ivyScala, log) - def updateClassifiers(ivySbt: IvySbt, id: ModuleID, modules: Seq[ModuleID], classifiers: Seq[String], configuration: UpdateConfiguration, ivyScala: Option[IvyScala], log: Logger): UpdateReport = + def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration, log: Logger): UpdateReport = { + import config.{configuration => c, _} assert(!classifiers.isEmpty, "classifiers cannot be empty") val baseModules = modules map restrictedCopy - val deps = baseModules.distinct map { m => m.copy(explicitArtifacts = classifiers map { c => Artifact.classified(m.name, c) }) } + val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude) val base = restrictedCopy(id).copy(name = id.name + classifiers.mkString("$","_","")) val module = new ivySbt.Module(InlineConfiguration(base, deps).copy(ivyScala = ivyScala)) - update(module, configuration, log) + 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(m, Set.empty) + val included = classifiers filterNot excluded + if(included.isEmpty) None else Some(m.copy(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 exclude.getOrElse(id, Set.empty[String])) } + def classifiedArtifacts(name: String, classifiers: Seq[String]): Seq[Artifact] = + classifiers map { c => Artifact.classified(name, c) } + + def extractExcludes(report: UpdateReport): Map[ModuleID, Set[String]] = + report.allMissing flatMap { case (_, mod, art) => art.classifier.map { c => (restrictedCopy(mod), c) } } groupBy(_._1) map { case (mod, pairs) => (mod, pairs.map(_._2).toSet) } + private[this] def restrictedCopy(m: ModuleID) = ModuleID(m.organization, m.name, m.revision, crossVersion = m.crossVersion) private[this] def resolve(logging: UpdateLogging.Value)(ivy: Ivy, module: DefaultModuleDescriptor, defaultConf: String): (ResolveReport, Option[ResolveException]) = { diff --git a/ivy/UpdateReport.scala b/ivy/UpdateReport.scala index 47a8dd5de..63e99f8ff 100644 --- a/ivy/UpdateReport.scala +++ b/ivy/UpdateReport.scala @@ -88,36 +88,40 @@ object UpdateReport /** Constructs a new report that only contains files matching the specified filter.*/ def filter(f: DependencyFilter): UpdateReport = - { - val newConfigurations = report.configurations.map { confReport => - import confReport._ - val newModules = - modules map { modReport => - import modReport._ - val newArtifacts = artifacts filter { case (art, file) => f(configuration, module, art) } - val newMissing = missingArtifacts filter { art => f(configuration, module, art) } - new ModuleReport(module, newArtifacts, newMissing) - } - new ConfigurationReport(configuration, newModules) + moduleReportMap { (configuration, modReport) => + import modReport._ + val newArtifacts = artifacts filter { case (art, file) => f(configuration, module, art) } + val newMissing = missingArtifacts filter { art => f(configuration, module, art) } + new ModuleReport(module, newArtifacts, newMissing) } - new UpdateReport(report.cachedDescriptor, newConfigurations) - } def substitute(f: (String, ModuleID, Seq[(Artifact, File)]) => Seq[(Artifact, File)]): UpdateReport = - { - val newConfigurations = report.configurations.map { confReport => - import confReport._ - val newModules = - modules map { modReport => - val newArtifacts = f(configuration, modReport.module, modReport.artifacts) - new ModuleReport(modReport.module, newArtifacts, Nil) - } - new ConfigurationReport(configuration, newModules) + moduleReportMap { (configuration, modReport) => + val newArtifacts = f(configuration, modReport.module, modReport.artifacts) + new ModuleReport(modReport.module, newArtifacts, Nil) } - new UpdateReport(report.cachedDescriptor, newConfigurations) - } def toSeq: Seq[(String, ModuleID, Artifact, File)] = for(confReport <- report.configurations; modReport <- confReport.modules; (artifact, file) <- modReport.artifacts) yield (confReport.configuration, modReport.module, artifact, file) + + def allMissing: Seq[(String, ModuleID, Artifact)] = + for(confReport <- report.configurations; modReport <- confReport.modules; artifact <- modReport.missingArtifacts) yield + (confReport.configuration, modReport.module, artifact) + + def addMissing(f: ModuleID => Seq[Artifact]): UpdateReport = + moduleReportMap { (configuration, modReport) => + import modReport._ + new ModuleReport(module, artifacts, (missingArtifacts ++ f(module)).distinct) + } + + def moduleReportMap(f: (String, ModuleReport) => ModuleReport): UpdateReport = + { + val newConfigurations = report.configurations.map { confReport => + import confReport._ + val newModules = modules map { modReport => f(configuration, modReport) } + new ConfigurationReport(configuration, newModules) + } + new UpdateReport(report.cachedDescriptor, newConfigurations) + } } } diff --git a/main/Defaults.scala b/main/Defaults.scala index 8163d2417..4dc2432ea 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -18,6 +18,7 @@ package sbt import descriptor.ModuleDescriptor, id.ModuleRevisionId import java.io.File import java.net.URL + import java.util.concurrent.Callable import sbinary.DefaultProtocol.StringFormat import Cache.seqFormat @@ -636,11 +637,16 @@ object Classpaths cachedUpdate(cacheDirectory / "update", Project.display(ref), module, config, Some(si), s.log) }, transitiveClassifiers in GlobalScope :== Seq(SourceClassifier, DocClassifier), - updateClassifiers <<= (ivySbt, projectID, update, transitiveClassifiers, updateConfiguration, ivyScala, streams) map { (is, pid, up, classifiers, c, ivyScala, s) => - IvyActions.transitive(is, pid, up, classifiers, c, ivyScala, s.log) + updateClassifiers <<= (ivySbt, projectID, update, transitiveClassifiers, updateConfiguration, ivyScala, baseDirectory in ThisBuild, appConfiguration, streams) map { (is, pid, up, classifiers, c, ivyScala, out, app, s) => + withExcludes(out, classifiers, lock(app)) { excludes => + IvyActions.updateClassifiers(is, GetClassifiersConfiguration(pid, up.allModules, classifiers, excludes, c, ivyScala), s.log) + } }, - updateSbtClassifiers <<= (ivySbt, projectID, transitiveClassifiers, updateConfiguration, sbtDependency, ivyScala, streams) map { (is, pid, classifiers, c, sbtDep, ivyScala, s) => - IvyActions.transitiveScratch(is, pid, "sbt", sbtDep :: Nil, classifiers, c, ivyScala, s.log) + updateSbtClassifiers <<= (ivySbt, projectID, transitiveClassifiers, updateConfiguration, sbtDependency, ivyScala, baseDirectory in ThisBuild, appConfiguration, streams) map { + (is, pid, classifiers, c, sbtDep, ivyScala, out, app, s) => + withExcludes(out, classifiers, lock(app)) { excludes => + IvyActions.transitiveScratch(is, "sbt", GetClassifiersConfiguration(pid, sbtDep :: Nil, classifiers, excludes, c, ivyScala), s.log) + } }, sbtResolver in GlobalScope :== typesafeResolver, sbtDependency in GlobalScope <<= appConfiguration { app => @@ -662,7 +668,20 @@ object Classpaths } import Cache._ - import CacheIvy.{classpathFormat, /*publishIC,*/ updateIC, updateReportF} + import CacheIvy.{classpathFormat, /*publishIC,*/ updateIC, updateReportF, excludeMap} + + def withExcludes(out: File, classifiers: Seq[String], lock: xsbti.GlobalLock)(f: Map[ModuleID, Set[String]] => UpdateReport): UpdateReport = + { + val exclName = "exclude_classifiers" + val file = out / exclName + lock(out / (exclName + ".lock"), new Callable[UpdateReport] { def call = { + val excludes = CacheIO.fromFile[Map[ModuleID, Set[String]]](excludeMap, Map.empty[ModuleID, Set[String]])(file) + val report = f(excludes) + val allExcludes = excludes ++ IvyActions.extractExcludes(report) + CacheIO.toFile(excludeMap)(allExcludes)(file) + IvyActions.addExcluded(report, classifiers, allExcludes) + }}) + } def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, scalaInstance: Option[ScalaInstance], log: Logger): UpdateReport = { diff --git a/main/actions/CacheIvy.scala b/main/actions/CacheIvy.scala index 06b9c85ca..f31d6e61d 100644 --- a/main/actions/CacheIvy.scala +++ b/main/actions/CacheIvy.scala @@ -52,11 +52,12 @@ object CacheIvy implicit def wrapHL[W, H, T <: HList](implicit f: W => H :+: T, cache: InputCache[H :+: T]): InputCache[W] = Cache.wrapIn(f, cache) - def updateIC: InputCache[IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil] = implicitly + lazy val excludeMap: Format[Map[ModuleID, Set[String]]] = implicitly + lazy val updateIC: InputCache[IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil] = implicitly /* def deliverIC: InputCache[IvyConfiguration :+: ModuleSettings :+: DeliverConfiguration :+: HNil] = implicitly def publishIC: InputCache[IvyConfiguration :+: ModuleSettings :+: PublishConfiguration :+: HNil] = implicitly*/ - def updateReportF: Format[UpdateReport] = + lazy val updateReportF: Format[UpdateReport] = { import DefaultProtocol.{BooleanFormat, FileFormat, StringFormat} updateReportFormat