diff options
-rwxr-xr-x | .ci/build | 3 | ||||
-rw-r--r-- | .travis.yml | 7 | ||||
-rw-r--r-- | README.md | 122 | ||||
-rw-r--r-- | build.sbt | 21 | ||||
-rw-r--r-- | project/plugins.sbt | 1 | ||||
-rwxr-xr-x | src/main/resources/scalafmt | bin | 12619 -> 0 bytes | |||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/IntegrationTestPackaging.scala | 44 | ||||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/Library.scala | 31 | ||||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/Linting.scala | 82 | ||||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/SbtSettings.scala | 390 | ||||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/Service.scala | 75 | ||||
-rw-r--r-- | src/main/scala/xyz.driver.sbt/Versioning.scala | 31 | ||||
-rw-r--r-- | src/sbt-test/sbt-settings/service/build.sbt | 3 | ||||
-rw-r--r-- | src/sbt-test/sbt-settings/service/project/plugins.sbt | 5 | ||||
-rw-r--r-- | src/sbt-test/sbt-settings/service/src/main/scala/Main.scala | 7 | ||||
-rw-r--r-- | src/sbt-test/sbt-settings/service/test | 4 |
16 files changed, 350 insertions, 476 deletions
@@ -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=" @@ -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. @@ -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 Binary files differdeleted file mode 100755 index c5026e8..0000000 --- a/src/main/resources/scalafmt +++ /dev/null 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 |