From 8ba48c0adc1d6c99b46683dd8745c4163c292aaf Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 2 Jul 2018 16:38:07 +0200 Subject: [PATCH] Compile with jdk9, targeting jdk8 (#24711) --- RELEASING.md | 72 ++++++++ .../akka/stream/javadsl/JavaFlowSupport.java | 0 .../stream/impl/JavaFlowAndRsConverters.scala | 0 .../stream/scaladsl/JavaFlowSupport.scala | 0 build.sbt | 2 +- project/AkkaBuild.scala | 19 ++- project/CrossJava.scala | 161 ++++++++++++++++++ project/Doc.scala | 2 + project/Jdk9.scala | 35 ++++ project/Jdk9CompileDirectoriesPlugin.scala | 44 ----- project/Release.scala | 1 + project/scripts/release | 4 +- 12 files changed, 290 insertions(+), 50 deletions(-) create mode 100644 RELEASING.md rename akka-stream/src/main/{java-jdk9-only => java-jdk-9}/akka/stream/javadsl/JavaFlowSupport.java (100%) rename akka-stream/src/main/{scala-jdk9-only => scala-jdk-9}/akka/stream/impl/JavaFlowAndRsConverters.scala (100%) rename akka-stream/src/main/{scala-jdk9-only => scala-jdk-9}/akka/stream/scaladsl/JavaFlowSupport.scala (100%) create mode 100644 project/CrossJava.scala create mode 100644 project/Jdk9.scala delete mode 100644 project/Jdk9CompileDirectoriesPlugin.scala diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..1184586262 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,72 @@ +# Releasing + +## Prerequisites + +### JDK 8 and JDK 9 + +Releasing Akka requires running on at least JDK 9, but also having JDK 8 +installed. The reason for this is that we want the Akka artifacts to be +usable with JRE 8, but also want to compile some classes with JDK9-specific +types. + +When we stop supporting Scala 2.11 we might be able to update the build towork +without having JDK 8 installed, by using the `-release` option. + +### MinGW + +When releasing from Windows, you need MinGW and a gpg distribution such as Gpg4Win + +### Git + +Make sure you have set `core.autocrlf` to `false` in your `~/.gitconfig`, +otherwise git might convert line endings in some cases. + +### Whitesource + +Make sure you have the Lightbend Whitesource credentials configured in +your `~/.sbt/1.0/private-credentials.sbt`. + +## Release steps + +* Check the instructions for `project/scripts/release` +* Do a `project/scripts/release ` dry run +* If all goes well, `project/scripts/release --real-run ` +* Log into sonatype, 'close' the staging repo. +* Test the artifacts by adding `resolvers += "Staging Repo" at "https://oss.sonatype.org/content/repositories/comtypesafe-xxxx"` to a test project +* If all is well, 'release' the staging repo. + +## Announcing + +* Prepare milestone on github: + * go to the [Milestones tab](https://github.com/akka/akka/milestones) + * move all open issues so that this milestone contains completed work only + * close that milestone + +* In case of a new major release: + * update the branch descriptions at CONTRIBUTING.md#branches-summary + +* Create an announcement as a PR against akka/akka.github.com . + * credits can be generated with `scripts/authors.scala v2.3.5 v2.3.6` + * also update the `latest` variable in `_config.yml`. + +* Update `MiMa.latestPatchOf` and PR that change (`project/MiMa.scala`) + +Now wait until all artifacts have been properly propagated. Then: + +* Change the symbolic links from 'current': `ssh akkarepo@gustav.akka.io ./update-akka-current-version.sh ` + +* Merge the release announcement +* Tweet about it +* Post about it on Gitter and Discuss + +## Update references + +Update the versions used in: + +* https://github.com/akka/akka-samples +* https://github.com/akka/akka-quickstart-java.g8 +* https://github.com/akka/akka-quickstart-scala.g8 +* https://github.com/akka/akka-http-quickstart-java.g8 +* https://github.com/akka/akka-http-quickstart-scala.g8 +* https://github.com/akka/akka-distributed-workers-scala.g8 +* https://github.com/lightbend/reactive-platform-docs/blob/master/build.sbt (this populates https://developer.lightbend.com/docs/reactive-platform/2.0/supported-modules/index.html#akka) diff --git a/akka-stream/src/main/java-jdk9-only/akka/stream/javadsl/JavaFlowSupport.java b/akka-stream/src/main/java-jdk-9/akka/stream/javadsl/JavaFlowSupport.java similarity index 100% rename from akka-stream/src/main/java-jdk9-only/akka/stream/javadsl/JavaFlowSupport.java rename to akka-stream/src/main/java-jdk-9/akka/stream/javadsl/JavaFlowSupport.java diff --git a/akka-stream/src/main/scala-jdk9-only/akka/stream/impl/JavaFlowAndRsConverters.scala b/akka-stream/src/main/scala-jdk-9/akka/stream/impl/JavaFlowAndRsConverters.scala similarity index 100% rename from akka-stream/src/main/scala-jdk9-only/akka/stream/impl/JavaFlowAndRsConverters.scala rename to akka-stream/src/main/scala-jdk-9/akka/stream/impl/JavaFlowAndRsConverters.scala diff --git a/akka-stream/src/main/scala-jdk9-only/akka/stream/scaladsl/JavaFlowSupport.scala b/akka-stream/src/main/scala-jdk-9/akka/stream/scaladsl/JavaFlowSupport.scala similarity index 100% rename from akka-stream/src/main/scala-jdk9-only/akka/stream/scaladsl/JavaFlowSupport.scala rename to akka-stream/src/main/scala-jdk-9/akka/stream/scaladsl/JavaFlowSupport.scala diff --git a/build.sbt b/build.sbt index af866f2b78..97b54122cd 100644 --- a/build.sbt +++ b/build.sbt @@ -340,7 +340,7 @@ lazy val stream = akkaModule("akka-stream") .settings(AutomaticModuleName.settings("akka.stream")) .settings(OSGi.stream) .settings(Protobuf.settings) - .enablePlugins(BoilerplatePlugin) + .enablePlugins(BoilerplatePlugin, Jdk9) lazy val streamTestkit = akkaModule("akka-stream-testkit") .dependsOn(stream, testkit % "compile->compile;test->test") diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index d820b131c8..83a63abdf9 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -83,16 +83,26 @@ object AkkaBuild { private def allWarnings: Boolean = System.getProperty("akka.allwarnings", "false").toBoolean + final val DefaultScalacOptions = Seq("-encoding", "UTF-8", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint") + + // -XDignore.symbol.file suppresses sun.misc.Unsafe warnings + final val DefaultJavacOptions = Seq("-encoding", "UTF-8", "-Xlint:unchecked", "-XDignore.symbol.file") + lazy val defaultSettings = resolverSettings ++ TestExtras.Filter.settings ++ Protobuf.settings ++ Seq[Setting[_]]( // compile options - scalacOptions in Compile ++= Seq("-encoding", "UTF-8", "-target:jvm-1.8", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint"), + scalacOptions in Compile ++= DefaultScalacOptions, + // Makes sure that, even when compiling with a jdk version greater than 8, the resulting jar will not refer to + // methods not found in jdk8. To test whether this has the desired effect, compile akka-remote and check the + // invocation of 'ByteBuffer.clear()' in EnvelopeBuffer.class with 'javap -c': it should refer to + // "java/nio/ByteBuffer.clear:()Ljava/nio/Buffer" and not "java/nio/ByteBuffer.clear:()Ljava/nio/ByteBuffer": + scalacOptions in Compile ++= (if (scalaBinaryVersion.value == "2.11") Seq("-target:jvm-1.8", "-javabootclasspath", CrossJava.Keys.fullJavaHomes.value("8") + "/jre/lib/rt.jar") else Seq("-release", "8")), scalacOptions in Compile ++= (if (allWarnings) Seq("-deprecation") else Nil), scalacOptions in Test := (scalacOptions in Test).value.filterNot(opt ⇒ opt == "-Xlog-reflective-calls" || opt.contains("genjavadoc")), - // -XDignore.symbol.file suppresses sun.misc.Unsafe warnings - javacOptions in compile ++= Seq("-encoding", "UTF-8", "-source", "1.8", "-target", "1.8", "-Xlint:unchecked", "-XDignore.symbol.file"), + javacOptions in compile ++= DefaultJavacOptions ++ Seq("-source", "8", "-target", "8", "-bootclasspath", CrossJava.Keys.fullJavaHomes.value("8") + "/jre/lib/rt.jar"), + javacOptions in test ++= DefaultJavacOptions ++ Seq("-source", "8", "-target", "8", "-bootclasspath", CrossJava.Keys.fullJavaHomes.value("8") + "/jre/lib/rt.jar"), javacOptions in compile ++= (if (allWarnings) Seq("-Xlint:deprecation") else Nil), javacOptions in doc ++= Seq(), @@ -187,7 +197,8 @@ object AkkaBuild { // show full stack traces and test case durations testOptions in Test += Tests.Argument("-oDF")) ++ mavenLocalResolverSettings ++ - docLintingSettings + docLintingSettings ++ + CrossJava.crossJavaSettings lazy val docLintingSettings = Seq( javacOptions in compile ++= Seq("-Xdoclint:none"), diff --git a/project/CrossJava.scala b/project/CrossJava.scala new file mode 100644 index 0000000000..b4f78f1fe9 --- /dev/null +++ b/project/CrossJava.scala @@ -0,0 +1,161 @@ +/** + * Copyright (C) 2009-2018 Lightbend Inc. + */ + +package akka + +import java.io.File + +import sbt._ + +import scala.annotation.tailrec +import scala.collection.immutable.ListMap + +case class JavaVersion(numbers: Vector[Long], vendor: Option[String]) { + def numberStr: String = numbers.mkString(".") + + def withVendor(vendor: Option[String]) = copy(vendor = vendor) + def withVendor(vendor: String) = copy(vendor = Option(vendor)) + def withNumbers(numbers: Vector[Long]) = copy(numbers = numbers) + + override def toString: String = { + vendor.map(_ + "@").getOrElse("") + numberStr + } +} +object JavaVersion { + def apply(version: String): JavaVersion = CrossJava.parseJavaVersion(version) + def apply(numbers: Vector[Long], vendor: String): JavaVersion = new JavaVersion(numbers, Option(vendor)) +} + + +/** Tools for discovering different Java versions, + * will be in sbt 1.1.7 (https://github.com/sbt/sbt/pull/4139) but until that time replicated here */ +object CrossJava { + object Keys { + 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.") + } + + import Keys._ + + val crossJavaSettings = Seq( + discoveredJavaHomes := CrossJava.discoverJavaHomes, + javaHomes := ListMap.empty, + fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value), + ) + + // 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 discoverJavaHomes: ListMap[String, File] = { + import JavaDiscoverConfig._ + val configs = Vector(jabba, linux, macOS) + ListMap(configs flatMap { _.javaHomes }: _*) + } + + sealed trait JavaDiscoverConf { + def javaHomes: Vector[(String, File)] + } + + // Sort version strings, considering 1.8.0 < 1.8.0_45 < 1.8.0_121 + @tailrec + def versionOrder(left: String, right: String): Boolean = { + val Pattern = """.*?([0-9]+)(.*)""".r + left match { + case Pattern(leftNumber, leftRest) => + right match { + case Pattern(rightNumber, rightRest) => + if (Integer.parseInt(leftNumber) < Integer.parseInt(rightNumber)) true + else if (Integer.parseInt(leftNumber) > Integer.parseInt(rightNumber)) false + else versionOrder(leftRest, rightRest) + case _ => + false + } + case _ => + true + } + } + + object JavaDiscoverConfig { + val linux = new JavaDiscoverConf { + val base: File = file("/usr") / "lib" / "jvm" + val JavaHomeDir = """java-([0-9]+)-.*""".r + + def javaHomes: Vector[(String, File)] = + wrapNull(base.list()) + .sortWith(versionOrder) + .collect { + case dir@JavaHomeDir(ver) => JavaVersion(ver).toString -> (base / dir) + } + } + + val macOS = new JavaDiscoverConf { + val base: File = file("/Library") / "Java" / "JavaVirtualMachines" + val JavaHomeDir = """jdk-?(1\.)?([0-9]+).*""".r + + def javaHomes: Vector[(String, File)] = + wrapNull(base.list()) + .sortWith(versionOrder) + .collect { + case dir@JavaHomeDir(m, n) => + JavaVersion(nullBlank(m) + n).toString -> (base / dir / "Contents" / "Home") + } + } + + // See https://github.com/shyiko/jabba + val jabba = new JavaDiscoverConf { + val base: File = Path.userHome / ".jabba" / "jdk" + val JavaHomeDir = """([\w\-]+)\@(1\.)?([0-9]+).*""".r + + def javaHomes: Vector[(String, File)] = + wrapNull(base.list()) + .sortWith(versionOrder) + .collect { + case dir@JavaHomeDir(vendor, m, n) => + val v = JavaVersion(nullBlank(m) + n).withVendor(vendor).toString + if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home") + else v -> (base / dir) + } + } + } + + def nullBlank(s: String): String = + if (s eq null) "" + else s + + + // expand Java versions to 1-20 to 1.x, and vice versa to accept both "1.8" and "8" + private val oneDot = Map((1L to 20L).toVector flatMap { i => + Vector(Vector(i) -> Vector(1L, i), Vector(1L, i) -> Vector(i)) + }: _*) + def expandJavaHomes(hs: Map[String, File]): Map[String, File] = + hs flatMap { + case (k, v) => + val jv = JavaVersion(k) + if (oneDot.contains(jv.numbers)) + Vector(k -> v, jv.withNumbers(oneDot(jv.numbers)).toString -> v) + else Vector(k -> v) + } + + def wrapNull(a: Array[String]): Vector[String] = + if (a eq null) Vector() + else a.toVector + +} diff --git a/project/Doc.scala b/project/Doc.scala index c8d9831c3c..cbe6cde30a 100644 --- a/project/Doc.scala +++ b/project/Doc.scala @@ -28,6 +28,8 @@ object Scaladoc extends AutoPlugin { override lazy val projectSettings = { inTask(doc)(Seq( scalacOptions in Compile ++= scaladocOptions(version.value, (baseDirectory in ThisBuild).value), + // -release caused build failures when generating javadoc: + scalacOptions in Compile --= Seq("-release", "8"), autoAPIMappings := CliOptions.scaladocAutoAPI.get)) ++ Seq(validateDiagrams in Compile := true) ++ CliOptions.scaladocDiagramsEnabled.ifTrue(doc in Compile := { diff --git a/project/Jdk9.scala b/project/Jdk9.scala new file mode 100644 index 0000000000..e8e6eda7de --- /dev/null +++ b/project/Jdk9.scala @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017-2018 Lightbend Inc. + */ + +package akka + +import sbt._ +import sbt.Keys._ + +object Jdk9 extends AutoPlugin { + + lazy val CompileJdk9 = config("CompileJdk9").extend(Compile) + + val compileJdk9Settings = Seq( + // following the scala-2.12, scala-sbt-1.0, ... convention + unmanagedSourceDirectories := Seq( + (Compile / sourceDirectory).value / "scala-jdk-9", + (Compile / sourceDirectory).value / "java-jdk-9" + ), + scalacOptions := AkkaBuild.DefaultScalacOptions ++ Seq("-release", "9"), + javacOptions := AkkaBuild.DefaultJavacOptions ++ Seq("--release", "9") + ) + + val compileSettings = Seq( + Compile / packageBin / mappings ++= + (CompileJdk9 / products).value.flatMap(Path.allSubpaths) + ) + + override def trigger = noTrigger + override def projectConfigurations = Seq(CompileJdk9) + override lazy val projectSettings = + inConfig(CompileJdk9)(Defaults.compileSettings) ++ + inConfig(CompileJdk9)(compileJdk9Settings) ++ + compileSettings +} diff --git a/project/Jdk9CompileDirectoriesPlugin.scala b/project/Jdk9CompileDirectoriesPlugin.scala deleted file mode 100644 index 3ea46b7554..0000000000 --- a/project/Jdk9CompileDirectoriesPlugin.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2017-2018 Lightbend Inc. - */ - -import java.io.File - -import sbt._ -import sbt.Keys._ - -object Jdk9CompileDirectoriesPlugin extends AutoPlugin { - - val jdkVersion: String = System.getProperty("java.version") - - override def trigger = allRequirements - - override lazy val projectSettings = Seq( - - javacOptions in Compile ++= { - // making sure we're really targeting 1.8 - if (isJDK9) Seq("-target", "1.8", "-source", "1.8", "-Xdoclint:none") - else Seq("-Xdoclint:none") - }, - - unmanagedSourceDirectories in Compile ++= { - if (isJDK9) { - println(s"[JDK9] Enabled [...-jdk9-only] directories to be compiled.") - Seq( - (sourceDirectory in Compile).value / "java-jdk9-only", - (sourceDirectory in Compile).value / "scala-jdk9-only") - } else Seq.empty - }, - - unmanagedSourceDirectories in Test ++= { - if (isJDK9) { - Seq( - (sourceDirectory in Test).value / "java-jdk9-only", - (sourceDirectory in Test).value / "scala-jdk9-only") - } else Seq.empty - }) - - private def isJDK9 = { - jdkVersion startsWith "9" - } -} diff --git a/project/Release.scala b/project/Release.scala index 91cdeebb9c..ced55e15d1 100644 --- a/project/Release.scala +++ b/project/Release.scala @@ -39,6 +39,7 @@ object Release extends ParadoxKeys { IO.copyDirectory(japi, release / "japi" / "akka" / releaseVersion) IO.copyDirectory(docs, release / "docs" / "akka" / releaseVersion) + println(repo) state3 } diff --git a/project/scripts/release b/project/scripts/release index 1c4ee8ef9d..fcf444fa6c 100755 --- a/project/scripts/release +++ b/project/scripts/release @@ -162,7 +162,9 @@ fi declare -r version=$1 declare -r publish_path="${release_server}:${release_path}" -[[ `java -version 2>&1 | grep -E "java version|openjdk version" | cut -d ' ' -f3 | cut -d '.' -f2` -eq 8 ]] || fail "Java version is not 1.8" +JAVA_VERSION=`java -version 2>&1 | grep -E "java version|openjdk version" | cut -d '"' -f2 | cut -d '.' -f1` + +[[ $JAVA_VERSION -ge 9 ]] || fail "Java version is not at least 9" # check for a git command type -P git &> /dev/null || fail "git command not found"