Skip to content

MacBinaries

MacBinaries #116

Workflow file for this run

# Builds the macOS viewer library and its self-contained runtime bundle. There is no Mac on this
# desk, so the runner is the build machine -- there is no hand-built fallback for this platform.
# Windows is still built by hand (deps/build.bat). Linux has both: LinuxBinaries.yml is the CI
# route, and deps/build.sh under WSL stays as the backup, producing the same two archives.
#
# TWO ARCHITECTURES, TWO JOBS. Homebrew bottles are per-arch and Qt/VTK are not universal binaries,
# so arm64 and x86_64 are separate builds producing separate assets. GitHub retired macos-13 in
# December 2025; macos-15-intel is the standard Intel runner label that replaces it.
#
# BUILD ON THE OLDEST OS YOU SUPPORT. Homebrew bottles are poured per macOS release, so a bundle
# staged on a newer macOS may not load on an older one. The runner labels below are therefore an
# explicit deployment choice, not incidental -- do not change them to macos-latest without meaning
# to move that target as GitHub updates the alias.
#
# THE TWO ARCHES DO NOT REACH THE SAME FLOOR, and the reason is GitHub's runner catalogue, not us:
# arm64 -- macos-14 is the OLDEST standard image still offered, so the bundle's floor is 14.x.
# x86_64 -- the only standard Intel labels are macos-15-intel and macos-26-intel. There is NO
# free macOS 14 Intel runner (macos-14-large is a LARGER, billed runner), so this leg
# stays on 15 and its bundle's real floor is 15.x, whatever deps/CMakeLists.txt
# declares. Our own objects carry the declared 14.0 minimum; the Homebrew VTK/Qt/TBB
# dylibs staged beside them carry the floor of the release their bottle was poured for,
# and that is the binding one.
# Lowering the Intel floor needs either a billed larger runner or Qt+VTK built from source. Do not
# "fix" the asymmetry by pointing x86_64 at a label that is not in the standard free set.
#
# NOTHING IS PUBLISHED AUTOMATICALLY. A push builds and verifies, and leaves the archives as
# workflow artifacts. Uploading to a release happens only on a manual run that explicitly asks for
# it, because a release asset is user-facing: `rolling` re-uploads the small library archive to the
# fixed dll-latest tag (the daily cadence), `full` also re-uploads the ~200 MB bundle to the tag
# pinned in deps/RUNTIME_VERSION (the rare one -- see the two-cadence contract at the top of
# deps/build.jl).
name: MacBinaries
on:
push:
branches: [master]
paths:
- 'deps/src/**'
- 'deps/CMakeLists.txt'
- 'deps/cmake/**'
- 'deps/build_mac.sh'
- '.github/workflows/MacBinaries.yml'
pull_request:
paths:
- 'deps/src/**'
- 'deps/CMakeLists.txt'
- 'deps/cmake/**'
- 'deps/build_mac.sh'
workflow_dispatch:
inputs:
publish:
description: 'What to upload to GitHub Releases'
type: choice
default: 'none'
options: ['none', 'rolling', 'full']
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
jobs:
build:
name: macOS ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
permissions:
contents: write # needed only by the publish step; harmless otherwise
strategy:
fail-fast: false
matrix:
include:
- os: macos-14 # Apple Silicon -- oldest standard arm64 image; sets the 14.x floor
arch: arm64
julia_arch: aarch64
- os: macos-15-intel # no free macOS 14 Intel runner exists; see the header
arch: x86_64
julia_arch: x64
steps:
- uses: actions/checkout@v6
# NOT a job-level `env:`. The job-level env context is evaluated before a runner is picked, so
# `runner.*` is unavailable there and GitHub rejects the whole file with "Unrecognized
# named-value: 'runner'" -- the workflow then never runs at all, and never reports why.
# $RUNNER_TEMP is the plain environment variable, always set on the runner itself.
- name: Stage directory
run: echo "IGMT_MACOS_STAGE=$RUNNER_TEMP/igmt" >> "$GITHUB_ENV"
# `vtk` is already built with Qt6 (-DVTK_GROUP_ENABLE_Qt=YES) -- the `--with-qt` option that
# older documentation mentions was removed from Homebrew in 2019 and is a hard error today.
# It pulls qtbase+qtdeclarative only, so qttools (which provides Qt6::UiTools, used by every
# runtime-loaded .ui dialog) and tbb have to be named explicitly.
- name: Install dependencies
run: brew install vtk qttools tbb ninja
- name: Build + stage the bundle
run: bash deps/build_mac.sh
# The real test of a relocated bundle: does dyld actually load it somewhere other than where
# it was staged, with no Homebrew paths left to fall back on? A compile that succeeded proves
# nothing about install names or the ad-hoc signature (on arm64 a bad signature is a hard
# load failure, not a warning). No window is created here -- dlopen + dlsym only -- so this
# works on a runner with no display.
- uses: julia-actions/setup-julia@v3
with:
version: '1.11'
arch: ${{ matrix.julia_arch }}
- name: Smoke-test the relocated library
run: |
set -euo pipefail
probe="${RUNNER_TEMP}/probe"
mkdir -p "$probe"
tar -xzf "${IGMT_MACOS_STAGE}/iGMT-macos-${{ matrix.arch }}-full.tar.gz" -C "$probe"
julia -e '
using Libdl
lib = joinpath(ENV["PROBE"], "deps", "build", "libgmtvtk.dylib")
h = Libdl.dlopen(lib)
p = Libdl.dlsym(h, :gmtvtk_abi_version)
println("ABI generation: ", ccall(p, Cint, ()))
'
env:
PROBE: ${{ runner.temp }}/probe
- uses: actions/upload-artifact@v4
with:
name: gmtvtk-macos-${{ matrix.arch }}
path: ${{ runner.temp }}/igmt/*.tar.gz
if-no-files-found: error
# Manual runs only, and only when the run explicitly asked for it. `--clobber` is the point of
# the rolling asset: one fixed tag whose file is replaced in place, never a new tag per build.
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.publish != 'none'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARCH: ${{ matrix.arch }}
PUBLISH: ${{ inputs.publish }}
run: |
set -euo pipefail
gh release upload dll-latest "${IGMT_MACOS_STAGE}/gmtvtk-macos-${ARCH}.tar.gz" --clobber
if [[ $PUBLISH == full ]]; then
tag=$(tr -d '[:space:]' < deps/RUNTIME_VERSION)
gh release upload "$tag" "${IGMT_MACOS_STAGE}/iGMT-macos-${ARCH}-full.tar.gz" --clobber
fi