Merge pull request #5265 from eatkins/cross-input-tasks

Fix cross command (+) for input tasks in subprojects with provided input
This commit is contained in:
eugene yokota 2019-12-03 21:42:18 -05:00 committed by GitHub
commit e5e462f2c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 72 deletions

View File

@ -82,16 +82,17 @@ object Cross {
s => if (s get sessionSettings isEmpty) failure("No project loaded") else p(s)
private def resolveAggregates(extracted: Extracted): Seq[ProjectRef] = {
import extracted._
def findAggregates(project: ProjectRef): List[ProjectRef] = {
project :: (structure.allProjects(project.build).find(_.id == project.project) match {
def findAggregates(project: ProjectRef): Seq[ProjectRef] = {
project :: (extracted.structure
.allProjects(project.build)
.find(_.id == project.project) match {
case Some(resolved) => resolved.aggregate.toList.flatMap(findAggregates)
case None => Nil
})
}
(currentRef :: currentProject.aggregate.toList.flatMap(findAggregates)).distinct
(extracted.currentRef +: extracted.currentProject.aggregate.flatMap(findAggregates)).distinct
}
private def crossVersions(extracted: Extracted, proj: ResolvedReference): Seq[String] = {
@ -127,76 +128,76 @@ object Cross {
Command.arb(requireSession(crossParser), crossHelp)(crossBuildCommandImpl)
private def crossBuildCommandImpl(state: State, args: CrossArgs): State = {
val x = Project.extract(state)
import x._
val (aggs, aggCommand) = parseSlashCommand(x)(args.command)
val projCrossVersions = aggs map { proj =>
proj -> crossVersions(x, 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 extracted = Project.extract(state)
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(x)) 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(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, x)
.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 =
@ -229,7 +230,6 @@ object Cross {
Command.arb(requireSession(switchParser), switchHelp)(switchCommandImpl)
private def switchCommandImpl(state: State, args: Switch): State = {
val x = Project.extract(state)
val (switchedState, affectedRefs) = switchScalaVersion(args, state)
val strictCmd =
@ -238,7 +238,7 @@ object Cross {
args.command
} else {
args.command.map { rawCmd =>
val (aggs, aggCommand) = parseSlashCommand(x)(rawCmd)
val (aggs, aggCommand) = parseSlashCommand(Project.extract(state))(rawCmd)
aggs
.intersect(affectedRefs)
.map({ case ProjectRef(_, proj) => s"$proj/$aggCommand" })

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$