aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@driver.xyz>2018-06-11 17:54:44 -0700
committerJakob Odersky <jakob@driver.xyz>2018-06-12 22:12:08 -0700
commitbe29a4964284f94d624ba25e4078fc382e80ac9a (patch)
tree958939d487aeb5bdb2e83c75349995b79ea06c93
parentd511f3c109becb8a8e52e20826f9cb991d46bf9c (diff)
downloadsbt-settings-be29a4964284f94d624ba25e4078fc382e80ac9a.tar.gz
sbt-settings-be29a4964284f94d624ba25e4078fc382e80ac9a.tar.bz2
sbt-settings-be29a4964284f94d624ba25e4078fc382e80ac9a.zip
Refactor settings to use autopluginsv2.0.0-RC1jo/plugins
**Overview and motivation** This consolidates settings that were previously implemented as functions and/or implicit conversions into a suite of sbt autoplugins. The rationale is that this is well-defined pattern by sbt and allows easy build introspection with standard sbt functionality (for example, `sbt plugins` will list all active plugins in a build). Furthermore, it makes it very easy to disable certain features when required, such as removing linting during development. **Migration from current version** All features from the previous version should still be provided by the changes proposed here. The migration path is quite straight-forward: - Replace `project.driverService(name)` with `project.enablePlugins(Service)` (same goes for libraries) and make sure the project's name corresponds to the service's name - Linting, which was previously enabled by adding `lintingSettings` and `formatSettings` to a project, is automatically enabled. It may be removed by disabling the plugin: `project.dsiablePlugin(Linting)` All tasks and settings provided by sbt-settings should remain the same. **Additional features** An additional feature is that versioning is now handled the same way between libraries and services; that is, the version is derived from the latest git tag. Since services may be deployed from the same tag mutliple times, it is required that versions can be explicitly set to include additional information, such as a build number from a CI system. This was previously done interactively, using sbt's `set` command: ``` export TAG="$(sbt -no-colors version | tail -1 | awk '{ print $2 }').$TRAVIS_BUILD_NUMBER" sbt "set version := \"$TAG\"" docker:publishLocal ``` While this approach works, it has the downsides of requiring mutliple sbt invocations. The changes proposed in this PR will read the version from a VERSION environment variable first, defaulting to git if unavailable. Therefore, the additional sbt invocation can be removed with a CI script similar to the following: ``` export VERSION="$(git describe).$TRAVIS_BUILD_NUMBER"" sbt docker:publishLocal // use version in other steps ``` Using an autoplugin-based approach may also make it easier to cross-compile projects to ScalaJS and Native in the future, as support for them is built into sbt-crossproject.
-rwxr-xr-x.ci/build3
-rw-r--r--.travis.yml7
-rw-r--r--README.md122
-rw-r--r--build.sbt21
-rw-r--r--project/plugins.sbt1
-rwxr-xr-xsrc/main/resources/scalafmtbin12619 -> 0 bytes
-rw-r--r--src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala44
-rw-r--r--src/main/scala/xyz.driver.sbt/Library.scala31
-rw-r--r--src/main/scala/xyz.driver.sbt/Linting.scala82
-rw-r--r--src/main/scala/xyz.driver.sbt/SbtSettings.scala390
-rw-r--r--src/main/scala/xyz.driver.sbt/Service.scala75
-rw-r--r--src/main/scala/xyz.driver.sbt/Versioning.scala31
-rw-r--r--src/sbt-test/sbt-settings/service/build.sbt3
-rw-r--r--src/sbt-test/sbt-settings/service/project/plugins.sbt5
-rw-r--r--src/sbt-test/sbt-settings/service/src/main/scala/Main.scala7
-rw-r--r--src/sbt-test/sbt-settings/service/test4
16 files changed, 350 insertions, 476 deletions
diff --git a/.ci/build b/.ci/build
index ad52e38..9e4930f 100755
--- a/.ci/build
+++ b/.ci/build
@@ -3,7 +3,8 @@ set -ev
sbt \
scalafmt::test \
- test
+ test \
+ scripted
# Automatic publishing for tags that start with `v<digit>`
if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_TAG" =~ ^v[0-9].* ]]; then
diff --git a/.travis.yml b/.travis.yml
index c841aab..171ebe3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,11 @@
+sudo: required
+
+services:
+ - docker
+
language: scala
jdk:
- - oraclejdk8 # this should be changed to openjdk as soon as it becomes available on Travis CI
+ - oraclejdk8
env:
- secure: "qZvexfj0uZLwgn0aKy9rQjBCw8LHbSQJNlA/DrNHDi10SsABNgeE4EDyoPD9+csNchbBmPpUObROCm7k/bDPLO6+cgncHbZlfqet0PGryGldF6XzY0uWVl06l40YXZrCyxeRcIKHzkneo4q3zSxJlkWXFBxFPanZVzyO94riMopKpDT41bKLqdiWIboq7ydeZWo538fuSOKjZjoTWwexX5KMnTePGeYVS2vbJIUXHa6kEzXx5MU8Lf1VcnGQER/1KMN7OrAtqJ72zvo/K6mcU1XGkEYxqQK2hxt3kUACunb1NWQoWjCKWKB9TgiCd/sOAkzhNgnZoYpcSXkBK0XPbjmYNHnIXL1aLdHl3n2jzpjVimFUzW8m7tMqJPJMn1igY+xTqjpoRI8sGS47XQMIhbH0SEQpwU0yY+Lnxo3WcXw0o+r3lW+P4jGv1PrzF6EkERjRICjU0B5XRXwgejvzuETjkzJRfiuvgSpdeCnlO6JK1IoKalJGMETme/Nb5pJfaFMzZ7UsnQzXwPZtZXKYEeWwt86RFggd/P6vmUcK4E1wOMXI5TRdIEN7RVA06GgzU5va7M41er1j8c2yCiu89YYLXchupQvoBAmLqWa5C/fk5dXVatsv13/XXuHBl6BFNRS/Rd850Z/254nQxAdupcZzztX5oMKu/QxBkor27wY="
diff --git a/README.md b/README.md
index c8eca3a..ab33606 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,105 @@
[![Build Status](https://travis-ci.org/drivergroup/sbt-settings.svg?branch=master)](https://travis-ci.org/drivergroup/sbt-settings)
[![Scaladex](https://index.scala-lang.org/drivergroup/sbt-settings/latest.svg)](https://index.scala-lang.org/drivergroup/sbt-settings)
-# _sbt_ plugin for common _sbt_ settings
-Provides common Driver Scala projects configuration for sbt, Scala compiler, testing, linting, formatting, release process, packaging, publication. Allows to use only necessary parts. Sets artifact `organization` to `xyz.driver`.
+# Common sbt settings
+
+Provides common settings for Scala projects, as they are typically
+configured at Driver.
+
+sbt-settings is a suite of [sbt
+autoplugins](https://www.scala-sbt.org/1.0/docs/Plugins.html) that
+provide common settings for the scala compiler, testing, linting,
+formatting, release process, packaging and publication.
## Getting started
-### project/plugins.sbt
+Adding the following snippet to `project/plugins.sbt` will make the
+plugins available:
+
+```scala
+addSbtPlugin("xyz.driver" % "sbt-settings" % "<latest_tag>")
+```
+
+The next section exlains what plugins are available and which ones are
+activated out-of-the-box.
+
+## Plugins
+
+### Summary
+
+| Name | Enabled |
+|--------------------------|----------------------------------------|
+| Linting | automatic |
+| Versioning | automatic |
+| IntegrationTestPackaging | automatic, if ServicePlugin is enabled |
+| Library | manual |
+| Service | manual |
+
+### Linting
+
+*[source](src/main/scala/xyz.driver.sbt/Linting.scala)*
+
+- Includes configuration for scalafmt and scalastyle, modifies the
+ `test` task to check for formatting and styling.
+- Sets strict compiler flags and treats warnings as errors (with the
+ exception of deprecations).
+
+This plugin can get in the way of developer productivity. If that is
+the case, it can simply be disabled.
+
+### Versioning
+
+*[source](src/main/scala/xyz.driver.sbt/Versioning.scala)*
- addSbtPlugin("xyz.driver" % "sbt-settings" % "<latest_tag>")
+Sets the project organization and reads version information from
+git. It also enables overriding the version by setting a `VERSION`
+environment variable (which may be useful to do from CI).
-### build.sbt
+### Integration Test Packaging
-There are two different ways to use the `sbt-settings` configuration:
+*[source](src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala)*
- * As a "Service" — deployed application distributed as a Docker container. Includes Driver Inc.'s artifactory settings, Docker packaging with truststore configuration, build info generation. Versioned using `version.sbt` file for major and minor versions. Usage example, as follows
+Augments the packaging configuration of ServicePlugin, to include
+integration tests in deployed applications images.
- ```
- lazy val root = (project in file("."))
- .driverService ("Name-Of-Your-Service")
- .integrationTestingConfiguration // Enable integration tests
- .packagingConfiguration // To package to zip file as java server app
- .settings(lintingSettings) // Scala code linting settings
- .settings(formatSettings) // Standard Scala code formatting
- .settings(releaseSettings(ServiceReleaseProcess)) // Release process configuration
- ```
+### Library Plugin
- * As a "Library" — commonly used code, distributed as jar using artifactory. Versioned using git tag-based versioning. Includes Driver Inc.'s artifactory settings, publication to artifactory, `sbt release` settings,
+*[source](src/main/scala/xyz.driver.sbt/Library.scala)*
- ```
- lazy val root = (project in file("."))
- .driverLibrary("Name-Of-Your-Library")
- .settings(lintingSettings ++ formatSettings)
- ```
+Common settings for libraries. It only sets the default publish
+target to Driver's artifactory.
-Do `sbt reload` after adding a plugin and changing project configuration.
+### Service Plugin
-## Reference
+*[source](src/main/scala/xyz.driver.sbt/Service.scala)*
-Artifact organization is set to `xyz.driver`.
+Packages an application as a docker image and provides a way to
+include internal TLS certificates.
-### Scala compiler settings
-Scala version — 2.12, flags configured:
+It also includes some utility commands such as `start` and `stop`
+(based on [sbt-revolver](https://github.com/spray/sbt-revolver), to
+enable rapid local development cycles of applications.
- - Common settings: `-unchecked -feature -encoding utf8`,
- - _Advanced Scala features_: `-language:higherKinds -language:implicitConversions -language:postfixOps -language:reflectiveCalls`,
- - _Compiler linting_: `-Xlint -deprecation -Ywarn-numeric-widen -Ywarn-dead-code -Ywarn-unused -Ywarn-unused-import -Xfatal-warnings -Xlint:-missing-interpolator`.
+## Canonical Use Case
+sbt-settings provides many plugins, which may be used in various
+ways. Typically however, a project is either a Library or a Service,
+and as such, it will enable one of those plugins explicitly. The other
+plugins will provide general settings for common conventions and are
+always enabled.
-### Used sbt plugins
+The following provides a typical example for configuring a project as
+a service called "myservice":
- - [sbt-scalafmt](https://olafurpg.github.io/scalafmt/) - code formatter for Scala,
- - [scalastyle-sbt-plugin](https://github.com/scalastyle) - examines your Scala code and indicates potential problems with it,
- - [sbt-revolver](https://github.com/spray/sbt-revolver) - for dangerously fast development turnaround in Scala,
- - [sbt-buildinfo](https://github.com/sbt/sbt-buildinfo) - generates Scala source from your build definitions,
- - [sbt-git](https://github.com/sbt/sbt-git) - to get access to git data (commit hash, branch) in sbt,
- - [sbt-native-packager](https://github.com/sbt/sbt-native-packager) - build application packages in native formats,
- - [sbt-assembly](https://github.com/sbt/sbt-assembly) - deploy fat JARs. Restart processes (sbt-native-packager is used instead,)
- - [sbt-release](https://github.com/sbt/sbt-release) - customizable release process,
- - [sbt-docker](https://github.com/marcuslonnberg/sbt-docker) - create Docker images directly from sbt.
+```scala
+lazy val myservice = project
+ .in(file("."))
+ .enablePlugin(ServicePlugin)
+ .disablePlugins(IntegrationTestPackaging) // we don't need integration tests
+ .disablePlugins(LintPlugin) // I don't need style check during development!
+ .settings( /* custom settings */)
+```
-## Developing
+## Developing this Plugin
This project is set up to auto-deploy on push. If an annotated tag in
the form `v<digit>.*` is pushed, a new version of this project will be
built and published to Maven Central.
diff --git a/build.sbt b/build.sbt
index 21b06e2..b89775c 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,18 +1,21 @@
sbtPlugin := true
name := "sbt-settings"
-scalaVersion := "2.12.5"
+scalaVersion := "2.12.6"
-addSbtPlugin("com.lucidchart" %% "sbt-scalafmt" % "1.14")
+// Plugins that will be included transitively in projects depending on sbt-settings
+addSbtPlugin("com.lucidchart" %% "sbt-scalafmt" % "1.15")
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
-
-// Launch and deploy/release plugins
addSbtPlugin("io.spray" %% "sbt-revolver" % "0.9.1")
-addSbtPlugin("com.eed3si9n" %% "sbt-buildinfo" % "0.7.0")
-addSbtPlugin("com.typesafe.sbt" %% "sbt-git" % "0.9.3")
-addSbtPlugin("com.typesafe.sbt" %% "sbt-native-packager" % "1.3.2")
-addSbtPlugin("com.eed3si9n" %% "sbt-assembly" % "0.14.5")
-addSbtPlugin("com.github.gseitz" %% "sbt-release" % "1.0.7")
+addSbtPlugin("com.eed3si9n" %% "sbt-buildinfo" % "0.9.0")
+addSbtPlugin("com.typesafe.sbt" %% "sbt-git" % "1.0.0")
+addSbtPlugin("com.typesafe.sbt" %% "sbt-native-packager" % "1.3.4")
+addSbtPlugin("com.github.gseitz" %% "sbt-release" % "1.0.8")
// the following prevents thousands of meaningless stacktraces by docker plugin on JDK 9
libraryDependencies += "javax.activation" % "activation" % "1.1.1" % Test
+
+scriptedLaunchOpts := { scriptedLaunchOpts.value ++
+ Seq("-Xmx1024M", "-Dplugin.version=" + version.value)
+}
+scriptedBufferLog := false
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 4da7bd5..a769906 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1 +1,2 @@
addSbtPlugin("com.lucidchart" %% "sbt-scalafmt" % "1.14")
+libraryDependencies += { "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value }
diff --git a/src/main/resources/scalafmt b/src/main/resources/scalafmt
deleted file mode 100755
index c5026e8..0000000
--- a/src/main/resources/scalafmt
+++ /dev/null
Binary files differ
diff --git a/src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala b/src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala
index 7f0c557..c89dcec 100644
--- a/src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala
+++ b/src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala
@@ -2,23 +2,18 @@ package xyz.driver.sbt
import java.nio.file._
-import scala.collection.JavaConverters._
-
-import com.lucidchart.sbt.scalafmt.ScalafmtCorePlugin.autoImport._
-import com.typesafe.sbt.packager._
import com.typesafe.sbt.packager.Keys._
-import com.typesafe.sbt.packager.docker._
import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport.Docker
-import com.typesafe.sbt.packager.universal._
import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport._
-import sbt._
-import sbt.plugins._
import sbt.Keys._
+import sbt._
+
+import scala.collection.JavaConverters._
object IntegrationTestPackaging extends AutoPlugin {
- override def requires = UniversalPlugin && DockerPlugin
- override def trigger = AllRequirements
+ override def requires = Service
+ override def trigger = allRequirements
object autoImport {
lazy val IntegrationTest = config("it") extend (Test) // make test classes available
@@ -26,11 +21,11 @@ object IntegrationTestPackaging extends AutoPlugin {
import autoImport._
private def list(base: Path): Seq[(Path, String)] = base match {
- case _ if Files.isRegularFile(base) => Seq(base -> base.getFileName.toString)
case _ if Files.isDirectory(base) =>
Files.walk(base).iterator().asScala.toSeq.map { file =>
file -> base.relativize(file).toString
}
+ case file => Seq(file -> file.getFileName.toString)
}
private def configurationSettings =
@@ -39,21 +34,8 @@ object IntegrationTestPackaging extends AutoPlugin {
ivyConfigurations := overrideConfigs(IntegrationTest)(ivyConfigurations.value)
)
- private def formatSettings =
- inConfig(IntegrationTest)(scalafmtSettings) ++
- Seq(
- scalafmt in Test := {
- (scalafmt in Test).dependsOn(scalafmt in IntegrationTest).value
- },
- // test:scalafmt::test -> tests scalafmt format in src/test + src/it
- test in scalafmt in Test := {
- (test in scalafmt in Test).dependsOn(test in scalafmt in IntegrationTest).value
- }
- )
-
override def projectSettings =
configurationSettings ++
- formatSettings ++
Seq(
mappings in Universal ++= {
val cp: Seq[(File, String)] = (dependencyClasspath in IntegrationTest).value
@@ -94,16 +76,10 @@ object IntegrationTestPackaging extends AutoPlugin {
cp ++ tests ++ Seq(scriptFile -> s"bin/${normalizedName.value}-it")
},
- dockerCommands in Docker := {
- (dockerCommands in Docker).value ++ Seq(
- ExecCmd("RUN", "mkdir", "-p", "/test"),
- ExecCmd("RUN",
- "ln",
- "-s",
- s"${(defaultLinuxInstallLocation in Docker).value}/bin/${normalizedName.value}-it",
- "/test/run_integration_test.sh")
- )
- }
+ Service.autoImport.customCommands := List(
+ "mkdir -p test",
+ s"ln -s ${(defaultLinuxInstallLocation in Docker).value}/bin/${normalizedName.value}-it /test/run_integration_test.sh"
+ )
)
}
diff --git a/src/main/scala/xyz.driver.sbt/Library.scala b/src/main/scala/xyz.driver.sbt/Library.scala
new file mode 100644
index 0000000..4a5dc7c
--- /dev/null
+++ b/src/main/scala/xyz.driver.sbt/Library.scala
@@ -0,0 +1,31 @@
+package xyz.driver.sbt
+
+import sbt.Keys._
+import sbt._
+import sbt.plugins.JvmPlugin
+
+/** Common settings for a library, Driver style. */
+object Library extends AutoPlugin {
+
+ override def requires = JvmPlugin
+
+ lazy val repositorySettings: Seq[Setting[_]] = Seq(
+ resolvers += "releases" at "https://drivergrp.jfrog.io/drivergrp/releases",
+ resolvers += "snapshots" at "https://drivergrp.jfrog.io/drivergrp/snapshots"
+ )
+
+ lazy val publicationSettings: Seq[Setting[_]] = Seq(
+ publishTo := {
+ val jfrog = "https://drivergrp.jfrog.io/drivergrp/"
+ if (isSnapshot.value) Some("snapshots" at jfrog + "snapshots;build.timestamp=" + new java.util.Date().getTime)
+ else Some("releases" at jfrog + "releases")
+ }
+ )
+
+ override def projectSettings: Seq[Def.Setting[_]] = repositorySettings ++ publicationSettings ++ Seq(
+ javacOptions ++= Seq("-target", "1.8"),
+ crossScalaVersions := List("2.12.6"),
+ scalaVersion := crossScalaVersions.value.last
+ )
+
+}
diff --git a/src/main/scala/xyz.driver.sbt/Linting.scala b/src/main/scala/xyz.driver.sbt/Linting.scala
new file mode 100644
index 0000000..8a293b6
--- /dev/null
+++ b/src/main/scala/xyz.driver.sbt/Linting.scala
@@ -0,0 +1,82 @@
+package xyz.driver.sbt
+
+import com.lucidchart.sbt.scalafmt.ScalafmtCorePlugin.autoImport.{scalafmtConfig, _}
+import com.lucidchart.sbt.scalafmt.ScalafmtPlugin
+import org.scalastyle.sbt.ScalastylePlugin
+import org.scalastyle.sbt.ScalastylePlugin.autoImport.{scalastyle, scalastyleConfig}
+import sbt.Keys._
+import sbt._
+
+import scala.collection.JavaConverters._
+
+/** Enforces common formatting and catches compiler warnings. */
+object Linting extends AutoPlugin {
+
+ override def requires = ScalafmtPlugin && ScalastylePlugin
+ override def trigger = allRequirements
+
+ lazy val formatSettings: Seq[Def.Setting[_]] = Seq(
+ scalafmtConfig := {
+ val packaged = getClass.getClassLoader.getResourceAsStream("scalafmt.conf")
+ val out = file(".scalafmt.conf")
+ IO.write(out, IO.readBytes(packaged))
+ out
+ },
+ scalafmtTestOnCompile in Test := true
+ )
+
+ lazy val scalastyleSettings: Seq[Def.Setting[_]] = Seq(
+ scalastyleConfig := {
+ val stream = getClass.getClassLoader.getResourceAsStream("scalastyle-config.xml")
+ val out = file(".scalastyle-config.xml")
+ IO.write(out, IO.readBytes(stream))
+ out
+ },
+ test in Test := (test in Test).dependsOn((scalastyle in Test).toTask("")).value
+ )
+
+ lazy val scalacSettings: Seq[Def.Setting[_]] = Seq(
+ scalacOptions in Compile ++= Seq(
+ "-language:higherKinds",
+ "-language:implicitConversions",
+ "-language:postfixOps",
+ "-language:reflectiveCalls", // TODO this should be discouraged
+ "-unchecked",
+ "-deprecation",
+ "-feature",
+ "-encoding",
+ "utf8",
+ "-Xlint:_,-unused,-missing-interpolator",
+ "-Ywarn-numeric-widen",
+ "-Ywarn-dead-code",
+ "-Ywarn-unused:_,-explicits,-implicits"
+ ),
+ // Currently, scalac does not provide a way to fine-tune the treating of
+ // warnings as errors. Either all are considered errors
+ // (with -Xfatal-warnings), or none are. This hack analyzes the compiler's
+ // output and treats all warnings as errors, except for deprecations.
+ compile in Compile := {
+ val log = streams.value.log
+ val compiled = (compile in Compile).value
+ val problems = compiled.readSourceInfos().getAllSourceInfos.asScala.flatMap {
+ case (_, info) => info.getReportedProblems
+ }
+ var deprecationsOnly = true
+ problems.foreach { problem =>
+ if (!problem.message().contains("is deprecated")) {
+ deprecationsOnly = false
+ log.error(s"[fatal warning] ${problem.message()}")
+ }
+ }
+ if (!deprecationsOnly)
+ throw new FatalWarningsException("Fatal warnings: some warnings other than deprecations were found.")
+ compiled
+ }
+ )
+
+ lazy val lintSettings = formatSettings ++ scalastyleSettings ++ scalacSettings
+
+ override def projectSettings: Seq[Def.Setting[_]] = inConfig(Compile)(lintSettings) ++ inConfig(Test)(lintSettings)
+
+}
+case class FatalWarningsException(message: String) extends Exception(message)
diff --git a/src/main/scala/xyz.driver.sbt/SbtSettings.scala b/src/main/scala/xyz.driver.sbt/SbtSettings.scala
deleted file mode 100644
index 96ee267..0000000
--- a/src/main/scala/xyz.driver.sbt/SbtSettings.scala
+++ /dev/null
@@ -1,390 +0,0 @@
-package xyz.driver.sbt
-
-import com.lucidchart.sbt.scalafmt.ScalafmtCorePlugin.autoImport._
-import com.typesafe.sbt.SbtGit.git
-import com.typesafe.sbt.SbtNativePackager.autoImport._
-import com.typesafe.sbt.packager.archetypes._
-import com.typesafe.sbt.packager.docker.Cmd
-import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport._
-import com.typesafe.sbt.{GitBranchPrompt, GitVersioning}
-import org.scalastyle.sbt.ScalastylePlugin.autoImport._
-import sbt.Keys._
-import sbt.{Project, State, _}
-import sbtassembly.AssemblyKeys._
-import sbtassembly._
-import sbtbuildinfo.BuildInfoPlugin
-import sbtbuildinfo.BuildInfoPlugin.autoImport.{BuildInfoKey, BuildInfoOption, _}
-import sbtrelease.ReleasePlugin.autoImport.ReleaseKeys._
-import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
-import sbtrelease.ReleasePlugin.autoImport._
-import sbtrelease.{Version, _}
-import scala.collection.JavaConverters._
-import IntegrationTestPackaging.autoImport.IntegrationTest
-
-// we hide the existing definition for setReleaseVersion to replace it with our own
-import sbtrelease.ReleaseStateTransformations.{setReleaseVersion => recordReleaseVersion, inquireVersions => _}
-
-/**
- * @see https://engineering.sharethrough.com/blog/2015/09/23/capturing-common-config-with-an-sbt-parent-plugin/
- */
-object SbtSettings extends AutoPlugin {
-
- val JMX_PORT = 8686
-
- object autoImport {
- lazy val formatSettings = {
- val generateScalafmtConfTask = Def.task {
- val scalafmtConfStream = getClass.getClassLoader.getResourceAsStream("scalafmt.conf")
- val formatConfFile = file(".scalafmt.conf")
- IO.write(formatConfFile, IO.readBytes(scalafmtConfStream))
- formatConfFile
- }
-
- Seq(
- scalafmtConfig := generateScalafmtConfTask.value,
- scalafmt in Compile := {
- (scalafmt in Compile).dependsOn(scalafmt in Test).value
- },
- // scalafmt::test -> tests scalafmt format in src/main + src/test (added behavior)
- test in scalafmt in Compile := {
- (test in scalafmt in Compile).dependsOn(test in scalafmt in Test).value
- },
- test in Test := {
- (test in scalafmt in Compile).value
- (test in Test).value
- }
- )
- }
-
- lazy val testScalastyle = taskKey[Unit]("testScalastyle")
-
- lazy val scalastyleSettings = {
- val generateScalastyleConfTask = Def.task {
- val stream = getClass.getClassLoader.getResourceAsStream("scalastyle-config.xml")
- val styleFile = file("scalastyle-config.xml")
- IO.write(styleFile, IO.readBytes(stream))
- Seq(styleFile)
- }
- Seq(
- resourceGenerators in Compile += generateScalastyleConfTask.taskValue,
- scalastyleConfig := file("scalastyle-config.xml"),
- testScalastyle := scalastyle.in(Compile).toTask("").value,
- testScalastyle in (Test, test) := {
- generateScalastyleConfTask.value
- (testScalastyle in (Test, test)).value
- },
- testExecution in (Test, test) := {
- generateScalastyleConfTask.value
- (testScalastyle in Compile).value
- (testScalastyle in Test).value
- (testExecution in (Test, test)).value
- }
- )
- }
-
- val scalacLintingSettings = Seq(
- scalacOptions ++= {
- Seq(
- "-Xlint:_,-unused,-missing-interpolator",
- "-Ywarn-numeric-widen",
- "-Ywarn-dead-code",
- "-Ywarn-unused:_,-explicits,-implicits"
- )
- },
- compile in Compile := {
- val compiled = (compile in Compile).value
- val problems = compiled.readSourceInfos().getAllSourceInfos.asScala.flatMap {
- case (_, info) =>
- info.getReportedProblems
- }
-
- val deprecationsOnly = problems.forall { problem =>
- problem.message().contains("is deprecated")
- }
-
- if (!deprecationsOnly) sys.error("Fatal warnings: some warnings other than deprecations were found.")
- compiled
- }
- )
-
- lazy val lintingSettings = scalacLintingSettings ++ scalastyleSettings
-
- lazy val repositoriesSettings: Seq[Setting[_]] = {
- Seq(
- resolvers += "releases" at "https://drivergrp.jfrog.io/drivergrp/releases",
- resolvers += "snapshots" at "https://drivergrp.jfrog.io/drivergrp/snapshots"
- )
- }
-
- lazy val publicationSettings: Seq[Setting[_]] = Seq(
- publishTo := {
- val jfrog = "https://drivergrp.jfrog.io/drivergrp/"
-
- if (isSnapshot.value) Some("snapshots" at jfrog + "snapshots;build.timestamp=" + new java.util.Date().getTime)
- else Some("releases" at jfrog + "releases")
- }
- )
-
- private def setVersionOnly(selectVersion: Versions => String): ReleaseStep = { st: State =>
- val vs = st
- .get(ReleaseKeys.versions)
- .getOrElse(sys.error("No versions are set! Was this release part executed before inquireVersions?"))
- val selected = selectVersion(vs)
-
- st.log.info("Setting version to '%s'." format selected)
- val useGlobal = Project.extract(st).get(releaseUseGlobalVersion)
-
- reapply(Seq(
- if (useGlobal) version in ThisBuild := selected else version := selected
- ),
- st)
- }
-
- lazy val setReleaseVersion: ReleaseStep = setVersionOnly(_._1)
-
- // Remove the prompt for next version
- lazy val inquireVersions: ReleaseStep = { st: State =>
- val extracted = Project.extract(st)
-
- val useDefs = st.get(useDefaults).getOrElse(false)
- val currentV = extracted.get(version)
-
- val (_, releaseFunc) = extracted.runTask(releaseVersion, st)
- val suggestedReleaseV = releaseFunc(currentV)
-
- // flatten the Option[Option[String]] as the get returns an Option, and the value inside is an Option
- val releaseV =
- readVersion(suggestedReleaseV, "Release version [%s] : ", useDefs, st.get(commandLineReleaseVersion).flatten)
- val nextV = releaseV
-
- st.put(versions, (releaseV, nextV))
-
- }
-
- def ServiceReleaseProcess = {
- Seq[ReleaseStep](
- checkSnapshotDependencies,
- inquireVersions,
- runTest,
- recordReleaseVersion, // set release version and persistent in version.sbt
- commitReleaseVersion, // performs the initial git checks
- tagRelease,
- pushChanges // also checks that an upstream branch is properly configured
- )
- }
-
- def LibraryReleaseProcess = {
- Seq[ReleaseStep](
- checkSnapshotDependencies,
- inquireVersions,
- setReleaseVersion,
- runTest,
- tagRelease,
- publishArtifacts,
- pushChanges // also checks that an upstream branch is properly configured
- )
- }
-
- def releaseSettings(releaseProcessSteps: Seq[ReleaseStep]): Seq[Setting[_]] = {
-
- val showReleaseVersion = taskKey[String]("the future version once releaseNextVersion has been applied to it")
- Seq(
- releaseCrossBuild := true,
- releaseIgnoreUntrackedFiles := true,
- // Check http://blog.byjean.eu/2015/07/10/painless-release-with-sbt.html for details
- releaseVersionBump := sbtrelease.Version.Bump.Bugfix,
- releaseVersion := {
- case ver @ snapshotVersion if snapshotVersion.endsWith("-SNAPSHOT") =>
- Version(ver).map(_.withoutQualifier.string).getOrElse(versionFormatError)
- case ver =>
- Version(ver).map(_.bumpBugfix.withoutQualifier.string).getOrElse(versionFormatError)
- },
- showReleaseVersion := {
- releaseVersion.value(version.value)
- },
- releaseProcess := releaseProcessSteps
- )
- }
-
- implicit class driverConfigurations(project: Project) {
-
- def gitPluginConfiguration: Project = {
- val VersionRegex = "v([0-9]+.[0-9]+.[0-9]+)-?(.*)?".r
-
- project
- .enablePlugins(GitVersioning, GitBranchPrompt)
- .settings(
- git.useGitDescribe := true,
- git.baseVersion := "0.0.0",
- git.gitTagToVersionNumber := {
- case VersionRegex(v, "SNAPSHOT") => // There are not committed changes at tagged commit
- val ver = Version(v)
- .map(_.withoutQualifier)
- .map(_.bump(sbtrelease.Version.Bump.Bugfix).string)
- .getOrElse(versionFormatError)
-
- Some(s"$ver-SNAPSHOT")
-
- case VersionRegex(v, "") =>
- Some(v)
-
- case VersionRegex(v, s) => // Commit is ahead of the last tagged commit
- val ver = Version(v)
- .map(_.withoutQualifier)
- .map(_.bump(sbtrelease.Version.Bump.Bugfix).string)
- .getOrElse(versionFormatError)
-
- Some(s"$ver-$s-SNAPSHOT")
-
- case _ => None
- }
- )
- }
-
- def buildInfoConfiguration(packageName: String = "xyz.driver"): Project = {
- project
- .enablePlugins(BuildInfoPlugin)
- .settings(
- buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion, git.gitHeadCommit),
- buildInfoPackage := packageName,
- buildInfoOptions += BuildInfoOption.BuildTime
- )
- }
-
- def packagingConfiguration: Project = {
- project
- .settings( // for assembly plugin
- test in assembly := {},
- assemblyMergeStrategy in assembly := {
- case PathList("org", "slf4j", "impl", xs @ _*) => MergeStrategy.first
- case "logback.xml" => MergeStrategy.first
- case strategy: String =>
- val oldStrategy = (assemblyMergeStrategy in assembly).value
- oldStrategy(strategy)
- }
- )
- }
-
- def dockerConfiguration(imageName: String,
- repository: String,
- exposedPorts: Seq[Int],
- baseImage: String = "java:8",
- customCommands: List[String] = List.empty[String],
- aggregateSubprojects: Boolean = false): Project = {
- import com.typesafe.sbt.packager.Keys._
-
- project
- .enablePlugins(JavaAppPackaging)
- .settings(
- // Settings reference http://www.scala-sbt.org/sbt-native-packager/formats/docker.html
- packageName in Docker := imageName,
- version in Docker := version.value.stripSuffix("-SNAPSHOT"),
- dockerRepository := Some(repository),
- maintainer := "Driver Inc. <info@driver.xyz>",
- dockerUpdateLatest := true, // to automatic update the latest tag
- dockerExposedPorts := exposedPorts,
- dockerBaseImage := baseImage,
- daemonUser in Docker := "root",
- dockerCommands := dockerCommands.value
- .flatMap { // @see http://blog.codacy.com/2015/07/16/dockerizing-scala/
- case cmd @ Cmd("FROM", _) => cmd :: customCommands.map(customCommand => Cmd("RUN", customCommand))
- case other => List(other)
- },
- aggregate in Docker := aggregateSubprojects // to include subprojects
- )
-
- // And then you can run "sbt docker:publish"
- }
-
- def deploymentConfiguration(imageName: String,
- exposedPorts: Seq[Int] = Seq(8080),
- clusterName: String = "sand-uw1a-1",
- clusterZone: String = "us-west1-a",
- gCloudProject: String = "driverinc-sandbox",
- baseImage: String = "java:8",
- dockerCustomCommands: List[String] = List.empty[String],
- aggregateSubprojects: Boolean = false) = {
-
- val repositoryName = "gcr.io/" + gCloudProject
-
- val keytoolCommand =
- "keytool -import -alias driverincInternal -keystore $JAVA_HOME/jre/lib/security/cacerts " +
- s"-file /etc/$imageName/ssl/issuing_ca -storepass changeit -noprompt"
-
- // If issuing_ca exists, import it into the internal default ca store
- val importTrustStoreCommand =
- s"if [ -f /etc/$imageName/ssl/issuing_ca ] ; then " + keytoolCommand + "; else echo \"No truststore customization.\"; fi"
-
- val dockerCommands = dockerCustomCommands // :+ importTrustStoreCommand
-
- val allExposedPorts = exposedPorts ++ Seq(JMX_PORT)
-
- dockerConfiguration(imageName, repositoryName, allExposedPorts, baseImage, dockerCommands, aggregateSubprojects)
- .settings(NativePackagerKeys.bashScriptExtraDefines += importTrustStoreCommand)
- .settings(NativePackagerKeys.bashScriptExtraDefines ++= Seq(
- """addJava "-Dcom.sun.management.jmxremote"""",
- s"""addJava "-Dcom.sun.management.jmxremote.port=$JMX_PORT"""",
- """addJava "-Dcom.sun.management.jmxremote.local.only=false"""",
- """addJava "-Dcom.sun.management.jmxremote.authenticate=false"""",
- """addJava "-Dcom.sun.management.jmxremote.ssl=false""""
- ))
- }
-
- def driverLibrary(libraryName: String): Project = {
- project
- .settings(name := libraryName)
- .gitPluginConfiguration
- .settings(repositoriesSettings ++ publicationSettings ++ releaseSettings(LibraryReleaseProcess))
- }
-
- def driverService(appName: String,
- exposedPorts: Seq[Int] = Seq(8080),
- clusterName: String = "sand-uw1a-1",
- clusterZone: String = "us-west1-a",
- gCloudProject: String = "driverinc-sandbox",
- baseImage: String = "java:8",
- dockerCustomCommands: List[String] = List.empty[String],
- aggregateSubprojects: Boolean = false): Project = {
- project
- .settings(name := appName)
- .settings(repositoriesSettings ++ releaseSettings(ServiceReleaseProcess))
- .buildInfoConfiguration()
- .deploymentConfiguration(appName,
- exposedPorts,
- clusterName,
- clusterZone,
- gCloudProject,
- baseImage,
- dockerCustomCommands,
- aggregateSubprojects)
- }
- }
- }
-
- val scalacDefaultOptions = Seq("-unchecked", "-deprecation", "-feature", "-Xlint", "-encoding", "utf8")
-
- val scalacLanguageFeatures = Seq(
- "-language:higherKinds",
- "-language:implicitConversions",
- "-language:postfixOps",
- "-language:reflectiveCalls"
- )
-
- override def trigger: PluginTrigger = allRequirements
- override def projectSettings: Seq[Setting[_]] = Seq(
- organization := "xyz.driver",
- crossScalaVersions := List("2.12.4"),
- scalaVersion := crossScalaVersions.value.last,
- scalacOptions := (scalacDefaultOptions ++ scalacLanguageFeatures),
- scalacOptions in (Compile, console) := (scalacDefaultOptions ++ scalacLanguageFeatures),
- scalacOptions in (Compile, consoleQuick) := (scalacDefaultOptions ++ scalacLanguageFeatures),
- scalacOptions in (Compile, consoleProject) := (scalacDefaultOptions ++ scalacLanguageFeatures),
- version := {
- // Sbt release versioning based on git given double -SNAPSHOT suffix
- // if current commit is not tagged AND there are uncommitted changes (e.g., some file is modified),
- // this setting fixes that, by just removing double -SNAPSHOT if it happened somehow
- Option(version.value).map(vv => vv.replaceAll("-SNAPSHOT-SNAPSHOT", "-SNAPSHOT")).getOrElse("0.0.0")
- },
- fork := true
- )
-}
diff --git a/src/main/scala/xyz.driver.sbt/Service.scala b/src/main/scala/xyz.driver.sbt/Service.scala
new file mode 100644
index 0000000..a0186e6
--- /dev/null
+++ b/src/main/scala/xyz.driver.sbt/Service.scala
@@ -0,0 +1,75 @@
+package xyz.driver.sbt
+
+import com.typesafe.sbt.GitPlugin.autoImport._
+import com.typesafe.sbt.packager.Keys.{
+ dockerBaseImage => _,
+ dockerCommands => _,
+ dockerExposedPorts => _,
+ dockerRepository => _,
+ dockerUpdateLatest => _,
+ _
+}
+import com.typesafe.sbt.packager.archetypes.JavaAppPackaging
+import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport._
+import com.typesafe.sbt.packager.docker.{Cmd, DockerPlugin}
+import sbt.Keys._
+import sbt.{Def, _}
+import sbtbuildinfo.BuildInfoPlugin
+import sbtbuildinfo.BuildInfoPlugin.autoImport._
+
+/** Common settings to a service. */
+object Service extends AutoPlugin {
+
+ override def requires = BuildInfoPlugin && DockerPlugin && JavaAppPackaging
+
+ object autoImport {
+ val customCommands = taskKey[List[String]]("Additional commands that are run as part of docker packaging.")
+ }
+ import autoImport._
+
+ lazy val buildInfoSettings = Seq(
+ buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion, git.gitHeadCommit),
+ buildInfoPackage := organization.value,
+ buildInfoOptions += BuildInfoOption.BuildTime
+ )
+
+ lazy val dockerSettings = Seq(
+ name in Docker := name.value,
+ // Settings reference http://www.scala-sbt.org/sbt-native-packager/formats/docker.html
+ maintainer in Docker := "Driver Inc. <info@driver.xyz>",
+ aggregate in Docker := true, // include subprojects,
+ dockerRepository := Some("gcr.io/driverinc-sandbox"),
+ dockerUpdateLatest := true, // automatically update the latest tag
+ dockerBaseImage := "openjdk:10",
+ dockerCommands := dockerCommands.value.flatMap { // @see http://blog.codacy.com/2015/07/16/dockerizing-scala/
+ case cmd @ Cmd("FROM", _) => cmd :: customCommands.value.map(customCommand => Cmd("RUN", customCommand))
+ case other => List(other)
+ },
+ bashScriptExtraDefines += {
+ s"""|if [[ -f /etc/${name.value}/ssl/issuing_ca ]]; then
+ | keytool -import \
+ | -alias driverincInternal \
+ | -keystore $$JAVA_HOME/jre/lib/security/cacerts \
+ | -file /etc/${name.value}/ssl/issuing_ca \
+ | -storepass changeit -noprompt
+ |else
+ | echo "No truststore customization." >&2
+ |fi
+ |""".stripMargin
+ }
+ )
+
+ override def projectSettings: Seq[Def.Setting[_]] =
+ Library.repositorySettings ++ buildInfoSettings ++ dockerSettings ++ Seq(
+ crossScalaVersions := List("2.12.6"),
+ scalaVersion := crossScalaVersions.value.last,
+ publish := {
+ streams.value.log.warn(s"Service ${name.value} won't be published.")
+ }
+ )
+
+ override def buildSettings: Seq[Def.Setting[_]] =
+ addCommandAlias("start", "reStart") ++
+ addCommandAlias("stop", "reStop")
+
+}
diff --git a/src/main/scala/xyz.driver.sbt/Versioning.scala b/src/main/scala/xyz.driver.sbt/Versioning.scala
new file mode 100644
index 0000000..fe3a218
--- /dev/null
+++ b/src/main/scala/xyz.driver.sbt/Versioning.scala
@@ -0,0 +1,31 @@
+package xyz.driver.sbt
+
+import com.typesafe.sbt.GitPlugin
+import com.typesafe.sbt.SbtGit.git
+import sbt.Keys._
+import sbt.plugins.JvmPlugin
+import sbt.{AutoPlugin, _}
+
+object Versioning extends AutoPlugin {
+
+ override def requires = JvmPlugin
+ override def trigger = allRequirements
+
+ // Get version from git unless a VERSION environment variable is set
+ lazy val versionSettings: Seq[Setting[_]] = sys.env.get("VERSION") match {
+ case None =>
+ GitPlugin.autoImport.versionWithGit ++ Seq(
+ git.useGitDescribe := true, // get version from git
+ git.baseVersion := "0.0.0" // this version is used for new projects without any commits
+ )
+ case Some(v) =>
+ Seq(
+ version := v
+ )
+ }
+
+ override def buildSettings = versionSettings ++ Seq(
+ organization := "xyz.driver"
+ )
+
+}
diff --git a/src/sbt-test/sbt-settings/service/build.sbt b/src/sbt-test/sbt-settings/service/build.sbt
new file mode 100644
index 0000000..c55af36
--- /dev/null
+++ b/src/sbt-test/sbt-settings/service/build.sbt
@@ -0,0 +1,3 @@
+lazy val service = project
+ .in(file("."))
+ .enablePlugins(Service)
diff --git a/src/sbt-test/sbt-settings/service/project/plugins.sbt b/src/sbt-test/sbt-settings/service/project/plugins.sbt
new file mode 100644
index 0000000..2e7ed62
--- /dev/null
+++ b/src/sbt-test/sbt-settings/service/project/plugins.sbt
@@ -0,0 +1,5 @@
+sys.props.get("plugin.version") match {
+ case Some(v) => addSbtPlugin("xyz.driver" % "sbt-settings" % v)
+ case _ => sys.error("""|The system property 'plugin.version' is not defined.
+ |Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
+}
diff --git a/src/sbt-test/sbt-settings/service/src/main/scala/Main.scala b/src/sbt-test/sbt-settings/service/src/main/scala/Main.scala
new file mode 100644
index 0000000..08a8f3f
--- /dev/null
+++ b/src/sbt-test/sbt-settings/service/src/main/scala/Main.scala
@@ -0,0 +1,7 @@
+import java.nio.file.{Files, Paths}
+
+object Main extends App {
+ val version = xyz.driver.BuildInfo.version
+ Files.write(Paths.get("out.txt"), s"$version\n".getBytes("utf-8"))
+ println(s"hello world ($version)")
+}
diff --git a/src/sbt-test/sbt-settings/service/test b/src/sbt-test/sbt-settings/service/test
new file mode 100644
index 0000000..99ad380
--- /dev/null
+++ b/src/sbt-test/sbt-settings/service/test
@@ -0,0 +1,4 @@
+> test
+> run
+$ exists out.txt
+> docker:publishLocal