Files

306 lines
14 KiB
YAML

name: Build & Release
# Builds distributable artifacts for BOTH binaries this repo ships and, on
# release/** branches, publishes them as one pre-release with separate,
# clearly-named assets. Modeled on logos-basecamp's build.yml.
#
# logoscore — the tool to use. Its asset names and the binary inside its
# bundle are unchanged, because downstream release sets fetch it
# and expect `bin/logoscore`.
# logosctl — the merged CLI, still being validated. Released alongside so it
# can be tried, never in place of logoscore.
#
# Each tool builds from its own flake outputs (`cli-*` vs `ctl-*`), so an asset
# labelled logoscore contains logoscore and nothing else.
#
# Version stamping: `--version` is baked at build time from a VERSION
# file at the repo root (read by flake.nix). VERSION exists only on release/**
# branches — add it (e.g. `echo 0.2.0 > VERSION`) on the release branch and this
# job bakes that version in and uses it as the release tag. master builds fall
# back to `pre-release-<sha7>`, dirty local builds to `dev`; the commit hash
# (with a `-dirty` marker) is always reported.
#
# Linux (x86_64 + aarch64): build the AppImage, chmod +x, wrap in a tar.gz.
# macOS (aarch64): build the portable bundle dir (bin/ + lib/),
# strip the Gatekeeper quarantine flag the way
# Homebrew handles casks, wrap in a tar.gz.
# Windows (x86_64): cross-build the bundle dir on a Linux runner and
# zip it. Nix does not run on Windows, so there is
# no native leg here at all.
#
# Nix and the binary cache come from logos-co/setup-nix-cache-action, replacing
# three different setups this file used to carry (install-nix-action@v27,
# DeterminateSystems, and cachix/cachix-action pointed at the `logos-co` cachix).
#
# The swap is what makes the Windows leg viable, and it was measured rather than
# assumed. The mingw Qt/boost closure is primed in the Logos Attic cache
# (cache.nix.logos.co), NOT in cachix, so the first Windows run substituted
# nothing from it -- 0 paths from Attic against 612 from cache.nixos.org -- and
# spent 27 minutes rebuilding boost, libpng, sqlite and Qt from source before
# falling over inside boost.
#
# Reading needs no credentials: both Attic caches are public to read, and the
# action configures them as substituters unconditionally, so fork PRs pull too.
# PUBLISHING is separate and currently inactive here -- this repo has neither
# ATTIC_TOKEN_CI nor the public-cache environment that carries
# ATTIC_TOKEN_PUBLIC, and the action skips the push on an empty token rather
# than failing. Adding the repo secret turns publishing on with no edit here.
on:
push:
branches:
- master
- 'release/**'
pull_request:
branches: [master]
jobs:
build-appimage:
# ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master
# jobs must opt into it to publish to the public cache.
environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }}
strategy:
# Don't let a failure in the under-validation logosctl block a
# logoscore release.
fail-fast: false
matrix:
arch:
- { id: x86_64-linux, runner: ubuntu-latest }
- { id: aarch64-linux, runner: ubuntu-24.04-arm }
tool:
- { bin: logoscore, output: cli-appimage }
- { bin: logosctl, output: ctl-appimage }
runs-on: ${{ matrix.arch.runner }}
steps:
- uses: actions/checkout@v4
- uses: logos-co/setup-nix-cache-action@v1
with:
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }}
extra-nix-config: |
experimental-features = nix-command flakes
- name: Build ${{ matrix.tool.output }}
run: nix build .#${{ matrix.tool.output }} -L
- name: Smoke test (AppImage runs)
run: |
src=$(find result/ -name '*.AppImage' -print -quit)
out=$(APPIMAGE_EXTRACT_AND_RUN=1 "$src" --help 2>&1 || true)
echo "$out"
echo "$out" | grep -qi usage
- name: Package AppImage as tarball
run: |
arch="${{ matrix.arch.id }}"
arch_short="${arch%%-*}"
bin="${{ matrix.tool.bin }}"
src=$(find result/ -name '*.AppImage' -print -quit)
cp "$src" "${bin}-${arch_short}.AppImage"
chmod +x "${bin}-${arch_short}.AppImage"
tar -czf "${bin}-${arch_short}-linux.tar.gz" "${bin}-${arch_short}.AppImage"
- uses: actions/upload-artifact@v4
with:
name: appimage-${{ matrix.tool.bin }}-${{ matrix.arch.id }}
path: ${{ matrix.tool.bin }}-*-linux.tar.gz
build-macos-bundle:
# ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master
# jobs must opt into it to publish to the public cache.
environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }}
strategy:
fail-fast: false
matrix:
tool:
- { bin: logoscore, output: cli-bundle-dir }
- { bin: logosctl, output: ctl-bundle-dir }
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: logos-co/setup-nix-cache-action@v1
with:
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }}
- name: Build ${{ matrix.tool.output }}
run: nix build .#${{ matrix.tool.output }} -L
- name: Smoke test (bundled binary runs)
run: |
out=$(./result/bin/${{ matrix.tool.bin }} --help 2>&1 || true)
echo "$out"
echo "$out" | grep -qi usage
- name: Package bundle as tarball
run: |
dst="${{ matrix.tool.bin }}-aarch64-macos"
# Copy out of the read-only Nix store into a writable, archive-named dir.
rm -rf "$dst" && mkdir "$dst"
cp -RL result/. "$dst/"
chmod -R u+w "$dst"
# Strip the Gatekeeper quarantine attribute the way Homebrew's cask
# Quarantine.release! does. No-op on freshly built binaries (the Nix
# store carries none) but keeps the published artifact clean. The
# binaries are already validly ad-hoc signed by Nix — no re-signing.
/usr/bin/xattr -r -d com.apple.quarantine "$dst" 2>/dev/null || true
tar -czf "$dst.tar.gz" "$dst"
- uses: actions/upload-artifact@v4
with:
name: macos-${{ matrix.tool.bin }}-aarch64-darwin
path: ${{ matrix.tool.bin }}-aarch64-macos.tar.gz
# ────────────────────────────────────────────────────────────────────────
# Windows. A CROSS build on a Linux runner -- nix does not run on Windows, so
# there is no windows-latest runner here. Executing the result on real Windows
# is covered separately by logos-windows-ci.
#
# Same two tools and the same bundle-dir outputs the macOS job packages, so
# this is one more leg of an established shape rather than a new one.
build-windows-bundle:
strategy:
fail-fast: false
matrix:
tool:
- { bin: logoscore, output: cli-bundle-dir }
- { bin: logosctl, output: ctl-bundle-dir }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: logos-co/setup-nix-cache-action@v1
with:
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
- name: Build ${{ matrix.tool.output }} for Windows
run: nix build .#packages.x86_64-windows.${{ matrix.tool.output }} -L
# NO smoke test here, and that is deliberate rather than an omission: this
# runner cannot execute a PE. Running the binary is what logos-windows-ci's
# native-smoke leg does, on a real windows-latest runner. Asserting
# `--help` under wine would be a weaker claim dressed as the same one.
- name: Package bundle as a zip
run: |
set -euo pipefail
dst="${{ matrix.tool.bin }}-x86_64-windows"
src=$(readlink -f result)
staged=$(find -L "$src" -type f | wc -l | tr -d ' ')
[ "$staged" -gt 0 ] || { echo "::error::the bundle is empty"; exit 1; }
# -L DEREFERENCES. The bundle's DLLs are symlinks into the nix store,
# and an archive of dangling links extracts to a tree that cannot
# start. nix-bundle-lgx shipped exactly that: `cp -a` implied
# --no-dereference, the payload lost 75% of its files, and the step
# exited 0. The count check below is why that cannot recur quietly.
rm -rf "$dst" && mkdir "$dst"
cp -rL "$src"/. "$dst/"
chmod -R u+w "$dst"
zip -qr "$dst.zip" "$dst"
zipped=$(unzip -l "$dst.zip" | tail -1 | awk '{print $2}')
echo "bundle $staged file(s) -> zip $zipped entr(ies)"
if [ "$zipped" -lt "$staged" ]; then
echo "::error::the zip holds $zipped entries but the bundle has $staged files."
echo "::error::Something was not dereferenced; the extracted tree would be"
echo "::error::missing DLLs and could not start."
exit 1
fi
n_exe=$(find "$dst" -name '*.exe' | wc -l | tr -d ' ')
[ "$n_exe" -gt 0 ] || { echo "::error::no .exe in the bundle"; exit 1; }
echo "exe(s): $n_exe"
- uses: actions/upload-artifact@v4
with:
name: windows-${{ matrix.tool.bin }}-x86_64
path: ${{ matrix.tool.bin }}-x86_64-windows.zip
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release/')
needs: [build-appimage, build-macos-bundle, build-windows-bundle]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# The artifacts contain only the bundles; check out the release branch
# as well so the GitHub release tag matches the version baked into them.
- uses: actions/checkout@v4
# Collect every matrix artifact rather than naming each one, so adding
# or removing a tool does not mean editing a list here.
- uses: actions/download-artifact@v4
with:
path: artifacts/
merge-multiple: true
- name: Generate tag
id: tag
run: |
version="$(tr -d '\r\n' < VERSION)"
[ -n "$version" ] || { echo "::error::VERSION must not be empty"; exit 1; }
echo "tag=$version" >> "$GITHUB_OUTPUT"
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: Pre-release ${{ steps.tag.outputs.tag }}
prerelease: true
body: |
Two binaries ship from this release, and they share no state — so
installing `logosctl` cannot disturb a `logoscore` setup.
| | |
|---|---|
| **`logoscore`** | The tool to use. Commands, flags, config format and `~/.logoscore` unchanged. |
| **`logosctl`** | `logoscore` + `lgpd` + `lgpm` merged into one, with package management built in. New surface, own `~/.logosctl` session directory. **Being validated — not yet the default.** |
The instructions below install `logoscore`; swap the name throughout
to install `logosctl` instead.
## Install
### Linux
The artifact is an [AppImage](https://appimage.org/) — a single self-contained executable. Download it, extract, and drop it on your `PATH` (swap `x86_64` for `aarch64` on ARM):
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/logoscore-x86_64-linux.tar.gz
tar -xvf logoscore-x86_64-linux.tar.gz
mkdir -p ~/.local/bin
install -m755 logoscore-x86_64.AppImage ~/.local/bin/logoscore
# make sure ~/.local/bin is on your PATH (add this to ~/.bashrc or ~/.zshrc):
export PATH="$HOME/.local/bin:$PATH"
```
Now `logoscore` runs from anywhere. (If you hit a FUSE error, run it as `APPIMAGE_EXTRACT_AND_RUN=1 logoscore`.)
### macOS (Apple Silicon)
> **Download from the terminal with `wget` or `curl` — _not_ a web browser.** Browsers tag downloads with `com.apple.quarantine`, which makes Gatekeeper block this unsigned binary. Fetching over the terminal and extracting with `tar` never sets that flag, so the binary runs as-is — no signing or notarization needed.
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/logoscore-aarch64-macos.tar.gz
tar -xvf logoscore-aarch64-macos.tar.gz
# move the whole folder somewhere permanent (keep its contents together —
# the binary finds its libraries via ../lib) and put its bin/ on your PATH:
mv logoscore-aarch64-macos ~/.local/logoscore
echo 'export PATH="$HOME/.local/logoscore/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
```
Now `logoscore` runs from anywhere.
# NOTE: `files:` below is a LITERAL block scalar -- every line in it is
# a glob handed to the action, and a '#' line would be passed as a
# pattern rather than ignored. Commentary belongs out here.
#
# The Windows bundles are .zip because Explorer opens one and nothing
# on Windows opens a .tar.gz. `merge-multiple: true` above flattens
# every artifact into artifacts/, so a depth-1 glob reaches them.
files: |
artifacts/*.tar.gz
artifacts/*-windows.zip