build.sbt (453 lines of code) (raw):
import com.typesafe.sbt.packager.docker.*
import org.typelevel.sbt.gha.JavaSpec.Distribution.Temurin
import org.typelevel.scalacoptions.ScalacOptions
import sbtcrossproject.{CrossProject, CrossType, Platform}
/// variables
val groupId = "org.scala-steward"
val projectName = "scala-steward"
val rootPkg = groupId.replace("-", "")
val gitHubOwner = "scala-steward-org"
val gitHubUrl = s"https://github.com/$gitHubOwner/$projectName"
val mainBranch = "main"
val gitHubUserContent = s"https://raw.githubusercontent.com/$gitHubOwner/$projectName/$mainBranch"
val moduleCrossPlatformMatrix: Map[String, List[Platform]] = Map(
"benchmark" -> List(JVMPlatform),
"core" -> List(JVMPlatform),
"docs" -> List(JVMPlatform),
"dummy" -> List(JVMPlatform)
)
val Scala213 = "2.13.16"
val Scala3 = "3.3.5"
/// sbt-typelevel configuration
ThisBuild / crossScalaVersions := Seq(Scala213, Scala3)
ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
ThisBuild / githubWorkflowPublishTargetBranches := Seq(
RefPredicate.Equals(Ref.Branch(mainBranch)),
RefPredicate.StartsWith(Ref.Tag("v"))
)
ThisBuild / githubWorkflowPublish := Seq(
WorkflowStep.Run(
List("sbt ci-release"),
name = Some("Publish JARs"),
env = Map(
"PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}",
"PGP_SECRET" -> "${{ secrets.PGP_SECRET }}",
"SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}",
"SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}"
)
),
WorkflowStep.Run(
List(
"docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}",
"sbt core/Docker/publish"
),
name = Some("Publish Docker image")
)
)
ThisBuild / githubWorkflowJavaVersions := Seq("21", "17", "11").map(JavaSpec(Temurin, _))
ThisBuild / githubWorkflowBuild :=
Seq(
WorkflowStep.Use(
UseRef.Public("coursier", "setup-action", "v1"),
params = Map("apps" -> "scalafmt:3.8.3")
),
WorkflowStep.Sbt(List("validate"), name = Some("Build project")),
WorkflowStep.Use(
ref = UseRef.Public("codecov", "codecov-action", "v4"),
name = Some("Codecov"),
env = Map("CODECOV_TOKEN" -> "${{ secrets.CODECOV_TOKEN }}")
)
)
ThisBuild / mergifyPrRules := {
val authorCondition = MergifyCondition.Or(
List(
MergifyCondition.Custom("author=scala-steward"),
MergifyCondition.Custom("author=scala-steward-dev")
)
)
Seq(
MergifyPrRule(
"label scala-steward's PRs",
List(authorCondition),
List(MergifyAction.Label(List("dependency-update")))
),
MergifyPrRule(
"merge scala-steward's PRs",
List(authorCondition) ++ mergifySuccessConditions.value,
List(MergifyAction.Merge(Some("merge")))
)
)
}
/// global build settings
ThisBuild / dynverSeparator := "-"
ThisBuild / evictionErrorLevel := Level.Info
ThisBuild / tpolecatDefaultOptionsMode := {
if (insideCI.value) org.typelevel.sbt.tpolecat.CiMode else org.typelevel.sbt.tpolecat.DevMode
}
/// projects
lazy val root = project
.in(file("."))
.aggregate(benchmark.jvm, core.jvm, docs.jvm, dummy.jvm)
.settings(commonSettings)
.settings(noPublishSettings)
lazy val benchmark = myCrossProject("benchmark")
.dependsOn(core)
.enablePlugins(JmhPlugin)
.settings(noPublishSettings)
.settings(
crossScalaVersions := Seq(Scala213, Scala3),
scalacOptions -= "-Wnonunit-statement",
coverageEnabled := false,
unusedCompileDependencies := Set.empty
)
lazy val core = myCrossProject("core")
.enablePlugins(BuildInfoPlugin, JavaAppPackaging, DockerPlugin)
.settings(dockerSettings)
.settings(
crossScalaVersions := Seq(Scala213, Scala3),
libraryDependencies ++= Seq(
Dependencies.bcprovJdk15to18,
Dependencies.betterFiles,
Dependencies.catsCore,
Dependencies.catsEffect,
Dependencies.catsParse,
Dependencies.circeConfig,
Dependencies.circeGeneric,
Dependencies.circeParser,
Dependencies.circeRefined,
Dependencies.commonsIo,
Dependencies.coursierCore.cross(CrossVersion.for3Use2_13),
Dependencies.coursierSbtMaven.cross(CrossVersion.for3Use2_13),
Dependencies.cron4sCore,
Dependencies.decline,
Dependencies.fs2Core,
Dependencies.fs2Io,
Dependencies.http4sCirce,
Dependencies.http4sClient,
Dependencies.http4sCore,
Dependencies.http4sJdkhttpClient,
Dependencies.jjwtApi,
Dependencies.jjwtImpl % Runtime,
Dependencies.jjwtJackson % Runtime,
Dependencies.log4catsSlf4j,
Dependencies.monocleCore,
Dependencies.refined,
Dependencies.scalacacheCaffeine,
Dependencies.tomlj,
Dependencies.logbackClassic % Runtime,
Dependencies.catsLaws % Test,
Dependencies.circeLiteral % Test,
Dependencies.disciplineMunit % Test,
Dependencies.http4sDsl % Test,
Dependencies.http4sEmberServer % Test,
Dependencies.munit % Test,
Dependencies.munitCatsEffect % Test,
Dependencies.munitScalacheck % Test,
Dependencies.refinedScalacheck % Test,
Dependencies.scalacheck % Test
),
// Workaround for https://github.com/cb372/sbt-explicit-dependencies/issues/117
unusedCompileDependenciesFilter -=
moduleFilter(organization = Dependencies.coursierCore.organization),
assembly / test := {},
assembly / assemblyMergeStrategy := {
case PathList("META-INF", "versions", "9", "module-info.class") =>
// (core / assembly) deduplicate: different file contents found in the following:
// https/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.4.20/kotlin-stdlib-1.4.20.jar:META-INF/versions/9/module-info.class
// https/repo1.maven.org/maven2/org/tukaani/xz/1.9/xz-1.9.jar:META-INF/versions/9/module-info.class
MergeStrategy.first
case PathList("module-info.class") =>
// (core / assembly) deduplicate: different file contents found in the following:
// https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.6/jackson-annotations-2.12.6.jar:module-info.class
// https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.6/jackson-core-2.12.6.jar:module-info.class
// https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.6.1/jackson-databind-2.12.6.1.jar:module-info.class
MergeStrategy.discard
case PathList("META-INF", "sisu", "javax.inject.Named") =>
// (core / assembly) deduplicate: different file contents found in the following:
// https/repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/4.5.0/plexus-archiver-4.5.0.jar:META-INF/sisu/javax.inject.Named
// https/repo1.maven.org/maven2/org/codehaus/plexus/plexus-io/3.4.0/plexus-io-3.4.0.jar:META-INF/sisu/javax.inject.Named
MergeStrategy.first
case otherwise =>
val defaultStrategy = (assembly / assemblyMergeStrategy).value
defaultStrategy(otherwise)
},
buildInfoKeys := Seq[BuildInfoKey](
organization,
version,
scalaVersion,
scalaBinaryVersion,
sbtVersion,
BuildInfoKey("gitHubUrl" -> gitHubUrl),
BuildInfoKey("gitHubUserContent" -> gitHubUserContent),
BuildInfoKey("mainBranch" -> mainBranch),
BuildInfoKey.map(git.gitHeadCommit) { case (k, v) => k -> v.getOrElse(mainBranch) },
BuildInfoKey("millPluginArtifactName" -> Dependencies.scalaStewardMillPluginArtifactName),
BuildInfoKey("millPluginVersion" -> Dependencies.scalaStewardMillPlugin.revision)
),
buildInfoPackage := moduleRootPkg.value,
initialCommands +=
s"""import ${moduleRootPkg.value}._
|import ${moduleRootPkg.value}.data._
|import ${moduleRootPkg.value}.util._
|import better.files.File
|import cats.effect.IO
|import org.http4s.client.Client
|import org.typelevel.log4cats.Logger
|import org.typelevel.log4cats.slf4j.Slf4jLogger
|implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
|""".stripMargin,
// Inspired by https://stackoverflow.com/a/41978937/460387
Test / sourceGenerators += Def.task {
val file = (Test / sourceManaged).value / "InitialCommandsTest.scala"
val content =
s"""object InitialCommandsTest {
| ${initialCommands.value.linesIterator.mkString("\n ")}
| // prevent warnings
| intellijThisImportIsUsed(Client); intellijThisImportIsUsed(File);
| intellijThisImportIsUsed(Nel); intellijThisImportIsUsed(Repo);
| intellijThisImportIsUsed(Main);
|}""".stripMargin
IO.write(file, content)
Seq(file)
}.taskValue,
run / fork := true,
// Uncomment for remote debugging:
// run / javaOptions += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005",
Test / fork := true,
Compile / resourceGenerators += Def.task {
val outDir = (Compile / resourceManaged).value
def downloadPlugin(v: String): File = {
val outFile = outDir / s"StewardPlugin_$v.scala"
if (!outFile.exists()) {
val u =
s"https://raw.githubusercontent.com/scala-steward-org/sbt-plugin/main/modules/sbt-plugin-$v/src/main/scala/org/scalasteward/sbt/plugin/StewardPlugin_$v.scala"
val content = scala.util.Using(scala.io.Source.fromURL(u))(_.mkString).get
IO.write(outFile, content)
}
outFile
}
Seq(downloadPlugin("1_0_0"), downloadPlugin("1_3_11"))
}.taskValue
)
lazy val docs = myCrossProject("docs")
.dependsOn(core)
.enablePlugins(MdocPlugin)
.settings(noPublishSettings)
.settings(
libraryDependencies ++= Seq(Dependencies.munitDiff),
scalacOptions += "-Ytasty-reader",
tpolecatExcludeOptions := Set(ScalacOptions.fatalWarnings),
mdocIn := baseDirectory.value / ".." / "mdoc",
mdocOut := (LocalRootProject / baseDirectory).value / "docs",
mdocVariables := Map(
"GITHUB_URL" -> gitHubUrl,
"MAIN_BRANCH" -> mainBranch
),
checkDocs := {
val inDir = mdocIn.value.getCanonicalPath
val outDir = mdocOut.value.getCanonicalPath
val rootDir = (LocalRootProject / baseDirectory).value
try git.runner.value.apply("diff", "--quiet", outDir)(rootDir, streams.value.log)
catch {
case t: Throwable =>
val diff = git.runner.value.apply("diff", outDir)(rootDir, streams.value.log)
val msg = s"""|Docs in $inDir and $outDir are out of sync.
|Run 'sbt docs/mdoc' and commit the changes to fix this.
|The diff is:
|$diff
|""".stripMargin
throw new Throwable(msg, t)
}
()
},
coverageEnabled := false,
unusedCompileDependencies := Set.empty
)
// Dummy project to receive updates from @scala-steward for this project's
// libraryDependencies.
lazy val dummy = myCrossProject("dummy")
.disablePlugins(ExplicitDepsPlugin)
.settings(noPublishSettings)
.settings(
libraryDependencies ++= Seq(
Dependencies.millMain,
Dependencies.scalaStewardMillPlugin
)
)
/// settings
def myCrossProject(name: String): CrossProject =
CrossProject(name, file(name))(moduleCrossPlatformMatrix(name): _*)
.crossType(CrossType.Pure)
.withoutSuffixFor(JVMPlatform)
.in(file(s"modules/$name"))
.settings(
moduleName := s"$projectName-$name",
moduleRootPkg := s"$rootPkg.${name.replace('-', '.')}"
)
.settings(commonSettings)
lazy val commonSettings = Def.settings(
compileSettings,
metadataSettings,
scaladocSettings
)
lazy val compileSettings = Def.settings(
scalaVersion := Scala213,
scalacOptions ++= {
scalaBinaryVersion.value match {
case "2.13" =>
Seq("-Xsource:3-cross")
case _ =>
Nil
}
},
doctestTestFramework := DoctestTestFramework.Munit
)
lazy val metadataSettings = Def.settings(
name := projectName,
organization := groupId,
homepage := Some(url(gitHubUrl)),
startYear := Some(2018),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
scmInfo := Some(ScmInfo(homepage.value.get, s"scm:git:$gitHubUrl.git")),
headerLicense := Some(HeaderLicense.ALv2("2018-2025", "Scala Steward contributors")),
developers := List(
Developer(
id = "alejandrohdezma",
name = "Alejandro Hernández",
email = "",
url = url("https://github.com/alejandrohdezma")
),
Developer(
id = "exoego",
name = "TATSUNO Yasuhiro",
email = "",
url = url("https://github.com/exoego")
),
Developer(
id = "fthomas",
name = "Frank S. Thomas",
email = "",
url = url("https://github.com/fthomas")
),
Developer(
id = "mzuehlke",
name = "Marco Zühlke",
email = "",
url = url("https://github.com/mzuehlke")
)
)
)
lazy val dockerSettings = Def.settings(
dockerBaseImage := Option(System.getenv("DOCKER_BASE_IMAGE"))
.getOrElse("eclipse-temurin:11-alpine"),
dockerCommands ++= {
val curl = "curl -fL --output"
val binDir = "/usr/local/bin"
val sbtVer = sbtVersion.value
val sbtTgz = s"sbt-$sbtVer.tgz"
val installSbt = Seq(
s"$curl $sbtTgz https://github.com/sbt/sbt/releases/download/v$sbtVer/$sbtTgz",
s"tar -xf $sbtTgz",
s"rm -f $sbtTgz"
).mkString(" && ")
val millVer = Dependencies.millMain.revision
val millBin = s"$binDir/mill"
val installMill = Seq(
s"$curl $millBin https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/$millVer/mill",
s"chmod +x $millBin"
).mkString(" && ")
val csBin = s"$binDir/cs"
val installCoursier = Seq(
s"$curl $csBin.gz https://github.com/coursier/coursier/releases/download/v${Dependencies.coursierCore.revision}/cs-x86_64-pc-linux-static.gz",
s"gunzip $csBin.gz",
s"chmod +x $csBin"
).mkString(" && ")
val scalaCliBin = s"$binDir/scala-cli"
val installScalaCli = Seq(
s"$curl $scalaCliBin.gz https://github.com/Virtuslab/scala-cli/releases/latest/download/scala-cli-x86_64-pc-linux-static.gz",
s"gunzip $scalaCliBin.gz",
s"chmod +x $scalaCliBin"
).mkString(" && ")
Seq(
Cmd("USER", "root"),
Cmd(
"RUN",
"apk --no-cache add bash git gpg ca-certificates curl maven openssh nodejs npm ncurses sqlite sqlite-dev"
),
Cmd("RUN", installSbt),
Cmd("RUN", installMill),
Cmd("RUN", installCoursier),
Cmd("RUN", installScalaCli),
Cmd("RUN", s"$csBin install --install-dir $binDir scalafix scalafmt"),
// Ensure binaries are in PATH
Cmd("RUN", "echo $PATH"),
Cmd("RUN", "npm install --global yarn"),
Cmd("RUN", "which cs mill mvn node npm sbt scala-cli scalafix scalafmt yarn")
)
},
Docker / packageName := s"fthomas/${name.value}",
dockerUpdateLatest := true,
dockerAliases ++= {
if (!isSnapshot.value) Seq(dockerAlias.value.withTag(Option("latest-release"))) else Nil
},
dockerEnvVars := Map(
"PATH" -> "/opt/docker/sbt/bin:${PATH}",
"COURSIER_PROGRESS" -> "false"
)
)
lazy val noPublishSettings = Def.settings(
publish / skip := true
)
lazy val scaladocSettings = Def.settings(
Compile / doc / scalacOptions ++= {
val tag = s"v${version.value}"
val tree = if (isSnapshot.value) git.gitHeadCommit.value.getOrElse(mainBranch) else tag
Seq(
"-doc-source-url",
s"${scmInfo.value.get.browseUrl}/blob/$tree€{FILE_PATH}.scala",
"-sourcepath",
(LocalRootProject / baseDirectory).value.getAbsolutePath
)
}
)
/// setting keys
lazy val checkDocs = taskKey[Unit]("")
lazy val moduleRootPkg = settingKey[String]("").withRank(KeyRanks.Invisible)
moduleRootPkg := rootPkg
// Run Scala Steward from sbt for development and testing.
// Members of the @scala-steward-org/core team can request an access token
// of @scala-steward-dev for local development from @fthomas.
lazy val runSteward = taskKey[Unit]("")
runSteward := Def.taskDyn {
val home = System.getenv("HOME")
val projectDir = (LocalRootProject / baseDirectory).value
val gitHubLogin = projectName + "-dev"
// val gitHubAppDir = projectDir.getParentFile / "gh-app"
val args = Seq(
Seq("--workspace", s"$projectDir/workspace"),
Seq("--repos-file", s"$projectDir/repos.md"),
Seq("--git-author-email", s"dev@$projectName.org"),
Seq("--forge-login", gitHubLogin),
Seq("--git-ask-pass", s"$home/.github/askpass/$gitHubLogin.sh"),
// Seq("--github-app-id", IO.read(gitHubAppDir / "scala-steward.app-id.txt").trim),
// Seq("--github-app-key-file", s"$gitHubAppDir/scala-steward.private-key.pem"),
Seq("--repo-config", s"$projectDir/.scala-steward.conf"),
Seq("--whitelist", s"$home/.cache/coursier"),
Seq("--whitelist", s"$home/.cache/JNA"),
Seq("--whitelist", s"$home/.cache/mill"),
Seq("--whitelist", s"$home/.ivy2"),
Seq("--whitelist", s"$home/.m2"),
Seq("--whitelist", s"$home/.mill"),
Seq("--whitelist", s"$home/.sbt")
).flatten.mkString(" ", " ", "")
(core.jvm / Compile / run).toTask(args)
}.value
lazy val runValidateRepoConfig = taskKey[Unit]("")
runValidateRepoConfig := Def.taskDyn {
val projectDir = (LocalRootProject / baseDirectory).value
val args = Seq(
Seq("validate-repo-config", s"$projectDir/.scala-steward.conf")
).flatten.mkString(" ", " ", "")
(core.jvm / Compile / run).toTask(args)
}.value
/// commands
def addCommandsAlias(name: String, cmds: Seq[String]) =
addCommandAlias(name, cmds.mkString(";", ";", ""))
addCommandsAlias(
"validate",
Seq(
"clean",
"headerCheck",
"scalafmtCheckAll",
"scalafmtSbtCheck",
"unusedCompileDependenciesTest",
"coverage",
"test",
"coverageReport",
"doc",
"docs/mdoc",
"docs/checkDocs",
"package",
"packageSrc",
"core/assembly",
"Docker/publishLocal"
)
)
addCommandsAlias(
"fmt",
Seq(
"headerCreate",
"scalafmtAll",
"scalafmtSbt"
)
)