project/PekkoBuild.scala (266 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
import JdkOptions.autoImport._
import MultiJvmPlugin.autoImport.MultiJvm
import com.lightbend.paradox.projectinfo.ParadoxProjectInfoPluginKeys._
import sbt.Def
import sbt.Keys._
import sbt._
import sbtassembly.AssemblyPlugin.autoImport._
import sbtwelcome.WelcomePlugin.autoImport._
import java.io.FileInputStream
import java.io.InputStreamReader
import java.util.Properties
object PekkoBuild {
object CliOptions {
// CI is the env var defined by Github Actions and Travis:
// - https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
// - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
val runningOnCi: CliOption[Boolean] = CliOption("pekko.ci-server", sys.env.contains("CI"))
}
val enableMiMa = true
val parallelExecutionByDefault = false // TODO: enable this once we're sure it does not break things
lazy val rootSettings = Def.settings(
UnidocRoot.pekkoSettings,
Protobuf.settings,
GlobalScope / parallelExecution := System
.getProperty("pekko.parallelExecution", parallelExecutionByDefault.toString)
.toBoolean,
// used for linking to API docs (overwrites `project-info.version`)
ThisBuild / projectInfoVersion := { if (isSnapshot.value) "snapshot" else version.value })
lazy val mayChangeSettings = Seq(description := """|This module of Apache Pekko is marked as
|'may change', which means that it is in early
|access mode. A module marked 'may change' doesn't
|have to obey the rule of staying binary compatible
|between minor releases. Breaking API changes may be
|introduced in minor releases without notice as we
|refine and simplify based on your feedback. Additionally
|such a module may be dropped in major releases
|without prior deprecation.
|""".stripMargin)
val (mavenLocalResolver, mavenLocalResolverSettings) =
System.getProperty("pekko.build.M2Dir") match {
case null => (Resolver.mavenLocal, Seq.empty)
case path =>
// Maven resolver settings
def deliverPattern(outputPath: File): String =
(outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath
val resolver = Resolver.file("user-publish-m2-local", new File(path))
(
resolver,
Seq(
otherResolvers := resolver :: publishTo.value.toList,
publishM2Configuration := Classpaths.publishConfig(
publishMavenStyle.value,
deliverPattern(crossTarget.value),
if (isSnapshot.value) "integration" else "release",
ivyConfigurations.value.map(c => ConfigRef(c.name)).toVector,
artifacts = packagedArtifacts.value.toVector,
resolverName = resolver.name,
checksums = (publishM2 / checksums).value.toVector,
logging = ivyLoggingLevel.value,
overwrite = true)))
}
lazy val resolverSettings = Def.settings(
pomIncludeRepository := (_ => false) // do not leak internal repositories during staging
)
private def allWarnings: Boolean = System.getProperty("pekko.allwarnings", "false").toBoolean
final val DefaultScalacOptions = Def.setting {
if (scalaVersion.value.startsWith("3.")) {
Seq(
"-encoding",
"UTF-8",
"-feature",
"-unchecked",
// 'blessed' since 2.13.1
"-language:higherKinds")
} else {
Seq(
"-encoding",
"UTF-8",
"-feature",
"-unchecked",
"-Xlog-reflective-calls",
// 'blessed' since 2.13.1
"-language:higherKinds")
}
}
private def jvmGCLogOptions(isJdk11OrHigher: Boolean, isJdk8: Boolean): Seq[String] = {
if (isJdk11OrHigher)
// -Xlog:gc* is equivalent to -XX:+PrintGCDetails. See:
// https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-BE93ABDC-999C-4CB5-A88B-1994AAAC74D5
Seq("-Xlog:gc*")
else if (isJdk8) Seq("-XX:+PrintGCTimeStamps", "-XX:+PrintGCDetails")
else Nil
}
// -XDignore.symbol.file suppresses sun.misc.Unsafe warnings
final val DefaultJavacOptions = Seq("-encoding", "UTF-8", "-Xlint:unchecked", "-XDignore.symbol.file")
lazy val defaultSettings: Seq[Setting[_]] = Def.settings(
projectInfoVersion := (if (isSnapshot.value) "snapshot" else version.value),
Dependencies.Versions,
resolverSettings,
TestExtras.Filter.settings,
// compile options
Compile / scalacOptions ++= DefaultScalacOptions.value,
Compile / scalacOptions ++=
JdkOptions.targetJdkScalacOptions(
targetSystemJdk.value,
optionalDir(jdk8home.value),
fullJavaHomes.value,
scalaVersion.value),
Compile / scalacOptions ++= (if (allWarnings) Seq("-deprecation") else Nil),
Test / scalacOptions := (Test / scalacOptions).value.filterNot(opt =>
opt == "-Xlog-reflective-calls" || opt.contains("genjavadoc")),
Compile / javacOptions ++= {
DefaultJavacOptions ++
JdkOptions.targetJdkJavacOptions(targetSystemJdk.value, optionalDir(jdk8home.value), fullJavaHomes.value)
},
Test / javacOptions ++= DefaultJavacOptions ++
JdkOptions.targetJdkJavacOptions(targetSystemJdk.value, optionalDir(jdk8home.value), fullJavaHomes.value),
Compile / javacOptions ++= (if (allWarnings) Seq("-Xlint:deprecation") else Nil),
doc / javacOptions := Seq(),
crossVersion := CrossVersion.binary,
// Adds a `src/main/scala-2.13+` source directory for code shared
// between Scala 2.13 and Scala 3
Compile / unmanagedSourceDirectories ++= {
val sourceDir = (Compile / sourceDirectory).value
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, n)) => Seq(sourceDir / "scala-2.13+")
case Some((2, n)) if n >= 13 => Seq(sourceDir / "scala-2.13+")
case _ => Nil
}
},
// Adds a `src/test/scala-2.13+` source directory for code shared
// between Scala 2.13 and Scala 3
Test / unmanagedSourceDirectories ++= {
val sourceDir = (Test / sourceDirectory).value
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, n)) => Seq(sourceDir / "scala-2.13+")
case Some((2, n)) if n >= 13 => Seq(sourceDir / "scala-2.13+")
case _ => Nil
}
},
ThisBuild / ivyLoggingLevel := UpdateLogging.Quiet,
licenses := Seq(("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html"))),
homepage := Some(url("https://pekko.apache.org/")),
description := "Apache Pekko is a toolkit for building highly concurrent, distributed, and resilient message-driven applications for Java and Scala.",
scmInfo := Some(
ScmInfo(
url("https://github.com/apache/pekko"),
"scm:git:https://github.com/apache/pekko.git",
"scm:git:git@github.com:apache/pekko.git")),
apiURL := Some(url(s"https://pekko.apache.org/api/pekko/${version.value}")),
initialCommands :=
"""|import language.postfixOps
|import org.apache.pekko.actor._
|import scala.concurrent._
|import com.typesafe.config.ConfigFactory
|import scala.concurrent.duration._
|import org.apache.pekko.util.Timeout
|var config = ConfigFactory.parseString("pekko.stdout-loglevel=INFO,pekko.loglevel=DEBUG,pinned{type=PinnedDispatcher,executor=thread-pool-executor,throughput=1000}")
|var remoteConfig = ConfigFactory.parseString("pekko.remote.classic.netty{port=0,use-dispatcher-for-io=pekko.actor.default-dispatcher,execution-pool-size=0},pekko.actor.provider=remote").withFallback(config)
|var system: ActorSystem = null
|implicit def _system: ActorSystem = system
|def startSystem(remoting: Boolean = false) = { system = ActorSystem("repl", if(remoting) remoteConfig else config); println("don’t forget to system.terminate()!") }
|implicit def ec: ExecutionContext = system.dispatcher
|implicit val timeout: Timeout = Timeout(5 seconds)
|""".stripMargin,
/**
* Test settings
*/
Test / fork := true,
// default JVM config for tests
Test / javaOptions ++= {
val defaults = Seq(
// ## core memory settings
"-XX:+UseG1GC",
// most tests actually don't really use _that_ much memory (>1g usually)
// twice used (and then some) keeps G1GC happy - very few or to no full gcs
"-Xms3g",
"-Xmx3g",
// increase stack size (todo why?)
"-Xss2m",
// ## extra memory/gc tuning
// this breaks jstat, but could avoid costly syncs to disc see https://www.evanjones.ca/jvm-mmap-pause.html
"-XX:+PerfDisableSharedMem",
// tell G1GC that we would be really happy if all GC pauses could be kept below this as higher would
// likely start causing test failures in timing tests
"-XX:MaxGCPauseMillis=300",
// nio direct memory limit for artery/aeron (probably)
"-XX:MaxDirectMemorySize=256m",
// faster random source
"-Djava.security.egd=file:/dev/./urandom")
defaults ++ CliOptions.runningOnCi
.ifTrue(jvmGCLogOptions(JdkOptions.isJdk11orHigher, JdkOptions.isJdk8))
.getOrElse(Nil) ++
JdkOptions.versionSpecificJavaOptions
},
// all system properties passed to sbt prefixed with "pekko." or "aeron." will be passed on to the forked jvms as is
Test / javaOptions := {
val base = (Test / javaOptions).value
val knownPrefix = Set("pekko.", "aeron.")
val pekkoSysProps: Seq[String] =
sys.props.iterator.collect {
case (key, value) if knownPrefix.exists(pre => key.startsWith(pre)) => s"-D$key=$value"
}.toList
base ++ pekkoSysProps
},
// with forked tests the working directory is set to each module's home directory
// rather than the Pekko root, some tests depend on Pekko root being working dir, so reset
Test / testGrouping := {
val original: Seq[Tests.Group] = (Test / testGrouping).value
original.map { group =>
group.runPolicy match {
case Tests.SubProcess(forkOptions) =>
// format: off
group.withRunPolicy(Tests.SubProcess(
forkOptions.withWorkingDirectory(workingDirectory = Some(new File(System.getProperty("user.dir"))))))
// format: on
case _ => group
}
}
},
Test / parallelExecution := System
.getProperty("pekko.parallelExecution", parallelExecutionByDefault.toString)
.toBoolean,
Test / logBuffered := System.getProperty("pekko.logBufferedTests", "false").toBoolean,
// show full stack traces and test case durations
Test / testOptions += Tests.Argument("-oDF"),
mavenLocalResolverSettings,
docLintingSettings,
JdkOptions.targetJdkSettings,
// a workaround for https://github.com/akka/akka/issues/27661
// see also project/Protobuf.scala that introduces /../ to make "intellij happy"
MultiJvm / assembly / fullClasspath := {
val old = (MultiJvm / assembly / fullClasspath).value.toVector
val files = old.map(_.data.getCanonicalFile).distinct
files.map { x =>
Attributed.blank(x)
}
})
lazy val welcomeSettings: Seq[Setting[_]] = Def.settings {
import sbtwelcome._
Seq(
logo := {
raw"""
|________ ______ ______
|___ __ \_______ /____ /_______
|__ /_/ / _ \_ //_/_ //_/ __ \
|_ ____// __/ ,< _ ,< / /_/ /
|/_/ \___//_/|_| /_/|_| \____/ ${version.value}
|
|""".stripMargin
},
logoColor := scala.Console.BLUE,
usefulTasks := Seq(
UsefulTask("compile", "Compile the current project"),
UsefulTask("test", "Run all the tests"),
UsefulTask("testQuick",
"Runs all the tests. When run multiple times will only run previously failing tests (shell mode only)"),
UsefulTask("testOnly *.AnySpec", "Only run a selected test"),
UsefulTask("TestJdk9 / testOnly *.AnySpec", "Only run a Jdk9+ selected test"),
UsefulTask("testQuick *.AnySpec",
"Only run a selected test. When run multiple times will only run previously failing tests (shell mode only)"),
UsefulTask("testQuickUntilPassed", "Runs all tests in a continuous loop until all tests pass"),
UsefulTask("publishLocal", "Publish current snapshot version to local ~/.ivy2 repo"),
UsefulTask("verifyCodeStyle", "Verify code style"),
UsefulTask("applyCodeStyle", "Apply code style"),
UsefulTask("sortImports", "Sort the imports"),
UsefulTask("mimaReportBinaryIssues ", "Check binary issues"),
UsefulTask("validatePullRequest ", "Validate pull request"),
UsefulTask("docs/paradox", "Build documentation (license report will be generate on CI or Publish)"),
UsefulTask("docs/paradoxBrowse",
"Browse the generated documentation (license report will be generate on CI or Publish)"),
UsefulTask("tips:", "prefix commands with `+` to run against cross Scala versions."),
UsefulTask("Contributing guide:", "https://github.com/apache/pekko/blob/main/CONTRIBUTING.md")).map(
_.noAlias))
}
private def optionalDir(path: String): Option[File] =
Option(path).filter(_.nonEmpty).map { path =>
val dir = new File(path)
if (!dir.exists)
throw new IllegalArgumentException(s"Path [$path] not found")
dir
}
lazy val docLintingSettings = Seq(
compile / javacOptions ++= Seq("-Xdoclint:none"),
test / javacOptions ++= Seq("-Xdoclint:none"),
doc / javacOptions ++= {
if (JdkOptions.isJdk8) Seq("-Xdoclint:none")
else Seq("-Xdoclint:none", "--ignore-source-errors")
})
def loadSystemProperties(fileName: String): Unit = {
import scala.collection.JavaConverters._
val file = new File(fileName)
if (file.exists()) {
println("Loading system properties from file `" + fileName + "`")
val in = new InputStreamReader(new FileInputStream(file), "UTF-8")
val props = new Properties
props.load(in)
in.close()
sys.props ++ props.asScala
}
}
def majorMinor(version: String): Option[String] = """\d+\.\d+""".r.findFirstIn(version)
}