From f49fb33e6d6bc82d86969fbc740341c45a742567 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 30 Nov 2015 10:03:40 +0100 Subject: [PATCH 1/5] Combine ConfigurationReport's conf with ModuleReport's confs --- ivy/src/main/scala/sbt/UpdateReport.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ivy/src/main/scala/sbt/UpdateReport.scala b/ivy/src/main/scala/sbt/UpdateReport.scala index 5a866632a..9df7c3feb 100644 --- a/ivy/src/main/scala/sbt/UpdateReport.scala +++ b/ivy/src/main/scala/sbt/UpdateReport.scala @@ -31,8 +31,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, evicted) From 6d7c8fe655be227d09c915560b32b444e938ca04 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 30 Nov 2015 22:53:36 +0100 Subject: [PATCH 2/5] Correct separator for configurations --- ivy/src/main/scala/sbt/UpdateReport.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/src/main/scala/sbt/UpdateReport.scala b/ivy/src/main/scala/sbt/UpdateReport.scala index 9df7c3feb..d7d364b47 100644 --- a/ivy/src/main/scala/sbt/UpdateReport.scala +++ b/ivy/src/main/scala/sbt/UpdateReport.scala @@ -35,7 +35,7 @@ final class ConfigurationReport( 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 "," + val conf = mr.configurations map (c => s"$configuration->$c") mkString ";" module.copy(configurations = Some(conf)) } else module } From 13410ca68eb813d9c8e642dc5513915d02949e97 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 2 Dec 2015 16:56:14 +0100 Subject: [PATCH 3/5] WIP --- ivy/src/main/scala/sbt/IvyActions.scala | 16 ++++++++++++---- main/src/main/scala/sbt/Defaults.scala | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 33436452c..419f41a56 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -231,20 +231,20 @@ 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, 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, log, ???) 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], log: Logger, upd: UpdateReport): 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) + val deps = baseModules.distinct flatMap classifiedArtifacts(upd, exclude) //classifiedArtifacts(classifiers, exclude) 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) @@ -254,6 +254,14 @@ object IvyActions { throw w.resolveException } } + def classifiedArtifacts(updateReport: UpdateReport, exclude: Map[ModuleID, Set[String]])(m: ModuleID): Option[ModuleID] = { + def sameModule(m1: ModuleID, m2: ModuleID): Boolean = m1.organization == m2.organization && m1.name == m2.name && m1.revision == m2.revision + updateReport.toSeq.groupBy(_._2) collectFirst { + case (k, v) if sameModule(k, m) => + val arts = v.collect { case (_, _, art, _) if art.classifier.isDefined => art }.distinct + m.copy(isTransitive = false, explicitArtifacts = arts) + } + } def classifiedArtifacts(classifiers: Seq[String], exclude: Map[ModuleID, Set[String]])(m: ModuleID): Option[ModuleID] = { val excluded = exclude getOrElse (restrictedCopy(m, false), Set.empty) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 844f8719b..24baf2d34 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1234,7 +1234,7 @@ object Classpaths { val uwConfig = (unresolvedWarningConfiguration in update).value val logicalClock = LogicalClock(state.value.hashCode) val depDir = dependencyCacheDirectory.value - IvyActions.updateClassifiers(is, GetClassifiersConfiguration(mod, excludes, c, ivyScala.value), uwConfig, LogicalClock(state.value.hashCode), Some(depDir), s.log) + IvyActions.updateClassifiers(is, GetClassifiersConfiguration(mod, excludes, c, ivyScala.value), uwConfig, LogicalClock(state.value.hashCode), Some(depDir), s.log, update.value) } } tag (Tags.Update, Tags.Network) ) From 3a3965b5f570d96375e6eba0815f1e90810a7d8d Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 2 Dec 2015 16:41:02 -0500 Subject: [PATCH 4/5] Fixes #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 #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 f49fb33e6d6bc82d86969fbc740341c45a742567. 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. --- ivy/src/main/scala/sbt/IvyActions.scala | 31 +++++++++++++------ ivy/src/main/scala/sbt/UpdateReport.scala | 16 +++++++++- main/src/main/scala/sbt/Defaults.scala | 3 +- ...x-updateclassifiers-configuration.markdown | 13 ++++++++ 4 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 notes/0.13.10/fix-updateclassifiers-configuration.markdown diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 419f41a56..657cdd65c 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -231,20 +231,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, upd: UpdateReport): 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(upd, exclude) //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) @@ -254,14 +258,21 @@ object IvyActions { throw w.resolveException } } - def classifiedArtifacts(updateReport: UpdateReport, 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 - updateReport.toSeq.groupBy(_._2) collectFirst { - case (k, v) if sameModule(k, m) => - val arts = v.collect { case (_, _, art, _) if art.classifier.isDefined => art }.distinct - m.copy(isTransitive = false, explicitArtifacts = arts) - } + 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 } + @deprecated("This is no longer public.", "0.13.10") def classifiedArtifacts(classifiers: Seq[String], exclude: Map[ModuleID, Set[String]])(m: ModuleID): Option[ModuleID] = { val excluded = exclude getOrElse (restrictedCopy(m, false), Set.empty) diff --git a/ivy/src/main/scala/sbt/UpdateReport.scala b/ivy/src/main/scala/sbt/UpdateReport.scala index d7d364b47..6cb401c5f 100644 --- a/ivy/src/main/scala/sbt/UpdateReport.scala +++ b/ivy/src/main/scala/sbt/UpdateReport.scala @@ -200,7 +200,21 @@ 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) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 24baf2d34..55730060c 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1234,7 +1234,8 @@ object Classpaths { val uwConfig = (unresolvedWarningConfiguration in update).value val logicalClock = LogicalClock(state.value.hashCode) val depDir = dependencyCacheDirectory.value - IvyActions.updateClassifiers(is, GetClassifiersConfiguration(mod, excludes, c, ivyScala.value), uwConfig, LogicalClock(state.value.hashCode), Some(depDir), s.log, update.value) + val artifacts = update.value.toSeq.toVector + IvyActions.updateClassifiers(is, GetClassifiersConfiguration(mod, excludes, c, ivyScala.value), uwConfig, LogicalClock(state.value.hashCode), Some(depDir), artifacts, s.log) } } tag (Tags.Update, Tags.Network) ) diff --git a/notes/0.13.10/fix-updateclassifiers-configuration.markdown b/notes/0.13.10/fix-updateclassifiers-configuration.markdown new file mode 100644 index 000000000..b69053d5a --- /dev/null +++ b/notes/0.13.10/fix-updateclassifiers-configuration.markdown @@ -0,0 +1,13 @@ + + [@eed3si9n]: https://github.com/eed3si9n + [@Duhemm]: https://github.com/Duhemm + [2264]: https://github.com/sbt/sbt/issues/2264 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Fixes `updateClassifiers` on Ivy modules without `default` configuration. + [#2264][2264] by [@eed3si9n][@eed3si9n]/[@Duhemm][@Duhemm] From 152e4e3d72b11bb93acdc61a9a28f1766a4c633c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 2 Dec 2015 19:30:28 -0500 Subject: [PATCH 5/5] Adds integration test --- .../update-classifiers/build.sbt | 46 +++++++++++++++++++ .../classifierproducer/foo.txt | 0 .../update-classifiers/test | 3 ++ 3 files changed, 49 insertions(+) create mode 100644 sbt/src/sbt-test/dependency-management/update-classifiers/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/update-classifiers/classifierproducer/foo.txt create mode 100644 sbt/src/sbt-test/dependency-management/update-classifiers/test diff --git a/sbt/src/sbt-test/dependency-management/update-classifiers/build.sbt b/sbt/src/sbt-test/dependency-management/update-classifiers/build.sbt new file mode 100644 index 000000000..37e6881db --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/update-classifiers/build.sbt @@ -0,0 +1,46 @@ +val fooFile = taskKey[File]("sample artifact") +val RuntimeX = config("runtime") +val check = taskKey[Unit]("check") + +lazy val commonSettings = Seq( + organization := "com.example", + version := "0.1-SNAPSHOT", + scalaVersion := "2.11.7", + ivyPaths := new IvyPaths( (baseDirectory in ThisBuild).value, Some((target in LocalRootProject).value / "ivy-cache")), + fullResolvers := fullResolvers.value.filterNot(_.name == "inter-project") +) + +lazy val root = (project in file(".")). + settings(commonSettings) + +lazy val classifierproducer = (project in file("classifierproducer")). + settings( + commonSettings, + fooFile := { baseDirectory.value / "foo.txt" }, + addArtifact( Artifact("classifierproducer", "text", "txt", "runtime"), fooFile), + ivyConfigurations := Seq(RuntimeX, Configurations.ScalaTool), + publishArtifact in Compile := false, + publishArtifact in Test := false, + publishMavenStyle := false, + autoScalaLibrary := false, + crossPaths := false + ) + +lazy val classifierconsumer = (project in file("classifierconsumer")). + settings( + commonSettings, + libraryDependencies += "com.example" % "classifierproducer" % "0.1-SNAPSHOT" % "compile->runtime", + check := { + val ur = updateClassifiers.value + val mrs = for { + cr <- ur.configurations if cr.configuration == "compile" + oar <- cr.details if (oar.organization == "com.example") && (oar.name == "classifierproducer") + mr <- oar.modules if (mr.module.revision == "0.1-SNAPSHOT") + } yield mr + val mr = mrs.head + if (mr.artifacts exists { case (a: Artifact, f) => + a.extension == "txt" + }) () + else sys.error("txt artifact was not found: " + mr.toString) + } + ) diff --git a/sbt/src/sbt-test/dependency-management/update-classifiers/classifierproducer/foo.txt b/sbt/src/sbt-test/dependency-management/update-classifiers/classifierproducer/foo.txt new file mode 100644 index 000000000..e69de29bb diff --git a/sbt/src/sbt-test/dependency-management/update-classifiers/test b/sbt/src/sbt-test/dependency-management/update-classifiers/test new file mode 100644 index 000000000..a427c514b --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/update-classifiers/test @@ -0,0 +1,3 @@ +> classifierproducer/publishLocal + +> classifierconsumer/check