GitHub Actions

    .github/workflows/ci.yml

    To get started with GitHub Actions, commit this YAML file into your Git repository under the directory , push it to GitHub, and observe the Actions tab.

    Quickstart

    Check out to quickly get a config with the CI features you need. Or continue reading for more details.

    This runs on GitHub’s default “latest Ubuntu” container. It downloads the source code from the repository itself (directly into the current directory), installs Crystal via , then runs the specs, assuming they are there in the spec/ directory.

    If any step fails, the build will show up as failed, notify the author and, if it’s a push, set the overall build status of the project to failing.

    Tip

    For a healthier codebase, consider these flags for crystal spec:
    --order=random --error-on-warnings

    If your test coverage isn’t great, consider at least adding an example program, and building it as part of CI:

    For a library:

    1. - name: Build example
    2. run: crystal build examples/hello.cr

    For an application (very good to do even if you have specs):

    1. - name: Build
    2. run: crystal build src/game_of_life.cr

    By default, the latest released version of Crystal is installed. But you may want to also test with the “nightly” build of Crystal, and perhaps some older versions that you still support for your project. Change the top of the workflow as follows:

    1. jobs:
    2. build:
    3. strategy:
    4. fail-fast: false
    5. crystal: [0.35.1, latest, nightly]
    6. runs-on: ubuntu-latest
    7. steps:
    8. - name: Download source
    9. uses: actions/checkout@v2
    10. - name: Install Crystal
    11. uses: crystal-lang/install-crystal@v1
    12. with:
    13. crystal: ${{ matrix.crystal }}

    By specifying the version of Crystal you could even opt out of supporting the latest version (which is a moving target), and only support particular ones.

    Typically, developers run tests only on Ubuntu, which is OK if there is no platform-sensitive code. But it’s easy to add another system into the test matrix, just add the following near the top of your job definition:

    Installing Shards packages

    Most projects will have external dependencies, “shards”. Having declared them in shard.yml, just add the installation step into your workflow (after install-crystal and before any testing):

    1. - name: Install shards
    2. run: shards install

    If your repository has a checked in shard.lock file (typically good for applications), consider the effect that this has on CI: shards install will always install the exact versions specified in that file. But if you’re developing a library, you probably want to be the first to find out in case a new version of a dependency breaks the installation of your library — otherwise the users will, because the lock doesn’t apply transitively. So, strongly consider running shards update instead of shards install, or don’t check in shard.lock. And then it makes sense to add to your repository.

    Our application or some shards may require external libraries. The approach to installing them can vary widely. The typical way is to install packages using the apt command in Ubuntu.

    Add the installation step somewhere near the beginning. For example, with libsqlite3:

    1. - name: Install packages
    2. run: sudo apt-get -qy install libsqlite3-dev

    Enforcing code formatting

    If you want to verify that all your code has been formatted with , add the according check as a step near the end of the workflow. If someone pushes code that is not formatted correctly, this will break the build just like failing tests would.

    1. - name: Check formatting
    2. run: crystal tool format --check

    Consider also adding this check as a Git pre-commit hook for yourself.

    We have been using an “action” to install Crystal into the default OS image that GitHub provides. Which has multiple advantages. But you may instead choose to use Crystal’s official Docker image(s), though that’s applicable only to Linux.

    The base config becomes this instead:

    .github/workflows/ci.yml

    Some for containers are crystallang/crystal:nightly, crystallang/crystal:0.36.1, crystallang/crystal:latest-alpine.

    Caching

    The safe approach is to add the step (before the step that uses shards) defined as follows:

    1. - name: Cache shards
    2. uses: actions/cache@v2
    3. with:
    4. key: ${{ runner.os }}-shards-${{ hashFiles('shard.yml') }}
    5. restore-keys: ${{ runner.os }}-shards-
    6. - name: Install shards
    7. run: shards update

    Important

    You must use the separate key and restore-keys. With just a static key, the cache would save only the state after the very first run and then keep reusing it forever, regardless of any changes.

    But this saves us only the time spent downloading the repositories initially.

    A “braver” approach is to cache the lib directory itself, but that works only if you fully rely on shard.lock (see ):

    1. - name: Cache shards
    2. uses: actions/cache@v2
    3. with:
    4. path: lib
    5. key: ${{ runner.os }}-shards-${{ hashFiles('**/shard.lock') }}
    6. - name: Install shards
    7. run: shards check || shards install

    Note that we also made the installation conditional on shards check. That saves even a little more time.

    If your project is an application, you likely want to distribute it as an executable (“binary”) file. For the case of Linux x86_64, by far the most popular option is to build and link statically . This means that you cannot use GitHub’s default Ubuntu container and the install action. Instead, just use the official container:

    .github/workflows/release.yml

    1. jobs:
    2. release_linux:
    3. runs-on: ubuntu-latest
    4. container:
    5. image: crystallang/crystal:latest-alpine
    6. steps:
    7. - uses: actions/checkout@v2
    8. - run: shards build --production --release --static --no-debug

    These steps would be followed by some action to publish the produced executable (), in one of the two ways (or both of them):

    • As part of the CI done for every commit, via actions/upload-artifact.
      Then consider linking to the latest “nightly” build using the external service

    Distributing executables for macOS (search for examples) and Windows () is also possible.