From dd497063e5fb41c655f0d18c186b4ef68f215d0b Mon Sep 17 00:00:00 2001 From: David Gregory Date: Wed, 30 Mar 2022 19:03:14 +0100 Subject: [PATCH 1/2] Add Remove instances for Set and Map --- main-settings/src/main/scala/sbt/Remove.scala | 10 ++++++++++ sbt-app/src/sbt-test/project/remove/build.sbt | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/main-settings/src/main/scala/sbt/Remove.scala b/main-settings/src/main/scala/sbt/Remove.scala index e09555f5b..6ab53e42b 100644 --- a/main-settings/src/main/scala/sbt/Remove.scala +++ b/main-settings/src/main/scala/sbt/Remove.scala @@ -34,4 +34,14 @@ object Remove { def removeValue(a: Seq[T], b: Option[T]): Seq[T] = b.fold(a)(a filterNot _.==) def removeValues(a: Seq[T], b: Option[T]): Seq[T] = b.fold(a)(a filterNot _.==) } + implicit def removeSet[T, V <: T]: Sequence[Set[T], Set[V], V] = + new Sequence[Set[T], Set[V], V] { + def removeValue(a: Set[T], b: V): Set[T] = a - b + def removeValues(a: Set[T], b: Set[V]): Set[T] = a diff (b.toSeq: Seq[T]).toSet + } + implicit def removeMap[A, B, X <: A]: Sequence[Map[A, B], Seq[X], X] = + new Sequence[Map[A, B], Seq[X], X] { + def removeValue(a: Map[A, B], b: X): Map[A, B] = a - b + def removeValues(a: Map[A, B], b: Seq[X]): Map[A, B] = a -- b + } } diff --git a/sbt-app/src/sbt-test/project/remove/build.sbt b/sbt-app/src/sbt-test/project/remove/build.sbt index d3ae2985d..5a16e59d8 100644 --- a/sbt-app/src/sbt-test/project/remove/build.sbt +++ b/sbt-app/src/sbt-test/project/remove/build.sbt @@ -1,6 +1,8 @@ val intsTask = taskKey[Seq[Int]]("A seq of ints task") val intsSetting = settingKey[Seq[Int]]("A seq of ints setting") val intsFromScalaV = settingKey[Seq[Int]]("a seq of ints from scalaVersion") +val intsSetSetting = settingKey[Set[Int]]("A set of ints setting") +val stringIntMapSetting = settingKey[Map[String, Int]]("A map of string to int setting") scalaVersion := "2.11.6" @@ -22,9 +24,19 @@ intsFromScalaV --= { if (scalaVersion.value == "2.11.6") Seq(1, 2) else Seq(4) } intsFromScalaV -= { if (scalaVersion.value == "2.11.6") Option(6) else None } intsFromScalaV --= { if (scalaVersion.value == "2.11.6") Option(7) else None } +intsSetSetting := Set(1, 2, 3, 4, 5, 6, 7) +intsSetSetting -= 3 +intsSetSetting --= Set(1, 2) + +stringIntMapSetting := Map("a" -> 1, "b" -> 2 , "c" -> 3, "d" -> 4, "e" -> 5) +stringIntMapSetting -= "c" +stringIntMapSetting --= Seq("a", "b") + val check = taskKey[Unit]("Runs the check") check := { assert(intsTask.value == Seq(4, 5), s"intsTask should be Seq(4, 5) but is ${intsTask.value}") assert(intsSetting.value == Seq(4, 5), s"intsSetting should be Seq(4, 5) but is ${intsSetting.value}") assert(intsFromScalaV.value == Seq(4, 5), s"intsFromScalaV should be Seq(4, 5) but is ${intsFromScalaV.value}") + assert(intsSetSetting.value == Set(4, 5, 6, 7), s"intsSetSetting should be Set(4, 5, 6, 7) but is ${intsSetSetting.value}") + assert(stringIntMapSetting.value == Map("d" -> 4, "e" -> 5), s"stringIntMapSetting should be Map(d -> 4, e -> 5) but is ${stringIntMapSetting.value}") } From 0d0a654c6f34958c6fb8939859b8315b1bb10f84 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 9 May 2022 16:41:26 +0200 Subject: [PATCH 2/2] Add support for wildcards in Scala version switch Picking from the `crossScalaVersions` As discussed in https://github.com/sbt/sbt/discussions/6893 --- main/src/main/scala/sbt/Cross.scala | 96 +++++++++++++------ main/src/test/scala/sbt/CrossSpec.scala | 20 ++++ .../sbt-test/actions/cross-multiproject/test | 21 ++++ 3 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 main/src/test/scala/sbt/CrossSpec.scala diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 8bb001d04..9966c5eb9 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -8,7 +8,7 @@ package sbt import java.io.File - +import java.util.regex.Pattern import sbt.Def.{ ScopedKey, Setting } import sbt.Keys._ import sbt.SlashSyntax0._ @@ -284,8 +284,8 @@ object Cross { } def logSwitchInfo( - included: Seq[(ProjectRef, Seq[ScalaVersion])], - excluded: Seq[(ProjectRef, Seq[ScalaVersion])] + included: Seq[(ResolvedReference, ScalaVersion, Seq[ScalaVersion])], + excluded: Seq[(ResolvedReference, Seq[ScalaVersion])] ) = { instance.foreach { @@ -304,56 +304,96 @@ object Cross { def detailedLog(msg: => String) = if (switch.verbose) state.log.info(msg) else state.log.debug(msg) - def logProject: (ProjectRef, Seq[ScalaVersion]) => Unit = (proj, scalaVersions) => { - val current = if (proj == currentRef) "*" else " " - detailedLog(s" $current ${proj.project} ${scalaVersions.mkString("(", ", ", ")")}") + def logProject: (ResolvedReference, Seq[ScalaVersion]) => Unit = (ref, scalaVersions) => { + val current = if (ref == currentRef) "*" else " " + ref match { + case proj: ProjectRef => + detailedLog(s" $current ${proj.project} ${scalaVersions.mkString("(", ", ", ")")}") + case _ => // don't log BuildRefs + } } detailedLog("Switching Scala version on:") - included.foreach(logProject.tupled) + included.foreach { case (project, _, versions) => logProject(project, versions) } detailedLog("Excluding projects:") excluded.foreach(logProject.tupled) } - val projects: Seq[(ResolvedReference, Seq[ScalaVersion])] = { + val projects: Seq[(ResolvedReference, Option[ScalaVersion], Seq[ScalaVersion])] = { val projectScalaVersions = structure.allProjectRefs.map(proj => proj -> crossVersions(extracted, proj)) if (switch.version.force) { - logSwitchInfo(projectScalaVersions, Nil) - projectScalaVersions ++ structure.units.keys + projectScalaVersions.map { + case (ref, options) => (ref, Some(version), options) + } ++ structure.units.keys .map(BuildRef.apply) - .map(proj => proj -> crossVersions(extracted, proj)) + .map(proj => (proj, Some(version), crossVersions(extracted, proj))) + } else if (version.contains('*')) { + projectScalaVersions.map { + case (project, scalaVersions) => + globFilter(version, scalaVersions) match { + case Nil => (project, None, scalaVersions) + case Seq(version) => (project, Some(version), scalaVersions) + case multiple => + sys.error( + s"Multiple crossScalaVersions matched query '$version': ${multiple.mkString(", ")}" + ) + } + } } else { val binaryVersion = CrossVersion.binaryScalaVersion(version) - - val (included, excluded) = projectScalaVersions.partition { - case (_, scalaVersions) => - scalaVersions.exists(v => CrossVersion.binaryScalaVersion(v) == binaryVersion) + projectScalaVersions.map { + case (project, scalaVersions) => + if (scalaVersions.exists(v => CrossVersion.binaryScalaVersion(v) == binaryVersion)) + (project, Some(version), scalaVersions) + else + (project, None, scalaVersions) } - if (included.isEmpty) { - sys.error( - s"""Switch failed: no subprojects list "$version" (or compatible version) in crossScalaVersions setting. - |If you want to force it regardless, call ++ $version!""".stripMargin - ) - } - logSwitchInfo(included, excluded) - included } } - (setScalaVersionForProjects(version, instance, projects, state, extracted), projects.map(_._1)) + val included = projects.collect { + case (project, Some(version), scalaVersions) => (project, version, scalaVersions) + } + val excluded = projects.collect { + case (project, None, scalaVersions) => (project, scalaVersions) + } + + if (included.isEmpty) { + sys.error( + s"""Switch failed: no subprojects list "$version" (or compatible version) in crossScalaVersions setting. + |If you want to force it regardless, call ++ $version!""".stripMargin + ) + } + + logSwitchInfo(included, excluded) + + (setScalaVersionsForProjects(instance, included, state, extracted), included.map(_._1)) } - private def setScalaVersionForProjects( - version: String, + def globFilter(pattern: String, candidates: Seq[String]): Seq[String] = { + def createGlobRegex(remainingPattern: String): String = + remainingPattern.indexOf("*") match { + case -1 => Pattern.quote(remainingPattern) + case n => + val chunk = Pattern.quote(remainingPattern.substring(0, n)) + ".*" + if (remainingPattern.length > n) + chunk + createGlobRegex(remainingPattern.substring(n + 1)) + else chunk + } + val compiledPattern = Pattern.compile(createGlobRegex(pattern)) + candidates.filter(compiledPattern.matcher(_).matches()) + } + + private def setScalaVersionsForProjects( instance: Option[(File, ScalaInstance)], - projects: Seq[(ResolvedReference, Seq[String])], + projects: Seq[(ResolvedReference, String, Seq[String])], state: State, extracted: Extracted ): State = { import extracted._ val newSettings = projects.flatMap { - case (project, scalaVersions) => + case (project, version, scalaVersions) => val scope = Scope(Select(project), Zero, Zero, Zero) instance match { diff --git a/main/src/test/scala/sbt/CrossSpec.scala b/main/src/test/scala/sbt/CrossSpec.scala new file mode 100644 index 000000000..40673d548 --- /dev/null +++ b/main/src/test/scala/sbt/CrossSpec.scala @@ -0,0 +1,20 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +object CrossSpec extends verify.BasicTestSuite { + import Cross._ + + test("glob filter should work as expected") { + assert(globFilter("2.13.*", Seq("2.12.8", "2.13.16", "3.0.1")) == Seq("2.13.16")) + assert(globFilter("3.*", Seq("2.12.8", "2.13.16", "3.0.1")) == Seq("3.0.1")) + assert(globFilter("3.*", Seq("3.0.1", "30.1")) == Seq("3.0.1")) + assert(globFilter("2.*", Seq("2.12.8", "2.13.16", "3.0.1")) == Seq("2.12.8", "2.13.16")) + assert(globFilter("4.*", Seq("2.12.8", "2.13.16", "3.0.1")) == Nil) + } +} diff --git a/sbt-app/src/sbt-test/actions/cross-multiproject/test b/sbt-app/src/sbt-test/actions/cross-multiproject/test index 68b8383d5..baf4db9b8 100644 --- a/sbt-app/src/sbt-test/actions/cross-multiproject/test +++ b/sbt-app/src/sbt-test/actions/cross-multiproject/test @@ -41,3 +41,24 @@ $ exists lib/target/scala-2.13 -$ exists lib/target/scala-2.12 # -$ exists sbt-foo/target/scala-2.12 -$ exists sbt-foo/target/scala-2.13 + +# test wildcard switching (2.12) +> clean +> ++ 2.12.* -v compile +$ exists lib/target/scala-2.12 +-$ exists lib/target/scala-2.13 +$ exists sbt-foo/target/scala-2.12 +-$ exists sbt-foo/target/scala-2.13 + +# test wildcard switching (2.13) +> clean +> ++ 2.13.* -v compile +$ exists lib/target/scala-2.13 +-$ exists lib/target/scala-2.12 +# -$ exists sbt-foo/target/scala-2.12 +-$ exists sbt-foo/target/scala-2.13 + +# test wildcard switching (no matches) +-> ++ 3.* +# test wildcard switching (multiple matches) +-> ++ 2.*