build: add macOS universal libmpv.dylib CI build

Vendor ci/build-libmpv-macos.sh, which builds every dependency statically
for the native runner architecture and links them into a single
self-contained libmpv dylib (coreaudio output, videotoolbox hardware
decode, OpenGL render API; no vulkan/cocoa player). build-macos.yml runs
it on macos-14 (x86_64) and macos-15 (arm64) in parallel and a merge job
combines the two dylibs into one universal libmpv.dylib with lipo, matching
the file name jlibmpv expects on macOS.
This commit is contained in:
david
2026-10-01 00:36:24 +02:00
parent ca0a5de2a9
commit 4b385cd92f
4 changed files with 424 additions and 4 deletions
+94
View File
@@ -0,0 +1,94 @@
name: build-macos-arch
# Builds the self-contained libmpv dylib for one macOS architecture
# (x86_64 on macos-14, arm64 on macos-15) using the vendored build script.
# The merge job in build-macos.yml combines the per-architecture dylibs into
# a universal binary.
on:
workflow_call:
inputs:
arch:
description: Target architecture (x86_64 or arm64)
required: true
type: string
runner:
description: GitHub runner image providing that architecture
required: true
type: string
env:
MPV_REF: v0.41.0
ARCH: ${{ inputs.arch }}
jobs:
build:
runs-on: ${{ inputs.runner }}
env:
CCACHE_BASEDIR: ${{ github.workspace }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 500M
steps:
- name: Check out mpv-natives (build scripts)
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Check out mpv (pinned tag)
uses: actions/checkout@v4
with:
repository: mpv-player/mpv
ref: ${{ env.MPV_REF }}
path: mpv
fetch-depth: 1
- name: Get time
id: get_time
run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ${{ env.CCACHE_DIR }}
key: macos-${{ env.ARCH }}-${{ steps.get_time.outputs.timestamp }}
restore-keys: macos-${{ env.ARCH }}-
- name: Install dependencies
run: |
brew install ccache nasm ninja
python3 -m pip install --user meson
export PATH="$HOME/Library/Python/3.12/bin:$PATH"
# export so meson finds pip-installed tools
echo "$HOME/Library/Python/3.12/bin" >> "$GITHUB_PATH"
# Run the vendored build script from inside the mpv source tree, so the
# native build writes macos_prefix/ and the meson build next to mpv's
# meson.build and produces the self-contained dylib in mpv/artifact/.
- name: Build self-contained libmpv
working-directory: mpv
run: bash "${{ github.workspace }}/ci/build-libmpv-macos.sh"
- name: Print meson log on failure
if: failure()
run: cat mpv/macos_build/meson-logs/meson-log.txt
- name: Collect artifact
run: |
mkdir -p "artifact-macos-${{ env.ARCH }}"
cp -pv mpv/artifact/libmpv-2.dylib "artifact-macos-${{ env.ARCH }}/"
cp -pv mpv/artifact/*.h "artifact-macos-${{ env.ARCH }}/" 2>/dev/null || true
echo "=== contents ==="
ls -l "artifact-macos-${{ env.ARCH }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: mpv-natives-macos-${{ env.ARCH }}-mpv-${{ env.MPV_REF }}
path: artifact-macos-${{ env.ARCH }}
- name: Save ccache
uses: actions/cache/save@v4
if: always()
with:
path: ${{ env.CCACHE_DIR }}
key: macos-${{ env.ARCH }}-${{ steps.get_time.outputs.timestamp }}
+78
View File
@@ -0,0 +1,78 @@
name: build-macos
# Builds a universal (arm64 + x86_64) self-contained libmpv.dylib for macOS
# from mpv source pinned to a release tag. Two native builds run in parallel
# (x86_64 on macos-14, arm64 on macos-15); a merge job combines the two dylibs
# with lipo into one universal library and uploads it.
#
# Every dependency is built static and linked into the one dylib (same
# approach as the Windows build). jlibmpv expects the file name
# `libmpv.dylib` (no -2) and one universal binary for both architectures.
#
# Pinned to mpv v0.41.0 — the version whose client API (131077) the jlibmpv
# JNA bindings are validated against. Bump the ref here for a libmpv update.
# The build recipe lives in ci/build-libmpv-macos.sh in this repository.
on:
push:
branches: [main]
workflow_dispatch:
env:
MPV_REF: v0.41.0
jobs:
build-x86_64:
uses: ./.github/workflows/build-macos-arch.yml
with:
arch: x86_64
runner: macos-14
build-arm64:
uses: ./.github/workflows/build-macos-arch.yml
with:
arch: arm64
runner: macos-15
merge:
needs: [build-x86_64, build-arm64]
runs-on: macos-15
steps:
- name: Check out mpv-natives (build scripts)
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Check out mpv (pinned tag)
uses: actions/checkout@v4
with:
repository: mpv-player/mpv
ref: ${{ env.MPV_REF }}
path: mpv
fetch-depth: 1
- name: Download architecture artifacts
uses: actions/download-artifact@v4
with:
pattern: mpv-natives-macos-${{ env.MPV_REF }}-*
path: arch
merge-multiple: true
- name: Create universal dylib
run: |
mkdir -p artifact-libmpv
lipo -create \
"arch/x86_64/libmpv-2.dylib" \
"arch/arm64/libmpv-2.dylib" \
-output artifact-libmpv/libmpv.dylib
lipo -info artifact-libmpv/libmpv.dylib
cp -pv mpv/mpvrender.h mpv/mpv/client.h mpv/ebml_types.h artifact-libmpv/ 2>/dev/null || true
sha256sum artifact-libmpv/libmpv.dylib > artifact-libmpv/checksums.txt
cat artifact-libmpv/checksums.txt
ls -l artifact-libmpv
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: mpv-natives-macos-universal-mpv-${{ env.MPV_REF }}
path: artifact-libmpv
+4 -4
View File
@@ -14,9 +14,9 @@ here, as versioned GitHub release assets.
jlibmpv JNA bindings are validated against. A libmpv update means bumping
`MPV_REF` in the workflows.
- **Build source:** the mpv source tree is checked out at that tag by
GitHub Actions and built with mpv's own CI scripts
(`ci/build-mingw64.sh`, ...). No mpv source or third-party binaries are
stored in this repository.
GitHub Actions and built with the vendored scripts in `ci/`
(`build-libmpv-mingw64.sh`, `build-libmpv-macos.sh`). No mpv source or
third-party binaries are stored in this repository.
## Releases
@@ -26,7 +26,7 @@ single file) and a `checksums.txt` (SHA-256) for verification.
| Asset | Contents |
|---|---|
| `mpv-natives-windows-x86_64-mpv-<ver>.zip` | `libmpv-2.dll` (all dependencies statically linked, mingw64 build) |
| `mpv-natives-macos-<arch>-mpv-<ver>.zip` | `libmpv.dylib` (in preparation) |
| `mpv-natives-macos-universal-mpv-<ver>.zip` | `libmpv.dylib` (universal arm64 + x86_64, all dependencies statically linked) |
The Windows DLL is fully self-contained: every dependency (ffmpeg, libass,
libplacebo, harfbuzz, freetype, fribidi, …) is statically linked into
+248
View File
@@ -0,0 +1,248 @@
#!/bin/bash -e
set -e
# (The shebang -e is not honored when the workflow invokes us via `bash file`,
# so set -e is repeated here to guarantee fail-fast regardless of caller.)
#
# Build a single self-contained libmpv dylib for macOS on the runner's own
# architecture (x86_64 on macos-14, arm64 on macos-15). The workflow runs
# this script on both runner kinds and merges the two dylibs into a
# universal binary with lipo.
#
# Recipe: every dependency is built STATIC and linked into the one dylib
# (same approach as ci/build-libmpv-mingw64.sh for Windows). macOS needs no
# cross-compilation: the host Apple Clang builds natively. No iconv is built
# (macOS ships one), no system frameworks are vendored (CoreAudio,
# VideoToolbox, ... are always present on the host).
#
# Vulkan is intentionally NOT built: jlibmpv renders through an externally
# supplied OpenGL context (JOGL + render API) and never uses the Vulkan video
# backend. CoreAudio is the audio output (ao=coreaudio) and VideoToolbox
# provides hardware decode.
#
# All third-party sources are pinned to the latest stable tag that existed on
# the mpv v0.41.0 release date (2025-12-21), so a rebuild reproduces the same
# binary instead of drifting to git HEAD.
# The mpv source tree is the current working directory (checked out by CI at
# MPV_REF). Everything is built next to it under macos_prefix/.
prefix_dir=$PWD/macos_prefix
mkdir -p "$prefix_dir"
wget="wget -nc --progress=bar:force"
export CC=cc
export CXX=c++
export AR=ar
export NM=nm
export RANLIB=ranlib
export CFLAGS="-O2 -pipe -Wall"
export LDFLAGS=""
# anything that uses pkg-config
export PKG_CONFIG_SYSROOT_DIR="$prefix_dir"
export PKG_CONFIG_LIBDIR="$PKG_CONFIG_SYSROOT_DIR/lib/pkgconfig"
# --- Pinned dependency versions -------------------------------------------
# Latest stable tag on or before the mpv v0.41.0 release (2025-12-21).
FFMPEG_VER=n8.0.1
DAV1D_VER=1.5.2
LIBPLACEBO_VER=v7.351.0
LIBASS_VER=0.17.4
LCMS2_VER=lcms2.17
ZLIB_VER=1.3.1
FREETYPE_VER=2.14.1
FRIBIDI_VER=1.0.16
HARFBUZZ_VER=12.2.0
# Static everywhere: every dependency becomes a .a archive that gets linked
# into the single libmpv dylib. (mpv's own script ships shared libs instead.)
commonflags="--disable-shared --enable-static"
function builddir {
[ -d "$1/builddir" ] && rm -rf "$1/builddir"
mkdir -p "$1/builddir"
pushd "$1/builddir"
}
function makeplusinstall {
if [ -f build.ninja ]; then
ninja
DESTDIR="$prefix_dir" ninja install
else
make -j$(sysctl -n hw.ncpu)
make DESTDIR="$prefix_dir" install
fi
}
function gettar {
local name="${1##*/}"
[ -d "${name%%.*}" ] && return 0
$wget "$1"
tar -xaf "$name"
}
# Clone a git dependency at a pinned tag (shallow, tag-checked-out).
# Submodules are fetched shallow too: libplacebo needs its bundled `glad`.
function gitpin {
local url=$1 dest=$2 tag=$3
[ -d "$dest" ] || git clone --depth=1 --branch "$tag" \
--recurse-submodules --shallow-submodules "$url" "$dest"
pushd "$dest"
git checkout --quiet "$tag"
popd
}
function build_if_missing {
local name=${1//-/_}
local mark_var=_${name}_mark
local mark_file=$prefix_dir/${!mark_var}
[ -e "$mark_file" ] && return 0
echo "::group::Building $1"
_$name
echo "::endgroup::"
if [ ! -e "$mark_file" ]; then
echo "Error: Build of $1 completed but $mark_file was not created."
return 2
fi
}
## mpv's dependencies (all static, native build)
_zlib () {
gettar "https://zlib.net/fossils/zlib-${ZLIB_VER}.tar.gz"
builddir zlib-${ZLIB_VER}
CC=cc AR=ar RANLIB=ranlib make -fMakefile.lib \
LIBSUFFIX= PREFIX=/ LIBPATH=$prefix_dir/lib install
popd
}
_zlib_mark=lib/libz.a
_dav1d () {
gitpin https://code.videolan.org/videolan/dav1d.git dav1d "$DAV1D_VER"
builddir dav1d
meson setup .. -Denable_{tools,tests}=false -Ddefault_library=static
makeplusinstall
popd
}
_dav1d_mark=lib/libdav1d.a
_lcms2 () {
gitpin https://github.com/mm2/Little-CMS.git lcms2 "$LCMS2_VER"
builddir lcms2
meson setup .. -Dtests=disabled -Dutils=false -Ddefault_library=static
makeplusinstall
popd
}
_lcms2_mark=lib/liblcms2.a
_ffmpeg () {
gitpin https://github.com/FFmpeg/FFmpeg.git ffmpeg "$FFMPEG_VER"
builddir ffmpeg
local args=(
--pkg-config=pkg-config --target-os=macosx --enable-gpl
--enable-cross-compile \
--extra-cflags="-I$prefix_dir/include" \
--extra-ldflags="-L$prefix_dir/lib" \
$commonflags
--disable-{doc,programs}
--enable-muxer=spdif --enable-encoder=mjpeg,png --enable-libdav1d
)
../configure "${args[@]}"
makeplusinstall
popd
}
_ffmpeg_mark=lib/libavcodec.a
_freetype () {
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VER}.tar.xz"
builddir freetype-${FREETYPE_VER}
meson setup .. -Ddefault_library=static
makeplusinstall
popd
}
_freetype_mark=lib/libfreetype.a
_fribidi () {
gettar "https://github.com/fribidi/fribidi/releases/download/v${FRIBIDI_VER}/fribidi-${FRIBIDI_VER}.tar.xz"
builddir fribidi-${FRIBIDI_VER}
meson setup .. -D{tests,docs}=false -Ddefault_library=static
makeplusinstall
popd
}
_fribidi_mark=lib/libfribidi.a
_harfbuzz () {
gettar "https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VER}/harfbuzz-${HARFBUZZ_VER}.tar.xz"
builddir harfbuzz-${HARFBUZZ_VER}
meson setup .. -Dtests=disabled -Ddefault_library=static
makeplusinstall
popd
}
_harfbuzz_mark=lib/libharfbuzz.a
_libass () {
gitpin https://github.com/libass/libass.git libass "$LIBASS_VER"
builddir libass
meson setup .. -Ddefault_library=static
makeplusinstall
popd
}
_libass_mark=lib/libass.a
for x in zlib dav1d lcms2; do
build_if_missing $x
done
for x in ffmpeg freetype fribidi harfbuzz libass; do
build_if_missing $x
done
_libplacebo () {
gitpin https://code.videolan.org/videolan/libplacebo.git libplacebo "$LIBPLACEBO_VER"
builddir libplacebo
# opengl enabled (the only renderer jlibmpv uses); vulkan/d3d11 off
meson setup .. -Ddemos=false -Dopengl=enabled -Dd3d11=disabled \
-Dvulkan=disabled -Dlcms=disabled -Ddefault_library=static
makeplusinstall
popd
}
_libplacebo_mark=lib/libplacebo.a
build_if_missing libplacebo
## mpv
build=macos_build
rm -rf $build
# libmpv itself is the ONE shared object (default_library=shared), linked
# against the static dependency archives built above. No CLI player, no
# tests, no lua/javascript, no vulkan. OpenGL stays (render API via JOGL);
# coreaudio is the audio output; videotoolbox gives hardware decode.
meson setup $build \
--buildtype release -Dstrip=true \
-Ddefault_library=shared \
-Dlibmpv=true \
-Dcplayer=false \
-Dtests=false \
-Dlua=disabled \
-Djavascript=disabled \
-Dshaderc=disabled \
-Dspirv-cross=disabled \
-Dvulkan=disabled \
-Dcocoa=disabled \
-Dgl-cocoa=disabled \
-Dcoreaudio=enabled \
-Dvideotoolbox-pl=enabled
meson compile -C $build
## Collect the self-contained dylib
mkdir -p artifact
LIBMPV_DYLIB=$(find $build -maxdepth 1 -name 'libmpv-2.dylib' | head -n 1)
[ -n "$LIBMPV_DYLIB" ] || { echo "ERROR: libmpv dylib not found in $build"; ls -l $build; exit 1; }
cp -pv "$LIBMPV_DYLIB" artifact/
cp -pv "$build"/*.h artifact/ 2>/dev/null || true
echo "=== artifact contents ==="
ls -l artifact
echo "=== libmpv-2.dylib load commands (should be system frameworks only) ==="
otool -L "artifact/$(basename "$LIBMPV_DYLIB")"