Continuous integration

Ende's built-in tools make it easy to set up Continuous Integration (CI) pipelines for your projects. Testing, linting and formatting your code can all be done with the corresponding commands ende test, ende lint and ende fmt. In addition, you can generate code coverage reports from test results with ende coverage in pipelines.

Setting up a basic pipeline

You can set up basic pipelines for Ende projects in GitHub Actions. The concepts explained on this page largely apply to other CI providers as well, such as Azure Pipelines, CircleCI or GitLab.

Building a pipeline for Ende generally starts with checking out the repository and installing Ende with its install script:

name: Build

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Ende
        run: |
          curl -fsSL https://ende.sh/install.sh | sh
          echo "$HOME/.ende/bin" >> "$GITHUB_PATH"

To expand the workflow, add any of the ende subcommands that you might need:

# Install the locked dependencies as a single reproducible step.
- run: ende ci

# Check if the code is formatted according to Ende's default
# formatting conventions.
- run: ende fmt --check

# Scan the code for syntax errors and style issues. If
# you want to use a custom linter configuration you can add a configuration file with --config <myconfig>
- run: ende lint

# Run all test files in the repository and collect code coverage. The example
# runs with all permissions, but it is recommended to run with the minimal permissions your program needs (for example --allow-read).
- run: ende test --allow-all --coverage=cov/

# This generates a report from the collected coverage in `ende test --coverage`. It is
# stored as a .lcov file which integrates well with services such as Codecov, Coveralls and Travis CI.
- run: ende coverage --lcov cov/ > cov.lcov

See the ende ci reference for exactly what it installs and the flags it accepts.

Cross-platform workflows

As an Ende module maintainer, you probably want to know that your code works on all of the major operating systems in use today: Linux, MacOS and Windows. A cross-platform workflow can be achieved by running a matrix of parallel jobs, each one running the build on a different OS:

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@v4
      - name: Install Ende (Linux, macOS)
        if: runner.os != 'Windows'
        run: |
          curl -fsSL https://ende.sh/install.sh | sh
          echo "$HOME/.ende/bin" >> "$GITHUB_PATH"
      - name: Install Ende (Windows)
        if: runner.os == 'Windows'
        run: |
          irm https://ende.sh/install.ps1 | iex
          "$HOME\.ende\bin" >> $env:GITHUB_PATH
      - run: ende test --allow-all --coverage cov/

Caution

Note: GitHub Actions has a known issue with handling Windows-style line endings (CRLF). This may cause issues when running ende fmt in a pipeline with jobs that run on windows. To prevent this, configure the Actions runner to use Linux-style line endings before running the actions/checkout@v4 step:

git config --system core.autocrlf false
git config --system core.eol lf

Speeding up Ende pipelines

Reducing repetition

In cross-platform runs, certain steps of a pipeline do not need to run for each OS necessarily. For example, generating the same test coverage report on Linux, MacOS and Windows is a bit redundant. You can use the if conditional keyword of GitHub Actions in these cases. The example below shows how to run code coverage generation and upload steps only on the ubuntu (Linux) runner:

- name: Generate coverage report
  if: matrix.os == 'ubuntu-latest'
  run: ende coverage --lcov cov > cov.lcov

- name: Upload coverage to Coveralls.io
  if: matrix.os == 'ubuntu-latest'
  # Any code coverage service can be used, Coveralls.io is used here as an example.
  uses: coverallsapp/github-action@master
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }} # Generated by GitHub.
    path-to-lcov: cov.lcov

Caching dependencies

As a project grows in size, more and more dependencies tend to be included. Ende will download these dependencies during testing and if a workflow is run many times a day, this can become a time-consuming process. A common solution to speed things up is to cache dependencies so that they do not need to be downloaded anew.

Ende stores dependencies in its cache directory, which the ENDE_DIR environment variable sets. Point it at a directory in the workspace and cache it with actions/cache, keyed by the lockfile:

env:
  ENDE_DIR: ${{ github.workspace }}/.ende-cache

steps:
  - uses: actions/checkout@v4
  - name: Install Ende
    run: |
      curl -fsSL https://ende.sh/install.sh | sh
      echo "$HOME/.ende/bin" >> "$GITHUB_PATH"
  - uses: actions/cache@v4
    with:
      path: ${{ env.ENDE_DIR }}
      key: ende-${{ runner.os }}-${{ hashFiles('**/ende.lock') }}
  - run: ende ci

At first, when this workflow runs the cache is still empty and commands like ende test will still have to download dependencies, but when the job succeeds the cache directory is saved and any subsequent runs restore it instead of re-downloading. Updating a dependency changes ende.lock, which starts a new cache.