diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 33436452c..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): 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) @@ -254,6 +258,21 @@ object IvyActions { throw w.resolveException } } + // 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 + } + @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 5a866632a..6cb401c5f 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) @@ -194,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 844f8719b..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) + 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] 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