From 2da1aa61eb499c01d14ca64491b1a46f3599269a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 3 May 2018 03:39:55 -0400 Subject: [PATCH] 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