build: add Linux libmpv.so build and drop the stray headers

Add a native x86_64 Linux build (ci/build-libmpv-linux.sh,
build-linux.yml). Every dependency is static and linked into a single
libmpv.so; only glibc/pthread/dl/m and the system zlib remain external.
The Linux-only additions are the font stack (fontconfig -> expat, where
macOS/Windows use CoreText/DirectWrite) and alsa audio output; gl is the
plain render-API support (no X11/Wayland context, since jlibmpv supplies
the GL context via JOGL).

For consistency, ship a single native per platform and no public headers
(jlibmpv loads the library via JNA and never compiles against it). Remove
the accidental header/import-library copies from the Windows build and the
phantom header copy from the macOS build.
This commit is contained in:
david
2026-10-01 18:10:21 +02:00
parent 1e8e053f21
commit 248a9a0ecf
7 changed files with 442 additions and 15 deletions
+112
View File
@@ -0,0 +1,112 @@
name: build-linux
# Builds a single self-contained libmpv.so for Linux (x86_64) from mpv source
# pinned to a release tag. Every dependency is built static and linked into
# the one .so; only glibc/pthread/dl/m and the system zlib stay external, so
# the artifact has no runtime dependency closure to ship (unlike mpv's own
# shared-lib CI build).
#
# Linux is the easiest of the three platforms: a single native build on an
# ubuntu runner, no cross-compile, no lipo. The only Linux-specific work is the
# font stack: libass (a hard mpv dependency) needs fontconfig (-> expat) where
# macOS/Windows use CoreText/DirectWrite.
#
# 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 (dependency versions, static flags) lives in
# ci/build-libmpv-linux.sh in this repository.
on:
push:
branches: [main]
workflow_dispatch:
env:
MPV_REF: v0.41.0
TARGET: x86_64-linux
jobs:
build:
runs-on: ubuntu-24.04
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%N)" >> $GITHUB_OUTPUT
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.TARGET }}-${{ steps.get_time.outputs.timestamp }}
restore-keys: ${{ env.TARGET }}-
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
ccache nasm ninja-build pkgconf cmake autoconf automake libtool
python3 -m pip install meson
# Run the vendored build script from inside the mpv source tree, so the
# native build writes linux_prefix/ and the meson build next to mpv's
# meson.build and produces the self-contained .so in mpv/artifact/.
- name: Build self-contained libmpv
working-directory: mpv
run: bash "${{ github.workspace }}/ci/build-libmpv-linux.sh"
- name: Print meson log on failure
if: failure()
run: cat mpv/linux_build/meson-logs/meson-log.txt
- name: Collect artifact
run: |
mkdir -p artifact-libmpv
cp -pv mpv/artifact/libmpv.so artifact-libmpv/
echo "=== contents ==="
ls -l artifact-libmpv
- name: Checksums
run: |
cd artifact-libmpv
sha256sum libmpv.so > checksums.txt
cat checksums.txt
- name: Artifact name
id: artifact
run: |
VER=$(cat mpv/MPV_VERSION | tr -d '[:space:]')
echo "name=mpv-natives-linux-x86_64-mpv-$VER" >> "$GITHUB_OUTPUT"
echo "version=$VER" >> "$GITHUB_OUTPUT"
# upload-artifact zips the directory itself, so handing it the folder
# (not a pre-zipped file) yields a single-level archive without the
# double-zip wrapper.
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact.outputs.name }}
path: artifact-libmpv
- name: Save ccache
uses: actions/cache/save@v4
if: always()
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.TARGET }}-${{ steps.get_time.outputs.timestamp }}
-1
View File
@@ -88,7 +88,6 @@ jobs:
mpv/artifact-x86_64/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
-1
View File
@@ -73,7 +73,6 @@ jobs:
run: |
mkdir -p artifact-libmpv
cp -pv mpv/artifact/libmpv-*.dll artifact-libmpv/
cp -pv mpv/artifact/*.h artifact-libmpv/ 2>/dev/null || true
echo "=== contents ==="
ls -l artifact-libmpv
+15 -9
View File
@@ -15,8 +15,8 @@ here, as versioned GitHub release assets.
`MPV_REF` in the workflows.
- **Build source:** the mpv source tree is checked out at that tag by
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.
(`build-libmpv-mingw64.sh`, `build-libmpv-macos.sh`, `build-libmpv-linux.sh`).
No mpv source or third-party binaries are stored in this repository.
## Releases
@@ -27,19 +27,25 @@ single file) and a `checksums.txt` (SHA-256) for verification.
|---|---|
| `mpv-natives-windows-x86_64-mpv-<ver>.zip` | `libmpv-2.dll` (all dependencies statically linked, mingw64 build) |
| `mpv-natives-macos-universal-mpv-<ver>.zip` | `libmpv.dylib` (universal arm64 + x86_64, all dependencies statically linked) |
| `mpv-natives-linux-x86_64-mpv-<ver>.zip` | `libmpv.so` (all dependencies statically linked, native x86_64 build) |
The Windows DLL is fully self-contained: every dependency (ffmpeg, libass,
libplacebo, harfbuzz, freetype, fribidi, …) is statically linked into
`libmpv-2.dll` together with a static C/C++ runtime. It depends only on
Windows system libraries, so there is no DLL closure to extract alongside it.
Each native is fully self-contained: every dependency (ffmpeg, libass,
libplacebo, harfbuzz, freetype, fribidi, …) is statically linked into the
single library together with the C/C++ runtime where needed. The library
depends only on the platform's system libraries, so there is no dependency
closure to extract alongside it — jlibmpv loads it at runtime via JNA and
never compiles against it, so no public headers are shipped.
Linux users can normally use the distribution package (`apt install libmpv1`)
instead; a self-built Linux artifact is planned only if needed.
On Linux the self-built `libmpv.so` additionally removes the need for the
distribution package (`libmpv1`), which lags behind in codec support (dav1d/
AV1, x264/x265): the pinned ffmpeg 8.0.1 guarantees the full codec set on any
recent glibc distribution.
## How to use with jlibmpv
1. Download the release asset for your platform and extract it.
2. Point jlibmpv at the extracted `libmpv-2.dll` / `libmpv.dylib`:
2. Point jlibmpv at the extracted native library — `libmpv-2.dll` (Windows),
`libmpv.dylib` (macOS) or `libmpv.so` (Linux):
`-Dmpv.libmpv.path=/path/to/extracted/libmpv-2.dll`.
## Building
+311
View File
@@ -0,0 +1,311 @@
#!/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.so for Linux x86_64 (native, no cross
# file, no lipo - the easiest of the three platforms). The workflow runs it on
# an ubuntu runner and uploads the result.
#
# Recipe: every dependency is built STATIC and linked into the one .so (same
# approach as ci/build-libmpv-mingw64.sh and ci/build-libmpv-macos.sh). glibc,
# libpthread, libdl, libm, libiconv and zlib are provided by the distro and
# linked dynamically - the resulting .so depends only on the system libraries.
#
# Differences vs macOS/Windows (Linux-only):
# - libass is a hard mpv dependency and needs a font provider. macOS uses
# CoreText, Windows uses DirectWrite, Linux uses fontconfig - so fontconfig
# (and its XML backend, expat) are additional deps here. harfbuzz and
# freetype are built without the fontconfig provider so the only font
# stack is the one we link in.
# - gl is the plain GL render-API support (no X11/Wayland context backend),
# which jlibmpv's externally supplied OpenGL context (JOGL) consumes via
# the mpv render API. No vo is built at all (cplayer=false).
# - alsa is the audio output (no PulseAudio/JACK/OSS).
#
# 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.
prefix_dir=$PWD/linux_prefix
mkdir -p "$prefix_dir"
wget="wget -nc --progress=bar:force"
export CC=gcc
export CXX=g++
export AR=ar
export NM=nm
export RANLIB=ranlib
export CFLAGS="-O2 -pipe -Wall"
export LDFLAGS=""
# Prefix for the static deps we build ourselves, added additively so the
# system pkg-config files (zlib, ...) are still visible.
export PKG_CONFIG_PATH="$prefix_dir/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
# --- 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
FREETYPE_VER=2.14.1
FRIBIDI_VER=1.0.16
HARFBUZZ_VER=12.2.0
FONTCONFIG_VER=2.16.2
EXPAT_VER=R_2_7_3
ALSA_VER=v1.2.15
# Static everywhere: every dependency becomes a .a archive that gets linked
# into the single libmpv.so. (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
ninja install
else
make -j$(nproc)
make 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; glibc/zlib/libiconv come from the system)
_expat () {
gitpin https://github.com/libexpat/libexpat.git expat "$EXPAT_VER"
# The tag places the CMake project one level down (expat/expat/).
pushd expat/expat
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$prefix_dir" \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_EXAMPLES=OFF -DBUILD_TESTS=OFF -DBUILD_TOOLS=OFF
cmake --build build -j$(nproc)
cmake --install build
popd
}
_expat_mark=lib/libexpat.a
_alsa () {
gitpin https://github.com/alsa-project/alsa-lib.git alsa-lib "$ALSA_VER"
builddir alsa-lib
# alsa-lib ships autotools inputs but no generated configure; generate it
# in the source tree (we build out-of-tree from ../).
( cd .. && autoreconf -fi )
../configure --prefix="$prefix_dir" $commonflags
makeplusinstall
popd
}
_alsa_mark=lib/libasound.a
_fontconfig () {
gitpin https://github.com/fontconfig/fontconfig.git fontconfig "$FONTCONFIG_VER"
builddir fontconfig
# expat is the XML backend (our static build); no tests/tools/nls. freetype
# resolves to our own static build via PKG_CONFIG_PATH (we link it into
# libmpv, not fc's system one).
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-Dxml-backend=expat -Dtests=disabled -Dtools=disabled -Dnls=disabled \
-Ddefault_library=static
makeplusinstall
popd
}
_fontconfig_mark=lib/libfontconfig.a
_dav1d () {
gitpin https://code.videolan.org/videolan/dav1d.git dav1d "$DAV1D_VER"
builddir dav1d
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-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 .. --buildtype release -Dprefix="$prefix_dir" \
-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
args=(
--prefix="$prefix_dir" --pkg-config=pkg-config --target-os=linux
--enable-gpl $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}
# zlib stays (system lib); every other third-party provider is disabled.
# harfbuzz off: we link our own static harfbuzz into libmpv, not FT's hook.
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-Dzlib=enabled -Dbrotli=disabled -Dbzip2=disabled -Dpng=disabled \
-Dharfbuzz=disabled -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 .. --buildtype release -Dprefix="$prefix_dir" \
-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}
# icu off (an extra dep; harfbuzz shapes fine with built-in unicode data).
# No freetype backend (matches macOS/Windows; the freetype_min_version in
# harfbuzz is not needed - libass drives freetype itself for font loading).
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-Dtests=disabled -Dutilities=disabled -Dicu=disabled \
-Dfreetype=disabled -Ddefault_library=static
makeplusinstall
popd
}
_harfbuzz_mark=lib/libharfbuzz.a
_libass () {
gitpin https://github.com/libass/libass.git libass "$LIBASS_VER"
builddir libass
# fontconfig is the Linux font provider (system framework equivalent);
# freetype and fribidi are auto-detected and resolve to our own static
# builds via PKG_CONFIG_PATH. asm off for determinism.
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-Dfontconfig=enabled -Dasm=disabled -Dlibunibreak=disabled \
-Ddefault_library=static
makeplusinstall
popd
}
_libass_mark=lib/libass.a
_libplacebo () {
gitpin https://code.videolan.org/videolan/libplacebo.git libplacebo "$LIBPLACEBO_VER"
builddir libplacebo
# opengl enabled (the only renderer jlibmpv uses); vulkan/d3d11 off;
# lcms/xxhash off. GL is resolved at runtime by libplacebo's glad loader,
# so no libGL link is required.
meson setup .. --buildtype release -Dprefix="$prefix_dir" \
-Ddemos=false -Dopengl=enabled -Dd3d11=disabled \
-Dvulkan=disabled -Dlcms=disabled -Dxxhash=disabled \
-Ddefault_library=static
makeplusinstall
popd
}
_libplacebo_mark=lib/libplacebo.a
# Group A: no intra-prefix deps (freetype is built with harfbuzz off, so it
# needs nothing we build here - only system zlib).
for x in expat alsa freetype fribidi dav1d lcms2; do
build_if_missing $x
done
# Group B: depend on group A (fontconfig/libass need freetype2.pc, libass
# needs harfbuzz+fontconfig+fribidi, ffmpeg needs dav1d+lcms2).
for x in ffmpeg fontconfig harfbuzz libass libplacebo; do
build_if_missing $x
done
## mpv
build=linux_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. plain-gl is the OpenGL render-API support
# (no X11/Wayland context backend - jlibmpv supplies the GL context via JOGL).
# alsa is the audio output.
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 \
-Dgl=enabled -Dplain-gl=enabled \
-Dgl-x11=disabled -Degl=disabled -Ddrm=disabled -Dwayland=disabled \
-Dalsa=enabled -Dpulse=disabled -Djack=disabled -Doss-audio=disabled \
-Dx11=disabled -Dx11-clipboard=disabled
meson compile -C $build
## Collect the self-contained .so
mkdir -p artifact
# meson produces libmpv.so -> libmpv.so.2 -> libmpv.so.2.0.0; -type f keeps
# only the real (versioned) object, skipping the two symlinks.
LIBMPV_SO=$(find $build -maxdepth 1 -type f -name 'libmpv.so*' | sort -V | tail -n 1)
[ -n "$LIBMPV_SO" ] || { echo "ERROR: libmpv.so not found in $build"; ls -l $build; exit 1; }
# jlibmpv loads libmpv.so (no version suffix), so copy the versioned object
# under the versionless name.
cp -pv "$LIBMPV_SO" artifact/libmpv.so
echo "=== artifact contents ==="
ls -l artifact
echo "=== libmpv.so dynamic dependencies (should be system libs only) ==="
ldd artifact/libmpv.so || true
-1
View File
@@ -285,7 +285,6 @@ mkdir -p artifact-$ARCH
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-$ARCH/
cp -pv "$build"/*.h artifact-$ARCH/ 2>/dev/null || true
echo "=== artifact-$ARCH contents ==="
ls -l artifact-$ARCH
+4 -3
View File
@@ -301,9 +301,10 @@ cp -pv "$LIBMPV_DLL" artifact/
# distributable native we strip them out (~130 MB) so only the code/data
# sections remain.
"$TARGET-strip" --strip-all "artifact/$(basename "$LIBMPV_DLL")"
# import library, for native linkers that want it (jlibmpv uses JNA, optional)
cp -pv "$build"/libmpv*.dll.a artifact/ 2>/dev/null || true
cp -pv "$build"/*.h artifact/ 2>/dev/null || true
# The artifact is the single self-contained DLL only. No public headers
# (jlibmpv loads it via JNA at runtime and never compiles against it) and no
# import library - keeping it a single binary stays consistent with the
# macOS build (one libmpv.dylib) and the Linux build (one libmpv.so).
echo "=== artifact contents ==="
ls -l artifact