Advanced uses of Travis CI with Nim

There have been a few guides describing the use of Circle CI, originating from here. But in my opinion Travis CI is a superior service, because it has more options and is more reliable.

Let's start with the minimal Travis configuration that allows you to test Nim projects.

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.travis.ymllanguage: c
install:
  - |
    # Download the latest release of Nim into the "nim-master" folder
    git clone -b master --depth 1 git://github.com/nim-lang/nim nim-master/
    cd nim-master
    # Download the latest release of Nim's prepared C sources, for bootstrapping
    git clone -b master --depth 1 git://github.com/nim-lang/csources csources/
    cd csources
    # Build C sources
    sh build.sh
    cd ..
    # This concludes the first step of bootstrapping, don't need C sources anymore
    rm -rf csources
    # Use the executable built from C sources to compile the build tool
    bin/nim c koch
    # Compile Nim in release mode, using the Nim compiler we already have
    ./koch boot -d:release
    cd ..
before_script:
  # Add the 'bin' folder to PATH
  - export PATH="nim-master/bin:$PATH"
script:
  # Run 'tests/all.nim'. Feel free to change this, but it needs to be a program
  # that returns a non-zero status code in case of failure. The testing facilities
  # in Nim's standard library do this.
  - nim compile --verbosity:0 --run tests/all

Look at this guide to get an idea about how to set up the actual tests.

You may think that the folder name nim-master is redundant, but you'll see later why I didn't call it just nim.

All this custom stuff is needed because Travis doesn't officially support Nim. And we need to pick some language. The choice of C language could be called arbitrary, but we do use C compilers here, so it's nice to make sure they're available.


Now, downloading, bootstrapping and recompiling everything every time is slow and wasteful. Let's add caching!

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.travis.ymllanguage: c
install:
  - |
    if [ ! -x nim-master/bin/nim ]; then
      # If the Nim executable does not exist (means we haven't installed Nim yet)
      # (do what we did before)
      git clone -b master --depth 1 git://github.com/nim-lang/nim nim-master/
      cd nim-master
      git clone -b master --depth 1 git://github.com/nim-lang/csources csources/
      cd csources
      sh build.sh
      cd ..
      rm -rf csources
      bin/nim c koch
      ./koch boot -d:release
    else
      # We already have the repository, go to it
      cd nim-master
      # Download latest commits from the repository
      git fetch origin
      if ! git merge FETCH_HEAD | grep "Already up-to-date"; then
        # Recompile Nim (using itself), only if there were new changes
        bin/nim c koch
        ./koch boot -d:release
      fi
    fi
    cd ..
before_script:
  - export PATH="nim-master/bin${PATH:+:$PATH}"
script:
  - nim compile --verbosity:0 --run tests/all
cache:
  directories:
    - nim-master

So, after successful builds, the directory nim-master will be stored in the cache, and we can reuse it in the next builds. This is especially important when testing using Nim's master branch which rarely changes.

Another change is how the directory is added to PATH. We avoid the stray colon if PATH is empty, which would mean that we also have an empty entry in PATH (current directory)


Our next step is testing the project under multiple different configurations.

Make sure you understand what the Build matrix is.

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
travis.ymllanguage: c
# Run builds with 2 different values of the `nim_branch` environment variable
env:
  - nim_branch=master
  - nim_branch=devel
# Run builds with 2 different choices of a C compiler
compiler:
  - gcc
  - clang
# This meams we get a 2x2 build matrix, with a total of 4 builds
matrix:
  # It's OK if our project fails to build with Nim devel, but we still want to check it
  allow_failures:
    - env: nim_branch=devel
  # Call the commit successful as soon as the builds with master branch complete.
  fast_finish: true
install:
  - |
    # Simply replacing "master" with `$nim_branch` everywhere means we can reuse
    # this installation script for both branches.
    if [ ! -x nim-$nim_branch/bin/nim ]; then
      git clone -b $nim_branch --depth 1 git://github.com/nim-lang/nim nim-$nim_branch/
      cd nim-$nim_branch
      git clone -b $nim_branch --depth 1 git://github.com/nim-lang/csources csources/
      cd csources
      sh build.sh
      cd ..
      rm -rf csources
      bin/nim c koch
      ./koch boot -d:release
    else
      cd nim-$nim_branch
      git fetch origin
      if ! git merge FETCH_HEAD | grep "Already up-to-date"; then
        bin/nim c koch
        ./koch boot -d:release
      fi
    fi
    cd ..
before_script:
  # `$nim_branch` is used here as well, to add the specific compiler to PATH
  - export PATH="nim-$nim_branch/bin${PATH:+:$PATH}"
script:
  # Specify the C compiler to Nim
  # (the `compiler` option of the build matrix sets the `$CC` variable)
  - nim compile --cc:$CC --verbosity:0 --run tests/all
cache:
  # Cache both compilers easily
  directories:
    - nim-master
    - nim-devel
branches:
  except:
    - gh-pages

One more thing I packed into this example is 'build all branches except for gh-pages'. But that's the default behavior of Travis anyway.

See this configuration in action:


Another thing you may want to do is maintain 2 branches of your project:

  • master, which is supposed to work with Nim master
  • devel, which is supposed to work with Nim devel

So let's get to action!

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.travis.ymllanguage: c
before_install:
  - |
    # Test the master branch with Nim master and other branches with Nim devel
    if [ "$TRAVIS_BRANCH" = master ]; then
        export branch=master
    else
        export branch=devel
    fi
install:
  - |
    git clone -b $branch --depth 1 git://github.com/nim-lang/nim nim-$branch/
    cd nim-$branch
    git clone -b $branch --depth 1 git://github.com/nim-lang/csources csources/
    cd csources
    sh build.sh
    cd ..
    rm -rf csources
    bin/nim c koch
    ./koch boot -d:release
    cd ..
before_script:
  - export PATH="nim-$branch/bin:$PATH"
script:
  - nim compile --verbosity:0 --run test/test
sudo: required
dist: trusty

Another change here is the last two lines. They enable The Trusty beta Build Environment. I needed this because the default environment has a very outdated version of PCRE, and Nim's regular expression support relies on a newer one. Sadly, cache doesn't work in this "sudo" environment, so that's gone.

See this configuration in action:

Created
Comments powered by Disqus