8000 fix: usable archives on every platform; consumer-test every build (v3) by adityamukho · Pull Request #6 · project-minigraf/minigraf-c · GitHub
[go: up one dir, main page]

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
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
required: false
type: string
default: ''
version:
description: 'Version in the archive names (release.yml passes the release version)'
required: false
type: string
default: '0.0.0-ci'

jobs:
test:
Expand Down Expand Up @@ -64,3 +69,43 @@ jobs:
run: |
diff include/minigraf.h /tmp/minigraf_generated.h || \
(echo "minigraf.h is out of date — run cbindgen locally and commit the result" && exit 1)

package:
name: Package
uses: ./.github/workflows/package.yml
with:
ref: ${{ inputs.ref }}
version: ${{ inputs.version || '0.0.0-ci' }}

# Unpacks this platform's archive and compiles and runs the README quick
# start and the consumer contract against it, as a user would.
consumer:
name: Consumer test (${{ matrix.os }})
needs: package
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-14, windows-latest]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/download-artifact@v4
with:
pattern: dist-*
path: dist
- uses: ilammy/msvc-dev-cmd@v1
if: runner.os == 'Windows'
- name: Run consumer test
shell: bash
run: bash consumer-test/run.sh local dist ${{ inputs.version || '0.0.0-ci' }}

readme-sync:
name: README quick start matches the consumer test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- run: python3 -I consumer-test/readme_sync.py README.md consumer-test/quickstart.c
138 changes: 138 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: Package

# Builds the release archives exactly as the release publishes them and uploads
# each as dist-<platform>. Every archive has the same layout:
# <shared library> libminigraf.so | libminigraf.dylib | minigraf.dll
# minigraf.dll.lib Windows only: the import library MSVC links against
# include/minigraf.h
# LICENSE-MIT, LICENSE-APACHE

on:
workflow_call:
inputs:
ref:
required: false
type: string
default: ''
version:
required: true
type: string

jobs:
archive:
name: Build archive (${{ matrix.platform }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux targets use cargo-zigbuild to pin glibc 2.17 instead of the
# runner's, so RHEL 8, Debian 11 etc. can load the library.
- os: ubuntu-latest
platform: linux-x86_64
target: x86_64-unknown-linux-gnu
zig-target: x86_64-unknown-linux-gnu.2.17
- os: ubuntu-24.04-arm
platform: linux-aarch64
target: aarch64-unknown-linux-gnu
zig-target: aarch64-unknown-linux-gnu.2.17
- os: macos-14
platform: macos-universal2
target: universal2
- os: windows-latest
platform: windows-x86_64
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target == 'universal2' && 'aarch64-apple-darwin x86_64-apple-darwin' || matrix.target }}

- uses: actions/setup-python@v5
if: matrix.zig-target
with:
python-version: '3.x'

- name: Install zig and cargo-zigbuild
if: matrix.zig-target
run: pip install ziglang cargo-zigbuild

- name: Build (Linux, zigbuild)
if: matrix.zig-target
run: |
cargo zigbuild --release --lib --target ${{ matrix.zig-target }}
mkdir -p stage
cp target/${{ matrix.target }}/release/libminigraf.so stage/

- name: Check glibc floor
if: matrix.zig-target
run: |
MAX=$(objdump -T stage/libminigraf.so | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -1)
echo "Highest required glibc: $MAX"
if [ "$(printf '%s\n2.17\n' "$MAX" | sort -V | tail -1)" != "2.17" ]; then
echo "::error::libminigraf.so requires glibc $MAX, above the 2.17 floor"
exit 1
fi

# rustc would record the build directory as the dylib's install name, so
# programs linked against it look for the library on the CI runner.
# @rpath lets them find it through their own rpath, as the README says.
- name: Build (universal2)
if: matrix.target == 'universal2'
env:
RUSTFLAGS: -C link-arg=-Wl,-install_name,@rpath/libminigraf.dylib
run: |
cargo build --release --lib --target aarch64-apple-darwin
cargo build --release --lib --target x86_64-apple-darwin
mkdir -p stage
lipo -create \
target/aarch64-apple-darwin/release/libminigraf.dylib \
target/x86_64-apple-darwin/release/libminigraf.dylib \
-output stage/libminigraf.dylib
# The x86_64 slice cannot run on an arm64 runner; check it is there.
ARCHS=$(lipo -archs stage/libminigraf.dylib)
echo "archs: $ARCHS"
[[ " $ARCHS " == *" x86_64 "* && " $ARCHS " == *" arm64 "* ]] \
|| { echo "::error::universal2 dylib lacks a slice"; exit 1; }
ID=$(otool -D stage/libminigraf.dylib | tail -1)
echo "install name: $ID"
[ "$ID" = "@rpath/libminigraf.dylib" ] \
|| { echo "::error::install name is $ID, not @rpath/libminigraf.dylib"; exit 1; }

- name: Build (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
cargo build --release --lib --target ${{ matrix.target }}
mkdir -p stage
cp target/${{ matrix.target }}/release/minigraf.dll target/${{ matrix.target }}/release/minigraf.dll.lib stage/

- name: Stage header and licenses
shell: bash
run: |
mkdir -p stage/include
cp include/minigraf.h stage/include/
cp LICENSE-MIT LICENSE-APACHE stage/
find stage -type f | sort

- name: Archive (unix)
if: runner.os != 'Windows'
run: |
mkdir -p dist
tar czf "dist/minigraf-c-v${{ inputs.version }}-${{ matrix.platform }}.tar.gz" -C stage .

- name: Archive (windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force dist | Out-Null
Compress-Archive -Path stage\* -DestinationPath "dist\minigraf-c-v${{ inputs.version }}-${{ matrix.platform }}.zip"

- uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.platform }}
path: dist/*
if-no-files-found: error
112 changes: 29 additions & 83 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ jobs:
uses: ./.github/workflows/ci.yml
with:
ref: ${{ needs.prepare.outputs.sha }}
# The archives are built and consumer-tested here with the release
# version in their names; publish uploads exactly these files.
version: ${{ needs.prepare.outputs.semver }}

tag:
name: Push tag and update main
Expand Down Expand Up @@ -129,94 +132,37 @@ jobs:
--title "minigraf-c ${{ needs.prepare.outputs.tag }}" \
--notes "C bindings for minigraf ${{ needs.prepare.outputs.tag }}. Download the library for your platform below."

build:
name: Build C library (${{ matrix.os }})
needs: [prepare, create-release]
runs-on: ${{ matrix.os }}
publish:
name: Upload the tested archives to the release
needs: [prepare, test, create-release]
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact-name: linux-x86_64
lib-name: libminigraf.so
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
artifact-name: linux-aarch64
lib-name: libminigraf.so
- os: macos-14
target: universal2
artifact-name: macos-universal2
lib-name: libminigraf.dylib
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact-name: windows-x86_64
lib-name: minigraf.dll

steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.tag }}

- uses: dtolnay/rust-toolchain@stable
- uses: actions/download-artifact@v4
with:
targets: ${{ matrix.target == 'universal2' && 'aarch64-apple-darwin x86_64-apple-darwin' || matrix.target }}

- name: Build (universal2)
if: matrix.target == 'universal2'
run: |
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin
lipo -create \
target/aarch64-apple-darwin/release/libminigraf.dylib \
target/x86_64-apple-darwin/release/libminigraf.dylib \
-output ${{ matrix.lib-name }}

- name: Build (Linux)
if: runner.os == 'Linux'
run: |
cargo build --release --target ${{ matrix.target }}
cp target/${{ matrix.target }}/release/libminigraf.so ${{ matrix.lib-name }}

- name: Build (Windows)
if: runner.os == 'Windows'
run: |
cargo build --release --target ${{ matrix.target }}
copy target\${{ matrix.target }}\release\minigraf.dll ${{ matrix.lib-name }}
shell: pwsh
pattern: dist-*
path: dist
merge-multiple: true

- name: Package (unix)
if: runner.os != 'Windows'
run: |
tar czf minigraf-c-${{ needs.prepare.outputs.tag }}-${{ matrix.artifact-name }}.tar.gz \
${{ matrix.lib-name }} include/minigraf.h

- name: Package (windows)
if: runner.os == 'Windows'
run: |
Compress-Archive -Path ${{ matrix.lib-name }},include\minigraf.h `
-DestinationPath minigraf-c-${{ needs.prepare.outputs.tag }}-${{ matrix.artifact-name }}.zip
shell: pwsh

- name: Upload to GitHub Release (unix)
if: runner.os != 'Windows'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ needs.prepare.outputs.tag }}" \
minigraf-c-${{ needs.prepare.outputs.tag }}-${{ matrix.artifact-name }}.tar.gz \
--repo "$GITHUB_REPOSITORY" --clobber

- name: Upload to GitHub Release (windows)
if: runner.os == 'Windows'
- name: Upload to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
gh release upload "${{ needs.prepare.outputs.tag }}" `
"minigraf-c-${{ needs.prepare.outputs.tag }}-${{ matrix.artifact-name }}.zip" `
--repo "$GITHUB_REPOSITORY" --clobber
shell: pwsh

ls -l dist
[ "$(ls dist | wc -l)" -eq 4 ] || { echo "::error::expected 4 archives"; exit 1; }
gh release upload "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --clobber

# Downloads the published archives and runs the consumer test on every
# platform; on failure the run fails and a release-broken issue is opened.
verify-published:
name: Verify the published archives
needs: [prepare, publish]
permissions:
contents: read
issues: write
uses: ./.github/workflows/verify-published.yml
with:
version: ${{ needs.prepare.outputs.semver }}
ref: ${{ needs.prepare.outputs.tag }}
Loading
Loading
0