Skip to content

Build ThorVG natives #2

Build ThorVG natives

Build ThorVG natives #2

Workflow file for this run

# Builds ThorVG from source, once per RID, and stages each library at the path it will occupy
# in this repository so the CD can merge the artifacts straight over the working tree.
#
# ThorVG publishes no binaries: every release carries a single source tarball. So unlike a
# binding that downloads official libraries, this repository has to compile its own, and the
# managed layer and the libraries must come from the same tag or the P/Invokes describe one
# revision while the binary is another.
name: Build ThorVG natives
on:
workflow_call:
inputs:
ref:
description: 'ThorVG tag to build'
required: false
type: string
default: 'v1.1.0'
workflow_dispatch:
inputs:
ref:
description: 'ThorVG tag to build'
required: false
type: string
default: 'v1.1.0'
only-rid:
description: 'Build a single RID (e.g. linux-arm64). Empty builds all four.'
required: false
type: string
default: ''
jobs:
build:
name: ${{ matrix.rid }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
rid: linux-x64
# A native ARM runner rather than a cross toolchain: meson cross-compilation needs a
# cross file, and ThorVG ships none for linux-arm64.
- os: ubuntu-24.04-arm
rid: linux-arm64
- os: windows-latest
rid: win-x64
- os: macos-latest
rid: osx-arm64
steps:
- name: Checkout ThorVG
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
uses: actions/checkout@v5
with:
repository: thorvg/thorvg
ref: ${{ inputs.ref || 'v1.1.0' }}
- name: Setup Python
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install meson and ninja
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
run: pip install meson ninja
# ThorVG's own Windows CI does this; meson needs cl.exe on PATH.
- name: Setup MSVC
if: (inputs.only-rid == '' || inputs.only-rid == matrix.rid) && startsWith(matrix.rid, 'win-')
uses: ilammy/msvc-dev-cmd@v1
# -Dbindings=capi is the flag that matters: it is OFF by default, and without it the
# library builds perfectly and exports not one tvg_* symbol.
# -Dextra drops openmp, which would otherwise pull a libomp/vcomp runtime alongside the
# library that the NuGet does not ship.
#
# Split by platform because bash on the Windows runner is Git bash, and it puts
# C:\Program Files\Git\usr\bin ahead of the MSVC toolchain — meson then finds GNU link.exe
# instead of MSVC's and refuses to configure.
- name: Configure and build (Unix)
if: (inputs.only-rid == '' || inputs.only-rid == matrix.rid) && !startsWith(matrix.rid, 'win-')
shell: bash
run: |
set -euo pipefail
meson setup build \
--buildtype=release \
-Ddefault_library=shared \
-Dbindings=capi \
-Dengines=cpu \
-Dloaders=all \
-Dsavers=all \
-Dextra=lottie_exp \
-Dstatic=false \
-Dlog=false \
-Dtools='' \
-Dtests=false
meson compile -C build
- name: Configure and build (Windows)
if: (inputs.only-rid == '' || inputs.only-rid == matrix.rid) && startsWith(matrix.rid, 'win-')
shell: pwsh
run: |
meson setup build --buildtype=release -Ddefault_library=shared -Dbindings=capi -Dengines=cpu -Dloaders=all -Dsavers=all -Dextra=lottie_exp -Dstatic=false -Dlog=false -Dtools='' -Dtests=false
if ($LASTEXITCODE -ne 0) { exit 1 }
meson compile -C build
if ($LASTEXITCODE -ne 0) { exit 1 }
# The library is named after the major version — thorvg-1.dll, libthorvg-1.so.1,
# libthorvg-1.1.dylib — and on Unix the plain name is a symlink, which does not survive
# NuGet packaging. Copy the real file under a stable name so DllImport("thorvg") keeps
# resolving when ThorVG goes to 2.x.
- name: Stage into the runtimes layout
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
shell: bash
run: |
set -euo pipefail
native="out/Evergine.Bindings.ThorVG/runtimes/${{ matrix.rid }}/native"
mkdir -p "$native"
case "${{ matrix.rid }}" in
win-*)
cp "$(find build -name 'thorvg*.dll' -print -quit)" "$native/thorvg.dll" ;;
linux-*)
cp "$(readlink -f "$(find build -name 'libthorvg*.so*' -print -quit)")" "$native/libthorvg.so" ;;
osx-*)
cp "$(readlink -f "$(find build -name 'libthorvg*.dylib' -print -quit)")" "$native/libthorvg.dylib" ;;
esac
find out -type f -exec ls -l {} \;
# Two ways this build fails without failing: -Dstatic or a missing -Ddefault_library=shared
# yields only an archive, and a missing -Dbindings=capi yields a library with no C API at
# all. Both would upload happily and be committed as a working platform.
- name: Verify the library is real and exports the C API
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
shell: bash
run: |
set -euo pipefail
lib=$(find out -type f | head -1)
size=$(stat -c%s "$lib" 2>/dev/null || stat -f%z "$lib")
echo "$lib: $size bytes"
[ "$size" -gt 300000 ] || { echo "::error::$lib is only $size bytes"; exit 1; }
# The symbol table is captured before it is searched rather than piped into grep -q:
# under `set -o pipefail`, grep -q exits at the first match, nm dies of SIGPIPE, and
# the pipeline reports failure precisely when the symbol WAS found.
case "${{ matrix.rid }}" in
linux-*) symbols=$(nm -D --defined-only "$lib" || true) ;;
osx-*) symbols=$(nm -gU "$lib" || true) ;;
# No dumpbin under bash and no strings on the Windows image; an export name is
# plain ASCII in the export table, so reading the file as text finds it.
win-*) symbols=$(grep -a -o 'tvg_engine_init' "$lib" || true) ;;
esac
case "$symbols" in
*tvg_engine_init*) echo "tvg_engine_init is exported." ;;
*) echo "::error::tvg_engine_init is not exported by $lib -- was -Dbindings=capi lost?"; exit 1 ;;
esac
- name: Upload
if: inputs.only-rid == '' || inputs.only-rid == matrix.rid
uses: actions/upload-artifact@v4
with:
name: natives-${{ matrix.rid }}
path: out/
# Not `warn`: an empty artifact merges over the tree as no change at all, and the
# publish would ship the previous libraries against a newer header.
if-no-files-found: error