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:
|
directories:
|
||||||
- $HOME/.ivy2/cache
|
- $HOME/.ivy2/cache
|
||||||
- $HOME/.sbt/boot
|
- $HOME/.sbt/boot
|
||||||
|
- $HOME/.jabba
|
||||||
|
|
||||||
language: scala
|
language: scala
|
||||||
|
|
||||||
|
|
@ -15,6 +16,14 @@ jdk:
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
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:
|
env:
|
||||||
global:
|
global:
|
||||||
- secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs=
|
- secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs=
|
||||||
|
|
@ -26,7 +35,7 @@ env:
|
||||||
- SBT_CMD="scripted dependency-management/*2of4"
|
- SBT_CMD="scripted dependency-management/*2of4"
|
||||||
- SBT_CMD="scripted dependency-management/*3of4"
|
- SBT_CMD="scripted dependency-management/*3of4"
|
||||||
- SBT_CMD="scripted dependency-management/*4of4"
|
- 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/*1of2"
|
||||||
- SBT_CMD="scripted project/*2of2"
|
- SBT_CMD="scripted project/*2of2"
|
||||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
- 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
|
AllRequirements
|
||||||
NoTrigger
|
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._
|
import DefaultParsers._
|
||||||
val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map {
|
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 =
|
def crossBuild: Command =
|
||||||
|
|
@ -115,12 +125,7 @@ object Cross {
|
||||||
private def crossBuildCommandImpl(state: State, args: CrossArgs): State = {
|
private def crossBuildCommandImpl(state: State, args: CrossArgs): State = {
|
||||||
val x = Project.extract(state)
|
val x = Project.extract(state)
|
||||||
import x._
|
import x._
|
||||||
|
val (aggs, aggCommand) = parseSlashCommand(x)(args.command)
|
||||||
val (aggs, aggCommand) = parseCommand(args.command) match {
|
|
||||||
case Right((project, cmd)) =>
|
|
||||||
(structure.allProjectRefs.filter(_.project == project), cmd)
|
|
||||||
case Left(cmd) => (resolveAggregates(x), cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
val projCrossVersions = aggs map { proj =>
|
val projCrossVersions = aggs map { proj =>
|
||||||
proj -> crossVersions(x, proj)
|
proj -> crossVersions(x, proj)
|
||||||
|
|
@ -229,14 +234,11 @@ object Cross {
|
||||||
args.command
|
args.command
|
||||||
} else {
|
} else {
|
||||||
args.command.map { rawCmd =>
|
args.command.map { rawCmd =>
|
||||||
parseCommand(rawCmd) match {
|
val (aggs, aggCommand) = parseSlashCommand(x)(rawCmd)
|
||||||
case Right(_) => rawCmd // A project is specified, run as is
|
aggs
|
||||||
case Left(cmd) =>
|
.intersect(affectedRefs)
|
||||||
resolveAggregates(x)
|
.map({ case ProjectRef(_, proj) => s"$proj/$aggCommand" })
|
||||||
.intersect(affectedRefs)
|
.mkString("all ", " ", "")
|
||||||
.collect { case ProjectRef(_, proj) => s"$proj/$cmd" }
|
|
||||||
.mkString("all ", " ", "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import sbt.librarymanagement.syntax._
|
||||||
import sbt.util.InterfaceUtil.{ toJavaFunction => f1 }
|
import sbt.util.InterfaceUtil.{ toJavaFunction => f1 }
|
||||||
import sbt.util._
|
import sbt.util._
|
||||||
import sbt.util.CacheImplicits._
|
import sbt.util.CacheImplicits._
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
import scala.xml.NodeSeq
|
import scala.xml.NodeSeq
|
||||||
|
|
@ -159,6 +160,9 @@ object Defaults extends BuildCommon {
|
||||||
scalaHome :== None,
|
scalaHome :== None,
|
||||||
apiURL := None,
|
apiURL := None,
|
||||||
javaHome :== None,
|
javaHome :== None,
|
||||||
|
discoveredJavaHomes := CrossJava.discoverJavaHomes,
|
||||||
|
javaHomes :== ListMap.empty,
|
||||||
|
fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value),
|
||||||
testForkedParallel :== false,
|
testForkedParallel :== false,
|
||||||
javaOptions :== Nil,
|
javaOptions :== Nil,
|
||||||
sbtPlugin :== false,
|
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 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 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 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 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)
|
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 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 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 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 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)
|
val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import sbt.internal.{
|
||||||
BuildUnit,
|
BuildUnit,
|
||||||
CommandExchange,
|
CommandExchange,
|
||||||
CommandStrings,
|
CommandStrings,
|
||||||
|
CrossJava,
|
||||||
DefaultBackgroundJobService,
|
DefaultBackgroundJobService,
|
||||||
EvaluateConfigurations,
|
EvaluateConfigurations,
|
||||||
Inspect,
|
Inspect,
|
||||||
|
|
@ -190,6 +191,8 @@ object BuiltinCommands {
|
||||||
oldLoadFailed,
|
oldLoadFailed,
|
||||||
Cross.crossBuild,
|
Cross.crossBuild,
|
||||||
Cross.switchVersion,
|
Cross.switchVersion,
|
||||||
|
CrossJava.switchJavaHome,
|
||||||
|
CrossJava.crossJavaHome,
|
||||||
PluginCross.pluginCross,
|
PluginCross.pluginCross,
|
||||||
PluginCross.pluginSwitch,
|
PluginCross.pluginSwitch,
|
||||||
Cross.crossRestoreSession,
|
Cross.crossRestoreSession,
|
||||||
|
|
|
||||||
|
|
@ -415,4 +415,29 @@ $SwitchCommand [<scala-version>=]<scala-home>[!] [-v] [<command>]
|
||||||
|
|
||||||
See also `help $CrossCommand`
|
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/update-sbt-classifiers" => LauncherBased // tbd
|
||||||
case "dependency-management/url" => LauncherBased // tbd
|
case "dependency-management/url" => LauncherBased // tbd
|
||||||
case "java/argfile" => LauncherBased // sbt/Package$
|
case "java/argfile" => LauncherBased // sbt/Package$
|
||||||
|
case "java/cross" => LauncherBased // sbt/Package$
|
||||||
case "java/basic" => LauncherBased // sbt/Package$
|
case "java/basic" => LauncherBased // sbt/Package$
|
||||||
case "java/varargs-main" => LauncherBased // sbt/Package$
|
case "java/varargs-main" => LauncherBased // sbt/Package$
|
||||||
case "package/lazy-name" => LauncherBased // sbt/Package$
|
case "package/lazy-name" => LauncherBased // sbt/Package$
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue