8000 fix(readme): document the uniffi.minigraf_ffi package 2.x ships; consumer-test v2.x by adityamukho · Pull Request #11 · project-minigraf/minigraf-java · 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.
8000 Loading
Diff view
Diff view
156 changes: 155 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [main, 'v*.x']
pull_request:
workflow_call:
inputs:
Expand All @@ -11,6 +11,11 @@ on:
required: false
type: string
default: ''
version:
description: 'Version stamped into the jar (release.yml passes the release version)'
required: false
type: string
default: '0.0.0-ci'

jobs:
rust-test:
Expand Down Expand Up @@ -47,3 +52,152 @@ jobs:
- name: Run tests
working-directory: java
run: ./gradlew test

# Runs the Java tests on musl libc, so NativeLoader's musl detection and the
# linux-musl native are exercised at runtime rather than only built.
java-test-alpine:
name: Java tests (Alpine / musl)
runs-on: ubuntu-latest
container: eclipse-temurin:17-jdk-alpine
env:
# musl defaults to crt-static, under which rustc drops the cdylib output.
RUSTFLAGS: -C target-feature=-crt-static
steps:
- name: Install build dependencies
run: apk add --no-cache bash curl gcc git musl-dev

- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

# release.yml runs this workflow from main against the pinned commit, which
# for a v2.x core patch is on the v2.x branch. v2.x predates musl support
# (no musl detection in copyLocalNative or NativeLoader), so there is
# nothing to test on Alpine there.
- name: Check musl support in tested commit
id: musl
run: |
if grep -q 'ld-musl' java/build.gradle.kts; then
echo "supported=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::Tested commit has no musl support; skipping Alpine tests."
echo "supported=false" >> "$GITHUB_OUTPUT"
fi

- name: Install Rust
if: steps.musl.outputs.supported == 'true'
run: |
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

- name: Build Rust library (release)
if: steps.musl.outputs.supported == 'true'
run: cargo build --release

- name: Generate Kotlin bindings + copy local native
if: steps.musl.outputs.supported == 'true'
working-directory: java
run: |
./gradlew generateKotlinBindings copyLocalNative
# Fails if musl detection sent the native to the glibc directory.
test -f src/main/resources/natives/linux-musl/x86_64/libminigraf_ffi.so

- name: Run tests
if: steps.musl.outputs.supported == 'true'
working-directory: java
run: ./gradlew test

# Builds every native and the fat JAR, as the release publishes them.
# release.yml calls this workflow, so the jar is built and consumer-tested at
# the release version from the tested commit before anything is tagged.
package:
name: Package
uses: ./.github/workflows/package.yml
with:
ref: ${{ inputs.ref }}
version: ${{ inputs.version || '0.0.0-ci' }}

# A Gradle project that depends on the built jar by Maven coordinates runs
# the README quick start (Kotlin) and the consumer contract (Java) on every
# platform the jar ships a native for.
consumer:
name: Consumer test (${{ matrix.name }})
needs: package
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
include:
- { name: linux-x86_64, os: ubuntu-latest, java: '17', distribution: temurin }
- { name: linux-aarch64, os: ubuntu-24.04-arm, java: '17', distribution: temurin }
- { name: macos, os: macos-14, java: '17', distribution: temurin }
- { name: windows-x86_64, os: windows-latest, java: '17', distribution: temurin }
# Temurin has no Windows arm64 build of 17.
- { name: windows-aarch64, os: windows-11-arm, java: '17', distribution: microsoft }
# The image brings its JDK; musl, so the linux-musl native is loaded.
- { name: alpine, os: ubuntu-latest, container: 'eclipse-temurin:17-jdk-alpine' }
steps:
- name: Install bash and curl
if: matrix.container
run: apk add --no-cache bash curl

- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: actions/setup-java@v4
if: ${{ !matrix.container }}
with:
java-version: ${{ matrix.java }}
distribution: ${{ matrix.distribution }}

- uses: actions/download-artifact@v4
with:
name: dist-jar
path: dist-jar

- name: Run consumer test
shell: bash
run: bash consumer-test/run.sh local dist-jar ${{ inputs.version || '0.0.0-ci' }}

# 2.0.x patch releases build the v2.x branch with main's workflows
# (release.yml selects the base branch). On a PR to main, package and
# consumer-test v2.x with this PR's workflows, so a workflow change that
# breaks the v2.x release fails here rather than at release time.
package-v2x:
name: Package v2.x
if: github.event_name == 'pull_request' && github.base_ref == 'main'
uses: ./.github/workflows/package.yml
with:
ref: v2.x
version: 0.0.0-ci
artifact-prefix: v2x-

consumer-v2x:
name: Consumer test v2.x (linux-x86_64)
needs: package-v2x
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: v2.x
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: temurin
- uses: actions/download-artifact@v4
with:
name: v2x-dist-jar
path: dist-jar
- name: Run consumer test
run: bash consumer-test/run.sh local dist-jar 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/src/test/kotlin/QuickstartTest.kt
222 changes: 222 additions & 0 deletions .github/workflows/package.yml
B7E7
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
name: Package

# Builds the fat JAR exactly as the release publishes it, stamped with
# `version`, and uploads:
# native-<target> each platform's native library, copied into the jar;
# the release's publish job rebuilds the jar from these
# dist-jar the Maven repository layout (build/m2): jar, sources and
# javadoc jars, pom, module
# Unsigned: the release rebuilds from the same commit, checks the jars are
# byte-identical to the tested ones, then signs and publishes.
#
# release.yml also runs this, through ci.yml, for v2.x patch releases with
# `ref` on the v2.x branch, so it must build that branch's sources too.

on:
workflow_call:
inputs:
ref:
required: false
type: string
default: ''
version:
required: true
type: string
artifact-prefix:
description: 'Prefix for the uploaded artifact names, so one run can package two refs'
required: false
type: string
default: ''

jobs:
natives:
name: Build native (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux targets use cargo-zigbuild to pin an old glibc floor (2.17)
# instead of the runner's, so RHEL 8, Debian 11 etc. can load them.
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
zig-target: x86_64-unknown-linux-gnu.2.17
native-dir: linux/x86_64
lib-name: libminigraf_ffi.so
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
zig-target: aarch64-unknown-linux-gnu.2.17
native-dir: linux/aarch64
lib-name: libminigraf_ffi.so
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
zig-target: x86_64-unknown-linux-musl
native-dir: linux-musl/x86_64
lib-name: libminigraf_ffi.so
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
zig-target: aarch64-unknown-linux-musl
native-dir: linux-musl/aarch64
lib-name: libminigraf_ffi.so
- os: macos-14
target: universal2
native-dir: macos/universal
lib-name: libminigraf_ffi.dylib
- os: windows-latest
target: x86_64-pc-windows-msvc
native-dir: windows/x86_64
lib-name: minigraf_ffi.dll
- os: windows-latest
target: aarch64-pc-windows-msvc
native-dir: windows/aarch64
lib-name: minigraf_ffi.dll
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 (universal2)
if: matrix.target == 'universal2'
run: |
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin
mkdir -p out
lipo -create \
target/aarch64-apple-darwin/release/libminigraf_ffi.dylib \
target/x86_64-apple-darwin/release/libminigraf_ffi.dylib \
-output out/libminigraf_ffi.dylib

- name: Build (Linux, zigbuild)
if: matrix.zig-target
# musl defaults to crt-static, under which rustc drops the cdylib output.
env:
RUSTFLAGS: ${{ contains(matrix.target, 'musl') && '-C target-feature=-crt-static' || '' }}
run: |
cargo zigbuild --release --lib --target ${{ matrix.zig-target }}
mkdir -p out
cp target/${{ matrix.target }}/release/${{ matrix.lib-name }} out/

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

- name: Show dynamic dependencies
if: runner.os == 'Linux'
run: readelf -d out/${{ matrix.lib-name }} | grep NEEDED

- name: Check glibc floor
if: endsWith(matrix.target, '-linux-gnu')
run: |
MAX=$(objdump -T out/${{ matrix.lib-name }} | 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::${{ matrix.lib-name }} requires glibc $MAX, above the 2.17 floor"
exit 1
fi

- name: Upload native
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}native-${{ matrix.target }}
path: out/${{ matrix.lib-name }}
if-no-files-found: error

jar:
name: Assemble fat JAR
needs: natives
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: dtolnay/rust-toolchain@stable

- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Download all natives
uses: actions/download-artifact@v4
with:
pattern: ${{ inputs.artifact-prefix }}native-*
path: natives-staging
merge-multiple: false

- name: Copy natives into resources
run: |
RESOURCES=java/src/main/resources/natives
# <rust target>:<resource dir>; must match NativeLoader.resolve().
for pair in \
x86_64-unknown-linux-gnu:linux/x86_64 \
aarch64-unknown-linux-gnu:linux/aarch64 \
x86_64-unknown-linux-musl:linux-musl/x86_64 \
aarch64-unknown-linux-musl:linux-musl/aarch64 \
universal2:macos/universal \
x86_64-pc-windows-msvc:windows/x86_64 \
aarch64-pc-windows-msvc:windows/aarch64; do
target="${pair%%:*}"; dir="${pair#*:}"
mkdir -p "$RESOURCES/$dir"
cp "natives-staging/${{ inputs.artifact-prefix }}native-$target/"* "$RESOURCES/$dir/"
done
find "$RESOURCES" -type f | sort

- name: Build Rust library for bindgen
run: cargo build --release

- name: Build and publish to build/m2
working-directory: java
env:
RELEASE_VERSION: ${{ inputs.version }}
run: ./gradlew --no-daemon generateKotlinBindings publishReleasePublicationToDistRepository

- name: Check the jar contents
env:
VERSION: ${{ inputs.version }}
run: |
DIR=java/build/m2/io/github/project-minigraf/minigraf-jvm/$VERSION
JAR=$DIR/minigraf-jvm-$VERSION.jar
unzip -l "$JAR" | awk '{print $4}' | grep -q '/MiniGrafDb.class$' \
|| { echo "::error::the jar has no MiniGrafDb class"; exit 1; }
NATIVES=$(unzip -l "$JAR" | awk '{print $4}' | grep '^natives/.*\.\(so\|dylib\|dll\)$' | sort)
echo "$NATIVES"
[ "$(echo "$NATIVES" | wc -l)" -eq 7 ] || { echo "::error::expected 7 natives in the jar"; exit 1; }
grep -q '<artifactId>jna</artifactId>' "$DIR/minigraf-jvm-$VERSION.pom" \
|| { echo "::error::pom does not declare JNA"; exit 1; }

# The release rebuilds the jars from the same commit and refuses to
# publish unless they are byte-identical to these. Prove here, on every
# PR, that a clean rebuild is identical.
- name: Check the build is reproducible
working-directory: java
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
DIR=io/github/project-minigraf/minigraf-jvm/$RELEASE_VERSION
cp -r "build/m2/$DIR" "$RUNNER_TEMP/first"
./gradlew --no-daemon clean generateKotlinBindings publishReleasePublicationToDistRepository
diff -r "$RUNNER_TEMP/first" "build/m2/$DIR"

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-prefix }}dist-jar
path: java/build/m2
if-no-files-found: error
Loading
Loading
0