Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The gate: `./gradlew clean build` on Linux, macOS and Windows, for every pull request
# and every push to main or develop.
# The gate: `./gradlew clean build`, then the platform archive's tests, on Linux, macOS
# and Windows, for every pull request and every push to main or develop.
name: CI

on:
Expand Down Expand Up @@ -33,6 +33,9 @@ jobs:
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y zsh fish
- run: ./gradlew clean build
# The platform archive, run as it ships: a pipeline through its launcher, the CDS
# archive mapped, the man pages rendered.
- run: ./gradlew packageTest
- name: Upload test and coverage reports
if: always()
uses: actions/upload-artifact@v6
Expand Down
198 changes: 198 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Releases the command (design §7.3): a platform archive for each of macOS, Linux and
# Windows, attached with their checksums to this repository's GitHub Release, and the
# Homebrew formula and Scoop manifest that install them, pushed to the tap
# (DarkCollective/homebrew-relix) and the bucket (DarkCollective/scoop-relix).
#
# A tag vX.Y.Z publishes X.Y.Z. A tag with a suffix (v1.2.0-rc1) publishes a pre-release
# and leaves the tap and the bucket alone.
#
# Run from the Actions tab, it is a dry run: it builds and tests every archive, writes the
# formula and the manifest and checks their checksums against the archives, and uploads
# them as the run's `release-files` artifact. It publishes nothing, and since it may run
# on develop, whose engine pin is a snapshot, it skips checkEnginePin.
name: Release

on:
push:
tags: ['v[0-9]*.[0-9]*.[0-9]*']
workflow_dispatch:
inputs:
version:
description: 'The version to build, without the v (a dry run publishes nothing)'
required: true
default: '0.0.0-dryrun'

permissions:
contents: read

jobs:
version:
name: Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
publish: ${{ steps.version.outputs.publish }}
stable: ${{ steps.version.outputs.stable }}
steps:
- id: version
env:
EVENT: ${{ github.event_name }}
TAG: ${{ github.ref_name }}
INPUT: ${{ inputs.version }}
run: |
if [ "$EVENT" = push ]; then
version="${TAG#v}"
publish=true
else
version="$INPUT"
publish=false
fi
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then
echo "::error::'$version' is not a version such as 1.2.0 or 1.2.0-rc1"
exit 1
fi
case "$version" in *-*) stable=false ;; *) stable=true ;; esac
{
echo "version=$version"
echo "publish=$publish"
echo "stable=$stable"
} >> "$GITHUB_OUTPUT"

package:
name: Package (${{ matrix.platform }})
needs: version
strategy:
fail-fast: true
matrix:
include:
- { os: macos-latest, platform: macos-aarch64 }
- { os: macos-15-intel, platform: macos-x86_64 }
- { os: ubuntu-latest, platform: linux-x86_64 }
- { os: ubuntu-24.04-arm, platform: linux-aarch64 }
- { os: windows-latest, platform: windows-x86_64 }
runs-on: ${{ matrix.os }}
timeout-minutes: 45
env:
VERSION: ${{ needs.version.outputs.version }}
# A dry run may build develop, whose engine pin is a snapshot; a release may not.
SKIP_PIN: ${{ needs.version.outputs.publish != 'true' && '-x checkEnginePin' || '' }}
steps:
- uses: actions/checkout@v7
# Corretto's JDK carries the jmods jlink links from.
- uses: actions/setup-java@v6
with:
distribution: corretto
java-version: '21'
- uses: gradle/actions/setup-gradle@v6
# The gate, then the archive, then the archive's own tests: a pipeline through its
# launcher, the CDS archive mapped, the man pages rendered.
- name: Build, package and test the archive
shell: bash
run: ./gradlew clean build releaseArchive packageTest -PcliVersion="$VERSION" $SKIP_PIN
- name: Check the archive is this platform's
shell: bash
run: |
case "${{ matrix.platform }}" in windows-*) ext=zip ;; *) ext=tar.gz ;; esac
test -f "build/distributions/relix-$VERSION-${{ matrix.platform }}.$ext"
- uses: actions/upload-artifact@v6
with:
name: archive-${{ matrix.platform }}
path: build/distributions/relix-*
if-no-files-found: error
- name: Upload test reports
if: failure()
uses: actions/upload-artifact@v6
with:
name: reports-${{ matrix.platform }}
path: build/reports/
if-no-files-found: ignore

release-files:
name: Checksums, formula and manifest
needs: [version, package]
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.version.outputs.version }}
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v7
with:
pattern: archive-*
path: dist
merge-multiple: true
# Fails unless every archive the formula and the manifest name is in dist with the
# checksum they give it.
- run: python3 packaging/release.py "$VERSION" dist release-files
- uses: actions/upload-artifact@v6
with:
name: release-files
path: release-files/
if-no-files-found: error

publish:
name: Publish the release
needs: [version, release-files]
if: needs.version.outputs.publish == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
env:
VERSION: ${{ needs.version.outputs.version }}
STABLE: ${{ needs.version.outputs.stable }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/download-artifact@v7
with:
pattern: archive-*
path: dist
merge-multiple: true
- uses: actions/download-artifact@v7
with:
name: release-files
path: release-files
- name: Create the GitHub Release
run: |
prerelease=()
if [ "$STABLE" != true ]; then prerelease=(--prerelease); fi
gh release create "v$VERSION" --repo "$GITHUB_REPOSITORY" --verify-tag \
--title "relix $VERSION" --generate-notes "${prerelease[@]}" \
dist/relix-"$VERSION"-* release-files/checksums.txt

tap:
name: Update the Homebrew tap and the Scoop bucket
needs: [version, publish]
if: needs.version.outputs.stable == 'true'
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.version.outputs.version }}
steps:
- uses: actions/download-artifact@v7
with:
name: release-files
path: release-files
- name: Push the formula to DarkCollective/homebrew-relix
env:
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT }}
run: |
git clone "https://x-access-token:${TOKEN}@github.com/DarkCollective/homebrew-relix.git" tap
mkdir -p tap/Formula
cp release-files/relix.rb tap/Formula/relix.rb
cd tap
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/relix.rb
git commit -m "relix $VERSION"
git push
- name: Push the manifest to DarkCollective/scoop-relix
env:
TOKEN: ${{ secrets.SCOOP_BUCKET_PAT }}
run: |
git clone "https://x-access-token:${TOKEN}@github.com/DarkCollective/scoop-relix.git" bucket
mkdir -p bucket/bucket
cp release-files/relix.json bucket/bucket/relix.json
cd bucket
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add bucket/relix.json
git commit -m "relix $VERSION"
git push
34 changes: 34 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ below. You need no Java 21 installed beforehand: Gradle provisions it.
the build unpacks into `build/engine-docs`. A page that disagrees is fixed in
relix-core.

### The release archive

The command ships as a platform archive: a jlink image with an AppCDS archive, the man
pages and the completion scripts (design §7.3). CI also runs its tests on each
platform, after `build`:

```bash
./gradlew packageTest
```

It builds the archive (`releaseArchive`, a minute or so), unpacks it with the system's
`tar`, and runs the launcher it holds: a script on standard input over a catalog in a
temporary tree, OPTIMIZE and `relix doc` (each finds a provider that jlink would leave
out unnoticed), `-Xshare:on` (which fails unless the CDS archive is used), and `man` over
every page. The man pages need `man` installed, and are not checked on Windows.

### Building against a local engine checkout

To try an engine change before it is published, build against your relix-core
Expand All @@ -138,6 +154,24 @@ that builds on Gradle 9. An older one fails while configuring, in a plugin that
Gradle 9 broke (for example `info.solidsoft.pitest`, which reports an unknown
property `baseDir`).

### Cutting a release (maintainers)

1. Pin a released engine in `build.gradle` (`checkEnginePin` refuses a snapshot) and
promote to `main`.
2. Run the **Release** workflow from the Actions tab with the version. It is a dry run:
it builds and tests the five platform archives, writes the Homebrew formula and the
Scoop manifest from [`packaging/`](packaging), checks their checksums against the
archives, and uploads them as the `release-files` artifact. Nothing is published.
3. Tag `main`: `git tag -a v1.2.0 -m "relix 1.2.0" && git push origin v1.2.0`. The
workflow runs again, attaches the archives and `checksums.txt` to the GitHub Release,
and pushes the formula to `DarkCollective/homebrew-relix` and the manifest to
`DarkCollective/scoop-relix`, with the `HOMEBREW_TAP_PAT` and `SCOOP_BUCKET_PAT`
secrets. A tag with a suffix (`v1.2.0-rc1`) makes a pre-release and leaves the tap and
the bucket alone.

The formula and the manifest are written whole from `packaging/` on every release, so a
change to either is made there, not in the tap or the bucket.

## Branches and commits

Work on a branch named with a prefix — `feat/`, `fix/`, `docs/`, `chore/` or
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ Requires Java 21, which the Gradle wrapper provisions if it is missing:

[CONTRIBUTING.md](CONTRIBUTING.md#the-build-gate) describes what the build checks.

`./gradlew releaseArchive` builds this platform's archive in `build/distributions`: a
self-contained image (no Java needed to run it) with `bin/relix`, the man pages in
`man/man1` and the completion scripts in `completions/`.

## Installing

Once there is a release:

```bash
brew install DarkCollective/relix/relix
```

```powershell
scoop bucket add relix https://github.com/DarkCollective/scoop-relix
scoop install relix
```

Or download the archive for your platform from the
[releases](https://github.com/DarkCollective/relix-cli/releases), check it against
`checksums.txt`, unpack it and put its `bin/` on your `PATH`.

## Licence

[Apache License 2.0](LICENSE.txt). See [CONTRIBUTING.md](CONTRIBUTING.md) to take
Expand Down
Loading
Loading