mirror of https://github.com/sbt/sbt.git
Merge pull request #4139 from eed3si9n/wip/discover-java-home
Cross JDK forking
This commit is contained in:
commit
2848770f85
11
.travis.yml
11
.travis.yml
|
|
@ -6,6 +6,7 @@ cache:
|
|||
directories:
|
||||
- $HOME/.ivy2/cache
|
||||
- $HOME/.sbt/boot
|
||||
- $HOME/.jabba
|
||||
|
||||
language: scala
|
||||
|
||||
|
|
@ -15,6 +16,14 @@ jdk:
|
|||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: SBT_CMD="scripted java/*"
|
||||
before_install:
|
||||
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.10.1/install.sh | bash && . ~/.jabba/jabba.sh
|
||||
install:
|
||||
- /home/travis/.jabba/bin/jabba install openjdk@1.10
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs=
|
||||
|
|
@ -26,7 +35,7 @@ env:
|
|||
- SBT_CMD="scripted dependency-management/*2of4"
|
||||
- SBT_CMD="scripted dependency-management/*3of4"
|
||||
- SBT_CMD="scripted dependency-management/*4of4"
|
||||
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
||||
- SBT_CMD="scripted package/* reporter/* run/* project-load/*"
|
||||
- SBT_CMD="scripted project/*1of2"
|
||||
- SBT_CMD="scripted project/*2of2"
|
||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt
|
||||
final class JavaVersion private (
|
||||
val numbers: Vector[Long],
|
||||
val vendor: Option[String]) extends Serializable {
|
||||
def numberStr: String = numbers.mkString(".")
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: JavaVersion => (this.numbers == x.numbers) && (this.vendor == x.vendor)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + numbers.##) + vendor.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
vendor.map(_ + "@").getOrElse("") + numberStr
|
||||
}
|
||||
private[this] def copy(numbers: Vector[Long] = numbers, vendor: Option[String] = vendor): JavaVersion = {
|
||||
new JavaVersion(numbers, vendor)
|
||||
}
|
||||
def withNumbers(numbers: Vector[Long]): JavaVersion = {
|
||||
copy(numbers = numbers)
|
||||
}
|
||||
def withVendor(vendor: Option[String]): JavaVersion = {
|
||||
copy(vendor = vendor)
|
||||
}
|
||||
def withVendor(vendor: String): JavaVersion = {
|
||||
copy(vendor = Option(vendor))
|
||||
}
|
||||
}
|
||||
object JavaVersion {
|
||||
def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
||||
def apply(numbers: Vector[Long], vendor: Option[String]): JavaVersion = new JavaVersion(numbers, vendor)
|
||||
def apply(numbers: Vector[Long], vendor: String): JavaVersion = new JavaVersion(numbers, Option(vendor))
|
||||
}
|
||||
|
|
@ -17,3 +17,13 @@ enum PluginTrigger {
|
|||
AllRequirements
|
||||
NoTrigger
|
||||
}
|
||||
|
||||
type JavaVersion {
|
||||
numbers: [Long]
|
||||
vendor: String
|
||||
|
||||
#x def numberStr: String = numbers.mkString(".")
|
||||
#xtostring vendor.map(_ + "@").getOrElse("") + numberStr
|
||||
|
||||
#xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,14 +99,24 @@ object Cross {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse the given command into either an aggregate command or a command for a project
|
||||
* Parse the given command into a list of aggregate projects and command to issue.
|
||||
*/
|
||||
private def parseCommand(command: String): Either[String, (String, String)] = {
|
||||
private[sbt] def parseSlashCommand(
|
||||
extracted: Extracted
|
||||
)(command: String): (Seq[ProjectRef], String) = {
|
||||
import extracted._
|
||||
import DefaultParsers._
|
||||
val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map {
|
||||
case project ~ cmd => (project, cmd.mkString)
|
||||
case seg1 ~ cmd => (seg1, cmd.mkString)
|
||||
}
|
||||
Parser.parse(command, parser) match {
|
||||
case Right((seg1, cmd)) =>
|
||||
structure.allProjectRefs.find(_.project == seg1) match {
|
||||
case Some(proj) => (Seq(proj), cmd)
|
||||
case _ => (resolveAggregates(extracted), command)
|
||||
}
|
||||
case _ => (resolveAggregates(extracted), command)
|
||||
}
|
||||
Parser.parse(command, parser).left.map(_ => command)
|
||||
}
|
||||
|
||||
def crossBuild: Command =
|
||||
|
|
@ -115,12 +125,7 @@ object Cross {
|
|||
private def crossBuildCommandImpl(state: State, args: CrossArgs): State = {
|
||||
val x = Project.extract(state)
|
||||
import x._
|
||||
|
||||
val (aggs, aggCommand) = parseCommand(args.command) match {
|
||||
case Right((project, cmd)) =>
|
||||
(structure.allProjectRefs.filter(_.project == project), cmd)
|
||||
case Left(cmd) => (resolveAggregates(x), cmd)
|
||||
}
|
||||
val (aggs, aggCommand) = parseSlashCommand(x)(args.command)
|
||||
|
||||
val projCrossVersions = aggs map { proj =>
|
||||
proj -> crossVersions(x, proj)
|
||||
|
|
@ -229,14 +234,11 @@ object Cross {
|
|||
args.command
|
||||
} else {
|
||||
args.command.map { rawCmd =>
|
||||
parseCommand(rawCmd) match {
|
||||
case Right(_) => rawCmd // A project is specified, run as is
|
||||
case Left(cmd) =>
|
||||
resolveAggregates(x)
|
||||
.intersect(affectedRefs)
|
||||
.collect { case ProjectRef(_, proj) => s"$proj/$cmd" }
|
||||
.mkString("all ", " ", "")
|
||||
}
|
||||
val (aggs, aggCommand) = parseSlashCommand(x)(rawCmd)
|
||||
aggs
|
||||
.intersect(affectedRefs)
|
||||
.map({ case ProjectRef(_, proj) => s"$proj/$aggCommand" })
|
||||
.mkString("all ", " ", "")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import sbt.librarymanagement.syntax._
|
|||
import sbt.util.InterfaceUtil.{ toJavaFunction => f1 }
|
||||
import sbt.util._
|
||||
import sbt.util.CacheImplicits._
|
||||
import scala.collection.immutable.ListMap
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.control.NonFatal
|
||||
import scala.xml.NodeSeq
|
||||
|
|
@ -159,6 +160,9 @@ object Defaults extends BuildCommon {
|
|||
scalaHome :== None,
|
||||
apiURL := None,
|
||||
javaHome :== None,
|
||||
discoveredJavaHomes := CrossJava.discoverJavaHomes,
|
||||
javaHomes :== ListMap.empty,
|
||||
fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value),
|
||||
testForkedParallel :== false,
|
||||
javaOptions :== Nil,
|
||||
sbtPlugin :== false,
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ object Keys {
|
|||
val scalaCompilerBridgeSource = settingKey[ModuleID]("Configures the module ID of the sources of the compiler bridge.").withRank(CSetting)
|
||||
val scalaArtifacts = settingKey[Seq[String]]("Configures the list of artifacts which should match the Scala binary version").withRank(CSetting)
|
||||
val enableBinaryCompileAnalysis = settingKey[Boolean]("Writes the analysis file in binary format")
|
||||
val crossJavaVersions = settingKey[Seq[String]]("The java versions used during JDK cross testing").withRank(BPlusSetting)
|
||||
|
||||
val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.").withRank(APlusTask)
|
||||
val console = taskKey[Unit]("Starts the Scala interpreter with the project classes on the classpath.").withRank(APlusTask)
|
||||
|
|
@ -271,6 +272,10 @@ object Keys {
|
|||
val outputStrategy = settingKey[Option[sbt.OutputStrategy]]("Selects how to log output when running a main class.").withRank(DSetting)
|
||||
val connectInput = settingKey[Boolean]("If true, connects standard input when running a main class forked.").withRank(CSetting)
|
||||
val javaHome = settingKey[Option[File]]("Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.").withRank(ASetting)
|
||||
val discoveredJavaHomes = settingKey[Map[String, File]]("Discovered Java home directories")
|
||||
val javaHomes = settingKey[Map[String, File]]("The user-defined additional Java home directories")
|
||||
val fullJavaHomes = settingKey[Map[String, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask)
|
||||
|
||||
val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask)
|
||||
val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import sbt.internal.{
|
|||
BuildUnit,
|
||||
CommandExchange,
|
||||
CommandStrings,
|
||||
CrossJava,
|
||||
DefaultBackgroundJobService,
|
||||
EvaluateConfigurations,
|
||||
Inspect,
|
||||
|
|
@ -190,6 +191,8 @@ object BuiltinCommands {
|
|||
oldLoadFailed,
|
||||
Cross.crossBuild,
|
||||
Cross.switchVersion,
|
||||
CrossJava.switchJavaHome,
|
||||
CrossJava.crossJavaHome,
|
||||
PluginCross.pluginCross,
|
||||
PluginCross.pluginSwitch,
|
||||
Cross.crossRestoreSession,
|
||||
|
|
|
|||
|
|
@ -415,4 +415,29 @@ $SwitchCommand [<scala-version>=]<scala-home>[!] [-v] [<command>]
|
|||
|
||||
See also `help $CrossCommand`
|
||||
"""
|
||||
|
||||
val JavaCrossCommand = "java+"
|
||||
val JavaSwitchCommand = "java++"
|
||||
|
||||
def javaCrossHelp: Help = Help.more(JavaCrossCommand, JavaCrossDetailed)
|
||||
def javaSwitchHelp: Help = Help.more(JavaSwitchCommand, JavaSwitchDetailed)
|
||||
|
||||
def JavaCrossDetailed =
|
||||
s"""$JavaCrossCommand <command>
|
||||
Runs <command> for each JDK version specified for cross-JDK testing.
|
||||
For each string in `crossJavaVersions` in the current project, this command sets the
|
||||
`javaHome` of all projects to the corresponding Java home, reloads the build,
|
||||
and executes <command>. When finished, it reloads the build with the original
|
||||
`javaHome`.
|
||||
Note that `Test / fork := true` is needed for `javaHome` to be effective.
|
||||
See also `help $JavaSwitchCommand`
|
||||
"""
|
||||
|
||||
def JavaSwitchDetailed =
|
||||
s"""$JavaSwitchCommand <java-version>
|
||||
Changes the JDK version and runs a command.
|
||||
Sets the `javaHome` of all projects to <java-version> and
|
||||
reloads the build. If <command> is provided, it is then executed.
|
||||
See also `help $JavaSwitchCommand`
|
||||
"""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2017, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under BSD-3-Clause license (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.immutable.ListMap
|
||||
import sbt.io.Path
|
||||
import sbt.io.syntax._
|
||||
import sbt.Cross._
|
||||
import sbt.Def.{ ScopedKey, Setting }
|
||||
import sbt.internal.util.complete.DefaultParsers._
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.util.complete.{ DefaultParsers, Parser }
|
||||
import sbt.internal.CommandStrings.{
|
||||
JavaCrossCommand,
|
||||
JavaSwitchCommand,
|
||||
javaCrossHelp,
|
||||
javaSwitchHelp
|
||||
}
|
||||
|
||||
private[sbt] object CrossJava {
|
||||
// parses jabaa style version number adopt@1.8
|
||||
def parseJavaVersion(version: String): JavaVersion = {
|
||||
def splitDot(s: String): Vector[Long] =
|
||||
Option(s) match {
|
||||
case Some(x) => x.split('.').toVector.filterNot(_ == "").map(_.toLong)
|
||||
case _ => Vector()
|
||||
}
|
||||
def splitAt(s: String): Vector[String] =
|
||||
Option(s) match {
|
||||
case Some(x) => x.split('@').toVector
|
||||
case _ => Vector()
|
||||
}
|
||||
splitAt(version) match {
|
||||
case Vector(vendor, rest) => JavaVersion(splitDot(rest), Option(vendor))
|
||||
case Vector(rest) => JavaVersion(splitDot(rest), None)
|
||||
case _ => sys.error(s"Invalid JavaVersion: $version")
|
||||
}
|
||||
}
|
||||
|
||||
def lookupJavaHome(jv: String, mappings: Map[String, File]): File = {
|
||||
val ms = mappings map { case (k, v) => (JavaVersion(k), v) }
|
||||
lookupJavaHome(JavaVersion(jv), ms)
|
||||
}
|
||||
|
||||
def lookupJavaHome(jv: JavaVersion, mappings: Map[JavaVersion, File]): File = {
|
||||
mappings.get(jv) match {
|
||||
case Some(dir) => dir
|
||||
|
||||
// when looking for "10" it should match "openjdk@10"
|
||||
case None if jv.vendor.isEmpty =>
|
||||
val noVendors: Map[JavaVersion, File] = mappings map {
|
||||
case (k, v) => k.withVendor(None) -> v
|
||||
}
|
||||
noVendors.get(jv).getOrElse(javaHomeNotFound(jv, mappings))
|
||||
case _ => javaHomeNotFound(jv, mappings)
|
||||
}
|
||||
}
|
||||
|
||||
private def javaHomeNotFound(version: JavaVersion, mappings: Map[JavaVersion, File]): Nothing = {
|
||||
sys.error(s"""Java home for $version was not found in $mappings
|
||||
|
|
||||
|use Global / javaHomes += JavaVersion("$version") -> file(...)""".stripMargin)
|
||||
}
|
||||
|
||||
private case class SwitchTarget(version: Option[JavaVersion], home: Option[File], force: Boolean)
|
||||
private case class SwitchJavaHome(target: SwitchTarget, verbose: Boolean, command: Option[String])
|
||||
|
||||
private def switchParser(state: State): Parser[SwitchJavaHome] = {
|
||||
import DefaultParsers._
|
||||
def versionAndCommand(spacePresent: Boolean) = {
|
||||
val x = Project.extract(state)
|
||||
import x._
|
||||
val javaHomes = getJavaHomesTyped(x, currentRef)
|
||||
val knownVersions = javaHomes.keysIterator.map(_.numberStr).toVector
|
||||
val version: Parser[SwitchTarget] =
|
||||
(token(
|
||||
(StringBasic <~ "@").? ~ ((NatBasic) ~ ("." ~> NatBasic).*)
|
||||
.examples(knownVersions: _*) ~ "!".?
|
||||
) || token(StringBasic))
|
||||
.map {
|
||||
case Left(((vendor, (v1, vs)), bang)) =>
|
||||
val force = bang.isDefined
|
||||
val versionArg = (Vector(v1) ++ vs) map { _.toLong }
|
||||
SwitchTarget(Option(JavaVersion(versionArg, vendor)), None, force)
|
||||
case Right(home) =>
|
||||
SwitchTarget(None, Option(new File(home)), true)
|
||||
}
|
||||
val spacedVersion =
|
||||
if (spacePresent) version
|
||||
else version & spacedFirst(JavaSwitchCommand)
|
||||
val verbose = Parser.opt(token(Space ~> "-v"))
|
||||
val optionalCommand = Parser.opt(token(Space ~> matched(state.combinedParser)))
|
||||
(spacedVersion ~ verbose ~ optionalCommand).map {
|
||||
case v ~ verbose ~ command =>
|
||||
SwitchJavaHome(v, verbose.isDefined, command)
|
||||
}
|
||||
}
|
||||
token(JavaSwitchCommand ~> OptSpace) flatMap { sp =>
|
||||
versionAndCommand(sp.nonEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
private def getJavaHomes(
|
||||
extracted: Extracted,
|
||||
proj: ResolvedReference
|
||||
): Map[String, File] = {
|
||||
import extracted._
|
||||
(Keys.fullJavaHomes in proj get structure.data).get
|
||||
}
|
||||
|
||||
private def getJavaHomesTyped(
|
||||
extracted: Extracted,
|
||||
proj: ResolvedReference
|
||||
): Map[JavaVersion, File] = {
|
||||
getJavaHomes(extracted, proj) map { case (k, v) => (JavaVersion(k), v) }
|
||||
}
|
||||
|
||||
private def getCrossJavaVersions(
|
||||
extracted: Extracted,
|
||||
proj: ResolvedReference
|
||||
): Seq[String] = {
|
||||
import extracted._
|
||||
import Keys._
|
||||
(crossJavaVersions in proj get structure.data).getOrElse(Nil)
|
||||
}
|
||||
|
||||
private def getCrossJavaHomes(extracted: Extracted, proj: ResolvedReference): Seq[File] = {
|
||||
import extracted._
|
||||
import Keys._
|
||||
val fjh = (fullJavaHomes in proj get structure.data).get
|
||||
(crossJavaVersions in proj get structure.data) map { jvs =>
|
||||
jvs map { jv =>
|
||||
lookupJavaHome(jv, fjh)
|
||||
}
|
||||
} getOrElse Vector()
|
||||
}
|
||||
|
||||
private def switchCommandImpl(state: State, switch: SwitchJavaHome): State = {
|
||||
val extracted = Project.extract(state)
|
||||
import extracted._
|
||||
import Keys.javaHome
|
||||
|
||||
// filter out subprojects based on switch target e.g. "10" vs what's in crossJavaVersions
|
||||
// for the subproject. Only if crossJavaVersions is non-empty, and does NOT include "10"
|
||||
// it will skip the subproject.
|
||||
val projects: Seq[(ResolvedReference, Seq[String])] = {
|
||||
val projectJavaVersions =
|
||||
structure.allProjectRefs.map(proj => proj -> getCrossJavaVersions(extracted, proj))
|
||||
if (switch.target.force) projectJavaVersions
|
||||
else
|
||||
switch.target.version match {
|
||||
case None => projectJavaVersions
|
||||
case Some(v) =>
|
||||
projectJavaVersions flatMap {
|
||||
case (proj, versions) =>
|
||||
if (versions.isEmpty || versions.contains(v)) Vector(proj -> versions)
|
||||
else Vector()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setJavaHomeForProjects: State = {
|
||||
val newSettings = projects.flatMap {
|
||||
case (proj, javaVersions) =>
|
||||
val fjh = getJavaHomesTyped(extracted, proj)
|
||||
val home = switch.target match {
|
||||
case SwitchTarget(Some(v), _, _) => lookupJavaHome(v, fjh)
|
||||
case SwitchTarget(_, Some(h), _) => h
|
||||
case _ => sys.error(s"unexpected ${switch.target}")
|
||||
}
|
||||
val scope = Scope(Select(proj), Zero, Zero, Zero)
|
||||
Seq(
|
||||
javaHome in scope := Some(home)
|
||||
)
|
||||
}
|
||||
|
||||
val filterKeys: Set[AttributeKey[_]] = Set(javaHome).map(_.key)
|
||||
|
||||
val projectsContains: Reference => Boolean = projects.map(_._1).toSet.contains
|
||||
|
||||
// Filter out any old javaHome version settings that were added, this is just for hygiene.
|
||||
val filteredRawAppend = session.rawAppend.filter(_.key match {
|
||||
case ScopedKey(Scope(Select(ref), Zero, Zero, Zero), key)
|
||||
if filterKeys.contains(key) && projectsContains(ref) =>
|
||||
false
|
||||
case _ => true
|
||||
})
|
||||
|
||||
val newSession = session.copy(rawAppend = filteredRawAppend ++ newSettings)
|
||||
|
||||
BuiltinCommands.reapply(newSession, structure, state)
|
||||
}
|
||||
|
||||
setJavaHomeForProjects
|
||||
}
|
||||
|
||||
def switchJavaHome: Command =
|
||||
Command.arb(requireSession(switchParser), javaSwitchHelp)(switchCommandImpl)
|
||||
|
||||
def crossJavaHome: Command =
|
||||
Command.arb(requireSession(crossParser), javaCrossHelp)(crossJavaHomeCommandImpl)
|
||||
|
||||
private case class CrossArgs(command: String, verbose: Boolean)
|
||||
|
||||
/**
|
||||
* Parse the given command into either an aggregate command or a command for a project
|
||||
*/
|
||||
private def crossParser(state: State): Parser[CrossArgs] =
|
||||
token(JavaCrossCommand <~ OptSpace) flatMap { _ =>
|
||||
(token(Parser.opt("-v" <~ Space)) ~ token(matched(state.combinedParser))).map {
|
||||
case (verbose, command) => CrossArgs(command, verbose.isDefined)
|
||||
} & spacedFirst(JavaCrossCommand)
|
||||
}
|
||||
|
||||
private def crossJavaHomeCommandImpl(state: State, args: CrossArgs): State = {
|
||||
val x = Project.extract(state)
|
||||
import x._
|
||||
val (aggs, aggCommand) = Cross.parseSlashCommand(x)(args.command)
|
||||
val projCrossVersions = aggs map { proj =>
|
||||
proj -> getCrossJavaHomes(x, proj)
|
||||
}
|
||||
// if we support javaHome, 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 verbose = ""
|
||||
// println(s"projVersions $projVersions")
|
||||
|
||||
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 Java cross building command, but not all sub projects have the same cross build " +
|
||||
"configuration. This could result in subprojects cross building against Java 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 Java version combinations " +
|
||||
"that are configured."
|
||||
)
|
||||
state.log.debug("Java 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"$JavaSwitchCommand $verbose $version!", aggCommand)
|
||||
}
|
||||
|
||||
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"$JavaSwitchCommand $verbose $version", s"$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"$JavaSwitchCommand $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"$JavaSwitchCommand $verbose $version",
|
||||
projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
allCommands.toList ::: captureCurrentSession(state, x)
|
||||
}
|
||||
}
|
||||
|
||||
private val JavaCapturedSession = AttributeKey[Seq[Setting[_]]]("javaCrossCapturedSession")
|
||||
|
||||
private def captureCurrentSession(state: State, extracted: Extracted): State = {
|
||||
state.put(JavaCapturedSession, extracted.session.rawAppend)
|
||||
}
|
||||
|
||||
def discoverJavaHomes: ListMap[String, File] = {
|
||||
import JavaDiscoverConfig._
|
||||
val configs = Vector(jabba, linux, macOS)
|
||||
ListMap(configs flatMap { _.javaHomes }: _*)
|
||||
}
|
||||
|
||||
sealed trait JavaDiscoverConf {
|
||||
def javaHomes: Vector[(String, File)]
|
||||
}
|
||||
|
||||
object JavaDiscoverConfig {
|
||||
val linux = new JavaDiscoverConf {
|
||||
val base: File = file("/usr") / "lib" / "jvm"
|
||||
val JavaHomeDir = """java-([0-9]+)-.*""".r
|
||||
def javaHomes: Vector[(String, File)] =
|
||||
wrapNull(base.list()).collect {
|
||||
case dir @ JavaHomeDir(ver) => JavaVersion(ver).toString -> (base / dir)
|
||||
}
|
||||
}
|
||||
|
||||
val macOS = new JavaDiscoverConf {
|
||||
val base: File = file("/Library") / "Java" / "JavaVirtualMachines"
|
||||
val JavaHomeDir = """jdk-?(1\.)?([0-9]+).*""".r
|
||||
def javaHomes: Vector[(String, File)] =
|
||||
wrapNull(base.list()).collect {
|
||||
case dir @ JavaHomeDir(m, n) =>
|
||||
JavaVersion(nullBlank(m) + n).toString -> (base / dir / "Contents" / "Home")
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/shyiko/jabba
|
||||
val jabba = new JavaDiscoverConf {
|
||||
val base: File = Path.userHome / ".jabba" / "jdk"
|
||||
val JavaHomeDir = """([\w\-]+)\@(1\.)?([0-9]+).*""".r
|
||||
def javaHomes: Vector[(String, File)] =
|
||||
wrapNull(base.list()).collect {
|
||||
case dir @ JavaHomeDir(vendor, m, n) =>
|
||||
val v = JavaVersion(nullBlank(m) + n).withVendor(vendor).toString
|
||||
if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home")
|
||||
else v -> (base / dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def nullBlank(s: String): String =
|
||||
if (s eq null) ""
|
||||
else s
|
||||
|
||||
// expand Java versions to 1-20 to 1.x, and vice versa to accept both "1.8" and "8"
|
||||
private val oneDot = Map((1L to 20L).toVector flatMap { i =>
|
||||
Vector(Vector(i) -> Vector(1L, i), Vector(1L, i) -> Vector(i))
|
||||
}: _*)
|
||||
def expandJavaHomes(hs: Map[String, File]): Map[String, File] =
|
||||
hs flatMap {
|
||||
case (k, v) =>
|
||||
val jv = JavaVersion(k)
|
||||
if (oneDot.contains(jv.numbers))
|
||||
Vector(k -> v, jv.withNumbers(oneDot(jv.numbers)).toString -> v)
|
||||
else Vector(k -> v)
|
||||
}
|
||||
|
||||
def wrapNull(a: Array[String]): Vector[String] =
|
||||
if (a eq null) Vector()
|
||||
else a.toVector
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package pkg
|
||||
|
||||
import java.nio.file.{ Paths, Files }
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object A extends App {
|
||||
val out = Paths.get("out.txt")
|
||||
val content = sys.props("java.version")
|
||||
val w = Files.newBufferedWriter(out, Charset.forName("UTF-8"))
|
||||
try {
|
||||
w.write(content)
|
||||
w.flush()
|
||||
} finally {
|
||||
w.close
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import complete.DefaultParsers._
|
||||
|
||||
val check = inputKey[Unit]("Runs the check")
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
ThisBuild / scalaVersion := "2.12.6",
|
||||
crossJavaVersions := List("1.8"),
|
||||
|
||||
// read out.txt and see if it starts with the passed in number
|
||||
check := {
|
||||
val arg1: Int = (Space ~> NatBasic).parsed
|
||||
file("out.txt") match {
|
||||
case out if out.exists =>
|
||||
IO.readLines(out).headOption match {
|
||||
case Some(v) if v startsWith arg1.toString => ()
|
||||
case Some(v) if v startsWith s"1.$arg1" => ()
|
||||
case x => sys.error(s"unexpected value: $x")
|
||||
}
|
||||
case out => sys.error(s"$out doesn't exist")
|
||||
}
|
||||
},
|
||||
|
||||
Compile / run / fork := true,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
> java+ run
|
||||
> check 8
|
||||
|
||||
> java++ 10!
|
||||
> run
|
||||
> check 10
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Global / javaHomes += "6" -> file("/good/old/times/java-6")
|
||||
|
||||
TaskKey[Unit]("check") := {
|
||||
assert(fullJavaHomes.value("1.6").getAbsolutePath.contains("java-6"))
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -206,6 +206,7 @@ final class ScriptedTests(
|
|||
case "dependency-management/update-sbt-classifiers" => LauncherBased // tbd
|
||||
case "dependency-management/url" => LauncherBased // tbd
|
||||
case "java/argfile" => LauncherBased // sbt/Package$
|
||||
case "java/cross" => LauncherBased // sbt/Package$
|
||||
case "java/basic" => LauncherBased // sbt/Package$
|
||||
case "java/varargs-main" => LauncherBased // sbt/Package$
|
||||
case "package/lazy-name" => LauncherBased // sbt/Package$
|
||||
|
|
|
|||
Loading…
Reference in New Issue