mirror of https://github.com/sbt/sbt.git
Merge pull request #4580 from eed3si9n/wip/sdkman
detection of SDKMAN managed JDKs
This commit is contained in:
commit
cdd264a5cb
|
|
@ -6,26 +6,32 @@
|
||||||
package sbt
|
package sbt
|
||||||
final class JavaVersion private (
|
final class JavaVersion private (
|
||||||
val numbers: Vector[Long],
|
val numbers: Vector[Long],
|
||||||
|
val tags: Vector[String],
|
||||||
val vendor: Option[String]) extends Serializable {
|
val vendor: Option[String]) extends Serializable {
|
||||||
def numberStr: String = numbers.mkString(".")
|
def numberStr: String = numbers.mkString(".")
|
||||||
|
private def tagStr: String = if (tags.isEmpty) "" else tags.mkString("-", "-", "")
|
||||||
|
private def this() = this(Vector(), Vector(), None)
|
||||||
|
private def this(numbers: Vector[Long], vendor: Option[String]) = this(numbers, Vector(), vendor)
|
||||||
|
|
||||||
override def equals(o: Any): Boolean = o match {
|
override def equals(o: Any): Boolean = o match {
|
||||||
case x: JavaVersion => (this.numbers == x.numbers) && (this.vendor == x.vendor)
|
case x: JavaVersion => (this.numbers == x.numbers) && (this.tags == x.tags) && (this.vendor == x.vendor)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
override def hashCode: Int = {
|
override def hashCode: Int = {
|
||||||
37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + numbers.##) + vendor.##)
|
37 * (37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + numbers.##) + tags.##) + vendor.##)
|
||||||
}
|
}
|
||||||
override def toString: String = {
|
override def toString: String = {
|
||||||
vendor.map(_ + "@").getOrElse("") + numberStr
|
vendor.map(_ + "@").getOrElse("") + numberStr + tagStr
|
||||||
}
|
}
|
||||||
private[this] def copy(numbers: Vector[Long] = numbers, vendor: Option[String] = vendor): JavaVersion = {
|
private[this] def copy(numbers: Vector[Long] = numbers, tags: Vector[String] = tags, vendor: Option[String] = vendor): JavaVersion = {
|
||||||
new JavaVersion(numbers, vendor)
|
new JavaVersion(numbers, tags, vendor)
|
||||||
}
|
}
|
||||||
def withNumbers(numbers: Vector[Long]): JavaVersion = {
|
def withNumbers(numbers: Vector[Long]): JavaVersion = {
|
||||||
copy(numbers = numbers)
|
copy(numbers = numbers)
|
||||||
}
|
}
|
||||||
|
def withTags(tags: Vector[String]): JavaVersion = {
|
||||||
|
copy(tags = tags)
|
||||||
|
}
|
||||||
def withVendor(vendor: Option[String]): JavaVersion = {
|
def withVendor(vendor: Option[String]): JavaVersion = {
|
||||||
copy(vendor = vendor)
|
copy(vendor = vendor)
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +41,9 @@ final class JavaVersion private (
|
||||||
}
|
}
|
||||||
object JavaVersion {
|
object JavaVersion {
|
||||||
def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
||||||
|
def apply(): JavaVersion = new JavaVersion()
|
||||||
def apply(numbers: Vector[Long], vendor: Option[String]): JavaVersion = new JavaVersion(numbers, vendor)
|
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))
|
def apply(numbers: Vector[Long], vendor: String): JavaVersion = new JavaVersion(numbers, Option(vendor))
|
||||||
|
def apply(numbers: Vector[Long], tags: Vector[String], vendor: Option[String]): JavaVersion = new JavaVersion(numbers, tags, vendor)
|
||||||
|
def apply(numbers: Vector[Long], tags: Vector[String], vendor: String): JavaVersion = new JavaVersion(numbers, tags, Option(vendor))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ enum PluginTrigger {
|
||||||
}
|
}
|
||||||
|
|
||||||
type JavaVersion {
|
type JavaVersion {
|
||||||
numbers: [Long]
|
numbers: [Long] @since("1.2.0")
|
||||||
vendor: String
|
tags: [String] @since("1.3.0")
|
||||||
|
vendor: String @since("1.2.0")
|
||||||
|
|
||||||
#x def numberStr: String = numbers.mkString(".")
|
#x def numberStr: String = numbers.mkString(".")
|
||||||
#xtostring vendor.map(_ + "@").getOrElse("") + numberStr
|
#x private def tagStr: String = if (tags.isEmpty) "" else tags.mkString("-", "-", "")
|
||||||
|
#xtostring vendor.map(_ + "@").getOrElse("") + numberStr + tagStr
|
||||||
|
|
||||||
#xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
#xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,49 @@ private[sbt] object CrossJava {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def parseSdkmanString(version: String): JavaVersion = {
|
||||||
|
val Num = """([0-9]+)""".r
|
||||||
|
def splitDash(str: String): Vector[String] =
|
||||||
|
Option(str) match {
|
||||||
|
case Some(x) => x.split('-').toVector
|
||||||
|
case _ => Vector()
|
||||||
|
}
|
||||||
|
def splitDot(str: String): Vector[String] =
|
||||||
|
Option(str) match {
|
||||||
|
case Some(x) => x.split('.').toVector.filterNot(_ == "")
|
||||||
|
case _ => Vector()
|
||||||
|
}
|
||||||
|
splitDash(version) match {
|
||||||
|
case xs if xs.size < 2 => sys.error(s"Invalid SDKMAN Java version: $version")
|
||||||
|
case xs =>
|
||||||
|
val ds = splitDot(xs.init.head)
|
||||||
|
val nums = ds.takeWhile(
|
||||||
|
_ match {
|
||||||
|
case Num(_) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
) map { _.toLong }
|
||||||
|
val nonNum = ds.drop(nums.size).mkString("")
|
||||||
|
// last dash indicates vendor code
|
||||||
|
val (vnd0, tag0) = (xs.last, nonNum) match {
|
||||||
|
case ("adpt", "hs") => ("adpt", "")
|
||||||
|
case ("adpt", "j9") => ("adopt-openj9", "")
|
||||||
|
case (v, t) => (v, t)
|
||||||
|
}
|
||||||
|
val vnd = vnd0 match {
|
||||||
|
case "adpt" => "adopt"
|
||||||
|
case "open" => "openjdk"
|
||||||
|
case "grl" => "graalvm"
|
||||||
|
case "amzn" => "corretto"
|
||||||
|
case _ => vnd0
|
||||||
|
}
|
||||||
|
val tag1: String = xs.init.tail.mkString("")
|
||||||
|
val tags = (if (tag0 == "") Vector.empty[String] else Vector(tag0)) ++
|
||||||
|
(if (tag1 == "") Vector.empty[String] else Vector(tag1))
|
||||||
|
JavaVersion().withNumbers(nums).withVendor(vnd).withTags(tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def lookupJavaHome(jv: String, mappings: Map[String, File]): File = {
|
def lookupJavaHome(jv: String, mappings: Map[String, File]): File = {
|
||||||
val ms = mappings map { case (k, v) => (JavaVersion(k), v) }
|
val ms = mappings map { case (k, v) => (JavaVersion(k), v) }
|
||||||
lookupJavaHome(JavaVersion(jv), ms)
|
lookupJavaHome(JavaVersion(jv), ms)
|
||||||
|
|
@ -377,6 +420,18 @@ private[sbt] object CrossJava {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SdkmanDiscoverConfig extends JavaDiscoverConf {
|
||||||
|
val base: File = Path.userHome / ".sdkman" / "candidates" / "java"
|
||||||
|
def candidates(): Vector[String] = wrapNull(base.list())
|
||||||
|
def javaHomes: Vector[(String, File)] =
|
||||||
|
candidates
|
||||||
|
.collect {
|
||||||
|
case dir if dir.contains("-") =>
|
||||||
|
val v = CrossJava.parseSdkmanString(dir).toString
|
||||||
|
v -> (base / dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WindowsDiscoverConfig(base: File) extends JavaDiscoverConf {
|
class WindowsDiscoverConfig(base: File) extends JavaDiscoverConf {
|
||||||
|
|
||||||
def candidates() = wrapNull(base.list())
|
def candidates() = wrapNull(base.list())
|
||||||
|
|
@ -408,6 +463,7 @@ private[sbt] object CrossJava {
|
||||||
|
|
||||||
val configs = Vector(
|
val configs = Vector(
|
||||||
new JabbaDiscoverConfig,
|
new JabbaDiscoverConfig,
|
||||||
|
new SdkmanDiscoverConfig,
|
||||||
new LinuxDiscoverConfig(file("/usr") / "java"),
|
new LinuxDiscoverConfig(file("/usr") / "java"),
|
||||||
new LinuxDiscoverConfig(file("/usr") / "lib" / "jvm"),
|
new LinuxDiscoverConfig(file("/usr") / "lib" / "jvm"),
|
||||||
new MacOsDiscoverConfig,
|
new MacOsDiscoverConfig,
|
||||||
|
|
@ -421,18 +477,34 @@ private[sbt] object CrossJava {
|
||||||
if (s eq null) ""
|
if (s eq null) ""
|
||||||
else s
|
else s
|
||||||
|
|
||||||
// expand Java versions to 1-20 to 1.x, and vice versa to accept both "1.8" and "8"
|
def expandJavaHomes(hs: Map[String, File]): Map[String, File] = {
|
||||||
private val oneDot = Map((1L to 20L).toVector flatMap { i =>
|
val parsed = hs map {
|
||||||
Vector(Vector(i) -> Vector(1L, i), Vector(1L, i) -> Vector(i))
|
case (k, v) => JavaVersion(k) -> v
|
||||||
}: _*)
|
}
|
||||||
def expandJavaHomes(hs: Map[String, File]): Map[String, File] =
|
// first ignore vnd
|
||||||
hs flatMap {
|
val withAndWithoutVnd = parsed flatMap {
|
||||||
case (k, v) =>
|
case (k, v) =>
|
||||||
val jv = JavaVersion(k)
|
if (k.vendor.isDefined) Vector(k -> v, k.withVendor(None) -> v)
|
||||||
if (oneDot.contains(jv.numbers))
|
|
||||||
Vector(k -> v, jv.withNumbers(oneDot(jv.numbers)).toString -> v)
|
|
||||||
else Vector(k -> v)
|
else Vector(k -> v)
|
||||||
}
|
}
|
||||||
|
val normalizeNumbers = withAndWithoutVnd flatMap {
|
||||||
|
case (k, v) =>
|
||||||
|
k.numbers match {
|
||||||
|
case Vector(1L, minor, _*) =>
|
||||||
|
Vector(k -> v, k.withNumbers(Vector(minor)) -> v)
|
||||||
|
case Vector(major) if major > 1 =>
|
||||||
|
Vector(k -> v, k.withNumbers(Vector(1L, major)) -> v)
|
||||||
|
case Vector(major, minor, _*) if major > 1 =>
|
||||||
|
Vector(k -> v, k.withNumbers(Vector(major)) -> v, k.withNumbers(Vector(1L, major)) -> v)
|
||||||
|
case _ =>
|
||||||
|
Vector(k -> v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result: Map[String, File] = normalizeNumbers map {
|
||||||
|
case (k, v) => (k.toString -> v)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
def wrapNull(a: Array[String]): Vector[String] =
|
def wrapNull(a: Array[String]): Vector[String] =
|
||||||
if (a eq null) Vector()
|
if (a eq null) Vector()
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,26 @@
|
||||||
* Licensed under Apache License 2.0 (see LICENSE)
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbt.internal
|
package sbt
|
||||||
|
package internal
|
||||||
|
|
||||||
import org.specs2.mutable.Specification
|
import org.scalatest._
|
||||||
import sbt.internal.CrossJava.JavaDiscoverConfig._
|
import sbt.internal.CrossJava.JavaDiscoverConfig._
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
|
||||||
class CrossJavaTest extends Specification {
|
class CrossJavaTest extends FunSuite with DiagrammedAssertions {
|
||||||
"The Java home selector" should {
|
test("The Java home selector should select the most recent") {
|
||||||
"select the most recent" in {
|
assert(
|
||||||
List("jdk1.8.0.jdk", "jdk1.8.0_121.jdk", "jdk1.8.0_45.jdk")
|
List("jdk1.8.0.jdk", "jdk1.8.0_121.jdk", "jdk1.8.0_45.jdk")
|
||||||
.sortWith(CrossJava.versionOrder)
|
.sortWith(CrossJava.versionOrder)
|
||||||
.last must be equalTo ("jdk1.8.0_121.jdk")
|
.last == "jdk1.8.0_121.jdk"
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"The Linux Java home selector" should {
|
test("The Linux Java home selector should correctly pick up Fedora Java installations") {
|
||||||
"correctly pick up fedora java installations" in {
|
val conf = new LinuxDiscoverConfig(sbt.io.syntax.file(".")) {
|
||||||
val conf = new LinuxDiscoverConfig(sbt.io.syntax.file(".")) {
|
override def candidates(): Vector[String] =
|
||||||
override def candidates(): Vector[String] =
|
"""
|
||||||
"""
|
|
||||||
|java-1.8.0-openjdk-1.8.0.162-3.b12.fc28.x86_64
|
|java-1.8.0-openjdk-1.8.0.162-3.b12.fc28.x86_64
|
||||||
|java-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64
|
|java-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64
|
||||||
|java-1.8.0
|
|java-1.8.0
|
||||||
|
|
@ -34,49 +35,106 @@ class CrossJavaTest extends Specification {
|
||||||
|jre-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64
|
|jre-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64
|
||||||
|jre-openjdk
|
|jre-openjdk
|
||||||
""".stripMargin.split("\n").filter(_.nonEmpty).toVector
|
""".stripMargin.split("\n").filter(_.nonEmpty).toVector
|
||||||
}
|
|
||||||
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
|
||||||
version must be equalTo ("1.8")
|
|
||||||
file.getName must be equalTo ("java-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64")
|
|
||||||
}
|
|
||||||
|
|
||||||
"correctly pick up Oracle RPM installations" in {
|
|
||||||
val conf = new LinuxDiscoverConfig(sbt.io.syntax.file(".")) {
|
|
||||||
override def candidates(): Vector[String] = Vector("jdk1.8.0_172-amd64")
|
|
||||||
}
|
|
||||||
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
|
||||||
version must be equalTo ("1.8")
|
|
||||||
file.getName must be equalTo ("jdk1.8.0_172-amd64")
|
|
||||||
}
|
}
|
||||||
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
|
assert(version == "1.8")
|
||||||
|
assert(file.getName == "java-1.8.0-openjdk-1.8.0.172-9.b11.fc28.x86_64")
|
||||||
}
|
}
|
||||||
|
|
||||||
"The Windows Java home selector" should {
|
test("The Linux Java home selector should correctly pick up Oracle RPM installations") {
|
||||||
"correctly pick up a JDK" in {
|
val conf = new LinuxDiscoverConfig(sbt.io.syntax.file(".")) {
|
||||||
val conf = new WindowsDiscoverConfig(sbt.io.syntax.file(".")) {
|
override def candidates(): Vector[String] = Vector("jdk1.8.0_172-amd64")
|
||||||
override def candidates() = Vector("jdk1.7.0")
|
|
||||||
}
|
|
||||||
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
|
||||||
version must equalTo("1.7")
|
|
||||||
file.getName must be equalTo ("jdk1.7.0")
|
|
||||||
}
|
}
|
||||||
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
|
assert(version == "1.8")
|
||||||
|
assert(file.getName == "jdk1.8.0_172-amd64")
|
||||||
}
|
}
|
||||||
|
|
||||||
"The JAVA_HOME selector" should {
|
test("The Windows Java home selector should correctly pick up a JDK") {
|
||||||
"correctly pick up a JDK" in {
|
val conf = new WindowsDiscoverConfig(sbt.io.syntax.file(".")) {
|
||||||
val conf = new JavaHomeDiscoverConfig {
|
override def candidates() = Vector("jdk1.7.0")
|
||||||
override def home() = Some("/opt/jdk8")
|
|
||||||
}
|
|
||||||
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
|
||||||
version must equalTo("8")
|
|
||||||
file.getName must be equalTo ("jdk8")
|
|
||||||
}
|
}
|
||||||
"correctly pick up an Oracle JDK" in {
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
val conf = new JavaHomeDiscoverConfig {
|
assert(version == "1.7")
|
||||||
override def home() = Some("/opt/oracle-jdk-bin-1.8.0.181")
|
assert(file.getName == "jdk1.7.0")
|
||||||
}
|
}
|
||||||
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
|
||||||
version must equalTo("1.8")
|
test("The JAVA_HOME selector should correctly pick up a JDK") {
|
||||||
file.getName must be equalTo ("oracle-jdk-bin-1.8.0.181")
|
val conf = new JavaHomeDiscoverConfig {
|
||||||
|
override def home() = Some("/opt/jdk8")
|
||||||
}
|
}
|
||||||
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
|
assert(version == "8")
|
||||||
|
assert(file.getName == "jdk8")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("The JAVA_HOME selector should correctly pick up an Oracle JDK") {
|
||||||
|
val conf = new JavaHomeDiscoverConfig {
|
||||||
|
override def home() = Some("/opt/oracle-jdk-bin-1.8.0.181")
|
||||||
|
}
|
||||||
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
|
assert(version == "1.8")
|
||||||
|
assert(file.getName == "oracle-jdk-bin-1.8.0.181")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("The SDKMAN selector should correctly pick up an AdoptOpenJDK") {
|
||||||
|
val conf = new SdkmanDiscoverConfig {
|
||||||
|
override def candidates() = Vector("11.0.2.hs-adpt")
|
||||||
|
}
|
||||||
|
val (version, file) = conf.javaHomes.sortWith(CrossJava.versionOrder).last
|
||||||
|
assert(version == "adopt@11.0.2")
|
||||||
|
assert(file.getName == "11.0.2.hs-adpt")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("expandJavaHomes") {
|
||||||
|
val conf = new SdkmanDiscoverConfig {
|
||||||
|
override def candidates() = Vector("11.0.2.hs-adpt")
|
||||||
|
}
|
||||||
|
val hs = CrossJava.expandJavaHomes(ListMap(conf.javaHomes: _*))
|
||||||
|
assert(hs.contains("11"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("SDKMAN candidate parsing") {
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("11.0.2.hs-adpt") == JavaVersion(Vector(11L, 0L, 2L), Some("adopt"))
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("11.0.2.j9-adpt") == JavaVersion(
|
||||||
|
Vector(11L, 0L, 2L),
|
||||||
|
Some("adopt-openj9")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("13.ea.13-open") == JavaVersion(
|
||||||
|
Vector(13L),
|
||||||
|
Vector("ea13"),
|
||||||
|
Some("openjdk")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("12.0.0-zulu") == JavaVersion(
|
||||||
|
Vector(12L, 0L, 0L),
|
||||||
|
Some("zulu")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("8.0.201-oracle") == JavaVersion(
|
||||||
|
Vector(8L, 0L, 201L),
|
||||||
|
Some("oracle")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
CrossJava
|
||||||
|
.parseSdkmanString("1.0.0-rc-14-grl") == JavaVersion(
|
||||||
|
Vector(1L, 0L, 0L),
|
||||||
|
Vector("rc14"),
|
||||||
|
Some("graalvm")
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue