name: Release
on:
push:
tags: ["v*"]
workflow_dispatch:
jobs:
test:
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake nasm
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Tag matches Cargo.toml version
run: |
tag="${GITHUB_REF_NAME#v}"
crate=$(grep -m1 '^version = ' Cargo.toml | cut -d'"' -f2)
test "$tag" = "$crate" || { echo "::error::tag $tag != Cargo.toml $crate"; exit 1; }
- name: Test
run: cargo test --release
publish:
needs: test
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write contents: read
steps:
- uses: actions/checkout@v5
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake nasm
- uses: dtolnay/rust-toolchain@stable
- uses: rust-lang/crates-io-auth-action@v1
id: auth
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
npm:
needs: test
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write contents: read
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: 24 registry-url: https://registry.npmjs.org
- name: Publish @oximg/oximg
working-directory: npm
run: npm publish --access public
github-release:
needs: test
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- name: Extract CHANGELOG section
run: |
ver="${GITHUB_REF_NAME#v}"
awk -v v="$ver" '
$0 ~ ("^## \\[" v "\\]") { grab=1; next }
grab && /^## \[/ { exit }
grab { print }
' CHANGELOG.md > notes.md
if [ ! -s notes.md ]; then
echo "See [CHANGELOG.md](https://github.com/oximg/oximg/blob/main/CHANGELOG.md)." > notes.md
fi
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --title "oximg ${GITHUB_REF_NAME#v}" --notes-file notes.md
binaries:
needs: github-release
if: github.ref_type == 'tag'
strategy:
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: macos-15
target: aarch64-apple-darwin
runs-on: ${{ matrix.runner }}
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- name: Install build dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake nasm
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build
run: cargo build --release --locked
env:
RUSTFLAGS: ${{ runner.os == 'Linux' && '-C link-arg=-static-libstdc++ -C link-arg=-static-libgcc' || '' }}
- name: Smoke test the artifact
run: |
set -euo pipefail
bin=target/release/oximg
"$bin" --version
"$bin" resize tests/fixtures/photo.jpg 100 100 /tmp/smoke.webp
"$bin" probe /tmp/smoke.webp | grep -q image/webp
echo "ok: resize + probe round-trip"
- name: Package and upload
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
name="oximg-${GITHUB_REF_NAME}-${{ matrix.target }}"
if [ "${{ runner.os }}" = "macOS" ]; then
strip -Sx target/release/oximg
else
strip target/release/oximg
fi
mkdir "$name"
cp target/release/oximg LICENSE README.md THIRD-PARTY-LICENSES.md "$name/"
tar czf "$name.tar.gz" "$name"
shasum -a 256 "$name.tar.gz" > "$name.tar.gz.sha256"
gh release upload "$GITHUB_REF_NAME" "$name.tar.gz" "$name.tar.gz.sha256" \
--repo "$GITHUB_REPOSITORY" --clobber
binaries-musl:
needs: github-release
if: github.ref_type == 'tag'
strategy:
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
runs-on: ${{ matrix.runner }}
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- name: Build in Alpine
run: |
docker run --rm -v "$PWD":/src -w /src rust:alpine sh -c '
apk add --no-cache build-base cmake nasm >/dev/null &&
cargo build --release --locked &&
strip target/release/oximg'
# The container builds as root into the mounted workspace, so
# every artifact it leaves behind is root-owned. Hand the
# whole tree back before any host-side step touches it.
sudo chown -R "$(id -u):$(id -g)" target
- name: Smoke test the artifact
run: |
set -euo pipefail
docker run --rm -v "$PWD":/src -w /src alpine:latest sh -c '
set -e
./target/release/oximg --version
./target/release/oximg resize tests/fixtures/photo.jpg 100 100 /tmp/smoke.webp
./target/release/oximg probe /tmp/smoke.webp | grep -q image/webp'
echo "ok: runs on bare Alpine, no runtime dependencies"
- name: Package and upload
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
name="oximg-${GITHUB_REF_NAME}-${{ matrix.target }}"
mkdir "$name"
cp target/release/oximg LICENSE README.md THIRD-PARTY-LICENSES.md "$name/"
tar czf "$name.tar.gz" "$name"
shasum -a 256 "$name.tar.gz" > "$name.tar.gz.sha256"
gh release upload "$GITHUB_REF_NAME" "$name.tar.gz" "$name.tar.gz.sha256" \
--repo "$GITHUB_REPOSITORY" --clobber
rubygem:
needs: [binaries, binaries-musl]
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write contents: read steps:
- uses: actions/checkout@v5
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
- name: Tag matches the gem version
run: |
tag="${GITHUB_REF_NAME#v}"
gem_version=$(grep -m1 'VERSION = ' rubygem/oximg/lib/oximg/version.rb | cut -d'"' -f2)
test "$tag" = "$gem_version" || { echo "::error::tag $tag != gem $gem_version"; exit 1; }
- name: Test
working-directory: rubygem/oximg
run: |
bundle install --jobs 4
bundle exec rake test
- name: Download the release binaries
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir -p dist
gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" \
--pattern 'oximg-*.tar.gz' --dir dist
- name: Build the gems
working-directory: rubygem/oximg
run: |
set -euo pipefail
dist="$GITHUB_WORKSPACE/dist"
mkdir -p exe && rm -f exe/oximg
# Plain-Ruby gem first, while exe/ is still empty.
gem build oximg.gemspec -o "$dist/oximg-ruby.gem"
# The Linux platforms are spelled out per libc: an
# unqualified `x86_64-linux` gem installs on musl too, where a
# glibc binary cannot run. RubyGems reports `x86_64-linux-musl`
# as the local platform on Alpine, so these are the names
# bundler matches against.
for pair in x86_64-unknown-linux-gnu:x86_64-linux-gnu \
aarch64-unknown-linux-gnu:aarch64-linux-gnu \
x86_64-unknown-linux-musl:x86_64-linux-musl \
aarch64-unknown-linux-musl:aarch64-linux-musl \
aarch64-apple-darwin:arm64-darwin; do
target="${pair%%:*}"
platform="${pair##*:}"
name="oximg-${GITHUB_REF_NAME}-${target}"
tar xzf "$dist/$name.tar.gz" -C "$dist"
install -m 0755 "$dist/$name/oximg" exe/oximg
OXIMG_GEM_PLATFORM="$platform" gem build oximg.gemspec -o "$dist/oximg-$platform.gem"
done
rm -f exe/oximg
- uses: rubygems/configure-rubygems-credentials@v2.1.0
- name: Publish
run: |
set -euo pipefail
for gem in "$GITHUB_WORKSPACE"/dist/oximg-*.gem; do
gem push "$gem"
done
rubygem-rails:
needs: rubygem
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v5
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
- name: Tag matches the gem version
run: |
tag="${GITHUB_REF_NAME#v}"
gem_version=$(grep -m1 'VERSION = ' rubygem/oximg-rails/lib/oximg/rails/version.rb | cut -d'"' -f2)
test "$tag" = "$gem_version" || { echo "::error::tag $tag != gem $gem_version"; exit 1; }
- name: Test
working-directory: rubygem/oximg-rails
run: |
bundle install --jobs 4
bundle exec rake test
- name: Build
working-directory: rubygem/oximg-rails
run: gem build oximg-rails.gemspec -o oximg-rails.gem
- uses: rubygems/configure-rubygems-credentials@v2.1.0
- name: Publish
run: gem push rubygem/oximg-rails/oximg-rails.gem