Support input tasks in cross (+) command

This commit is contained in:
Ethan Atkins 2019-12-03 09:49:02 -08:00
parent 0cbbee4418
commit 53788ba356
6 changed files with 83 additions and 64 deletions

View File

@ -129,74 +129,75 @@ object Cross {
private def crossBuildCommandImpl(state: State, args: CrossArgs): State = {
val extracted = Project.extract(state)
val (aggs, aggCommand) = parseSlashCommand(extracted)(args.command)
val projCrossVersions = aggs map { proj =>
proj -> crossVersions(extracted, proj)
}
// if we support scalaVersion, projVersions should be cached somewhere since
// running ++2.11.1 is at the root level is going to mess with the scalaVersion for the aggregated subproj
val projVersions = (projCrossVersions flatMap {
case (proj, versions) => versions map { proj.project -> _ }
}).toList
val parser = Act.aggregatedKeyParser(extracted) ~ matched(any.*)
val verbose = if (args.verbose) "-v" else ""
val allCommands = Parser.parse(args.command, parser) match {
case Left(_) =>
val (aggs, aggCommand) = parseSlashCommand(extracted)(args.command)
val projCrossVersions = aggs map { proj =>
proj -> crossVersions(extracted, proj)
}
// It's definitely not a task, check if it's a valid command, because we don't want to emit the warning
// message below for typos.
val validCommand = Parser.parse(aggCommand, state.combinedParser).isRight
if (projVersions.isEmpty) {
state
} else {
// Detect whether a task or command has been issued
val allCommands = Parser.parse(aggCommand, Act.aggregatedKeyParser(extracted)) match {
case Left(_) =>
// It's definitely not a task, check if it's a valid command, because we don't want to emit the warning
// message below for typos.
val validCommand = Parser.parse(aggCommand, state.combinedParser).isRight
val distinctCrossConfigs = projCrossVersions.map(_._2.toSet).distinct
if (validCommand && distinctCrossConfigs.size > 1) {
state.log.warn(
"Issuing a cross building command, but not all sub projects have the same cross build " +
"configuration. This could result in subprojects cross building against Scala versions that they are " +
"not compatible with. Try issuing cross building command with tasks instead, since sbt will be able " +
"to ensure that cross building is only done using configured project and Scala version combinations " +
"that are configured."
)
state.log.debug("Scala versions configuration is:")
projCrossVersions.foreach {
case (project, versions) => state.log.debug(s"$project: $versions")
}
val distinctCrossConfigs = projCrossVersions.map(_._2.toSet).distinct
if (validCommand && distinctCrossConfigs.size > 1) {
state.log.warn(
"Issuing a cross building command, but not all sub projects have the same cross build " +
"configuration. This could result in subprojects cross building against Scala versions that they are " +
"not compatible with. Try issuing cross building command with tasks instead, since sbt will be able " +
"to ensure that cross building is only done using configured project and Scala version combinations " +
"that are configured."
)
state.log.debug("Scala versions configuration is:")
projCrossVersions.foreach {
case (project, versions) => state.log.debug(s"$project: $versions")
}
}
// Execute using a blanket switch
projCrossVersions.toMap.apply(extracted.currentRef).flatMap { version =>
// Force scala version
Seq(s"$SwitchCommand $verbose $version!", aggCommand)
// Execute using a blanket switch
projCrossVersions.toMap.apply(extracted.currentRef).flatMap { version =>
// Force scala version
Seq(s"$SwitchCommand $verbose $version!", aggCommand)
}
case Right((keys, taskArgs)) =>
def project(key: ScopedKey[_]): Option[ProjectRef] = key.scope.project.toOption match {
case Some(p: ProjectRef) => Some(p)
case _ => None
}
val fullArgs = if (taskArgs.trim.isEmpty) "" else s" ${taskArgs.trim}"
val keysByVersion = keys
.flatMap { k =>
project(k).toSeq.flatMap(crossVersions(extracted, _).map(v => v -> k))
}
case Right(_) =>
// We have a key, we're likely to be able to cross build this using the per project behaviour.
// Group all the projects by scala version
projVersions.groupBy(_._2).mapValues(_.map(_._1)).toSeq.flatMap {
case (version, Seq(project)) =>
// If only one project for a version, issue it directly
Seq(s"$SwitchCommand $verbose $version $project/$aggCommand")
case (version, projects) if aggCommand.contains(" ") =>
// If the command contains a space, then the `all` command won't work because it doesn't support issuing
// commands with spaces, so revert to running the command on each project one at a time
s"$SwitchCommand $verbose $version" :: projects
.map(project => s"$project/$aggCommand")
case (version, projects) =>
// First switch scala version, then use the all command to run the command on each project concurrently
Seq(
s"$SwitchCommand $verbose $version",
projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")
)
.groupBy(_._1)
.mapValues(_.map(_._2).toSet)
val commandsByVersion = keysByVersion.toSeq
.flatMap {
case (v, keys) =>
val projects = keys.flatMap(project)
keys.toSeq.flatMap { k =>
project(k).filter(projects.contains).flatMap { p =>
if (p == extracted.currentRef || !projects.contains(extracted.currentRef)) {
val parts = project(k).map(_.project) ++ k.scope.config.toOption.map {
case ConfigKey(n) => n.head.toUpper + n.tail
} ++ k.scope.task.toOption.map(_.label) ++ Some(k.key.label)
Some(v -> parts.mkString("", " / ", fullArgs))
} else None
}
}
}
}
allCommands.toList ::: CrossRestoreSessionCommand :: captureCurrentSession(state, extracted)
.groupBy(_._1)
.mapValues(_.map(_._2))
.toSeq
.sortBy(_._1)
commandsByVersion.flatMap {
case (v, commands) =>
Seq(s"$SwitchCommand $verbose $v!") ++ commands
}
}
allCommands.toList ::: CrossRestoreSessionCommand :: captureCurrentSession(state, extracted)
}
def crossRestoreSession: Command =

View File

@ -0,0 +1,2 @@
val foo = project
val root = (project in file(".")).aggregate(foo)

View File

@ -0,0 +1,3 @@
crossScalaVersions := Seq("2.12.10", "2.13.1")
libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.0"

View File

@ -0,0 +1,9 @@
package foo
import org.scalatest.FlatSpec
class FooSpec extends FlatSpec {
"foo" should "bar" in {
assert(true)
}
}

View File

@ -0,0 +1,3 @@
> + foo / testOnly foo.FooSpec
> + testOnly foo.FooSpec

View File

@ -192,9 +192,10 @@ final class ScriptedTests(
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
case "actions/multi-command" =>
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
case "actions/external-doc" => LauncherBased // sbt/Package$
case "actions/input-task" => LauncherBased // sbt/Package$
case "actions/input-task-dyn" => LauncherBased // sbt/Package$
case "actions/cross-test-only" => LauncherBased // tbd
case "actions/external-doc" => LauncherBased // sbt/Package$
case "actions/input-task" => LauncherBased // sbt/Package$
case "actions/input-task-dyn" => LauncherBased // sbt/Package$
case gn if gn.startsWith("classloader-cache/") =>
LauncherBased // This should be tested using launcher
case "compiler-project/dotty-compiler-plugin" => LauncherBased // sbt/Package$