diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4ef3976a5..3a00396bc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,6 +8,13 @@ on:
tags:
- "v*"
pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ workflow_dispatch:
+ inputs:
+ tests:
+ description: "hspec match patterns, one per line; / runs all tests, empty runs none"
+ type: string
+ default: ""
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -24,7 +31,15 @@ jobs:
maybe-release:
runs-on: ubuntu-latest
+ outputs:
+ reduced: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == true }}
steps:
+ - name: Reject a manual run on a tag
+ if: github.event_name == 'workflow_dispatch' && github.ref_type == 'tag'
+ run: |
+ echo "::error::A manual run starts on a branch; to retry a release, re-run the jobs of its tag push."
+ exit 1
+
- name: Clone project
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/checkout@v3
@@ -66,21 +81,22 @@ jobs:
build:
name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }}, GHC: ${{ matrix.ghc }}"
needs: maybe-release
+ if: needs.maybe-release.outputs.reduced != 'true'
permissions:
contents: write
packages: write
- env:
+ env: &build-env
apps: "smp-server xftp-server ntf-server xftp"
- opt: ${{ github.event_name == 'pull_request' && 'O0' || 'O2' }}
- ghc_options: ${{ github.event_name == 'pull_request' && '--ghc-options=-O0' || '' }}
+ opt: ${{ github.event_name == 'push' && 'O2' || 'O0' }}
+ ghc_options: ${{ github.event_name != 'push' && '--ghc-options=-O0' || '' }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
postgres_dist: ${{ startsWith(github.ref, 'refs/tags/v') && 'dist-newstyle' || 'dist-postgres' }}
branch: ${{ github.base_ref || github.ref_name }}
cache_o0: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable') }}
- image_cache: ghcr.io/simplex-chat/simplexmq-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}
+ image_cache: ghcr.io/${{ github.repository_owner }}/simplexmq-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}
source_hashes: dist-newstyle/sources.sha256
runs-on: ${{ matrix.runner }}
- services:
+ services: &build-services
postgres:
image: postgres:15
env:
@@ -127,7 +143,7 @@ jobs:
runner: "ubuntu-24.04-arm"
ghc: "9.6.3"
should_run: true
- steps:
+ steps: &build-steps
- name: Clone project
if: matrix.should_run == true
uses: actions/checkout@v3
@@ -291,7 +307,7 @@ jobs:
- name: Build everything else (standard)
id: build-standard
- if: matrix.should_run == true
+ if: matrix.should_run == true && needs.maybe-release.outputs.reduced != 'true'
shell: docker exec -t builder sh -eu {0}
run: |
cabal build --jobs=$(nproc) ${{ env.ghc_options }}
@@ -394,23 +410,11 @@ jobs:
shell: bash
env:
PGHOST: localhost
- run: |
- i=1
- attempts=1
- ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3
- while [ "$i" -le "$attempts" ]; do
- if ./simplexmq-test; then
- break
- else
- echo "Attempt $i failed, retrying..."
- i=$((i + 1))
- sleep 1
- fi
- done
- if [ "$i" -gt "$attempts" ]; then
- echo "All "$attempts" attempts failed."
- exit 1
- fi
+ REDUCED: ${{ needs.maybe-release.outputs.reduced }}
+ TESTS: ${{ github.event_name == 'workflow_dispatch' && inputs.tests || '' }}
+ PR_BODY: ${{ github.event.pull_request.draft == true && github.event.pull_request.body || '' }}
+ ATTEMPTS: ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) && 3 || 1 }}
+ run: scripts/ci/run-tests.sh ./simplexmq-test
- name: Remove -O2 build directories
id: remove-o2
@@ -458,6 +462,27 @@ jobs:
dist-postgres
key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }}
+ build-reduced:
+ name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }}, GHC: ${{ matrix.ghc }}"
+ needs: maybe-release
+ if: needs.maybe-release.outputs.reduced == 'true'
+ permissions:
+ contents: write
+ packages: write
+ env: *build-env
+ runs-on: ${{ matrix.runner }}
+ services: *build-services
+ strategy:
+ matrix:
+ include:
+ - os: 24.04
+ os_underscore: 24_04
+ arch: x86-64
+ runner: "ubuntu-24.04"
+ ghc: "9.6.3"
+ should_run: true
+ steps: *build-steps
+
# =============================
# Resolver test job
# =============================
diff --git a/scripts/ci/README.md b/scripts/ci/README.md
new file mode 100644
index 000000000..c4064dcdb
--- /dev/null
+++ b/scripts/ci/README.md
@@ -0,0 +1,50 @@
+# CI on GitHub's runners
+
+## Builds
+
+`build.yml` runs one of two builds:
+
+| Trigger | Build | Jobs |
+|---|---|---|
+| a push to a draft pull request
a manual run (workflow_dispatch) | reduced | build-reduced: ubuntu-24.04 x86-64, GHC 9.6.3, at -O0, without the standard build |
+| a pull request marked ready for review, and later pushes to it
a push to master or stable
a push of a v* tag | full | build: 5 entries (GHC 8.10.7 and 9.6.3, x86-64 and aarch64), at -O0 on pull requests and -O2 on pushes |
+
+Both builds also run maybe-release and resolver (python). Tests run on the x86-64 entries. The reduced build restores the -O0 build caches saved by master pushes; the Docker image cache is tagged by ISO week, so the first run of a week rebuilds the image.
+
+## Test selection
+
+- Draft pull request: a fenced block with the info string `ci-tests` in the description, one hspec match pattern per line. The block is read at each push; an edit applies from the next push.
+- Manual run: input `tests`, one pattern per line.
+- `/` runs all tests. With no pattern, a reduced build runs no tests. A full build runs all tests and ignores the selection.
+- Each pattern is passed as `-m `. With at least one pattern, `--fail-on=empty` is added, so a pattern that matches nothing fails the run.
+- A pattern matches the item path `/group/subgroup/item/` as a substring. Group names are the `describe` names in tests/Test.hs.
+
+Example block:
+
+````
+```ci-tests
+/Core tests/Encoding tests/
+SMP proxy
+```
+````
+
+## Manual run
+
+```sh
+curl -fsS -X POST \
+ -H "Authorization: Bearer $GH_TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ https://api.github.com/repos//simplexmq/actions/workflows/build.yml/dispatches \
+ -d '{"ref": "", "inputs": {"tests": "/Core tests/Encoding tests/\nSMP proxy"}}'
+```
+
+A manual run starts on a branch; a release is retried by re-running the jobs of its tag push.
+
+## Local run
+
+scripts/ci/run-tests.sh runs the selection as in CI: it appends the patterns of `TESTS` to the test command.
+
+```sh
+cabal build --enable-tests --ghc-options=-O0 simplexmq-test
+TESTS='/Core tests/Encoding tests/' scripts/ci/run-tests.sh "$(cabal list-bin simplexmq-test --ghc-options=-O0)"
+```
diff --git a/scripts/ci/run-tests.sh b/scripts/ci/run-tests.sh
new file mode 100755
index 000000000..327ff667c
--- /dev/null
+++ b/scripts/ci/run-tests.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+set -euo pipefail
+
+if [ "$#" -eq 0 ]; then
+ echo "usage: $0 " >&2
+ exit 1
+fi
+
+if [ -n "${PR_BODY:-}" ]; then
+ TESTS=$(printf '%s\n' "$PR_BODY" | tr -d '\r' | awk '/^```ci-tests[ \t]*$/ {f = 1; next} /^```[ \t]*$/ {f = 0} f')
+fi
+args=()
+while read -r p; do
+ if [ -n "$p" ]; then args+=(-m "$p"); fi
+done <<< "${TESTS:-}"
+if [ "${#args[@]}" -gt 0 ]; then
+ args+=(--fail-on=empty)
+elif [ "${REDUCED:-}" = true ]; then
+ echo "No tests selected"
+ exit 0
+fi
+
+i=1
+attempts=${ATTEMPTS:-1}
+while [ "$i" -le "$attempts" ]; do
+ if "$@" "${args[@]}"; then
+ break
+ else
+ echo "Attempt $i failed, retrying..."
+ i=$((i + 1))
+ sleep 1
+ fi
+done
+if [ "$i" -gt "$attempts" ]; then
+ echo "All $attempts attempts failed."
+ exit 1
+fi