Skip to content

Commit 9888fc2

Browse files
Improvements to build.sbt (#51)
1 parent d967a6c commit 9888fc2

File tree

3 files changed

+199
-165
lines changed

3 files changed

+199
-165
lines changed

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,30 @@
1818

1919
## How to use
2020

21-
First you need to build the project:
21+
### Prerequisites
22+
23+
You need to have [sbt](https://www.scala-sbt.org/) installed to build the project.
24+
25+
You can install sbt using [sdkman](https://sdkman.io/):
26+
```bash
27+
$ sdk install sbt
28+
```
29+
30+
### Running the server
31+
32+
You can run the server with the following command:
2233
```bash
23-
$ sbt "project docker" "docker:publishLocal"
34+
$ sbt run
2435
```
2536

26-
This will create a docker image with the name `das-salesforce`.
37+
### Docker
38+
39+
To run the server in a docker container you need to follow these steps:
40+
41+
First, you need to build the project:
42+
```bash
43+
$ sbt "docker:publishLocal"
44+
```
2745

2846
Then you can run the image with the following command:
2947
```bash
@@ -35,4 +53,4 @@ This will start the server, typically on port 50051.
3553
You can find the image id by looking at the sbt output or by running:
3654
```bash
3755
$ docker images
38-
```
56+
```

build.sbt

Lines changed: 8 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,20 @@
1-
import java.nio.file.Paths
2-
3-
import sbt.Keys._
4-
import sbt._
5-
6-
import com.typesafe.sbt.packager.docker.{Cmd, LayeredMapping}
7-
8-
ThisBuild / credentials += Credentials(
9-
"GitHub Package Registry",
10-
"maven.pkg.github.com",
11-
"raw-labs",
12-
sys.env.getOrElse("GITHUB_TOKEN", ""))
13-
14-
lazy val commonSettings = Seq(
15-
homepage := Some(url("https://www.raw-labs.com/")),
16-
organization := "com.raw-labs",
17-
organizationName := "RAW Labs SA",
18-
organizationHomepage := Some(url("https://www.raw-labs.com/")),
19-
// Use cached resolution of dependencies
20-
// http://www.scala-sbt.org/0.13/docs/Cached-Resolution.html
21-
updateOptions := updateOptions.in(Global).value.withCachedResolution(true),
22-
resolvers ++= Seq(Resolver.mavenLocal),
23-
resolvers += "RAW Labs GitHub Packages" at "https://maven.pkg.github.com/raw-labs/_")
24-
25-
lazy val buildSettings = Seq(
26-
scalaVersion := "2.13.15",
27-
javacOptions ++= Seq("-source", "21", "-target", "21"),
28-
scalacOptions ++= Seq(
29-
"-feature",
30-
"-unchecked",
31-
"-deprecation",
32-
"-Xlint:-stars-align,_",
33-
"-Ywarn-dead-code",
34-
"-Ywarn-macros:after", // Fix for false warning of unused implicit arguments in traits/interfaces.
35-
"-Ypatmat-exhaust-depth",
36-
"160"))
37-
38-
lazy val compileSettings = Seq(
39-
Compile / doc / sources := Seq.empty,
40-
Compile / packageDoc / mappings := Seq(),
41-
Compile / packageSrc / publishArtifact := true,
42-
Compile / packageDoc / publishArtifact := false,
43-
Compile / packageBin / packageOptions += Package.ManifestAttributes(
44-
"Automatic-Module-Name" -> name.value.replace('-', '.')),
45-
// Ensure Java annotations get compiled first, so that they are accessible from Scala.
46-
compileOrder := CompileOrder.JavaThenScala,
47-
Compile / mainClass := Some("com.rawlabs.das.server.DASServer")
48-
)
49-
50-
lazy val testSettings = Seq(
51-
// Ensuring tests are run in a forked JVM for isolation.
52-
Test / fork := true,
53-
// Disabling parallel execution of tests.
54-
// Test / parallelExecution := false,
55-
// Pass system properties starting with "raw." to the forked JVMs.
56-
Test / javaOptions ++= {
57-
import scala.collection.JavaConverters._
58-
val props = System.getProperties
59-
props
60-
.stringPropertyNames()
61-
.asScala
62-
.filter(_.startsWith("raw."))
63-
.map(key => s"-D$key=${props.getProperty(key)}")
64-
.toSeq
65-
},
66-
// Set up heap dump options for out-of-memory errors.
67-
Test / javaOptions ++= Seq(
68-
"-XX:+HeapDumpOnOutOfMemoryError",
69-
s"-XX:HeapDumpPath=${Paths.get(sys.env.getOrElse("SBT_FORK_OUTPUT_DIR", "target/test-results")).resolve("heap-dumps")}"),
70-
Test / publishArtifact := true)
71-
72-
val isCI = sys.env.getOrElse("CI", "false").toBoolean
73-
74-
lazy val publishSettings = Seq(
75-
versionScheme := Some("early-semver"),
76-
publish / skip := false,
77-
publishMavenStyle := true,
78-
publishTo := Some("GitHub raw-labs Apache Maven Packages" at "https://maven.pkg.github.com/raw-labs/das-salesforce"),
79-
publishConfiguration := publishConfiguration.value.withOverwrite(isCI))
80-
81-
lazy val strictBuildSettings =
82-
commonSettings ++ compileSettings ++ buildSettings ++ testSettings ++ Seq(scalacOptions ++= Seq("-Xfatal-warnings"))
1+
import SbtDASPlugin.autoImport.*
832

843
lazy val root = (project in file("."))
85-
.enablePlugins(JavaAppPackaging, DockerPlugin)
4+
.enablePlugins(SbtDASPlugin)
865
.settings(
87-
name := "das-salesforce",
88-
strictBuildSettings,
89-
publishSettings,
6+
repoNameSetting := "das-salesforce",
907
libraryDependencies ++= Seq(
918
// DAS
92-
"com.raw-labs" %% "das-server-scala" % "0.4.1" % "compile->compile;test->test",
93-
"com.raw-labs" %% "protocol-das" % "1.0.0" % "compile->compile;test->test",
9+
"com.raw-labs" %% "das-server-scala" % "0.6.0" % "compile->compile;test->test",
9410
// Salesforce client
9511
"com.frejo" % "force-rest-api" % "0.0.45",
9612
// Jackson
9713
"joda-time" % "joda-time" % "2.12.7",
9814
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.18.2",
9915
"com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % "2.18.2",
10016
"com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.18.2",
101-
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.2"
102-
)
103-
,
104-
dependencyOverrides ++= Seq(
105-
"io.netty" % "netty-handler" % "4.1.118.Final",
106-
"com.google.protobuf" % "protobuf-java" % "3.25.5"
107-
),
108-
dockerSettings
109-
)
110-
111-
lazy val dockerSettings = Seq(
112-
Docker/ packageName := "das-salesforce-server",
113-
dockerBaseImage := "eclipse-temurin:21-jre",
114-
dockerLabels ++= Map(
115-
"vendor" -> "RAW Labs SA",
116-
"product" -> "das-salesforce-server",
117-
"image-type" -> "final",
118-
"org.opencontainers.image.source" -> "https://github.com/raw-labs/das-salesforce"),
119-
Docker / daemonUser := "raw",
120-
Docker / daemonUserUid := Some("1001"),
121-
Docker / daemonGroup := "raw",
122-
Docker / daemonGroupGid := Some("1001"),
123-
dockerExposedVolumes := Seq("/var/log/raw"),
124-
dockerExposedPorts := Seq(50051),
125-
dockerEnvVars := Map("PATH" -> s"${(Docker / defaultLinuxInstallLocation).value}/bin:$$PATH"),
126-
dockerEnvVars += "LANG" -> "C.UTF-8",
127-
updateOptions := updateOptions.value.withLatestSnapshots(true),
128-
Linux / linuxPackageMappings += packageTemplateMapping(s"/var/lib/${packageName.value}")(),
129-
bashScriptDefines := {
130-
val ClasspathPattern = "declare -r app_classpath=\"(.*)\"\n".r
131-
bashScriptDefines.value.map {
132-
case ClasspathPattern(classpath) => s"""
133-
|declare -r app_classpath="$${app_home}/../conf:$classpath"
134-
|""".stripMargin
135-
case _ @entry => entry
136-
}
137-
},
138-
Docker / dockerLayerMappings := (Docker / dockerLayerMappings).value.map {
139-
case lm @ LayeredMapping(Some(1), file, path) => {
140-
val fileName = java.nio.file.Paths.get(path).getFileName.toString
141-
if (!fileName.endsWith(".jar")) {
142-
// If it is not a jar, put it on the top layer. Configuration files and other small files.
143-
LayeredMapping(Some(2), file, path)
144-
} else if (fileName.startsWith("com.raw-labs") && fileName.endsWith(".jar")) {
145-
// If it is one of our jars, also top layer. These will change often.
146-
LayeredMapping(Some(2), file, path)
147-
} else {
148-
// Otherwise it is a 3rd party library, which only changes when we change dependencies, so leave it in layer 1
149-
lm
150-
}
151-
}
152-
case lm @ _ => lm
153-
},
154-
dockerAlias := dockerAlias.value.withTag(Option(version.value.replace("+", "-"))),
155-
dockerAliases := {
156-
val devRegistry = sys.env.getOrElse("DEV_REGISTRY", "ghcr.io/raw-labs/das-salesforce")
157-
val releaseRegistry = sys.env.get("RELEASE_DOCKER_REGISTRY")
158-
val baseAlias = dockerAlias.value.withRegistryHost(Some(devRegistry))
159-
160-
releaseRegistry match {
161-
case Some(releaseReg) => Seq(baseAlias, dockerAlias.value.withRegistryHost(Some(releaseReg)))
162-
case None => Seq(baseAlias)
163-
}
164-
})
165-
166-
lazy val printDockerImageName = taskKey[Unit]("Prints the full Docker image name that will be produced")
167-
168-
printDockerImageName := {
169-
// Get the main Docker alias (the first one in the sequence)
170-
val alias = (Docker / dockerAliases).value.head
171-
// The toString method already returns the full image name with registry and tag
172-
println(s"DOCKER_IMAGE=${alias}")
173-
}
17+
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.2"),
18+
dependencyOverrides ++= Seq(
19+
"io.netty" % "netty-handler" % "4.1.118.Final"
20+
))

project/SbtDASPlugin.scala

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import sbt.*
2+
import sbt.Keys.*
3+
4+
import com.typesafe.sbt.SbtNativePackager.{Docker, Linux}
5+
import com.typesafe.sbt.packager.Keys.*
6+
import com.typesafe.sbt.packager.archetypes.JavaAppPackaging
7+
import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport.dockerLayerMappings
8+
import com.typesafe.sbt.packager.docker.{DockerPlugin, LayeredMapping}
9+
import com.typesafe.sbt.packager.linux.Mapper.packageTemplateMapping
10+
11+
object SbtDASPlugin extends AutoPlugin {
12+
13+
// We require Docker + JavaAppPackaging so projects automatically get them.
14+
override def requires = DockerPlugin && JavaAppPackaging
15+
override def trigger = allRequirements
16+
17+
object autoImport {
18+
// The only project-specific setting that we want devs to override in build.sbt
19+
val repoNameSetting = settingKey[String]("Repository/project name for Docker, publishing, etc.")
20+
}
21+
import autoImport._
22+
23+
// Hardcoded organization name, username, etc., since everything else is "common"
24+
private val orgUsername = "raw-labs"
25+
private val orgName = "com.raw-labs"
26+
27+
// We inline these...
28+
override def projectSettings: Seq[Setting[_]] = Seq(
29+
// Add GitHub credentials
30+
credentials += Credentials(
31+
"GitHub Package Registry",
32+
"maven.pkg.github.com",
33+
orgUsername,
34+
sys.env.getOrElse("GITHUB_TOKEN", "")),
35+
36+
// Basic metadata
37+
homepage := Some(url("https://www.raw-labs.com/")),
38+
organization := orgName,
39+
organizationName := "RAW Labs SA",
40+
organizationHomepage := Some(url("https://www.raw-labs.com/")),
41+
42+
// Use cached resolution
43+
updateOptions := updateOptions.in(Global).value.withCachedResolution(true),
44+
45+
// Add local Maven + RAW Labs Package Registry resolvers
46+
resolvers ++= Seq(Resolver.mavenLocal, "RAW Labs GitHub Packages" at s"https://maven.pkg.github.com/raw-labs/_"),
47+
48+
// We use Scala 2.13
49+
scalaVersion := "2.13.15",
50+
51+
// Compile settings
52+
Compile / doc / sources := Seq.empty,
53+
Compile / packageDoc / mappings := Seq(),
54+
Compile / packageSrc / publishArtifact := true,
55+
Compile / packageDoc / publishArtifact := false,
56+
Compile / packageBin / packageOptions +=
57+
Package.ManifestAttributes("Automatic-Module-Name" -> name.value.replace('-', '.')),
58+
compileOrder := CompileOrder.JavaThenScala,
59+
Compile / run / fork := true,
60+
Compile / mainClass := Some("com.rawlabs.das.server.DASServer"),
61+
62+
// Test settings
63+
Test / fork := true,
64+
Test / publishArtifact := true,
65+
66+
// Make all warnings fatal
67+
scalacOptions ++= Seq("-Xfatal-warnings"),
68+
69+
// Publish settings
70+
versionScheme := Some("early-semver"),
71+
publish / skip := false,
72+
publishMavenStyle := true,
73+
publishTo := {
74+
val repoName = repoNameSetting.value
75+
Some(s"GitHub $orgUsername Apache Maven Packages" at s"https://maven.pkg.github.com/$orgUsername/$repoName")
76+
},
77+
// Overwrite artifacts in CI
78+
publishConfiguration := {
79+
val isCI = sys.env.getOrElse("CI", "false").toBoolean
80+
publishConfiguration.value.withOverwrite(isCI)
81+
},
82+
83+
// Let the `name` of this project follow our custom repoNameSetting
84+
name := repoNameSetting.value,
85+
86+
// Docker settings
87+
Docker / packageName := s"${repoNameSetting.value}-server",
88+
dockerBaseImage := "eclipse-temurin:21-jre",
89+
dockerLabels ++= Map(
90+
"vendor" -> "RAW Labs SA",
91+
"product" -> s"${repoNameSetting.value}-server",
92+
"image-type" -> "final",
93+
"org.opencontainers.image.source" -> s"https://github.com/$orgUsername/${repoNameSetting.value}"),
94+
Docker / daemonUser := "raw",
95+
Docker / daemonUserUid := Some("1001"),
96+
Docker / daemonGroup := "raw",
97+
Docker / daemonGroupGid := Some("1001"),
98+
dockerExposedVolumes := Seq("/var/log/raw"),
99+
dockerExposedPorts := Seq(50051),
100+
dockerEnvVars := Map("PATH" -> s"${(Docker / defaultLinuxInstallLocation).value}/bin:$$PATH", "LANG" -> "C.UTF-8"),
101+
updateOptions := updateOptions.value.withLatestSnapshots(true),
102+
103+
// Make /var/lib/<packageName> be an explicit Linux package mapping
104+
Linux / linuxPackageMappings += packageTemplateMapping(s"/var/lib/${(Docker / packageName).value}")(),
105+
106+
// Modify the bash script so we can prepend our conf folder
107+
bashScriptDefines := {
108+
val pattern = "declare -r app_classpath=\"(.*)\"\n".r
109+
bashScriptDefines.value.map {
110+
case pattern(cp) =>
111+
s"""
112+
|declare -r app_classpath="$${app_home}/../conf:$cp"
113+
|""".stripMargin
114+
case other => other
115+
}
116+
},
117+
118+
// Put certain jars on the top layer for Docker layering
119+
Docker / dockerLayerMappings := {
120+
(Docker / dockerLayerMappings).value.map {
121+
case lm @ LayeredMapping(Some(1), file, path) =>
122+
val fileName = java.nio.file.Paths.get(path).getFileName.toString
123+
if (!fileName.endsWith(".jar")) {
124+
// If it is not a jar, put it on the top layer
125+
LayeredMapping(Some(2), file, path)
126+
} else if (fileName.startsWith("com.raw-labs") && fileName.endsWith(".jar")) {
127+
// Our own jars -> top layer
128+
LayeredMapping(Some(2), file, path)
129+
} else {
130+
// 3rd party jars stay in layer 1
131+
lm
132+
}
133+
case lm => lm
134+
}
135+
},
136+
137+
// Clean up Docker version tag
138+
Docker / version := {
139+
val ver = version.value
140+
ver.replaceAll("[+]", "-").replaceAll("[^\\w.-]", "-")
141+
},
142+
143+
// Let Docker push to GHCR by default, or dev override with DEV_REGISTRY
144+
dockerAlias := {
145+
val devRegistry = sys.env.getOrElse("DEV_REGISTRY", s"ghcr.io/$orgUsername/${repoNameSetting.value}")
146+
dockerAlias.value.withRegistryHost(Some(devRegistry))
147+
},
148+
149+
// Optionally push to multiple registries if RELEASE_DOCKER_REGISTRY is set
150+
dockerAliases := {
151+
val devRegistry = sys.env.getOrElse("DEV_REGISTRY", s"ghcr.io/$orgUsername/${repoNameSetting.value}")
152+
val releaseRegistry = sys.env.get("RELEASE_DOCKER_REGISTRY")
153+
154+
val baseAlias = dockerAlias.value.withRegistryHost(Some(devRegistry))
155+
releaseRegistry match {
156+
case Some(rr) => Seq(baseAlias, dockerAlias.value.withRegistryHost(Some(rr)))
157+
case None => Seq(baseAlias)
158+
}
159+
},
160+
161+
// Define the printDockerImageName task
162+
printDockerImageName := {
163+
val alias = (Docker / dockerAliases).value.head
164+
println(s"DOCKER_IMAGE=$alias")
165+
})
166+
167+
// A task key for printing the Docker name
168+
val printDockerImageName = taskKey[Unit]("Prints the full Docker image name that will be produced")
169+
}

0 commit comments

Comments
 (0)