Skip to content

GitLab CI template for sbt

This project implements a GitLab CI/CD template to build, test and analyse your sbt-based projects.

Languages supported by this build tool are :

Usage

This template can be used both as a CI/CD component or using the legacy include:project syntax.

Use as a CI/CD component

Add the following to your .gitlab-ci.yml:

include:
  # 1: include the component
  - component: $CI_SERVER_FQDN/to-be-continuous/sbt/gitlab-ci-sbt@1.7.1
    # 2: set/override component inputs
    inputs:
      image: "registry.hub.docker.com/sbtscala/scala-sbt:17.0.2_1.6.2_3.1.3" # ⚠ this is only an example

Use as a CI/CD template (legacy)

Add the following to your .gitlab-ci.yml:

include:
  # 1: include the template
  - project: 'to-be-continuous/sbt'
    ref: '1.7.1'
    file: '/templates/gitlab-ci-sbt.yml'

variables:
  # 2: set/override template variables
  SBT_IMAGE: "registry.hub.docker.com/sbtscala/scala-sbt:17.0.2_1.6.2_3.1.3" # ⚠ this is only an example

Project prerequisites

dependencies resolution

In order to configure your project to use a repository cache/proxy such as artifactory or nexus, add those two files into your project root directory :

  • .sbtopts with this content :
    -Dsbt.override.build.repos=true
    -Dsbt.repository.config=.sbtrepositories
    
  • .sbtrepositories with such content :
    [repositories]
    local
    mirror_of_central: https://mavenrepo.acme.host/maven-proxy-asis-apache-releases
    confluent-releases : https://mavenrepo.acme.host/maven-proxy-asis-confluent-releases
    

If some repositories requires credentials, add a file credentials.sbt (you can choose the basename you want) to your project home directory with such content :

credentials ++= {
  List("MAVEN_REPOSITORY_HOST", "MAVEN_REPOSITORY_USERNAME", "MAVEN_REPOSITORY_PASSWORD").flatMap(sys.env.get) match {
    case List(host, user, password) =>
      streams.value.log.info(s"private/internal dependencies resolution credentials for host '$host' user '$user' has been configured")
      Some(Credentials("Artifactory Realm", host, user, password))

    case _ =>
      streams.value.log.warn(
        "private/internal dependencies resolution may fail as some environment variables are missing : MAVEN_REPOSITORY_USERNAME / MAVEN_REPOSITORY_PASSWORD / MAVEN_REPOSITORY_HOST"
      )
      None
  }
}

Which uses three environment variables MAVEN_REPOSITORY_HOST, MAVEN_REPOSITORY_USERNAME, MAVEN_REPOSITORY_PASSWORD from which sbt will extract credentials information.

Note that thanks to those configurations files, your favorite IDE will be able to automatically take the right repository configuration without any specific configuration.

Global configuration

The sbt template uses some global configuration used throughout all jobs.

Input / Variable Description Default value
image / SBT_IMAGE The Docker image used to run sbt
⚠ set the version required by your project
registry.hub.docker.com/sbtscala/scala-sbt:17.0.2_1.6.2_3.1.3
opts / SBT_OPTS Global sbt options -Dsbt.global.base=sbt-cache/sbtboot -Dsbt.boot.directory=sbt-cache/boot -Dsbt.coursier.home=sbt-cache/coursier -Dsbt.ci=true -Dsbt.color=always
cli-opts / SBT_CLI_OPTS Additional sbt options used on the command line --batch

As you can see, your sbt cache policy is preconfigured to use local cache in sbt-cache directory (not to download dependencies over and over again).

If you have a good reason to do differently, you'll have to override the SBT_OPTS variable as well as the cache policy.

Jobs

sbt-build job

The sbt template features a job sbt-build that performs build and tests at once. This stage is performed in a single job for optimization purpose (it saves time) and also for test jobs dependency reasons (some test jobs such as SONAR analysis have a dependency on test results).

It uses the following environment variable:

Input / Variable Description Default value
build-args / SBT_BUILD_ARGS sbt arguments for the build job packaging clean package
test-args / SBT_TEST_ARGS sbt arguments for the build job test phase coverage test coverageAggregate

Note: - Always keep the clean goal to generate bytes code without any instrumentation coming from the test phasis.

About Code Coverage

With its default arguments, the GitLab CI template for sbt forces the use of sbt-coverage to compute code coverage during unit tests execution.

But this requires that you add this plugin to your project:

Add the plugin in your project/plugins.sbt:

addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.1") // Check for newer releases : https://search.maven.org/artifact/org.scoverage/sbt-scoverage

sbt-sbom job

This job generates a SBOM file listing all dependencies using syft.

It is bound to the test stage, and uses the following variables:

Input / Variable Description Default value
sbom-disabled / SBT_SBOM_DISABLED Set to true to disable this job none
sbom-image / SBT_SBOM_IMAGE The syft image used for SBOM analysis registry.hub.docker.com/anchore/syft:debug
sbom-opts / SBT_SBOM_OPTS Options for syft used for SBOM analysis dir:sbt-cache/coursier --catalogers java-cataloger

In addition to logs in the console, this job produces the following reports, kept for one week:

Report Format Usage
reports/sbt-sbom.cyclonedx.json CycloneDX JSON Security & Compliance integration

publish jobs

The sbt template supports publishing:

  • snapshot artifacts (on any branch change),
  • and/or release artifacts (on versioned tags such as x.y.z)

The expected behavior of publish jobs is controlled with the SBT_PUBLISH_MODE variable, supporting one of the following values:

  • none (default): Publishing is disabled.
  • snapshot: Auto-publishes snapshot artifacts on any branch change.
  • ontag: Auto-publishes snapshot artifacts on any branch change, and publishes release artifacts as soon as a new release tag is committed. In this mode it is up to you to manage release numbering and tagging operations.
  • release: Auto-publishes snapshot artifacts on any branch change, and implements a full publishing workflow with automatic release numbering and git tags management (see release job below for details).

Repository authentication

Your publication repository(ies) may require authentication credentials to publish artifacts.

You shall handle them in the following way:

  1. define all required credentials as: lock: project variables,
  2. define the target repositories and the credentials with a dedicated sbt file such as publish.sbt in your project home directory

Example:

Define the following environment variables :

Input / Variable Description
maven-repository-publish-snapshot-url / MAVEN_REPOSITORY_PUBLISH_SNAPSHOT_URL Destination repository to publish snpashot artifacts
maven-repository-publish-release-url / MAVEN_REPOSITORY_PUBLISH_RELEASE_URL Destination repository to publish release artifacts
MAVEN_REPOSITORY_PUBLISH_USERNAME Repository Username
MAVEN_REPOSITORY_PUBLISH_PASSWORD Repository Password
credentials ++= {
  val publishURLProperty = if (isSnapshot.value) "MAVEN_REPOSITORY_PUBLISH_SNAPSHOT_URL" else "MAVEN_REPOSITORY_PUBLISH_RELEASE_URL"

  List(publishURLProperty, "MAVEN_REPOSITORY_PUBLISH_USERNAME", "MAVEN_REPOSITORY_PUBLISH_PASSWORD").flatMap(sys.env.get) match {
    case List(url, username, password) =>
      val host = new java.net.URL(url).getHost
      streams.value.log.info(s"publish/release credentials for host '$host' user '$username' has been configured")
      Some(Credentials("Artifactory Realm", host, username, password))

    case _ =>
      streams.value.log.warn(
        "publish/release may fail as some environment variables are missing : MAVEN_REPOSITORY_PUBLISH_USERNAME / MAVEN_REPOSITORY_PUBLISH_PASSWORD / MAVEN_REPOSITORY_PUBLISH_SNAPSHOT_URL / MAVEN_REPOSITORY_PUBLISH_RELEASE_URL"
      )
      None
  }
}

publishTo := {
  val publishURLProperty = if (isSnapshot.value) "MAVEN_REPOSITORY_PUBLISH_SNAPSHOT_URL" else "MAVEN_REPOSITORY_PUBLISH_RELEASE_URL"

  val mayBeTarget =
    if (isSnapshot.value) sys.env.get(publishURLProperty).map(url => "snapshots on artifactory".at(url))
    else sys.env.get(publishURLProperty).map(url => "releases on artifactory".at(url))

  mayBeTarget match {
    case None         => streams.value.log.warn(s"publish repository target url not given ($publishURLProperty)")
    case Some(target) => streams.value.log.info(s"publish to ${target.name} -> $target")
  }
  mayBeTarget
}

// To avoid error such as "java.net.ProtocolException: Too many follow-up requests: 21"
updateOptions := updateOptions.value.withGigahorse(false)

sbt-release job

This job is disabled by default and can be activated by setting the SBT_PUBLISH_MODE to release.

It is based on the sbt-release plugin. You'll have to add it in your project/plugins.sbt:

// Check for newer releases : https://search.maven.org/artifact/com.github.sbt/sbt-release
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") 

SCM authentication

A sbt release involves some Git push operations. For this you shall use a SSH key.

We recommend you to use a project deploy key with write access to your project.

The key should not have a passphrase ( see how to generate a new SSH key pair).

Specify 🔒 $GIT_PRIVATE_KEY as protected project variable with the private part of the deploy key.

-----BEGIN 0PENSSH PRIVATE KEY-----
blablabla
-----END OPENSSH PRIVATE KEY-----