From ac571371c6ef0b15969675b73ffd404119ab40ad Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Thu, 3 Dec 2015 15:53:29 +0100 Subject: [PATCH] Fixes sbt/sbt#2264. Use explicit artifacts if any, fallback to hardcoded Even though it's not really used, updateClassifiers constructs dependency graph based on the result from update. The direct cause of sbt/sbt#2264 came from the fact that the `allModules` returned from ConfigurationReport did not include dependency configurations. For example it returned "compile" instead of "compile->runtime". I've identified that in #2264 and was fixed by @Duhemm in sbt/sbt@f49fb33. Martin identified that the fix still does not address the fact that updateClassifier hardcodes the classifiers to be tried. This commit adds the fallback behavior so for Ivy-published modules it will use the explicit list of artifacts, and for others it will fallback to the hardcoded list of classifiers. --- .../librarymanagement/IvyActions.scala | 30 +++++++++++++++---- .../sbt/librarymanagement/UpdateReport.scala | 27 +++++++++++++++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index 817d41a69..d6ce9b20d 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -236,20 +236,24 @@ object IvyActions { throw w.resolveException } val newConfig = config.copy(module = mod.copy(modules = report.allModules)) - updateClassifiers(ivySbt, newConfig, uwconfig, logicalClock, depDir, log) + updateClassifiers(ivySbt, newConfig, uwconfig, logicalClock, depDir, Vector(), log) } @deprecated("This is no longer public.", "0.13.6") def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration, log: Logger): UpdateReport = - updateClassifiers(ivySbt, config, UnresolvedWarningConfiguration(), LogicalClock.unknown, None, log) + updateClassifiers(ivySbt, config, UnresolvedWarningConfiguration(), LogicalClock.unknown, None, Vector(), log) + // artifacts can be obtained from calling toSeq on UpdateReport private[sbt] def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration, - uwconfig: UnresolvedWarningConfiguration, logicalClock: LogicalClock, depDir: Option[File], log: Logger): UpdateReport = + uwconfig: UnresolvedWarningConfiguration, logicalClock: LogicalClock, depDir: Option[File], + artifacts: Vector[(String, ModuleID, Artifact, File)], + log: Logger): UpdateReport = { import config.{ configuration => c, module => mod, _ } import mod.{ configurations => confs, _ } assert(classifiers.nonEmpty, "classifiers cannot be empty") val baseModules = modules map { m => restrictedCopy(m, true) } - val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude) + // Adding list of explicit artifacts here. + val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude, artifacts) val base = restrictedCopy(id, true).copy(name = id.name + classifiers.mkString("$", "_", "")) val module = new ivySbt.Module(InlineConfigurationWithExcludes(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs)) val upConf = new UpdateConfiguration(c.retrieve, true, c.logging) @@ -259,7 +263,23 @@ object IvyActions { throw w.resolveException } } - def classifiedArtifacts(classifiers: Seq[String], exclude: Map[ModuleID, Set[String]])(m: ModuleID): Option[ModuleID] = + // This version adds explicit artifact + private[sbt] def classifiedArtifacts( + classifiers: Seq[String], + exclude: Map[ModuleID, Set[String]], + artifacts: Vector[(String, ModuleID, Artifact, File)] + )(m: ModuleID): Option[ModuleID] = { + def sameModule(m1: ModuleID, m2: ModuleID): Boolean = m1.organization == m2.organization && m1.name == m2.name && m1.revision == m2.revision + def explicitArtifacts = + { + val arts = (artifacts collect { case (_, x, art, _) if sameModule(m, x) && art.classifier.isDefined => art }).distinct + if (arts.isEmpty) None + else Some(m.copy(isTransitive = false, explicitArtifacts = arts)) + } + def hardcodedArtifacts = classifiedArtifacts(classifiers, exclude)(m) + explicitArtifacts orElse hardcodedArtifacts + } + private 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 diff --git a/librarymanagement/src/main/scala/sbt/librarymanagement/UpdateReport.scala b/librarymanagement/src/main/scala/sbt/librarymanagement/UpdateReport.scala index 611de450f..c51617dee 100644 --- a/librarymanagement/src/main/scala/sbt/librarymanagement/UpdateReport.scala +++ b/librarymanagement/src/main/scala/sbt/librarymanagement/UpdateReport.scala @@ -33,8 +33,14 @@ final class ConfigurationReport( * All resolved modules for this configuration. * For a given organization and module name, there is only one revision/`ModuleID` in this sequence. */ - def allModules: Seq[ModuleID] = modules.map(mr => addConfiguration(mr.module)) - private[this] def addConfiguration(mod: ModuleID): ModuleID = if (mod.configurations.isEmpty) mod.copy(configurations = Some(configuration)) else mod + def allModules: Seq[ModuleID] = modules map addConfiguration + private[this] def addConfiguration(mr: ModuleReport): ModuleID = { + val module = mr.module + if (module.configurations.isEmpty) { + val conf = mr.configurations map (c => s"$configuration->$c") mkString ";" + module.copy(configurations = Some(conf)) + } else module + } def retrieve(f: (String, ModuleID, Artifact, File) => File): ConfigurationReport = new ConfigurationReport(configuration, modules map { _.retrieve((mid, art, file) => f(configuration, mid, art, file)) }, details) @@ -208,7 +214,22 @@ final class UpdateReport(val cachedDescriptor: File, val configurations: Seq[Con override def toString = "Update report:\n\t" + stats + "\n" + configurations.mkString /** All resolved modules in all configurations. */ - def allModules: Seq[ModuleID] = configurations.flatMap(_.allModules).distinct + def allModules: Seq[ModuleID] = + { + val key = (m: ModuleID) => (m.organization, m.name, m.revision) + configurations.flatMap(_.allModules).groupBy(key).toSeq map { + case (k, v) => + v reduceLeft { (agg, x) => + agg.copy( + configurations = (agg.configurations, x.configurations) match { + case (None, _) => x.configurations + case (Some(ac), None) => Some(ac) + case (Some(ac), Some(xc)) => Some(s"$ac;$xc") + } + ) + } + } + } def retrieve(f: (String, ModuleID, Artifact, File) => File): UpdateReport = new UpdateReport(cachedDescriptor, configurations map { _ retrieve f }, stats, stamps)