From c31583e4f8f19a6c2d060b6a75b274050439c203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Wed, 18 Apr 2018 16:28:43 +0100 Subject: [PATCH 1/8] Discovery of java homes --- .../contraband-scala/sbt/JavaVersion.scala | 40 +++++++++++++++ main/src/main/contraband/main.contra | 6 +++ main/src/main/scala/sbt/Defaults.scala | 4 ++ main/src/main/scala/sbt/Keys.scala | 4 ++ .../main/scala/sbt/internal/CrossJava.scala | 50 +++++++++++++++++++ .../sbt-test/java/home-discovery/build.sbt | 5 ++ sbt/src/sbt-test/java/home-discovery/test | 1 + 7 files changed, 110 insertions(+) create mode 100644 main/src/main/contraband-scala/sbt/JavaVersion.scala create mode 100644 main/src/main/scala/sbt/internal/CrossJava.scala create mode 100644 sbt/src/sbt-test/java/home-discovery/build.sbt create mode 100644 sbt/src/sbt-test/java/home-discovery/test diff --git a/main/src/main/contraband-scala/sbt/JavaVersion.scala b/main/src/main/contraband-scala/sbt/JavaVersion.scala new file mode 100644 index 000000000..68d3941f1 --- /dev/null +++ b/main/src/main/contraband-scala/sbt/JavaVersion.scala @@ -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 vendor: Option[String], + val version: String) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: JavaVersion => (this.vendor == x.vendor) && (this.version == x.version) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + vendor.##) + version.##) + } + override def toString: String = { + "JavaVersion(" + vendor + ", " + version + ")" + } + private[this] def copy(vendor: Option[String] = vendor, version: String = version): JavaVersion = { + new JavaVersion(vendor, version) + } + def withVendor(vendor: Option[String]): JavaVersion = { + copy(vendor = vendor) + } + def withVendor(vendor: String): JavaVersion = { + copy(vendor = Option(vendor)) + } + def withVersion(version: String): JavaVersion = { + copy(version = version) + } +} +object JavaVersion { + def apply(version: String): JavaVersion = new JavaVersion(None, version) + def apply(vendor: Option[String], version: String): JavaVersion = new JavaVersion(vendor, version) + def apply(vendor: String, version: String): JavaVersion = new JavaVersion(Option(vendor), version) +} diff --git a/main/src/main/contraband/main.contra b/main/src/main/contraband/main.contra index eb9e9f42c..10ec4a469 100644 --- a/main/src/main/contraband/main.contra +++ b/main/src/main/contraband/main.contra @@ -17,3 +17,9 @@ enum PluginTrigger { AllRequirements NoTrigger } + +type JavaVersion { + vendor: String + version: String! + #xcompanion def apply(version: String): JavaVersion = new JavaVersion(None, version) +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4a8a34f1a..c4fa875ac 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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 := sbt.internal.CrossJava.discoverJavaHomes, + javaHomes :== ListMap.empty, + fullJavaHomes := discoveredJavaHomes.value ++ javaHomes.value, testForkedParallel :== false, javaOptions :== Nil, sbtPlugin :== false, diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 82191f556..751540de7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -271,6 +271,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[JavaVersion, File]]("Discovered Java home directories") + val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") + val fullJavaHomes = taskKey[Map[JavaVersion, 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) diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala new file mode 100644 index 000000000..c93d44257 --- /dev/null +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -0,0 +1,50 @@ +/* + * 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.IO +import sbt.io.syntax._ + +private[sbt] object CrossJava { + def discoverJavaHomes: ListMap[JavaVersion, File] = { + val configs = Vector(JavaDiscoverConfig.linux, JavaDiscoverConfig.macOS) + ListMap(configs flatMap { _.javaHomes }: _*) + } + + sealed trait JavaDiscoverConf { + def javaHomes: Vector[(JavaVersion, File)] + } + + object JavaDiscoverConfig { + val linux = new JavaDiscoverConf { + val base: File = file("/usr") / "lib" / "jvm" + val JavaHomeDir = """java-([0-9]+)-.*""".r + def javaHomes: Vector[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(ver) => JavaVersion(ver) -> (base / dir) + } + } + + val macOS = new JavaDiscoverConf { + val base: File = file("/Library") / "Java" / "JavaVirtualMachines" + val JavaHomeDir = """jdk-?(1\.)?([0-9]+).*""".r + def javaHomes: Vector[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(m, n) => + JavaVersion(n) -> (base / dir / "Contents" / "Home") + } + } + } + + def wrapNull(a: Array[String]): Vector[String] = + if (a eq null) Vector() + else a.toVector +} diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt new file mode 100644 index 000000000..b5d79caeb --- /dev/null +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -0,0 +1,5 @@ +Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") + +TaskKey[Unit]("check") := { + assert(fullJavaHomes.value(JavaVersion("6")).getAbsolutePath.contains("java-6")) +} diff --git a/sbt/src/sbt-test/java/home-discovery/test b/sbt/src/sbt-test/java/home-discovery/test new file mode 100644 index 000000000..15675b169 --- /dev/null +++ b/sbt/src/sbt-test/java/home-discovery/test @@ -0,0 +1 @@ +> check From aff9e0110cd7f4b942e194e1eb979f89fbf64f1f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 3 May 2018 00:59:27 -0400 Subject: [PATCH 2/8] Accept both 1.x and x for 1.1 to 1.8 --- main/src/main/scala/sbt/Defaults.scala | 4 ++-- main/src/main/scala/sbt/internal/CrossJava.scala | 12 +++++++++++- sbt/src/sbt-test/java/home-discovery/build.sbt | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c4fa875ac..f3e550205 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -160,9 +160,9 @@ object Defaults extends BuildCommon { scalaHome :== None, apiURL := None, javaHome :== None, - discoveredJavaHomes := sbt.internal.CrossJava.discoverJavaHomes, + discoveredJavaHomes := CrossJava.discoverJavaHomes, javaHomes :== ListMap.empty, - fullJavaHomes := discoveredJavaHomes.value ++ javaHomes.value, + fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value), testForkedParallel :== false, javaOptions :== Nil, sbtPlugin :== false, diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index c93d44257..1c035a4d8 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -10,7 +10,6 @@ package internal import java.io.File import scala.collection.immutable.ListMap -import sbt.io.IO import sbt.io.syntax._ private[sbt] object CrossJava { @@ -44,6 +43,17 @@ private[sbt] object CrossJava { } } + // expand Java versions to 1-8 to 1.x, and vice versa to accept both "1.8" and "8" + private val oneDot = Map((1 to 8).toVector flatMap { i => + Vector(s"$i" -> s"1.$i", s"1.$i" -> s"$i") + }: _*) + def expandJavaHomes(hs: Map[JavaVersion, File]): Map[JavaVersion, File] = + hs flatMap { + case (k, v) => + if (oneDot.contains(k.version)) Vector(k -> v, k.withVersion(oneDot(k.version)) -> v) + else Vector(k -> v) + } + def wrapNull(a: Array[String]): Vector[String] = if (a eq null) Vector() else a.toVector diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt index b5d79caeb..7dd72c1d1 100644 --- a/sbt/src/sbt-test/java/home-discovery/build.sbt +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -1,5 +1,5 @@ Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") TaskKey[Unit]("check") := { - assert(fullJavaHomes.value(JavaVersion("6")).getAbsolutePath.contains("java-6")) + assert(fullJavaHomes.value(JavaVersion("1.6")).getAbsolutePath.contains("java-6")) } From 2da1aa61eb499c01d14ca64491b1a46f3599269a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 3 May 2018 03:39:55 -0400 Subject: [PATCH 3/8] implement cross JDK forking ``` sbt:helloworld> java++ 10 [info] Reapplying settings... sbt:helloworld> run [info] Running (fork) Hello [info] 10.0.1 sbt:helloworld> java++ 8 [info] Reapplying settings... sbt:helloworld> run [info] Running (fork) Hello [info] 1.8.0_171 ``` --- .travis.yml | 12 +- .../contraband-scala/sbt/JavaVersion.scala | 28 +- main/src/main/contraband/main.contra | 8 +- main/src/main/scala/sbt/Cross.scala | 24 +- main/src/main/scala/sbt/Keys.scala | 3 +- main/src/main/scala/sbt/Main.scala | 3 + .../scala/sbt/internal/CommandStrings.scala | 25 ++ .../main/scala/sbt/internal/CrossJava.scala | 295 +++++++++++++++++- sbt/src/sbt-test/java/cross/A.scala | 16 + sbt/src/sbt-test/java/cross/build.sbt | 25 ++ sbt/src/sbt-test/java/cross/test | 6 + 11 files changed, 411 insertions(+), 34 deletions(-) create mode 100644 sbt/src/sbt-test/java/cross/A.scala create mode 100644 sbt/src/sbt-test/java/cross/build.sbt create mode 100644 sbt/src/sbt-test/java/cross/test diff --git a/.travis.yml b/.travis.yml index c6e2d000e..7f257e9ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ cache: directories: - $HOME/.ivy2/cache - $HOME/.sbt/boot + - $HOME/.jabba language: scala @@ -15,6 +16,15 @@ jdk: matrix: fast_finish: true +matrix: + include: + - env: SBT_CMD="scripted java/*" + sudo: true + before_install: + - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.4/install.sh | bash && . ~/.jabba/jabba.sh + install: + - sudo /home/travis/.jabba/bin/jabba install openjdk@1.10 + env: global: - secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs= @@ -26,7 +36,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" diff --git a/main/src/main/contraband-scala/sbt/JavaVersion.scala b/main/src/main/contraband-scala/sbt/JavaVersion.scala index 68d3941f1..4c630e3cd 100644 --- a/main/src/main/contraband-scala/sbt/JavaVersion.scala +++ b/main/src/main/contraband-scala/sbt/JavaVersion.scala @@ -5,23 +5,26 @@ // DO NOT EDIT MANUALLY package sbt final class JavaVersion private ( - val vendor: Option[String], - val version: String) extends Serializable { - + 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.vendor == x.vendor) && (this.version == x.version) + case x: JavaVersion => (this.numbers == x.numbers) && (this.vendor == x.vendor) case _ => false } override def hashCode: Int = { - 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + vendor.##) + version.##) + 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + numbers.##) + vendor.##) } override def toString: String = { - "JavaVersion(" + vendor + ", " + version + ")" + vendor.map(_ + "@").getOrElse("") + numberStr } - private[this] def copy(vendor: Option[String] = vendor, version: String = version): JavaVersion = { - new JavaVersion(vendor, version) + 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) @@ -29,12 +32,9 @@ final class JavaVersion private ( def withVendor(vendor: String): JavaVersion = { copy(vendor = Option(vendor)) } - def withVersion(version: String): JavaVersion = { - copy(version = version) - } } object JavaVersion { - def apply(version: String): JavaVersion = new JavaVersion(None, version) - def apply(vendor: Option[String], version: String): JavaVersion = new JavaVersion(vendor, version) - def apply(vendor: String, version: String): JavaVersion = new JavaVersion(Option(vendor), version) + 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)) } diff --git a/main/src/main/contraband/main.contra b/main/src/main/contraband/main.contra index 10ec4a469..5cabb0cd4 100644 --- a/main/src/main/contraband/main.contra +++ b/main/src/main/contraband/main.contra @@ -19,7 +19,11 @@ enum PluginTrigger { } type JavaVersion { + numbers: [Long] vendor: String - version: String! - #xcompanion def apply(version: String): JavaVersion = new JavaVersion(None, version) + + #x def numberStr: String = numbers.mkString(".") + #xtostring vendor.map(_ + "@").getOrElse("") + numberStr + + #xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version) } diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index c1257db5a..e68f2f20f 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -99,14 +99,23 @@ 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 +124,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) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 751540de7..f9e57bc1a 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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[JavaVersion]]("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) @@ -273,7 +274,7 @@ object Keys { 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[JavaVersion, File]]("Discovered Java home directories") val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") - val fullJavaHomes = taskKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) + val fullJavaHomes = settingKey[Map[JavaVersion, 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) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index b29cca948..d6185d41e 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -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, diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 99ec4637e..d30a31f29 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -415,4 +415,29 @@ $SwitchCommand [=][!] [-v] [] 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 + Runs 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 . 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 + Changes the JDK version and runs a command. + Sets the `javaHome` of all projects to and + reloads the build. If is provided, it is then executed. + See also `help $JavaSwitchCommand` +""" } diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 1c035a4d8..0f8a4730b 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -10,11 +10,277 @@ 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: 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 = getJavaHomes(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[JavaVersion, File] = { + import extracted._ + (Keys.fullJavaHomes in proj get structure.data).get + } + + private def getCrossJavaVersions(extracted: Extracted, + proj: ResolvedReference): Seq[JavaVersion] = { + 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[JavaVersion])] = { + 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 = getJavaHomes(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[JavaVersion, File] = { - val configs = Vector(JavaDiscoverConfig.linux, JavaDiscoverConfig.macOS) + import JavaDiscoverConfig._ + val configs = Vector(jabba, linux, macOS) ListMap(configs flatMap { _.javaHomes }: _*) } @@ -38,19 +304,36 @@ private[sbt] object CrossJava { def javaHomes: Vector[(JavaVersion, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(m, n) => - JavaVersion(n) -> (base / dir / "Contents" / "Home") + JavaVersion(nullBlank(m) + n) -> (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[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(vendor, m, n) => + val v = JavaVersion(nullBlank(m) + n).withVendor(vendor) + if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home") + else v -> (base / dir) } } } - // expand Java versions to 1-8 to 1.x, and vice versa to accept both "1.8" and "8" - private val oneDot = Map((1 to 8).toVector flatMap { i => - Vector(s"$i" -> s"1.$i", s"1.$i" -> s"$i") + 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[JavaVersion, File]): Map[JavaVersion, File] = hs flatMap { case (k, v) => - if (oneDot.contains(k.version)) Vector(k -> v, k.withVersion(oneDot(k.version)) -> v) + if (oneDot.contains(k.numbers)) Vector(k -> v, k.withNumbers(oneDot(k.numbers)) -> v) else Vector(k -> v) } diff --git a/sbt/src/sbt-test/java/cross/A.scala b/sbt/src/sbt-test/java/cross/A.scala new file mode 100644 index 000000000..e9ff1f72a --- /dev/null +++ b/sbt/src/sbt-test/java/cross/A.scala @@ -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 + } +} diff --git a/sbt/src/sbt-test/java/cross/build.sbt b/sbt/src/sbt-test/java/cross/build.sbt new file mode 100644 index 000000000..dad1dc347 --- /dev/null +++ b/sbt/src/sbt-test/java/cross/build.sbt @@ -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(JavaVersion("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, + ) diff --git a/sbt/src/sbt-test/java/cross/test b/sbt/src/sbt-test/java/cross/test new file mode 100644 index 000000000..ff76c51bf --- /dev/null +++ b/sbt/src/sbt-test/java/cross/test @@ -0,0 +1,6 @@ +> java+ run +> check 8 + +> java++ 10! +> run +> check 10 From 951eaa646f2cda69123ef8da8a2b5fd370106aa9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 4 May 2018 19:09:25 -0400 Subject: [PATCH 4/8] jabba 0.9.6 (no sudo) Ref https://github.com/shyiko/jabba/issues/190 Bumping to jabba 0.9.6 fixes sporaditc permission issues. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f257e9ed..f75525898 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,10 @@ matrix: matrix: include: - env: SBT_CMD="scripted java/*" - sudo: true before_install: - - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.4/install.sh | bash && . ~/.jabba/jabba.sh + - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.6/install.sh | bash && . ~/.jabba/jabba.sh install: - - sudo /home/travis/.jabba/bin/jabba install openjdk@1.10 + - /home/travis/.jabba/bin/jabba install openjdk@1.10 env: global: From 35e98f51fd386b15c27c678392e90acd44787cd1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 15:50:40 -0400 Subject: [PATCH 5/8] Adjust to upstream change --- main/src/main/scala/sbt/Cross.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index e68f2f20f..420432707 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -233,14 +233,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 ", " ", "") } } From a7d85c87243b06ecbb5186902367a85197aa7c71 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 15:51:29 -0400 Subject: [PATCH 6/8] Formatting --- main/src/main/scala/sbt/Cross.scala | 5 ++-- .../main/scala/sbt/internal/CrossJava.scala | 28 ++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 420432707..78571d78b 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -101,8 +101,9 @@ object Cross { /** * Parse the given command into a list of aggregate projects and command to issue. */ - private[sbt] def parseSlashCommand(extracted: Extracted)( - command: String): (Seq[ProjectRef], String) = { + private[sbt] def parseSlashCommand( + extracted: Extracted + )(command: String): (Seq[ProjectRef], String) = { import extracted._ import DefaultParsers._ val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map { diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 0f8a4730b..5836b2c17 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -77,7 +77,8 @@ private[sbt] object CrossJava { val version: Parser[SwitchTarget] = (token( (StringBasic <~ "@").? ~ ((NatBasic) ~ ("." ~> NatBasic).*) - .examples(knownVersions: _*) ~ "!".?) || token(StringBasic)) + .examples(knownVersions: _*) ~ "!".? + ) || token(StringBasic)) .map { case Left(((vendor, (v1, vs)), bang)) => val force = bang.isDefined @@ -101,14 +102,18 @@ private[sbt] object CrossJava { } } - private def getJavaHomes(extracted: Extracted, - proj: ResolvedReference): Map[JavaVersion, File] = { + private def getJavaHomes( + extracted: Extracted, + proj: ResolvedReference + ): Map[JavaVersion, File] = { import extracted._ (Keys.fullJavaHomes in proj get structure.data).get } - private def getCrossJavaVersions(extracted: Extracted, - proj: ResolvedReference): Seq[JavaVersion] = { + private def getCrossJavaVersions( + extracted: Extracted, + proj: ResolvedReference + ): Seq[JavaVersion] = { import extracted._ import Keys._ (crossJavaVersions in proj get structure.data).getOrElse(Nil) @@ -235,7 +240,8 @@ private[sbt] object CrossJava { "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.") + "that are configured." + ) state.log.debug("Java versions configuration is:") projCrossVersions.foreach { case (project, versions) => state.log.debug(s"$project: $versions") @@ -259,12 +265,14 @@ private[sbt] object CrossJava { 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") + 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 ", " ", "")) + Seq( + s"$JavaSwitchCommand $verbose $version", + projects.map(_ + "/" + aggCommand).mkString("all ", " ", "") + ) } } From 72ebdeb1990667add76c40f3c28c29281a32d310 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 7 May 2018 02:15:42 -0400 Subject: [PATCH 7/8] jabba 0.10.1 https://github.com/shyiko/jabba/blob/master/CHANGELOG.md#0101---2018-05-07 Ref https://github.com/shyiko/jabba/issues/190 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f75525898..08a61721f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ matrix: include: - env: SBT_CMD="scripted java/*" before_install: - - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.6/install.sh | bash && . ~/.jabba/jabba.sh + - 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 From 9b7c224f93ef9ca2a81d43cdd1a406f15531376b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 30 May 2018 00:59:12 -0400 Subject: [PATCH 8/8] use stringly-typed key so we can define it machine-wide --- main/src/main/scala/sbt/Keys.scala | 8 ++-- .../main/scala/sbt/internal/CrossJava.scala | 44 ++++++++++++------- sbt/src/sbt-test/java/cross/build.sbt | 2 +- .../sbt-test/java/home-discovery/build.sbt | 4 +- .../sbt/scriptedtest/ScriptedTests.scala | 1 + 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index f9e57bc1a..bf8c9549f 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -222,7 +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[JavaVersion]]("The java versions used during JDK cross testing").withRank(BPlusSetting) + 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) @@ -272,9 +272,9 @@ 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[JavaVersion, File]]("Discovered Java home directories") - val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") - val fullJavaHomes = settingKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) + 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) diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 5836b2c17..9ee668042 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -44,6 +44,11 @@ private[sbt] object CrossJava { } } + 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 @@ -72,7 +77,7 @@ private[sbt] object CrossJava { def versionAndCommand(spacePresent: Boolean) = { val x = Project.extract(state) import x._ - val javaHomes = getJavaHomes(x, currentRef) + val javaHomes = getJavaHomesTyped(x, currentRef) val knownVersions = javaHomes.keysIterator.map(_.numberStr).toVector val version: Parser[SwitchTarget] = (token( @@ -105,15 +110,22 @@ private[sbt] object CrossJava { private def getJavaHomes( extracted: Extracted, proj: ResolvedReference - ): Map[JavaVersion, File] = { + ): 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[JavaVersion] = { + ): Seq[String] = { import extracted._ import Keys._ (crossJavaVersions in proj get structure.data).getOrElse(Nil) @@ -138,7 +150,7 @@ private[sbt] object CrossJava { // 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[JavaVersion])] = { + val projects: Seq[(ResolvedReference, Seq[String])] = { val projectJavaVersions = structure.allProjectRefs.map(proj => proj -> getCrossJavaVersions(extracted, proj)) if (switch.target.force) projectJavaVersions @@ -157,7 +169,7 @@ private[sbt] object CrossJava { def setJavaHomeForProjects: State = { val newSettings = projects.flatMap { case (proj, javaVersions) => - val fjh = getJavaHomes(extracted, proj) + val fjh = getJavaHomesTyped(extracted, proj) val home = switch.target match { case SwitchTarget(Some(v), _, _) => lookupJavaHome(v, fjh) case SwitchTarget(_, Some(h), _) => h @@ -286,33 +298,33 @@ private[sbt] object CrossJava { state.put(JavaCapturedSession, extracted.session.rawAppend) } - def discoverJavaHomes: ListMap[JavaVersion, File] = { + def discoverJavaHomes: ListMap[String, File] = { import JavaDiscoverConfig._ val configs = Vector(jabba, linux, macOS) ListMap(configs flatMap { _.javaHomes }: _*) } sealed trait JavaDiscoverConf { - def javaHomes: Vector[(JavaVersion, File)] + 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[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { - case dir @ JavaHomeDir(ver) => JavaVersion(ver) -> (base / dir) + 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[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(m, n) => - JavaVersion(nullBlank(m) + n) -> (base / dir / "Contents" / "Home") + JavaVersion(nullBlank(m) + n).toString -> (base / dir / "Contents" / "Home") } } @@ -320,10 +332,10 @@ private[sbt] object CrossJava { val jabba = new JavaDiscoverConf { val base: File = Path.userHome / ".jabba" / "jdk" val JavaHomeDir = """([\w\-]+)\@(1\.)?([0-9]+).*""".r - def javaHomes: Vector[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(vendor, m, n) => - val v = JavaVersion(nullBlank(m) + n).withVendor(vendor) + val v = JavaVersion(nullBlank(m) + n).withVendor(vendor).toString if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home") else v -> (base / dir) } @@ -338,10 +350,12 @@ private[sbt] object CrossJava { 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[JavaVersion, File]): Map[JavaVersion, File] = + def expandJavaHomes(hs: Map[String, File]): Map[String, File] = hs flatMap { case (k, v) => - if (oneDot.contains(k.numbers)) Vector(k -> v, k.withNumbers(oneDot(k.numbers)) -> v) + val jv = JavaVersion(k) + if (oneDot.contains(jv.numbers)) + Vector(k -> v, jv.withNumbers(oneDot(jv.numbers)).toString -> v) else Vector(k -> v) } diff --git a/sbt/src/sbt-test/java/cross/build.sbt b/sbt/src/sbt-test/java/cross/build.sbt index dad1dc347..c71bd179e 100644 --- a/sbt/src/sbt-test/java/cross/build.sbt +++ b/sbt/src/sbt-test/java/cross/build.sbt @@ -5,7 +5,7 @@ val check = inputKey[Unit]("Runs the check") lazy val root = (project in file(".")) .settings( ThisBuild / scalaVersion := "2.12.6", - crossJavaVersions := List(JavaVersion("1.8")), + crossJavaVersions := List("1.8"), // read out.txt and see if it starts with the passed in number check := { diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt index 7dd72c1d1..7177ca692 100644 --- a/sbt/src/sbt-test/java/home-discovery/build.sbt +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -1,5 +1,5 @@ -Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") +Global / javaHomes += "6" -> file("/good/old/times/java-6") TaskKey[Unit]("check") := { - assert(fullJavaHomes.value(JavaVersion("1.6")).getAbsolutePath.contains("java-6")) + assert(fullJavaHomes.value("1.6").getAbsolutePath.contains("java-6")) } diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index dabaeeca1..77e3b7467 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -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$