pekko/project/AkkaBuild.scala

352 lines
15 KiB
Scala
Raw Normal View History

/*
2022-02-04 12:36:44 +01:00
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
2011-09-23 10:21:03 +02:00
package akka
import akka.JdkOptions.autoImport._
2019-10-03 16:42:41 +02:00
import com.lightbend.paradox.projectinfo.ParadoxProjectInfoPluginKeys._
import com.typesafe.sbt.MultiJvmPlugin.autoImport.MultiJvm
import sbt.Def
import sbt.Keys._
2017-06-09 04:34:21 -07:00
import sbt._
import sbtassembly.AssemblyPlugin.autoImport._
import sbtwelcome.WelcomePlugin.autoImport._
import java.io.FileInputStream
import java.io.InputStreamReader
import java.util.Properties
2011-07-04 19:16:43 +12:00
object AkkaBuild {
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("akka.ci-server", sys.env.contains("CI"))
}
val enableMiMa = true
2016-05-10 10:49:33 +02:00
val parallelExecutionByDefault = false // TODO: enable this once we're sure it does not break things
lazy val buildSettings = Def.settings(organization := "com.typesafe.akka")
2019-05-21 17:35:42 +01:00
lazy val rootSettings = Def.settings(
commands += switchVersion,
2019-05-21 17:35:42 +01:00
UnidocRoot.akkaSettings,
Protobuf.settings,
GlobalScope / parallelExecution := System
.getProperty("akka.parallelExecution", parallelExecutionByDefault.toString)
.toBoolean,
2019-10-03 16:42:41 +02:00
// used for linking to API docs (overwrites `project-info.version`)
ThisBuild / projectInfoVersion := { if (isSnapshot.value) "snapshot" else version.value })
2019-05-21 17:35:42 +01:00
lazy val mayChangeSettings = Seq(description := """|This module of Akka is marked as
|'may change', which means that it is in early
|access mode, which also means that it is not covered
|by commercial support. An 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.
2017-10-06 10:30:28 +02:00
|""".stripMargin)
val (mavenLocalResolver, mavenLocalResolverSettings) =
System.getProperty("akka.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)))
}
2019-05-21 17:35:42 +01:00
lazy val resolverSettings = Def.settings(
// should we be allowed to use artifacts published to the local maven repository
2017-10-06 10:30:28 +02:00
if (System.getProperty("akka.build.useLocalMavenResolver", "false").toBoolean)
2019-05-21 17:35:42 +01:00
resolvers += mavenLocalResolver
else Seq.empty,
// should we be allowed to use artifacts from sonatype snapshots
2017-10-06 10:30:28 +02:00
if (System.getProperty("akka.build.useSnapshotSonatypeResolver", "false").toBoolean)
2019-05-21 17:35:42 +01:00
resolvers += Resolver.sonatypeRepo("snapshots")
else Seq.empty,
pomIncludeRepository := (_ => false) // do not leak internal repositories during staging
)
private def allWarnings: Boolean = System.getProperty("akka.allwarnings", "false").toBoolean
2015-01-30 11:33:33 +01:00
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")
2019-05-21 17:35:42 +01:00
lazy val defaultSettings: Seq[Setting[_]] = Def.settings(
Dependencies.Versions,
2019-05-21 17:35:42 +01:00
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(),
2019-05-21 17:35:42 +01:00
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
}
},
ThisBuild / ivyLoggingLevel := UpdateLogging.Quiet,
licenses := Seq(("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html"))),
2019-05-21 17:35:42 +01:00
homepage := Some(url("https://akka.io/")),
description := "Akka is a toolkit for building highly concurrent, distributed, and resilient message-driven applications for Java and Scala.",
scmInfo := Some(
ScmInfo(
url("https://github.com/akka/akka"),
"scm:git:https://github.com/akka/akka.git",
"scm:git:git@github.com:akka/akka.git")),
2019-05-21 17:35:42 +01:00
apiURL := Some(url(s"https://doc.akka.io/api/akka/${version.value}")),
initialCommands :=
"""|import language.postfixOps
|import akka.actor._
|import scala.concurrent._
|import com.typesafe.config.ConfigFactory
|import scala.concurrent.duration._
|import akka.util.Timeout
|var config = ConfigFactory.parseString("akka.stdout-loglevel=INFO,akka.loglevel=DEBUG,pinned{type=PinnedDispatcher,executor=thread-pool-executor,throughput=1000}")
|var remoteConfig = ConfigFactory.parseString("akka.remote.classic.netty{port=0,use-dispatcher-for-io=akka.actor.default-dispatcher,execution-pool-size=0},akka.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("dont forget to system.terminate()!") }
|implicit def ec: ExecutionContext = system.dispatcher
|implicit val timeout: Timeout = Timeout(5 seconds)
|""".stripMargin,
2019-05-21 17:35:42 +01:00
/**
* Test settings
*/
Test / fork := true,
2019-05-21 17:35:42 +01:00
// default JVM config for tests
Test / javaOptions ++= {
2019-05-21 17:35:42 +01:00
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",
2019-05-21 17:35:42 +01:00
// 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
2019-05-21 17:35:42 +01:00
"-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))
2022-01-27 16:14:29 +01:00
.getOrElse(Nil) ++
JdkOptions.versionSpecificJavaOptions
2019-05-21 17:35:42 +01:00
},
// all system properties passed to sbt prefixed with "akka." or "aeron." will be passed on to the forked jvms as is
Test / javaOptions := {
val base = (Test / javaOptions).value
val knownPrefix = Set("akka.", "aeron.")
2019-05-21 17:35:42 +01:00
val akkaSysProps: Seq[String] =
sys.props.iterator.collect {
case (key, value) if knownPrefix.exists(pre => key.startsWith(pre)) => s"-D$key=$value"
}.toList
2019-05-21 17:35:42 +01:00
base ++ akkaSysProps
},
// with forked tests the working directory is set to each module's home directory
// rather than the Akka root, some tests depend on Akka root being working dir, so reset
Test / testGrouping := {
val original: Seq[Tests.Group] = (Test / testGrouping).value
2019-05-21 17:35:42 +01:00
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
2019-05-21 17:35:42 +01:00
case _ => group
2017-10-04 14:22:25 +02:00
}
2019-05-21 17:35:42 +01:00
}
},
Test / parallelExecution := System
.getProperty("akka.parallelExecution", parallelExecutionByDefault.toString)
.toBoolean,
Test / logBuffered := System.getProperty("akka.logBufferedTests", "false").toBoolean,
2019-05-21 17:35:42 +01:00
// show full stack traces and test case durations
Test / testOptions += Tests.Argument("-oDF"),
2019-05-21 17:35:42 +01:00
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)
}
})
2016-01-13 12:00:46 +01:00
lazy val welcomeSettings: Seq[Setting[_]] = Def.settings {
import sbtwelcome._
Seq(
logo := {
s"""
|_______ ______ ______
|___ |___ /_____ /________ _
|__ /| |__ //_/__ //_/_ __ `/
|_ ___ |_ ,< _ ,< / /_/ /
|/_/ |_|/_/|_| /_/|_| \\__,_/ ${version.value}
|
|""".stripMargin
},
logoColor := scala.Console.BLUE,
usefulTasks := Seq(
UsefulTask("", "compile", "Compile the current project"),
UsefulTask("", "test", "Run all the tests "),
UsefulTask("", "testOnly *.AnySpec", "Only run a selected test"),
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("", "akka-docs/paradox", "Build documentation"),
UsefulTask("", "akka-docs/paradoxBrowse", "Browse the generated documentation"),
UsefulTask("", "tips:", "prefix commands with `+` to run against cross Scala versions."),
UsefulTask("", "Contributing guide:", "https://github.com/akka/akka/blob/main/CONTRIBUTING.md")))
}
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
}
2016-01-13 12:00:46 +01:00
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
}
}
2017-10-06 10:30:28 +02:00
def majorMinor(version: String): Option[String] = """\d+\.\d+""".r.findFirstIn(version)
// So we can `sbt "+~ 3 clean compile"`
//
// The advantage over `++` is twofold:
// * `++` also requires the patch version, `+~` finds the first supported Scala version that matches the prefix (if any)
// * When subprojects need to be excluded, ++ needs to be specified for each command
//
2022-04-20 15:00:03 +08:00
// So the `++` equivalent of the above example is `sbt "++ 3.1.2 clean" "++ 3.1.2 compile"`
val switchVersion: Command = Command.args("+~", "<version> <args>")({ (initialState: State, args: Seq[String]) =>
{
val requestedVersionPrefix = args.head
val requestedVersion = Dependencies.allScalaVersions.filter(_.startsWith(requestedVersionPrefix)).head
def run(state: State, command: String): State = {
val parsed = s"++ $requestedVersion $command".foldLeft(Cross.switchVersion.parser(state))((p, i) => p.derive(i))
parsed.resultEmpty match {
case e: sbt.internal.util.complete.Parser.Failure =>
throw new IllegalStateException(e.errors.mkString(", "))
case sbt.internal.util.complete.Parser.Value(v) =>
v()
}
}
val commands = args.tail
commands.foldLeft(initialState)(run)
}
})
2011-07-04 19:16:43 +12:00
}