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
167 changes: 167 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: Release

# Builds a prebuilt binary for every supported platform on a `v*` tag and attaches the archives to
# the GitHub release. This exists so that trying Oxidiris does not require a Rust toolchain — see
# OXD-077. `workflow_dispatch` runs the same build matrix but publishes nothing, which is how you
# smoke-test a change to this file without cutting a tag.

on:
push:
tags: ["v*"]
workflow_dispatch:

permissions:
contents: write

env:
CARGO_TERM_COLOR: always

jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# musl rather than gnu on Linux: every dependency in the tree is pure Rust, so a musl
# build is fully static and runs on any distribution regardless of its glibc version.
# A gnu build would silently bind to the builder's glibc and fail on older systems.
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
cross: false
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
cross: true
# macos-latest is arm64; the Apple toolchain cross-compiles to x86_64 without extra setup.
- target: x86_64-apple-darwin
os: macos-latest
cross: false
- target: aarch64-apple-darwin
os: macos-latest
cross: false
- target: x86_64-pc-windows-msvc
os: windows-latest
cross: false

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}

# The musl linker is not present on the GitHub runner image by default.
- name: Install the musl toolchain
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y musl-tools

# aarch64 musl needs a cross linker that apt does not provide in a usable form; `cross` runs
# the build inside an image that already has one.
- name: Install cross
if: matrix.cross
run: cargo install cross --locked

- name: Build
shell: bash
run: |
tool=cargo
if [ "${{ matrix.cross }}" = "true" ]; then tool=cross; fi
"$tool" build --release --locked -p oxidiris --target ${{ matrix.target }}

# The archive holds a top-level directory rather than a bare binary so that unpacking it in a
# working directory cannot overwrite the user's own README or LICENCE. GPL-3.0 requires the
# licence to travel with the binary, so it ships inside the archive.
- name: Package (tar.gz)
if: runner.os != 'Windows'
shell: bash
run: |
stage="oxidiris-${{ matrix.target }}"
mkdir "$stage"
cp "target/${{ matrix.target }}/release/oxidiris" "$stage/"
cp LICENSE README.md CHANGELOG.md "$stage/"
tar -czf "$stage.tar.gz" "$stage"

- name: Package (zip)
if: runner.os == 'Windows'
shell: pwsh
run: |
$stage = "oxidiris-${{ matrix.target }}"
New-Item -ItemType Directory -Path $stage | Out-Null
Copy-Item "target/${{ matrix.target }}/release/oxidiris.exe" $stage
Copy-Item LICENSE, README.md, CHANGELOG.md $stage
Compress-Archive -Path $stage -DestinationPath "$stage.zip"

- uses: actions/upload-artifact@v4
with:
name: oxidiris-${{ matrix.target }}
path: |
oxidiris-${{ matrix.target }}.tar.gz
oxidiris-${{ matrix.target }}.zip
if-no-files-found: error
retention-days: 7

publish:
name: Publish the release
needs: build
runs-on: ubuntu-latest
# workflow_dispatch exercises the matrix above but must never create a release.
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4

# v0.1.0 shipped a manifest claim that did not match reality and only CI caught it. The same
# class of mistake — tagging v0.3.0 while Cargo.toml still says 0.2.0 — is cheap to rule out
# here, before any asset is published under the wrong name.
- name: Verify the tag matches the manifest version
run: |
version=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
tag="${GITHUB_REF_NAME#v}"
if [ "$version" != "$tag" ]; then
echo "tag v$tag does not match the workspace version $version in Cargo.toml"
exit 1
fi

- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- name: Checksum every archive
working-directory: dist
run: sha256sum * > SHA256SUMS

- name: Create or update the release
env:
GH_TOKEN: ${{ github.token }}
run: |
tag="$GITHUB_REF_NAME"
notes=$(cat <<'EOF'
Prebuilt binaries — no Rust toolchain required. See CHANGELOG.md for what changed.

**Linux (x86_64)**
```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-x86_64-unknown-linux-musl.tar.gz | tar -xz
sudo install -m755 oxidiris-x86_64-unknown-linux-musl/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-x86_64-unknown-linux-musl
```

**macOS (Apple Silicon)**
```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-aarch64-apple-darwin.tar.gz | tar -xz
sudo install -m755 oxidiris-aarch64-apple-darwin/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-aarch64-apple-darwin
```

Other platforms and the `SHA256SUMS` file are attached below.
EOF
)
if gh release view "$tag" >/dev/null 2>&1; then
gh release upload "$tag" dist/* --clobber
else
gh release create "$tag" --title "$tag" --notes "$notes" dist/*
fi
25 changes: 22 additions & 3 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1139,12 +1139,31 @@ Acceptance

Phase 7 · Depends OXD-003 · Crate repo · Spec §9.5 · Size M

Scope — `cargo-dist` for cross-platform binary builds · publish to crates.io · Homebrew tap · AUR · Nix flake · `cargo-binstall`.
Scope — cross-platform binary builds · publish to crates.io · Homebrew tap · AUR · Nix flake · `cargo-binstall`.

Acceptance
- [ ] Automated release workflow on tag push
- [ ] Binaries built for Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64)
- [x] Automated release workflow on tag push
- [x] Binaries built for Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64)
- [ ] `cargo install oxidiris` works from crates.io
- [ ] Homebrew tap serving the release archives
- [ ] AUR, Nix flake, `cargo-binstall`

Partially done. `.github/workflows/release.yml` covers the first two criteria; the task stays Todo
until distribution channels exist.

The binary builds are hand-rolled rather than delegated to `cargo-dist`, which the original scope
named. Reasons: the matrix is five targets of a single binary with no C dependencies, so the
generated config would be larger than the workflow it replaces; and the archive names are load
bearing for the README install commands and the future Homebrew formula, so they are worth owning
directly. Revisit if the target list grows or an installer script is wanted.

Linux targets musl, not gnu. Every dependency in the tree is pure Rust, so the musl build is fully
static (verified: 2.2 MB x86_64, 1.9 MB aarch64, `ldd` reports "statically linked") and runs on any
distribution. A gnu build would bind to the runner's glibc and fail on older systems.

Docker was considered as the first distribution channel and rejected. Oxidiris is a full-screen TUI
that reads a file from the user's working directory, so a container adds TTY and volume-mount
friction to every invocation, and on macOS it would demand a VM to run a 2 MB static binary.

---

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the
Phase 3 is not finished: `OXD-033` (`<n>%` jump) and `OXD-035` (ramp-up on resume) remain. Both are
small; see [`BACKLOG.md`](BACKLOG.md).

### Added

- **Prebuilt binaries on every tagged release.** A `Release` workflow builds `oxidiris` for Linux
(x86_64, aarch64), macOS (x86_64, aarch64) and Windows (x86_64) on a `v*` tag, and attaches the
archives plus a `SHA256SUMS` file to the GitHub release. Trying the reader no longer requires a
Rust toolchain. Part of `OXD-077`.
- The Linux builds target musl and are statically linked, so one archive runs on any distribution
rather than binding to the builder's glibc.
- The release job refuses to publish when the tag does not match the workspace version in
`Cargo.toml`. v0.1.0 shipped a manifest claim that did not match reality; this makes the
equivalent mistake impossible to tag.
- README now carries a complete copy-paste install block per platform, plus the `cargo install
--git` route for people who already have Rust.

## [0.2.0] - 2026-09-02

Phase 3 lands the context features (`OXD-030`…`OXD-036`). They are not extras: RSVP works by
Expand Down
77 changes: 75 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,85 @@ paper carefully, and it does not advertise a WPM number as an achievement.

## Install

### Prebuilt binary

No Rust toolchain required. Every release carries a binary for the platforms below; the Linux
builds are statically linked against musl, so they run on any distribution regardless of its glibc
version. Each block is complete — paste it, and you have `oxidiris` on your `PATH`.

**Linux, x86_64**

```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-x86_64-unknown-linux-musl.tar.gz | tar -xz
sudo install -m755 oxidiris-x86_64-unknown-linux-musl/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-x86_64-unknown-linux-musl
```

**Linux, aarch64**

```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-aarch64-unknown-linux-musl.tar.gz | tar -xz
sudo install -m755 oxidiris-aarch64-unknown-linux-musl/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-aarch64-unknown-linux-musl
```

**macOS, Apple Silicon**

```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-aarch64-apple-darwin.tar.gz | tar -xz
sudo install -m755 oxidiris-aarch64-apple-darwin/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-aarch64-apple-darwin
```

**macOS, Intel**

```sh
curl -fsSL https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-x86_64-apple-darwin.tar.gz | tar -xz
sudo install -m755 oxidiris-x86_64-apple-darwin/oxidiris /usr/local/bin/oxidiris
rm -rf oxidiris-x86_64-apple-darwin
```

The macOS binaries are unsigned. Downloading with `curl` as above is fine, because Gatekeeper only
quarantines what a browser or Finder wrote. If you fetched the archive in a browser instead, clear
the flag once: `xattr -d com.apple.quarantine /usr/local/bin/oxidiris`.

**Windows, x86_64** (PowerShell)

```powershell
$dst = "$env:LOCALAPPDATA\Programs\oxidiris"
Invoke-WebRequest https://github.com/AlexanderSlokov/oxidiris/releases/latest/download/oxidiris-x86_64-pc-windows-msvc.zip -OutFile oxidiris.zip
Expand-Archive oxidiris.zip -DestinationPath $dst -Force
Move-Item "$dst\oxidiris-x86_64-pc-windows-msvc\oxidiris.exe" $dst -Force
Remove-Item oxidiris.zip, "$dst\oxidiris-x86_64-pc-windows-msvc" -Recurse
[Environment]::SetEnvironmentVariable("Path", "$([Environment]::GetEnvironmentVariable('Path','User'));$dst", "User")
```

Open a new terminal afterward so the `PATH` change takes effect.

**Check it worked**, on any platform:

```sh
cargo install --path crates/oxidiris
oxidiris --version
```

A `SHA256SUMS` file is attached to every release if you want to verify the download.

### From source

Requires Rust 1.88 or newer.

```sh
cargo install --git https://github.com/AlexanderSlokov/oxidiris oxidiris --locked
```

From a clone of this repository:

```sh
cargo install --path crates/oxidiris --locked
```

A Homebrew tap and a crates.io publish are tracked as `OXD-077` in [`BACKLOG.md`](BACKLOG.md).

## Use

```sh
Expand Down Expand Up @@ -144,7 +217,7 @@ The workspace is two crates:
- **`oxidiris`** — the terminal application.

Design notes live in [`docs/informations/proposals.md`](docs/informations/proposals.md); decisions
that departed from it are recorded in [`docs/decisions/`](docs/decisions/) and back-annotated into
that departed from it are recorded in [`docs/decisions/`](docs/decisions) and back-annotated into
the spec.

## Licence
Expand Down
Loading