Using Travis CI for Racket projects
(Updated 2014–06–06)
Travis CI is a continuous integration service for open source projects that has nice integration with GitHub.
Whenever you push a commit to GitHub, a build/test can launch. Notification of the result comes via a variety of methods. Also there’s a “badge” to show the build status, which you can link to in your README.md.
For example here’s the badge for Frog, which hopefully says “build passing” when you read this:
One way I’ve seen Travis CI help is with pull requests. When someone submits a PR via GitHub, Travis runs your tests on it. The OK-or-fail result is inserted in the comment thread for that pull request on GitHub. As a result, the contributor gets some feedback, even if they neglected or forgot to run tests themselves. Ideally, the submittor notices and fixes before the acceptor even needs to point out the problem.
I’ve wanted to use Travis CI on my own projects. Mainly I wanted the spiffy badge for my README.md. Seriously, I wanted the other benefits, too. Unfortunately Racket is not one of the dozen languages officially supported by Travis CI.
The proper way to support Racket in Travis CI is, well, the proper way: Making Chef cookbooks. Hopefully someone1 will do this.
Meanwhile
In the meantime, here’s a work-around.
Add a .travis.yml file to the root of your project. The contents should be something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
language: racket env: - RACKET_VERSION=6.0 - RACKET_VERSION=HEAD before_install: - git clone https://github.com/greghendershott/travis-racket.git - cat travis-racket/install-racket.sh | bash # pipe to bash not sh! install: before_script: script: - /usr/racket/bin/raco make main.rkt - /usr/racket/bin/raco test -x . after_script: |
Or simply this:
1 2 3 4 5 6 7 8 9 10 11 |
env: - RACKET_VERSION=6.0 - RACKET_VERSION=HEAD before_install: - git clone https://github.com/greghendershott/travis-racket.git - cat travis-racket/install-racket.sh | bash # pipe to bash not sh! script: - /usr/racket/bin/raco make main.rkt - /usr/racket/bin/raco test -x . |
The key points:
-
RACKET_VERSIONis the version of Racket you want to be downloaded, installed, and used. -
RACKET_VERSIONcan be specified mulitple times. Travis CI will run a build for each version. -
The
scriptsection says what should be run. For a simple project this might consist of runningraco makeon one or more files, and runningraco teston all files. Justraco testmight be sufficient. If you have amakefile, it could simply bemake. You get the idea.
If your repo is for a Racket package
If your repo is for a Racket package, and your info.rkt has a deps listing some dependencies, there’s a more elegant way to do the script: section. Let’s say your repo name is foo:
1 2 3 4 |
script: - cd .. # Travis did a cd into the dir. Back up, for the next: - /usr/racket/bin/raco pkg install --deps search-auto --link foo - /usr/racket/bin/raco test -x -p foo |
Using the --link option of raco pkg install lets you install a package locally. The --deps search-auto option will automatically install all the deps from your info.rkt, and, do so without asking.
Then, using -p foo with raco test tells it that foo is an installed package. (I also use the -x flag because I only put tests inside (module+ test) submodules, and don’t want raco test to run other code. But if you put tests in top-level modules, you’ll want to omit that.)
How and why does this work?
In the before_install step, Racket is installed by obtaining2 a install-racket.sh script and piping it to bash. The install-racket.sh script figures out where to download the Ubuntu 64-bit Racket installer for the specified RACKET_VERSION, uses curl to download it, and runs it using the default values.
Below is the current version, as I write this blog post. You can look at the latest here.
Most of the work here is simply translating a version string like 5.3.5 or 6.0 or HEAD into the location from which it can be downloaded.
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 |
# IMPORTANT: Your .travis.yml must pipe this to bash (not to sh)! # In the Travis CI environment a #!/bin/bash shebang here won't help. set -e if [[ "$RACKET_VERSION" = "HEAD" ]]; then URL="http://www.cs.utah.edu/plt/snapshots/current/installers/racket-current-x86_64-linux-precise.sh" elif [[ "$RACKET_VERSION" = 5.9* ]]; then URL="http://download.racket-lang.org/installers/${RACKET_VERSION}/racket-${RACKET_VERSION}-x86_64-linux-ubuntu-quantal.sh" elif [[ "$RACKET_VERSION" = 6.0* ]]; then URL="http://download.racket-lang.org/installers/${RACKET_VERSION}/racket-${RACKET_VERSION}-x86_64-linux-ubuntu-precise.sh" else URL="http://download.racket-lang.org/installers/${RACKET_VERSION}/racket/racket-${RACKET_VERSION}-bin-x86_64-linux-debian-squeeze.sh" fi INSTALL="./racket-${RACKET_VERSION}.sh" echo "Downloading $URL to $INSTALL:" curl -L -o $INSTALL $URL echo "Running $INSTALL to install Racket:" chmod u+rx "$INSTALL" sudo "$INSTALL" <<EOF no /usr/racket # EOF exit 0 |
Caveats and Conclusion
The Ubuntu 64-bit Racket installer is about 75 MB. This is downloaded and installed every time Travis CI runs your test. Although it’s relatively quick (single digit seconds), it is a bit wasteful of bandwidth.
Feel free to use this. Maybe have fun understanding how it works. Perhaps even point out some improvements I could make. However do keep in mind this is an interim hack rather than the long-term solution.
-
Translation: I passive-aggressively issue a call-to-action for someone to do this so that I don’t have to. ↩
-
The
.travis.ymlneeds to get theinstall-racket.shscript onto the Travis CI VM in order to run it. Simple? Well, I went through a few approaches.-
Originally I simply used
curlto fetch the file fromraw.github.com. Bonus: That automatically gets the latest version ofinstall-racket.sh. Unfortunately, someone told me that GitHub doesn’t want people usingraw.github.comto download files. Instead we should use GitHub’s “Releases” feature. -
I used the GitHub “Releases” feature. However this meant manually creating a new release every time I had to update
install-racket.sh. Worse, it meant that everyone had to update their.travis.ymlto supply the URL to the new release’s file. (Although GitHub has a “latest version” URL that redirects to the information page for the latest release, there is no such URL (that I could find) that redirects to the file to be downloaded, i.e. whatcurlneeds). -
Finally, I realized that
.travis.ymlcould simplygit clonethe wholetravis-racketrepo, and use theinstall-racket.shfile from it. Like approach 1, this means that.travis.ymldoesn’t need to be updated, it will automatically always get the latest version ofinstall-racket.sh. (Even thoughtravis-racketisn’t a big repo, it seems to me that a fullgit cloneis more bandwidth for GitHub than theraw.github.comapproach #1 ever was. But it’s their system architecture and their rules, so I defer to what they request.) ↩
-
