diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml new file mode 100644 index 0000000..634f454 --- /dev/null +++ b/.github/workflows/build-linux.yml @@ -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 }} \ No newline at end of file diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7532d71..01d52bb 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -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 diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9f89a5c..6eb6c0a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -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 diff --git a/README.md b/README.md index 2624eec..9fb1d4f 100644 --- a/README.md +++ b/README.md @@ -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-.zip` | `libmpv-2.dll` (all dependencies statically linked, mingw64 build) | | `mpv-natives-macos-universal-mpv-.zip` | `libmpv.dylib` (universal arm64 + x86_64, all dependencies statically linked) | +| `mpv-natives-linux-x86_64-mpv-.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 diff --git a/ci/build-libmpv-linux.sh b/ci/build-libmpv-linux.sh new file mode 100644 index 0000000..3508a72 --- /dev/null +++ b/ci/build-libmpv-linux.sh @@ -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 \ No newline at end of file diff --git a/ci/build-libmpv-macos.sh b/ci/build-libmpv-macos.sh index acb2bbe..0451042 100644 --- a/ci/build-libmpv-macos.sh +++ b/ci/build-libmpv-macos.sh @@ -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 diff --git a/ci/build-libmpv-mingw64.sh b/ci/build-libmpv-mingw64.sh index f48b02a..a6c0bbb 100644 --- a/ci/build-libmpv-mingw64.sh +++ b/ci/build-libmpv-mingw64.sh @@ -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