CLCI Gitlab CI v2.0.0 ΒΆ

Gitlab CI is the default CI solution for projects hosted on Gitlab. While in-depth knowledge of how Gitlab CI works is not needed to get very basic pipelines set up using this project, it can be useful when attempting to build more complicated pipelines. See https://docs.gitlab.com/ee/ci/ for more documentation. The repository for this project is located at https://gitlab.common-lisp.net/clci/gitlab-ci.

Gitlab CI ultimately requires one or more YAML files that describe the jobs to perform. This project is written using a single org file (this file) that is then tangled in order to produce the various YAML files consumed by Gitlab CI. This file is also exported to produce documentation.

In order to produce the YAML files, run org-babel-tangle (typically bound to C-c C-v t). Tangling produces the following files:

Currently, this project only works on Linux runners (and may Mac runners if you configure them correctly). However, first-class Windows and MacOS support is on the roadmap. This support is likely to arrive because this project is an offshoot of another (private) project that does have Windows and MacOS support. We mostly just need to get some runners for testing.

Quickstart

This section describes how to set up Gitlab CI for a CL-based project using these helpers. Gitlab CI has a dizzying amount of flexibility, so this quickstart will quickly get you something that works and will be sufficient for many use cases. But you may want to dive deeper into this project or Gitlab CI at some point to perform more advanced tasks.

Prep

This section describes setup that you must perform other than modifying your .gitlab-ci.yml file.

Testing

In order to use the test pipeline, you must provide a method to run your tests (this project does not attempt to guess how they should be run). You must provide either a Makefile target to run your tests or write a script for the CI helpers to run.

Create a file called scripts/ci-test.lisp (alternatively, set $CLCI_TEST_SCRIPT to point to another file). This file will be loaded (with cl:load) into a CL process that has ASDF available (and your chosen dependency manager, if applicable). This file must either enter the debugger (by signaling an uncaught error) or exit with a non-zero exit code ((uiop:quit 1)) if your tests fail.

For most projects, the contents of this file should be as simple as:

(asdf:test-system "my-cool-system")

NOTE: If you have a Makefile in your repo, the helpers will instead run make test (after setting some environment variables). If you wish to disable this behavior, set $CLCI_TEST_USE_MAKEFILE to no.

Release

In order to use the release pipeline, you should create a CHANGELOG.md file in your repo. The first level heading (line starting with a single #) should be your project name. The second level headings (starting with ##) should start with the version number and the contents should describe the changes to your project. See the CHANGELOG.md file in this repo for an example.

Additionally, you must protect your release tags. Go to "Settings > Repository > Protected Tags" and protect v*.

Then, when you're ready to make a release, push a tag of the form v$VERSION_NUMBER.

Update CLPM bundle

If you are using CLPM for your dependency management, you can use these helpers to periodically update your lock file for you.

To do this, you should create a bot for your project that can open merge requests. Go to "Settings > Access Tokens" and create a token with write_repository and api permissions. Then create a CI variable ("Settings > CI/CD > Variables") called CLCI_MR_TOKEN, whose value is the token you got from the first step.

Last, you should create a scheduled job to check for updates to your dependencies. Go to "CI/CD > Schedules". Create a new scheduled job for your default branch that sets PIPELINE_TYPE to clpm-dep-update.

.gitlab-ci.yml

Copy the following to .gitlab-ci.yml in your repo. Be sure to read the comments, as they give some hints on customizations that may be appropriate for you.

include:
  project: 'clci/gitlab-ci'
  ref: v2-stable
  file:
    - definitions.gitlab-ci.yml
    - guarded-linux-test-pipeline.gitlab-ci.yml
    - guarded-release-pipeline.gitlab-ci.yml
    # Uncomment if you are using CLPM and want to automatically update your
    # bundle.
    #
    # - guarded-clpm-dep-update-pipeline.gitlab-ci.yml

variables:
  # Uncomment if you want to use Quicklisp as your dependency manager during
  # tests.
  #
  # CLCI_DEPENDENCY_MANAGER: "quicklisp"
  #
  # Uncomment if you have Git submodules that you want the runner to
  # automatically init and update for you. Submodules are sometimes used by
  # projects to bundle their test dependencies.
  #
  # GIT_SUBMODULE_STRATEGY: recursive
  #
  # Uncomment these lines if you want to test against Allegro, you have read
  # the Allegro express license
  # <https://franz.com/ftp/pub/legal/ACL-Express-20170301.pdf>, *and* your use
  # of Allegro Express does not violate the license. Alternatively, uncomment
  # and provide your own Docker image (or runner) that has Allegro installed
  # with your actual license.
  #
  # CLCI_TEST_ALLEGRO: "yes"
  # I_AGREE_TO_ALLEGRO_EXPRESS_LICENSE: "yes"


# This section is not strictly required, but prevents Gitlab CI from launching
# multiple redundent pipelines when a Merge Request is opened.
workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - if: '$CI_COMMIT_BRANCH'
    - if: '$CI_COMMIT_TAG'

FAQs

This project is still very new, so I can't really say that these questions are frequently asked, but I imagine they would be :).

How do I install a non-CL dependency before running tests?

First, make sure your dependency isn't already installed. The default Linux Docker images are based on the buildpack-deps image, which includes a lot of commonly used libraries.

If you really need to install something before your tests run, add the following to your .gitlab-ci.yml:

.common:test:clci:
  before_script:
    - apt-get update
    - apt-get install -y foo bar baz

How do I use CLPM as my dependency manager?

Just commit your clpmfile.lock (in the root of your repo) without setting $CLCI_DEPENDENCY_MANAGER and the helpers will take care of the rest.

How do I test using dependencies from Ultralisp?

Set $CLCI_DEPENDENCY_MANAGER to quicklisp. Then set $CLCI_QL_DIST_NAME to ultralisp.

How do I test against both Quicklisp and Ultralisp?

Add the following to your .gitlab-ci.yml:

.common:test:clci:
  parallel:
    matrix:
      - CLCI_QL_DIST_NAME: [quicklisp, ultralisp]

How do I test against both the ASDF bundled with the implementation and the latest released ASDF?

Add the following to your .gitlab-ci.yml:

.common:test:clci:
  parallel:
    matrix:
      - CLCI_ASDF_VERSION: [REQUIRE, latest]

How do I generate documentation (or any other CL-based task)?

Create a CL file in your repo that does what you want and enters the debugger or exits with a non-zero status if it fails. Then add a job in your .gitlab-ci.yml:

generate docs:
  extends:
    - .clci sbcl
    - .clci DEPENDENCY_MANAGER script
  variables:
    CLCI_SCRIPT: path/to/script.lisp
  artifacts:
    paths:
      - doc/

Where DEPENDENCY_MANAGER is replaced with asdf (no dependency manager), clpm, or quicklisp. In the future, a more flexible job may be added that choses the correct dependency manager based on the value of $CLCI_DEPENDENCY_MANAGER, but that will require a non-trivial amount of refactoring to support.

Your script will then be loaded into a CL process that has ASDF and your dependency manager (if applicable) available.

Utilities

This section contains some definitions that are tangled into other definitions and, as such, are not directly accessible to users. However, they are included in the documentation for reference purposes.

Single Implementation Scripts

Each CL implementation has its own CLI, resulting in different ways to run scripts for each of them. It'd be great if we could use cl-launch for this purpose, but it doesn't work on Windows and is too opinionated when it comes to ASDF (it requires a minimum version of ASDF). Additionally, Roswell might be nice, but it's too heavyweight to bring in for just a unified CLI interface. It's also very opinionated in different areas than cl-launch.

Therefore, we end up with these helpers. When tangled into other source blocks, they provide the correct command to start a Lisp, disable init scripts, and load a file. The process exits with a non-zero code if the debugger is entered.

ABCL

abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))"

Allegro

alisp --qq --batch --backtrace-on-error -L "$_clci_script"

CCL

ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))"

CLASP

clasp -N --norc --load "$_clci_script"

CLISP

clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))"

CMUCL

cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))"

ECL

ecl --norc --shell "$_clci_script"

SBCL

sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script"

Multi Implementation Scripts

This section contains snippets that can be expanded into other source blocks to run a CL script. It chooses the implementation to run based on the $LISP environment variable.

Run CL script

case "$LISP" in
  abcl)
    abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  allegro)
    alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
    ;;
  ccl)
    ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  clasp)
    clasp -N --norc --load "$_clci_script" || exit 1
    ;;
  clisp)
    clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  cmucl)
    cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
    ;;
  ecl)
    ecl --norc --shell "$_clci_script" || exit 1
    ;;
  sbcl)
    sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
    ;;
  *)
    echo "Unkown LISP: $LISP"
    exit 1
    ;;
esac

CLPM Run CL script

Run a script using CLPM, using the $LISP environment variable to determine which implementation to run.

case "$LISP" in
  abcl)
    $CLPM bundle exec -- abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  allegro)
    $CLPM bundle exec -- alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
    ;;
  ccl)
    $CLPM bundle exec -- ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  clasp)
    $CLPM bundle exec -- clasp -N --norc --load "$_clci_script" || exit 1
    ;;
  clisp)
    $CLPM bundle exec -- clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
    ;;
  cmucl)
    $CLPM bundle exec -- cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
    ;;
  ecl)
    $CLPM bundle exec -- ecl --norc --shell "$_clci_script" || exit 1
    ;;
  sbcl)
    $CLPM bundle exec -- sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
    ;;
  *)
    echo "Unkown LISP: $LISP"
    exit 1
    ;;
esac

Job Definitions

This section produces the definitions.gitlab-ci.yml file.

All of these jobs are hidden by default, meaning that including this file will result in no jobs being added to your pipeline.

All jobs now and in the future will start with .clci. If a job starts with ._clci or an environment variable starts with a _, then its continued existence and semantics are not guaranteed.

At the present time, we do not support any modifications to these jobs, other than overriding variable values that do not start with _.

It is recommended that you extend, or !reference these jobs to include their functionality in other jobs.

ASDF

This section contains definitions to use ASDF.

Variables

These variables are set by default:

CLCI_ASDF_CACHE_KEY: $CI_JOB_NAME-clci-asdf
CLCI_ASDF_VERSION: latest
_CLCI_ASDF_CACHE_PATH: $CI_PROJECT_DIR/.clci-cache/asdf

Cache

Reference this in a cache list to get caching of ASDF files.

.clci asdf cache: &clci_asdf_cache
  key: $CLCI_ASDF_CACHE_KEY
  paths:
    - .clci-cache/asdf/

Download ASDF

Download ASDF version $CLCI_ASDF_VERSION and build asdf.lisp. Requires that make and curl are installed.

Downloads code directly from ASDF's git repo. Can download any tag (technically any reference, but if you are caching you should not use branch names). Will store the code in the CI cache.

If $CLCI_ASDF_VERSION is not set, empty, or "REQUIRE", this does nothing.

Exports the following env vars:

.clci download asdf:
  script: &clci_download_asdf_script
    - |
      if [ "z$CLCI_ASDF_VERSION" = "z" ]; then
        echo "CLCI_ASDF_VERSION not set. Skipping downloading ASDF."
      elif [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo "CLCI_ASDF_VERSION set to REQUIRE. Skipping downloading ASDF."
      else
        if [ "$CLCI_ASDF_VERSION" = "latest" ]; then
          ASDF_VERSION=3.3.5
        else
          ASDF_VERSION="$CLCI_ASDF_VERSION"
        fi
        ASDF_DIR="$_CLCI_ASDF_CACHE_PATH/asdf-$ASDF_VERSION"
        ASDF_LISP="$ASDF_DIR/build/asdf.lisp"
        if [ -e "$ASDF_LISP" ]; then
          echo "ASDF $ASDF_VERSION already downloaded."
        else
          echo "Downloading ASDF $ASDF_VERSION"
          mkdir -p "$_CLCI_ASDF_CACHE_PATH"
          _clci_asdf_loc="$(mktemp)"
          curl -fsSL "https://gitlab.common-lisp.net/asdf/asdf/-/archive/$ASDF_VERSION/asdf-$ASDF_VERSION.tar.gz" > "$_clci_asdf_loc"
          tar x -z -f "$_clci_asdf_loc" -C "$_CLCI_ASDF_CACHE_PATH"
          ( cd "$_CLCI_ASDF_CACHE_PATH/asdf-$ASDF_VERSION/" && make ) || exit 1
          echo "ASDF $ASDF_VERSION downloaded"
        fi
        echo "(:source-registry-cache)" > "$_CLCI_ASDF_CACHE_PATH/.cl-source-registry.cache"
        export ASDF_DIR
        export ASDF_LISP
      fi

Install ASDF

Make it so that ASDF can be found and self upgraded by itself. Adds $ASDF_DIR to ASDF's search path by adding it to ~/.config/common-lisp/source-registry.conf.d/50-cici-asdf.conf. Does nothing if $ASDF_DIR is not set or empty.

.clci install asdf:
  script: &clci_install_asdf_script
    - |
      if [ "z$ASDF_DIR" = "z" ]; then
        echo "ASDF_DIR not set. Skipping installing ASDF."
      else
        mkdir -p ~/.config/common-lisp/source-registry.conf.d/
        echo "(:tree \"$ASDF_DIR\")" > ~/.config/common-lisp/source-registry.conf.d/50-cici-asdf.conf
        echo "$ASDF_DIR added to ASDF source registry."
      fi

Compile ASDF

Compile the asdf.lisp file at $ASDF_LISP into a fasl. If $ASDF_LISP is not set, does nothing.

Exports $ASDF_FASL, the path to the generated fasl.

Requires that $LISP is set.

.clci compile asdf:
  script: &clci_compile_asdf_script
    - |
      if [ "z$ASDF_LISP" = "z" ] || [ "z$LISP" = "z" ]; then
        echo "ASDF_LISP or LISP not set. Skipping ASDF compilation."
      else
        _clci_asdf_fasl_file="$(mktemp)"
        _clci_script="$(mktemp)"
        cat > "$_clci_script" <<EOF
      (require "asdf")

      (let* ((fasl-type (pathname-type (compile-file-pathname "$ASDF_LISP")))
             (output-pn (merge-pathnames (make-pathname
                                          :directory (list :relative
                                                           (uiop:implementation-identifier))
                                          :type fasl-type)
                                         "$ASDF_LISP")))
        (ensure-directories-exist output-pn)
        (if (probe-file output-pn)
            (with-open-file (s "$_clci_asdf_fasl_file" :direction :output
                                                       :if-exists :supersede)
              (write-string (uiop:native-namestring output-pn) s))
            (multiple-value-bind (output-truename warnings-p failure-p)
                (compile-file "$ASDF_LISP" :output-file output-pn)
              (if (probe-file output-pn)
                  (with-open-file (s "$_clci_asdf_fasl_file" :direction :output
                                                             :if-exists :supersede)
                    (write-string (uiop:native-namestring output-pn) s))
                  (uiop:quit 1)))))
          (uiop:quit 0)
      EOF
          case "$LISP" in
            abcl)
              abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
              ;;
            allegro)
              alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
              ;;
            ccl)
              ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
              ;;
            clasp)
              clasp -N --norc --load "$_clci_script" || exit 1
              ;;
            clisp)
              clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
              ;;
            cmucl)
              cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
              ;;
            ecl)
              ecl --norc --shell "$_clci_script" || exit 1
              ;;
            sbcl)
              sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
              ;;
            *)
              echo "Unkown LISP: $LISP"
              exit 1
              ;;
          esac

          if [ -e "$_clci_asdf_fasl_file" ]; then
            ASDF_FASL="$(cat "$_clci_asdf_fasl_file")"
            export ASDF_FASL
          else
            echo "Unable to determine fasl location."
            exit 1
          fi
      fi

ASDF Job

This job is meant to be extended. It provides a cache entry for ASDF, as well as downloading, installing, and compiling it in a before script.

.clci asdf job:
  before_script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
  cache:
    - *clci_asdf_cache

ASDF Script

This job is meant to be extended. It provides cache entries for ASDF, as well as downloading, installing and compiling ASDF. You can add your own before_script. You must define a variable CLCI_SCRIPT containing the path to a script to be run as the last step of script.

.clci asdf script:
  script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      echo "(load \"$CLCI_SCRIPT\")" >> "$_clci_script"
    - echo "Running script:"
    - cat "$_clci_script"
    - |
      case "$LISP" in
        abcl)
          abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        allegro)
          alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
          ;;
        ccl)
          ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        clasp)
          clasp -N --norc --load "$_clci_script" || exit 1
          ;;
        clisp)
          clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        cmucl)
          cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
          ;;
        ecl)
          ecl --norc --shell "$_clci_script" || exit 1
          ;;
        sbcl)
          sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
          ;;
        *)
          echo "Unkown LISP: $LISP"
          exit 1
          ;;
      esac
  cache:
    - *clci_asdf_cache

CLPM

This section contains definitions for using CLPM.

Variables

CLCI_CLPM_CACHE_KEY: clci-clpm-releases
CLCI_CLPM_VERSION: latest
_CLCI_CLPM_CACHE_PATH: $CI_PROJECT_DIR/.clci-cache/clpm
# Use Dexador as the default HTTP client for CLPM. There seems to
# occasionally be issues with Drakma and Gitlab.
CLPM_HTTP_CLIENT_TYPE: "dexador"

CLPM Cache

Include in jobs that use CLPM to cache the results.

The job .clci clpm cache variables is meant to be extended. It sets some environment variables to ensure that CLPM stores things in the appropriate folders in the cache.

The jobs .clci clpm release cache and .clci clpm cache are meant to be included in cache lists via !reference.

.clci clpm cache variables:
  variables:
     CLPM_CACHE_DIR: $CI_PROJECT_DIR/.clci-cache/clpm/cache
     CLPM_DATA_DIR: $CI_PROJECT_DIR/.clci-cache/clpm/data

.clci clpm release cache: &clci_clpm_release_cache
  key: $CLCI_CLPM_CACHE_KEY
  paths:
    - .clci-cache/clpm/releases
  when: always

.clci clpm cache: &clci_clpm_cache
  key:
    files:
      - clpmfile.lock
    prefix: $CI_JOB_NAME
  paths:
    - .clci-cache/clpm/data
    - .clci-cache/clpm/cache

Download CLPM

Download CLPM version $CLCI_CLPM_VERSION for $CLCI_CLPM_OS on $CLCI_CLPM_ARCH. Requires that curl is installed.

If $CLCI_CLPM_VERSION is empty, this does nothing. If $CLCI_CLPM_OS or $CLCI_CLPM_ARCH are empty, they are guessed from the current system.

Exports the following env vars:

.clci download clpm:
  script: &clci_download_clpm_script
    - |
      if [ "z$CLCI_CLPM_VERSION" = "z" ]; then
        echo "CLCI_CLPM_VERSION not set. Skipping downloading CLPM."
      else
        if [ "$CLCI_CLPM_VERSION" = "latest" ]; then
          CLPM_VERSION=0.4.1
        else
          CLPM_VERSION="$CLCI_CLPM_VERSION"
        fi

        if [ "z$CLCI_CLPM_ARCH" = "z" ]; then
          case $(uname -m) in
            x86_64)
              CLCI_CLPM_ARCH="amd64"
              ;;
            aarch64)
              CLCI_CLPM_ARCH="arm64"
              ;;
            *)
              echo "Unknown platform" >&2
              exit 1
             ;;
          esac
          export CLCI_CLPM_ARCH
        fi

        if [ "z$CLCI_CLPM_OS" = "z" ]; then
          case $(uname -s) in
            Linux)
              CLCI_CLPM_OS="linux"
              ;;
            Darwin)
              CLCI_CLPM_OS="darwin"
              ;;
            MINGW*)
              CLCI_CLPM_OS="windows"
              ;;
            *)
              echo "Unknown platform" >&2
              exit 1
              ;;
          esac
          export CLCI_CLPM_OS
        fi

        if [ "$CLCI_CLPM_OS" = "windows" ]; then
          _CLCI_CLPM_ARCHIVE_TYPE="zip"
        else
          _CLCI_CLPM_ARCHIVE_TYPE="tar.gz"
        fi

        _CLCI_CLPM_ARCHIVE_NAME="clpm-$CLPM_VERSION-$CLCI_CLPM_OS-$CLCI_CLPM_ARCH.$_CLCI_CLPM_ARCHIVE_TYPE"
        CLPM_ARCHIVE="$_CLCI_CLPM_CACHE_PATH/releases/$_CLCI_CLPM_ARCHIVE_NAME"

        mkdir -p "$_CLCI_CLPM_CACHE_PATH/releases/"

        if [ -e "$CLPM_ARCHIVE" ]; then
          echo "$_CLCI_CLPM_ARCHIVE_NAME already downloaded. Skipping."
        else
          echo "Downloading $_CLCI_CLPM_ARCHIVE_NAME"
          curl -fsSL "https://files.clpm.dev/clpm/$_CLCI_CLPM_ARCHIVE_NAME" > "$CLPM_ARCHIVE"
        fi
        export CLPM_ARCHIVE
        export CLPM_VERSION
      fi

install clpm

A job to install CLPM. It must already be downloaded. $CLPM_ARCHIVE must point to the archive.

CLPM is installed to $CLCI_CLPM_PREFIX which defaults to $HOME/.local.

Exports $CLPM which points to the CLPM executable.

.clci install clpm:
  script: &clci_install_clpm_script
    - |
      if [ ! -f "$CLPM_ARCHIVE" ]; then
        echo "$CLPM_ARCHIVE does not exist."
        exit 1
      fi
    # Unpack it.
    - mkdir -p "${CLCI_CLPM_PREFIX:-$HOME/.local}"
    - |
      if [ "${CLPM_ARCHIVE%zip}" = "$CLPM_ARCHIVE" ]; then
        # tar.gz
        tar -C "${CLCI_CLPM_PREFIX:-$HOME/.local}" -xf "$CLPM_ARCHIVE" --exclude=install.sh --strip-components=1
        export CLPM="${CLCI_CLPM_PREFIX:-$HOME/.local}/bin/clpm"
      else
        unzip "$CLPM_ARCHIVE" -d "${CLCI_CLPM_PREFIX:-$HOME/.local}"
        export CLPM="${CLCI_CLPM_PREFIX:-$HOME/.local}/bin/clpm.exe"
      fi

CLPM Job

This job is meant to be extended. It provides cache entries for ASDF and CLPM, as well as downloading, installing, and compiling ASDF, and downloading and installing CLPM.

.clci clpm job:
  extends:
    - .clci clpm cache variables
  before_script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
    - *clci_download_clpm_script
    - *clci_install_clpm_script
  cache:
    - *clci_asdf_cache
    - *clci_clpm_release_cache
    - *clci_clpm_cache

CLPM Script

This job is meant to be extended. It provides cache entries for ASDF and CLPM, as well as downloading, installing and compiling ASDF, and downloading and installing CLPM. You can add your own before_script. You must define a variable CLCI_SCRIPT containing the path to a script to be run as the last step of script.

.clci clpm script:
  extends:
    - .clci clpm cache variables
  script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
    - *clci_download_clpm_script
    - *clci_install_clpm_script
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      echo "(load \"$CLCI_SCRIPT\")" >> "$_clci_script"
    - $CLPM bundle install --no-resolve
    - echo "Running script:"
    - cat "$_clci_script"
    - |
      case "$LISP" in
        abcl)
          $CLPM bundle exec -- abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        allegro)
          $CLPM bundle exec -- alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
          ;;
        ccl)
          $CLPM bundle exec -- ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        clasp)
          $CLPM bundle exec -- clasp -N --norc --load "$_clci_script" || exit 1
          ;;
        clisp)
          $CLPM bundle exec -- clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        cmucl)
          $CLPM bundle exec -- cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
          ;;
        ecl)
          $CLPM bundle exec -- ecl --norc --shell "$_clci_script" || exit 1
          ;;
        sbcl)
          $CLPM bundle exec -- sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
          ;;
        *)
          echo "Unkown LISP: $LISP"
          exit 1
          ;;
      esac
  cache:
    - *clci_asdf_cache
    - *clci_clpm_release_cache
    - *clci_clpm_cache

Common Lisp

Jobs, meant to be extended, for running different Common Lisp jobs.

Variables

CLCI_ABCL_IMAGE: containers.common-lisp.net/cl-docker-images/abcl
CLCI_ABCL_TAG: latest
CLCI_ALLEGRO_IMAGE: containers.common-lisp.net/cl-docker-images/allegro
CLCI_ALLEGRO_TAG: latest
CLCI_CCL_IMAGE: containers.common-lisp.net/cl-docker-images/ccl
CLCI_CCL_TAG: latest
CLCI_CLASP_IMAGE: containers.common-lisp.net/cl-docker-images/clasp
CLCI_CLASP_TAG: latest
CLCI_CLISP_IMAGE: containers.common-lisp.net/cl-docker-images/clisp
CLCI_CLISP_TAG: latest
CLCI_CMUCL_IMAGE: containers.common-lisp.net/cl-docker-images/cmucl
CLCI_CMUCL_TAG: latest
CLCI_ECL_IMAGE: containers.common-lisp.net/cl-docker-images/ecl
CLCI_ECL_TAG: latest
CLCI_SBCL_IMAGE: containers.common-lisp.net/cl-docker-images/sbcl
CLCI_SBCL_TAG: latest

ABCL

Armed Bear Common Lisp

.clci abcl:
  image: $CLCI_ABCL_IMAGE:$CLCI_ABCL_TAG
  variables:
    LISP: abcl

Allegro

Allegro Common Lisp

.clci allegro:
  image: $CLCI_ALLEGRO_IMAGE:$CLCI_ALLEGRO_TAG
  variables:
    LISP: allegro

CCL

Clozure Common Lisp

.clci ccl:
  image: $CLCI_CCL_IMAGE:$CLCI_CCL_TAG
  variables:
    LISP: ccl

CLASP

clasp

.clci clasp:
  image: $CLCI_CLASP_IMAGE:$CLCI_CLASP_TAG
  variables:
    LISP: clasp

CLISP

clisp

.clci clisp:
  image: $CLCI_CLISP_IMAGE:$CLCI_CLISP_TAG
  variables:
    LISP: clisp

CMUCL

CMU Common Lisp

.clci cmucl:
  image: $CLCI_CMUCL_IMAGE:$CLCI_CMUCL_TAG
  variables:
    LISP: cmucl

ECL

Embeddable Common Lisp

.clci ecl:
  image: $CLCI_ECL_IMAGE:$CLCI_ECL_TAG
  variables:
    LISP: ecl

SBCL

Steel Bank Common Lisp

.clci sbcl:
  image: $CLCI_SBCL_IMAGE:$CLCI_SBCL_TAG
  variables:
    LISP: sbcl

Quicklisp

Variables

These variables are set by default:

CLCI_QL_CACHE_KEY: $CI_JOB_NAME-clci-ql
CLCI_QL_CLIENT_VERSION: latest
CLCI_QL_DIST_NAME: quicklisp
CLCI_QL_DIST_VERSION: latest
_CLCI_QL_CACHE_PATH: $CI_PROJECT_DIR/.clci-cache/ql

Cache

Reference this in a cache list to get caching of QL files.

.clci ql cache: &clci_ql_cache
  key: $CLCI_QL_CACHE_KEY
  paths:
    - .clci-cache/ql/

Download Quicklisp Installer

Download the Quicklisp installer. Requires that curl and sha256sum are installed.

Exports the following env vars:

.clci download ql installer:
  script: &clci_download_ql_installer_script
    - QUICKLISP_INSTALLER="$_CLCI_QL_CACHE_PATH/installer/quicklisp.lisp"
    - |
      if [ -e "$QUICKLISP_INSTALLER" ]; then
        echo "Quicklisp installer already downloaded."
      else
        echo "Downloading Quicklisp installer."
        mkdir -p "$_CLCI_QL_CACHE_PATH/installer/"
        curl -fsSL "https://beta.quicklisp.org/quicklisp.lisp" > "$QUICKLISP_INSTALLER"
        if echo "4a7a5c2aebe0716417047854267397e24a44d0cce096127411e9ce9ccfeb2c17  $QUICKLISP_INSTALLER" | sha256sum -c - ; then
          echo "SHA256 verified"
        else
          echo "SHA256 validation of Quicklisp installer failed!"
          rm "$QUICKLISP_INSTALLER"
          exit 1
        fi
      fi
    - export QUICKLISP_INSTALLER

Install Quicklisp

Install Quicklisp using a previously downloaded installer. Installs Quicklisp using the installer located at $QUICKLISP_INSTALLER using the CL implementation specified by $LISP. Requires that curl is installed if the latest client or dist versions are used. Requires that sha256sum is installed.

Its behavior can be configured using the following environment variables:

It installs Quicklisp in a cache friendly way, in folders corresponding to both the concrete client version and concrete dist version.

Exports the following env vars:

.clci installer ql:
  script: &clci_install_ql_script
    - |
      if [ "$CLCI_QL_CLIENT_VERSION" == "latest" ]; then
        echo "Computing the concrete client version."
        _ql_client_sexp="$(mktemp)"
        curl -fsSL "https://beta.quicklisp.org/client/quicklisp.sexp" > "$_ql_client_sexp"
        _clci_script="$(mktemp)"
        _ql_version_out="$(mktemp)"
        cat > "$_clci_script" <<EOF
      (with-open-file (info "$_ql_client_sexp" :direction :input)
        (with-open-file (out "$_ql_version_out" :direction :output :if-exists :supersede)
          (let* ((*read-eval* nil)
                 (sexp (read info)))
            (write-line (getf sexp :version) out))))
      EOF
        echo "Running script:"
        cat "$_clci_script"
        case "$LISP" in
          abcl)
            abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          allegro)
            alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
            ;;
          ccl)
            ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          clasp)
            clasp -N --norc --load "$_clci_script" || exit 1
            ;;
          clisp)
            clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          cmucl)
            cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
            ;;
          ecl)
            ecl --norc --shell "$_clci_script" || exit 1
            ;;
          sbcl)
            sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
            ;;
          *)
            echo "Unkown LISP: $LISP"
            exit 1
            ;;
        esac
        QUICKLISP_CLIENT_VERSION="$(cat "$_ql_version_out")"
      else
        QUICKLISP_CLIENT_VERSION="$CLCI_QL_CLIENT_VERSION"
      fi
    - export QUICKLISP_CLIENT_VERSION
    - |
      if [ "z$CLCI_QL_DIST_URL" == "z" ]; then
        case "$CLCI_QL_DIST_NAME" in
          quicklisp)
            if [ "$CLCI_QL_DIST_VERSION" == "latest" ]; then
              echo "Computing the concrete dist version."
              _ql_distinfo="$(mktemp)"
              curl -fsSL "https://beta.quicklisp.org/dist/quicklisp.txt" > "$_ql_distinfo"
              CLCI_QL_DIST_VERSION="$(cat "$_ql_distinfo" | grep "^version: " | cut -c10- )"
            fi
            QUICKLISP_DIST_URL="http://beta.quicklisp.org/dist/quicklisp/$CLCI_QL_DIST_VERSION/distinfo.txt"
            ;;
          ultralisp)
            if [ "$CLCI_QL_DIST_VERSION" == "latest" ]; then
              echo "Computing the concrete dist version."
              _ql_distinfo="$(mktemp)"
              curl -fsSL "https://dist.ultralisp.org/" > "$_ql_distinfo"
              CLCI_QL_DIST_VERSION="$(cat "$_ql_distinfo" | grep "^version: " | cut -c10- )"
            fi
            QUICKLISP_DIST_URL="http://dist.ultralisp.org/ultralisp/$CLCI_QL_DIST_VERSION/distinfo.txt"
            ;;
          *)
            echo "Unknown Quicklisp Dist $CLCI_QL_DIST_NAME"
            exit 1
            ;;
        esac
      else
        QUICKLISP_DIST_URL="$CLCI_QL_DIST_URL"
      fi
    - export QUICKLISP_DIST_URL
    - |
      _CLCI_QL_DIR="$_CLCI_QL_CACHE_PATH/dists/client-$QUICKLISP_CLIENT_VERSION/$(echo "$QUICKLISP_DIST_URL" | sha256sum - | cut -d' ' -f1 )"
    - |
      if [ -d "$_CLCI_QL_DIR" ]; then
        echo "Quicklisp already installed."
      else
        echo "Installing Quicklisp $QUICKLISP_DIST_URL to $_CLCI_QL_DIR"
        _clci_script="$(mktemp)"
        cat > "$_clci_script" <<EOF
      (load "$QUICKLISP_INSTALLER")
      (quicklisp-quickstart:install :dist-url "$QUICKLISP_DIST_URL" :client-version "$QUICKLISP_CLIENT_VERSION" :path "$_CLCI_QL_DIR/")
      EOF
        echo "Running script:"
        cat "$_clci_script"
        case "$LISP" in
          abcl)
            abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          allegro)
            alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
            ;;
          ccl)
            ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          clasp)
            clasp -N --norc --load "$_clci_script" || exit 1
            ;;
          clisp)
            clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          cmucl)
            cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
            ;;
          ecl)
            ecl --norc --shell "$_clci_script" || exit 1
            ;;
          sbcl)
            sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
            ;;
          *)
            echo "Unkown LISP: $LISP"
            exit 1
            ;;
        esac
      fi
    - QUICKLISP_SETUP_LISP="$_CLCI_QL_DIR/setup.lisp"
    - export QUICKLISP_SETUP_LISP
    - echo "(:source-registry-cache)" > "$_CLCI_QL_CACHE_PATH/.cl-source-registry.cache"

QL Job

This job is meant to be extended. It provides cache entries for ASDF and QL, as well as downloading, installing, and compiling ASDF, and downloading and installing QL.

.clci ql job:
  before_script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
    - *clci_download_ql_installer_script
    - *clci_install_ql_script
  cache:
    - *clci_asdf_cache
    - *clci_ql_cache

QL Script

This job is meant to be extended. It provides cache entries for ASDF and QL, as well as downloading, installing and compiling ASDF, and downloading and installing QL. You can add your own before_script. You must define a variable CLCI_SCRIPT containing the path to a script to be run as the last step of script.

.clci ql script:
  script:
    - *clci_download_asdf_script
    - *clci_install_asdf_script
    - *clci_compile_asdf_script
    - *clci_download_ql_installer_script
    - *clci_install_ql_script
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      echo "(load \"$QUICKLISP_SETUP_LISP\")" >> "$_clci_script"
    - |
      echo "(load \"$CLCI_SCRIPT\")" >> "$_clci_script"
    - echo "Running script:"
    - cat "$_clci_script"
    - |
      case "$LISP" in
        abcl)
          abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        allegro)
          alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
          ;;
        ccl)
          ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        clasp)
          clasp -N --norc --load "$_clci_script" || exit 1
          ;;
        clisp)
          clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
          ;;
        cmucl)
          cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
          ;;
        ecl)
          ecl --norc --shell "$_clci_script" || exit 1
          ;;
        sbcl)
          sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
          ;;
        *)
          echo "Unkown LISP: $LISP"
          exit 1
          ;;
      esac
  cache:
    - *clci_asdf_cache
    - *clci_ql_cache

Releases

This section defines some helpers for creating releases.

tagged release rule

A rule that runs the job only if tagged and looks like a version string (starting with v).

.clci tagged release rule:
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v[0-9]+(\.[0-9]+)*(-.*)?$/'

Linux test pipeline

This section produces the linux-test-pipeline.gitlab-ci.yml file.

It currently supports the following implementations:

If a particular implementation is used for testing is controlled by the variable CLCI_TEST_$IMPL. If set to the string yes, the implementation is used for testing. Every implementation except Allegro and Clasp defaults to yes. Once Clasp reaches 1.0.0, it will likely be enabled by default.

This pipeline supports the following dependency managers:

You can specify which one to use via the $CLCI_DEPENDENCY_MANAGER variable. It defaults to empty. CLPM is used if the variable is set to clpm or the file clpmfile.lock exists in the root of your repository. If the variable is set to quicklisp, then the Quicklisp client is installed, using the dist as specified by $CLCI_QL_DIST_URL and/or $CLCI_QL_DIST_VERSION / $CLCI_QL_DIST_NAME. If the variable is explicitly set to none, no dependency manager is installed.

Each job first installs the ASDF version requested by $CLCI_ASDF_VERSION. Then the relevant package manager is installed. Next, the implementation is fired up (under clpm bundle exec if CLPM is used), ASDF loaded (if $CLCI_ASDF_VERSION is specified), Quicklisp loaded (if Quicklisp is used), and the test script is run. The test script is specified by the CLCI_TEST_SCRIPT variable, which defaults to scripts/ci-test.lisp.

The test script is run with no rc files loaded and the debugger disabled. If the debugger is entered, the lisp is exited with a non-zero exit code.

Normally, this pipeline produces one job for every enabled implementation. That job is called either $LISP:clpm:test:clci, $LISP:ql:test:clci, or $LISP:test:clci, depending on what library manager is used (if any).

However, if $NEW_IMPLEMENTATION_RELEASE or $NEW_IMPLEMENTATION_RC are set, only the implementation corresponding to the value of those variables is tested. Additionally, $CLCI_$IMPL_TAG is overridden to the value of $NEW_IMPLMENTATION_RELEASE_TAG or $NEW_IMPLEMENTATION_RC_TAG.

Last, a job is added called finished:test:clci. This job is a noop that simply waits for every other job in this pipeline to end. This can be used to help order jobs (look at the release pipeline for an example).

This pipeline can be customized by:

Variables

variables:
  CLCI_TEST_ABCL: "yes"
  CLCI_TEST_ALLEGRO: "no"
  CLCI_TEST_CCL: "yes"
  CLCI_TEST_CLASP: "no"
  CLCI_TEST_CLISP: "yes"
  CLCI_TEST_CMUCL: "yes"
  CLCI_TEST_ECL: "yes"
  CLCI_TEST_SBCL: "yes"
  CLCI_TEST_SCRIPT: scripts/ci-test.lisp

Common

Every job in this pipeline extends this one. If you would like to install packages or do other common setup, put it in the beforescript of this job.

.common:test:clci:
  before_script:
    - "true"

._rules:test:clci:
  rules: []

No manager

This section is the piece of the pipeline dedicated to non-CLPM or QL jobs.

Template

This provides the common template for non-CLPM based jobs. It downloads, installs, and compiles ASDF. Then installs Quicklisp if requested.

._template:none:test:clci:
  script:
    - !reference [.clci asdf job, before_script]
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      if [ "z$ASDF_DIR" != "z" ]; then
        echo "(push #p\"$ASDF_DIR/\" asdf:*central-registry*)" >> "$_clci_script"
      fi
    - |
      echo "(push #p\"$CI_PROJECT_DIR/\" asdf:*central-registry*)" >> "$_clci_script"
    - |
      echo "(load \"$CLCI_TEST_SCRIPT\")" >> "$_clci_script"
    - |
      if [ -e Makefile ] && [ "$CLCI_TEST_USE_MAKEFILE" != "no" ] ; then
        make test
      else
        echo "Running script:"
        cat "$_clci_script"
        case "$LISP" in
          abcl)
            abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          allegro)
            alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
            ;;
          ccl)
            ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          clasp)
            clasp -N --norc --load "$_clci_script" || exit 1
            ;;
          clisp)
            clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          cmucl)
            cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
            ;;
          ecl)
            ecl --norc --shell "$_clci_script" || exit 1
            ;;
          sbcl)
            sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
            ;;
          *)
            echo "Unkown LISP: $LISP"
            exit 1
            ;;
        esac
      fi
.common:none:test:clci:
  extends:
    - ".common:test:clci"
  cache:
    - !reference [.clci asdf cache]

ABCL

Test on ABCL without a dependency manager.

abcl:test:clci:
  extends:
    - ".clci abcl"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_ABCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "abcl"'
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "abcl"'
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

Allegro

Test on Allegro without a dependency manager.

allegro:test:clci:
  extends:
    - ".clci allegro"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_ALLEGRO != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "allegro"'
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "allegro"'
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CCL

Test on CCL without a dependency manager.

ccl:test:clci:
  extends:
    - ".clci ccl"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_CCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ccl"'
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ccl"'
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

Clasp

Test on Clasp without a dependency manager.

clasp:test:clci:
  extends:
    - ".clci clasp"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_CLASP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clasp"'
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clasp"'
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CLISP

Test on CLISP without a dependency manager.

clisp:test:clci:
  extends:
    - ".clci clisp"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_CLISP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clisp"'
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clisp"'
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CMUCL

Test on CMUCL without a dependency manager.

cmucl:test:clci:
  extends:
    - ".clci cmucl"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_CMUCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "cmucl"'
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "cmucl"'
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

ECL

Test on ECL without a dependency manager.

ecl:test:clci:
  extends:
    - ".clci ecl"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_ECL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ecl"'
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ecl"'
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

SBCL

Test on SBCL without a dependency manager.

sbcl:test:clci:
  extends:
    - ".clci sbcl"
    - ".common:none:test:clci"
  script:
    - !reference ["._template:none:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - exists:
        - clpmfile.lock
      if: '$CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "none"'
      when: never
    - if: '$CLCI_TEST_SBCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "sbcl"'
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "sbcl"'
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

Quicklisp

This section is the piece of the pipeline dedicated to QL based jobs.

Template

This provides the common template for QL based jobs. It downloads, installs, and compiles ASDF. Then installs Quicklisp.

._template:ql:test:clci:
  script:
    - !reference [.clci ql job, before_script]
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      if [ "z$ASDF_DIR" != "z" ]; then
        echo "(push #p\"$ASDF_DIR/\" asdf:*central-registry*)" >> "$_clci_script"
      fi
    - |
      echo "(push #p\"$CI_PROJECT_DIR/\" asdf:*central-registry*)" >> "$_clci_script"
    - |
      echo "(load \"$QUICKLISP_SETUP_LISP\")" >> "$_clci_script"
    - |
      echo "(load \"$CLCI_TEST_SCRIPT\")" >> "$_clci_script"
    - |
      if [ -e Makefile ] && [ "$CLCI_TEST_USE_MAKEFILE" != "no" ] ; then
        make test
      else
        echo "Running script:"
        cat "$_clci_script"
        case "$LISP" in
          abcl)
            abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          allegro)
            alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
            ;;
          ccl)
            ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          clasp)
            clasp -N --norc --load "$_clci_script" || exit 1
            ;;
          clisp)
            clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          cmucl)
            cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
            ;;
          ecl)
            ecl --norc --shell "$_clci_script" || exit 1
            ;;
          sbcl)
            sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
            ;;
          *)
            echo "Unkown LISP: $LISP"
            exit 1
            ;;
        esac
      fi
.common:ql:test:clci:
  extends:
    - ".common:test:clci"
  cache:
    - !reference [.clci asdf cache]
    - !reference [.clci ql cache]

ABCL

Test on ABCL without CLPM.

abcl:ql:test:clci:
  extends:
    - ".clci abcl"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_ABCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "abcl"'
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "abcl"'
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

Allegro

Test on Allegro without CLPM.

allegro:ql:test:clci:
  extends:
    - ".clci allegro"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_ALLEGRO != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "allegro"'
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "allegro"'
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CCL

Test on CCL without CLPM.

ccl:ql:test:clci:
  extends:
    - ".clci ccl"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_CCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ccl"'
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ccl"'
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

Clasp

Test on Clasp without CLPM.

clasp:ql:test:clci:
  extends:
    - ".clci clasp"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_CLASP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clasp"'
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clasp"'
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CLISP

Test on CLISP without CLPM.

clisp:ql:test:clci:
  extends:
    - ".clci clisp"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_CLISP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clisp"'
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clisp"'
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CMUCL

Test on CMUCL without CLPM.

cmucl:ql:test:clci:
  extends:
    - ".clci cmucl"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_CMUCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "cmucl"'
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "cmucl"'
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

ECL

Test on ECL without CLPM.

ecl:ql:test:clci:
  extends:
    - ".clci ecl"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_ECL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ecl"'
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ecl"'
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

SBCL

Test on SBCL without CLPM.

sbcl:ql:test:clci:
  extends:
    - ".clci sbcl"
    - ".common:ql:test:clci"
  script:
    - !reference ["._template:ql:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER != "quicklisp"'
      when: never
    - if: '$CLCI_TEST_SBCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "sbcl"'
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "sbcl"'
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - when: on_success

CLPM

This section is the piece of the pipeline dedicated to CLPM based jobs.

Template

This provides the common template. It downloads, installs, and compiles ASDF. Then downloads and installs CLPM. Next, it installs the bundle.

._template:clpm:test:clci:
  script:
    - !reference [.clci clpm job, before_script]
    - _clci_script="$(mktemp)"
    - |
      if [ "z$ASDF_FASL" = "z" ]; then
        if [ ! "z$ASDF_LISP" = "z" ]; then
          echo "(load \"$ASDF_LISP\")" >> "$_clci_script"
        fi
      else
        echo "(load \"$ASDF_FASL\")" >> "$_clci_script"
      fi
    - |
      if [ "$CLCI_ASDF_VERSION" = "REQUIRE" ]; then
        echo '(require "asdf")' >> "$_clci_script"
      fi
    - |
      if [ "z$ASDF_DIR" != "z" ]; then
        echo "(push #p\"$ASDF_DIR/\" asdf:*central-registry*)" >> "$_clci_script"
      fi
    - |
      echo "(load \"$CLCI_TEST_SCRIPT\")" >> "$_clci_script"
    - $CLPM bundle install --no-resolve
    - echo "Running script:"
    - cat "$_clci_script"
    - |
      if [ -e Makefile ] && [ "$CLCI_TEST_USE_MAKEFILE" != "no" ] ; then
        make test
      else
        case "$LISP" in
          abcl)
            $CLPM bundle exec -- abcl --noinit --nosystem --batch --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit :status 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          allegro)
            $CLPM bundle exec -- alisp --qq --batch --backtrace-on-error -L "$_clci_script" || exit 1
            ;;
          ccl)
            $CLPM bundle exec -- ccl -n -b --eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ccl:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          clasp)
            $CLPM bundle exec -- clasp -N --norc --load "$_clci_script" || exit 1
            ;;
          clisp)
            $CLPM bundle exec -- clisp -ansi -norc -x "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (ext:quit 1)))) (load \"$_clci_script\"))" || exit 1
            ;;
          cmucl)
            $CLPM bundle exec -- cmucl -noinit -eval "(let ((*debugger-hook* (lambda (c prev) (declare (ignore prev)) (format *error-output* \"Caught error: ~A~%\" c) (unix:unix-exit 1)))) (load \"$_clci_script\") (unix:unix-exit 0))" || exit 1
            ;;
          ecl)
            $CLPM bundle exec -- ecl --norc --shell "$_clci_script" || exit 1
            ;;
          sbcl)
            $CLPM bundle exec -- sbcl --disable-ldb --lose-on-corruption --no-sysinit --disable-debugger --script "$_clci_script" || exit 1
            ;;
          *)
            echo "Unkown LISP: $LISP"
            exit 1
            ;;
        esac
      fi
.common:clpm:test:clci:
  variables:
    CLPM_CACHE_DIR: !reference [.clci clpm cache variables, variables, CLPM_CACHE_DIR]
    CLPM_DATA_DIR: !reference [.clci clpm cache variables, variables, CLPM_DATA_DIR]
  extends:
    - ".common:test:clci"
  cache:
    - !reference [.clci asdf cache]
    - !reference [.clci clpm cache]
    - !reference [.clci clpm release cache]

ABCL

Test on ABCL.

abcl:clpm:test:clci:
  extends:
    - ".clci abcl"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_ABCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "abcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "abcl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "abcl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ABCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

Allegro

Test on Allegro

allegro:clpm:test:clci:
  extends:
    - ".clci allegro"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_ALLEGRO != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "allegro"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "allegro"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "allegro"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ALLEGRO_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

CCL

Test on CCL.

ccl:clpm:test:clci:
  extends:
    - ".clci ccl"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_CCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ccl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ccl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ccl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

CLASP

Test on CLASP

clasp:clpm:test:clci:
  extends:
    - ".clci clasp"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_CLASP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clasp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clasp"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clasp"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CLASP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

CLISP

Test on CLISP

clisp:clpm:test:clci:
  extends:
    - ".clci clisp"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_CLISP != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "clisp"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "clisp"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "clisp"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CLISP_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

CMUCL

Test on CMUCL.

cmucl:clpm:test:clci:
  extends:
    - ".clci cmucl"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_CMUCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "cmucl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "cmucl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "cmucl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_CMUCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

ECL

Test on ECL.

ecl:clpm:test:clci:
  extends:
    - ".clci ecl"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_ECL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "ecl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "ecl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "ecl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_ECL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

SBCL

Test on SBCL.

sbcl:clpm:test:clci:
  extends:
    - ".clci sbcl"
    - ".common:clpm:test:clci"
  script:
    - !reference ["._template:clpm:test:clci", script]
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - if: '$CLCI_DEPENDENCY_MANAGER && $CLCI_DEPENDENCY_MANAGER != "clpm"'
      when: never
    - if: '$CLCI_TEST_SBCL != "yes"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE && $NEW_IMPLEMENTATION_RELEASE != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RC && $NEW_IMPLEMENTATION_RC != "sbcl"'
      when: never
    - if: '$NEW_IMPLEMENTATION_RELEASE == "sbcl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RELEASE_TAG
    - if: '$NEW_IMPLEMENTATION_RC == "sbcl"'
      exists:
        - clpmfile.lock
      variables:
        CLCI_SBCL_TAG: $NEW_IMPLEMENTATION_RC_TAG
    - exists:
        - clpmfile.lock

Finished

This job simply waits until every other job in the pipeline is finished.

finished:test:clci:
  script:
    - echo "All tests finished"
  rules:
    - !reference ["._rules:test:clci", "rules"]
    - when: on_success
  needs:
    - job: "abcl:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "allegro:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "ccl:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "clasp:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "clisp:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "cmucl:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "ecl:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "sbcl:clpm:test:clci"
      optional: true
      artifacts: false
    - job: "abcl:test:clci"
      optional: true
      artifacts: false
    - job: "allegro:test:clci"
      optional: true
      artifacts: false
    - job: "ccl:test:clci"
      optional: true
      artifacts: false
    - job: "clasp:test:clci"
      optional: true
      artifacts: false
    - job: "clisp:test:clci"
      optional: true
      artifacts: false
    - job: "cmucl:test:clci"
      optional: true
      artifacts: false
    - job: "ecl:test:clci"
      optional: true
      artifacts: false
    - job: "sbcl:test:clci"
      optional: true
      artifacts: false
    - job: "abcl:ql:test:clci"
      optional: true
      artifacts: false
    - job: "allegro:ql:test:clci"
      optional: true
      artifacts: false
    - job: "ccl:ql:test:clci"
      optional: true
      artifacts: false
    - job: "clasp:ql:test:clci"
      optional: true
      artifacts: false
    - job: "clisp:ql:test:clci"
      optional: true
      artifacts: false
    - job: "cmucl:ql:test:clci"
      optional: true
      artifacts: false
    - job: "ecl:ql:test:clci"
      optional: true
      artifacts: false
    - job: "sbcl:ql:test:clci"
      optional: true
      artifacts: false

Guarded Test Pipeline

This section tangles to the file guarded-linux-test-pipeline.gitlab-ci.yml. It conditionally includes linux-test-pipeline.gitlab-ci.yml if the value of $PIPELINE_TYPE allows it. This file is used by the suggested configuration in order to limit verbosity.

CLPM Dependency Update

This section tangles to the clpm-dep-update-pipeline.gitlab-ci.yml file.

This pipeline is meant to be run on a schedule. Its purpose is to update your CLPM lock file and open an MR against the default branch if there are changes.

You must set $CLCI_MR_TOKEN to a Gitlab token that has permissions to create merge requests on your project.

This pipeline cannot otherwise be customized.

Common

Defaults.

._rules:clpm dep update:clci:
  rules: []

Sanity Check

Checks that CLCIMRTOKEN is set.

sanity-check:clpm dep update:clci:
  image: !reference [.clci sbcl, image]
  script:
    - '[ -n "$CLCI_MR_TOKEN" ] || { echo "CLCI_MR_TOKEN needs to be set" >&2; exit 1; }'
  rules:
    - !reference ["._rules:clpm dep update:clci", "rules"]
    - when: on_success

Update

Run clpm update, and open an MR if there's a change.

update:clpm dep update:clci:
  image: !reference [.clci sbcl, image]
  variables:
    LISP: sbcl
  script:
    - BRANCH_NAME="update-deps-$(date +%Y-%m-%d)"
    - git checkout -B "$BRANCH_NAME" "$CI_COMMIT_SHA"
    - !reference [.clci download asdf, script]
    - !reference [.clci install asdf, script]
    - !reference [.clci compile asdf, script]
    - !reference [.clci download clpm, script]
    - !reference [.clci install clpm, script]
    - '$CLPM bundle update -y -V'
    - |
      if [ -z "$(git status --untracked-files=no --porcelain)" ]; then
        exit 0
      fi
    - git config user.email "$GITLAB_USER_EMAIL"
    - git config user.name "$GITLAB_USER_NAME"
    - git add clpmfile.lock
    - git commit -m 'Update bundle dependencies'
    - 'git push "https://git:$CLCI_MR_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" "$BRANCH_NAME"'
    - 'curl -X POST "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests" -H "PRIVATE-TOKEN: $CLCI_MR_TOKEN" -d "labels=bot" -d "source_branch=$BRANCH_NAME" -d "target_branch=$CI_COMMIT_BRANCH" -d "title=Update clpmfile.lock" -d "description=Automated update of clpmfile.lock"'
  needs:
    - "sanity-check:clpm dep update:clci"
  rules:
    - !reference ["._rules:clpm dep update:clci", "rules"]
    - when: on_success

Guarded CLPM Dependency Update

This section tangles to the file guarded-clpm-dep-update-pipeline.gitlab-ci.yml. It conditionally includes clpm-dep-update-pipeline.gitlab-ci.yml if the value of $PIPELINE_TYPE allows it. This file is used by the suggested configuration in order to limit verbosity.

Release Pipeline

This section tangles to the file release-pipeline.gitlab-ci.yml. It contains jobs that run when tags are pushed to the repo. that look like releases (a v followed by a version number).

In order to use this pipeline, you must protect the tags you use for releasing. To do this, go to "Settings > Repository > Protected Tags". Most projects will probably want to protect v*.

Additionally, you must be able to create a changelog entry for the release. By default, the extract changelog:release:clci job will look at the file CHANGELOG.md and extract the entry corresponding to the second level heading that starts with $CI_COMMIT_TAG (with anything after a - stripped). The file location can be changed by changing the value of $CLCI_CHANGELOG.

Changelog extraction can be customized by overriding the script of the extract changelog:release:clci job. It must write the changelog entry to the file .clci-build/changelog-entry.md.

Actually performing the release blocks on both sanity checks and any tests added by the test pipeline succeeded. If you would like to add additionally blockers, add needs: to the job user blocker:release:clci.

By default, this pipeline only releases to Gitlab. (Look at the "Deployments > Releases" tab).

However, you can also release to a CLPI server by setting the variables $CLCI_CLPI_SERVER, $CLCI_CLPI_USERNAME, and $CLCI_CLPI_PASSWORD. This feature is in beta and stability is not guaranteed.

After all releases have been performed, the release is announced. Currently, the only announcement mechanism supported is via Slack Webhook. To enable this, set the variable $CLCI_SLACK_ANNOUNCE_WEBHOOK_URL.

Beyond setting these environment variables, adding needs to the job user blocker:release:clci, and overriding the script of extract changelog:release:clci, no further customizations are currently supported.

Common

Defaults.

._rules:release:clci:
  rules: []

Sanity Check

Checks that the current tag is protected.

check tag protected:release:clci:
  script:
    - '[ "$CI_COMMIT_REF_PROTECTED" == "true" ] || { echo "You must protect this tag" >&2; exit 1; }'
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - when: on_success

Extract Changelog Entry

extract changelog:release:clci:
  script:
    - mkdir -p .clci-build
    - _CLCI_INTERMEDIATE="${CI_COMMIT_TAG%%-*}"
    - |
      awk "
      /^## /{flag=0}
      /^## ${_CLCI_INTERMEDIATE//./\\.}/{flag=1}
      flag
      " "$CLCI_CHANGELOG" > .clci-build/changelog-entry.md
    - '[[ -s .clci-build/changelog-entry.md ]] || { echo "Unable to extract changelog entry" >&2; exit 1; }'
  artifacts:
    paths:
      - .clci-build/changelog-entry.md
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - when: on_success

user blocker

This job is a noop job that simply waits for any jobs the user desires to finish before proceeding.

user blocker:release:clci:
  script:
    - echo "All user blockers for release finished"
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - when: on_success

test blocker

This job is a noop job that simply waits before all tests and sanity checks are complete before proceeding.

blocker:release:clci:
  script:
    - echo "All release blockers finished"
  needs:
    - job: "user blocker:release:clci"
      artifacts: false
      optional: true
    - job: "check tag protected:release:clci"
      artifacts: false
    - job: "extract changelog:release:clci"
      artifacts: false
    - job: "finished:test:clci"
      optional: true
      artifacts: false
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - when: on_success

Release to Gitlab

This job uses Gitlab's release API to create a release from the tag name.

gitlab:release:clci:
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  script:
    - echo 'release job'
  release:
    tag_name: $CI_COMMIT_TAG
    description: './.clci-build/changelog-entry.md'
  needs:
    - "extract changelog:release:clci"
    - "blocker:release:clci"
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - when: on_success

Release to CLPI

clpi:release:clci:
  extends:
    - .clci sbcl
    - .clci clpm job
  script:
    - apt-get update
    - apt-get install -y --no-install-recommends jq
    - $CLPM bundle install --no-resolve
    - _clci_asd_args=""
    - for asd in $(ls *.asd); do _clci_asd_args="$_clci_asd_args --asd $asd"; done
    - mkdir -p .clci-build
    - $CLPM clpi release $_clci_asd_args --deps-from-lockfile -VV "$CI_PROJECT_NAME" "${CI_COMMIT_TAG#v}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/archive.tar.gz?sha=${CI_COMMIT_TAG}" | tee .clci-build/clpi-release.json
    # First, make sure the project is present in CLPI, with the repo set.
    - |
      repo_desc="$(echo "{}" | jq --arg host "$CI_SERVER_HOST" --arg path "$CI_PROJECT_PATH" --arg type gitlab '. + {type: $type, host: $host, path: $path}')"
    - echo "$repo_desc"
    - |
      curl -X PUT \
           -f \
           -d "$repo_desc" \
           -H "Content-Type: application/json" \
           "${CLCI_CLPI_SERVER}/api/v0.1/projects/$CI_PROJECT_NAME/repo" \
           --user "${CLCI_CLPI_USERNAME}:${CLCI_CLPI_PASSWORD}"
    - |
      curl -f -X PUT \
           -d @.clci-build/clpi-release.json \
           -H "Content-Type: application/json" \
           "${CLCI_CLPI_SERVER}/api/v0.1/projects/$CI_PROJECT_NAME/releases/${CI_COMMIT_TAG#v}" \
           --user "${CLCI_CLPI_USERNAME}:${CLCI_CLPI_PASSWORD}"
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - if: '$CLCI_CLPI_SERVER == null || $CLCI_CLPI_SERVER == "" || $CLCI_CLPI_USERNAME == null || $CLCI_CLPI_USERNAME == "" || $CLCI_CLPI_PASSWORD == null || $CLCI_CLPI_PASSWORD == ""'
      when: never
    - when: on_success
  needs:
    - "extract changelog:release:clci"
    - "blocker:release:clci"

Announce to Slack

slack announcement:release:clci:
  image: alpine:latest
  script:
    - apk add curl jq
    - |
      curl -X POST \
           --data-urlencode "payload=$(echo "{}" | jq --arg text "$CI_PROJECT_TITLE $CI_COMMIT_TAG has been released.

      $(cat "./.clci-build/changelog-entry.md")" '. + {text: $text}')" \
           "$CLCI_SLACK_ANNOUNCE_WEBHOOK_URL"
  rules:
    - !reference ["._rules:release:clci", "rules"]
    - if: '$CLCI_SLACK_ANNOUNCE_WEBHOOK_URL == null || $CLCI_SLACK_ANNOUNCE_WEBHOOK_URL == ""'
      when: never
    - when: on_success
  needs:
    - "gitlab:release:clci"
    - job: "clpi:release:clci"
      optional: true
    - "extract changelog:release:clci"

Guarded Release Pipeline

This section tangles to the file guarded-release-pipeline.gitlab-ci.yml. It conditionally includes release-pipeline.gitlab-ci.yml if the value of $PIPELINE_TYPE allows it and if a tag is pushed that looks like a version number (v followed by any number of dot separated integers and optionally a - followed by anything). This file is used by the suggested configuration in order to limit verbosity.