First step will be creating a hello world style entry point which will be put into a fat jar later.
So code for that is trivial and it’ll be placed in src/main/scala
directory following maven convention of Java projects.
CLIMain.scala
package pl.stmi.dota2.crawler.main
object CLIMain extends App {
println("Dota 2 Stats Crawler")
}
Now that there’s entry point for application let’s configure building and packaging.
For build automation sbt tool will be used
and sbt expects build configuration in build.sbt
file placed at project root.
First lines define top level project parameters like name, organization and version, also Scala version is explicitly defined. After this configuration of sbt-assembly plugin is provided. Initially it consists only of final fat jar name and qualified name of mainClass - CLIMain in this case.
build.sbt
ThisBuild / name := "dota2-stats-crawler"
ThisBuild / version := "1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.13.2"
ThisBuild / organization := "pl.stmi"
lazy val dota2_stats_crawler = (project in file("."))
.settings(
assemblyJarName in assembly := "dota2-stats-crawler.jar",
mainClass in assembly := Some("pl.stmi.dota2.crawler.main.CLIMain")
)
In order for this to work sbt-assembly has to acctually be added as a plugin to a build. In sbt it’s done via
placing files with *.sbt extension under project
directory. By convention it can be named plugins.sbt
,
but actuall name is irrelevant any file with *.sbt extension will be picked up by sbt.
plugins.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
Also in project
directory sbt version can be fixed for build reproducibilty.
This is done by creating build.properties
and setting right props.
build.properties
sbt.version=1.3.10
After this invocation of sbt assembly
command from terminal should produce a runnable jar in target
folder.
Unlike in Java projects it won’t be placed directly in target
, but rather in appropriate scala
subdirectory
scala-2.13
in this case.
It should look something like this
> sbt assembly
> java -jar target/scala-2.13/dota2-stats-crawler.jar
Dota 2 Stats Crawler
Let’s also make sure that no content generated in build will be accidentally added by defining .gitignore rules for repo. Those will be fairly short - just pass over anything in target and don’t include IntelliJ project files and dirs.
# SBT/Maven output folder
**/target
# IntelliJ IDEA config files and dirs
.idea/**
*.iml
Last and a little bit complicated step will be setting up CI pipelines configuration for GitLab.
Gitlab pipelines configuration should be supplied in .gitlab-ci.yml
file placed in repository root.
Starting point is defining default options for pipelines.
Begin with selecting image that will be used to run jobs in pipelines. Value should be a name of Docker image chosen for use in build stages. For this project image based I’ll use one based on Alpine Linux which is optimized for running in containers. Particular build will be from OpenJDK as it has OpenJDK 8 included, so that code and build can be run.
Next is before_script section where actions preceding each stage will be defined. In this case testing repo for apk needs to be added and sbt installed from there, as unfortunately sbt still isn’t included in stable Alpine release.
As last point set cache to keep dependencies downloaded by sbt between builds. This allows to save running time and bandwidth per pipeline thus allowing for better use of pipelines free minutes from Gitlab. Paths defined for caching are chosen based on sbt reference manual suggestions.
gitlab-ci.yml
default section
default:
# Use OpenJDK 8 Alpine image from DockerHub
# https://hub.docker.com/r/_/openjdk/
image: openjdk:8-jdk-alpine
# Configure sbt installation before jobs, currently there's only sbt in testing
# https://pkgs.alpinelinux.org/packages?name=sbt
before_script:
- echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing/' >> /etc/apk/repositories
- apk add sbt
# Indicate that those paths should be cached between builds
cache:
paths:
- ".sbtboot"
- ".coursier"
- ".ivy2"
Next will be defining build steps, GitLab default stages shall be used. To preserve runnable jar build from assembly task artifacts parameter will be defined.
gitlab-ci.yml
stages definitions
test:
script:
- sbt clean test
deploy:
script:
- sbt assembly
artifacts:
paths:
- target/scala-2.13/dota2-stats-crawler.jar
Finishing touch will be making sure that there’s no ambiguity in definitions of directories to be cached, so env variables will be defined for pipelines.
gitlab-ci.yml
env variables definitions
# Based on SBT Command Line Reference (https://www.scala-sbt.org/1.x/docs/Command-Line-Reference.html)
# define and cache directories for: sbt.boot.directory, sbt.coursier.home, sbt.ivy.home
# Use $CI_PROJECT_DIR predefined variable to avoid ambiguity in path definition
# (https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)
variables:
SBT_OPTS: "-Dsbt.boot.directory=$CI_PROJECT_DIR/.sbtboot
-Dsbt.coursier.home=$CI_PROJECT_DIR/.coursier
-Dsbt.ivy.home=$CI_PROJECT_DIR/.ivy2"
That completes initial project work.
Links to project repo: