Travis CI is a hosted continuous integration service for open source and private projects. Many of the OSS projects hosted on GitHub uses open source edition of Travis CI to validate pushes and pull requests. We’ll discuss some of the best practices setting up Travis CI.
project/build.properties
Continuous integration is a great way of checking that your code works outside of your machine.
If you haven’t created one already, make sure to create project/build.properties
and explicitly set the
sbt.version
number:
sbt.version=1.9.8
Your build will now use 1.9.8.
A treasure trove of Travis tricks can be found in the Travis’s official documentation. Use this guide as an inspiration, but consult the official source for more details.
Setting up your build for Travis CI is mostly about setting up .travis.yml
.
Scala page says the basic file can look like:
language: scala
jdk: openjdk8
scala:
- 2.10.4
- 2.12.18
By default Travis CI executes sbt ++$TRAVIS_SCALA_VERSION test
.
Let’s specify that explicitly:
language: scala
jdk: openjdk8
scala:
- 2.10.4
- 2.12.18
script:
- sbt ++$TRAVIS_SCALA_VERSION test
More info on script
section can be found in Configuring your build.
As noted on the Scala page, Travis CI uses paulp/sbt-extras as the sbt
command.
This becomes relevant when you want to override JVM options, which we’ll see later.
For sbt plugins, there is no need for cross building on Scala, so the following is all you need:
language: scala
jdk: openjdk8
script:
- sbt scripted
Another source of good information is to read the output by Travis CI itself to learn about how the virtual environment is set up.
For example, from the following output we learn that it is using JVM_OPTS
environment variable to pass in the JVM options.
$ export JVM_OPTS=@/etc/sbt/jvmopts
$ export SBT_OPTS=@/etc/sbt/sbtopts
The default sbt and JVM options are set by Travis CI people,
and it should work for most cases.
If you do decide to customize it, read what they currently use as the defaults first.
Because Travis is already using the environment variable JVM_OPTS
, we can instead create a file travis/jvmopts
:
-Dfile.encoding=UTF8
-Xms2048M
-Xmx2048M
-Xss6M
-XX:ReservedCodeCacheSize=256M
and then write out the script
section with -jvm-opts
option:
script:
- sbt ++$TRAVIS_SCALA_VERSION -jvm-opts travis/jvmopts test
After making the change, confirm on the Travis log to see if the flags are taking effect:
# Executing command line:
java
-Dfile.encoding=UTF8
-Xms2048M
-Xmx2048M
-Xss6M
-XX:ReservedCodeCacheSize=256M
-jar
/home/travis/.sbt/launchers/1.9.8/sbt-launch.jar
It seems to be working. One downside of setting all of the parameters is that we might be left behind when the environment updates and the default values gives us more memory in the future.
Here’s how we can add just a few JVM options:
script:
- sbt ++$TRAVIS_SCALA_VERSION -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M -J-Xms1024M test
sbt-extra script passes any arguments starting with either -D
or -J
directly to JVM.
Again, let’s check the Travis log to see if the flags are taking effect:
# Executing command line:
java
-Xms2048M
-Xmx2048M
-Xss6M
-Dfile.encoding=UTF8
-XX:ReservedCodeCacheSize=256M
-Xms1024M
-jar
/home/travis/.sbt/launchers/1.9.8/sbt-launch.jar
Note: This duplicates the -Xms
flag as intended, which might not the best thing to do.
You can speed up your sbt
builds on Travis CI by using their caching feature.
Here’s a sample cache:
configuration that you can use:
cache:
directories:
- $HOME/.cache/coursier
- $HOME/.ivy2/cache
- $HOME/.sbt
Note: Coursier uses different cache location depending on the OS, so the above needs to be changed accordingly for macOS or Windows images.
You’ll also need the following snippet to avoid unnecessary cache updates:
before_cache:
- rm -fv $HOME/.ivy2/.sbt.ivy.lock
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete
With the above changes combined Travis CI will tar up the cached directories and uploads them to a cloud storage provider. Overall, the use of caching should shave off a few minutes of build time per job.
We’ve already seen the example of Scala cross building.
language: scala
jdk: openjdk8
scala:
- 2.10.4
- 2.12.18
script:
- sbt ++$TRAVIS_SCALA_VERSION test
We can also form a build matrix using environment variables:
env:
global:
- SOME_VAR="1"
# This splits the build into two parts
matrix:
- TEST_COMMAND="scripted sbt-assembly/*"
- TEST_COMMAND="scripted merging/* caching/*"
script:
- sbt "$TEST_COMMAND"
Now two jobs will be created to build this sbt plugin, simultaneously running different integration tests. This technique is described in Parallelizing your builds across virtual machines.
You can configure Travis CI to notify you.
By default, email notifications will be sent to the committer and the commit author, if they are members of the repository[…].
And it will by default send emails when, on the given branch:
- a build was just broken or still is broken
- a previously broken build was just fixed
The default behavior looks reasonable, but if you want, we can override the notifications
section to email you on successful builds too, or to use some other channel of communication like IRC.
# Email specific recipient all the time
notifications:
email:
recipients:
- [email protected]
on_success: always # default: change
This might also be a good time to read up on encryption using the command line travis
tool.
$ travis encrypt [email protected]
For builds that are more prone to flaky network or tests, Travis CI has created some tricks described in the page My builds is timing out.
Starting your command with travis_retry
retries the command three times if the return code is non-zero.
With caching, hopefully the effect of flaky network is reduced, but it’s an interesting one nonetheless.
Here are some cautionary words from the documentation:
We recommend careful use of
travis_retry
, as overusing it can extend your build time when there could be a deeper underlying issue.
Another tidbit about Travis is the output timeout:
Our builds have a global timeout and a timeout that’s based on the output. If no output is received from a build for 10 minutes, it’s assumed to have stalled for unknown reasons and is subsequently killed.
There’s a function called travis_wait
that can extend this to 20 minutes.
There are more thing you can do, such as set up databases, installing Ubuntu packages, and deploy continuously.
Travis offers the ability to run tests in parallel, and also imposes time limits on builds. If you have an especially long-running suite of scripted tests for your plugin, you can run a subset of scripted tests in a directory, for example:
- TEST_COMMAND="scripted tests/*1of3"
- TEST_COMMAND="scripted tests/*2of3"
- TEST_COMMAND="scripted tests/*3of3"
Will create three chunks and run each of the chunks separately for the
directory tests
.
Here’s a sample that puts them all together. Remember, most of the sections are optional.
language: scala
jdk: openjdk8
env:
# This splits the build into two parts
matrix:
- TEST_COMMAND="scripted sbt-assembly/*"
- TEST_COMMAND="scripted merging/* caching/*"
script:
- sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M "$TEST_COMMAND"
before_cache:
- rm -fv $HOME/.ivy2/.sbt.ivy.lock
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete
cache:
directories:
- $HOME/.cache/coursier
- $HOME/.ivy2/cache
- $HOME/.sbt