Skip to content

Commit 1782076

Browse files
authored
Add python wheel build pipeline (#123) (#131)
Publish on tag push.
1 parent d7dfdf2 commit 1782076

3 files changed

Lines changed: 430 additions & 26 deletions

File tree

.github/workflows/publish-pypi.yml

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
name: Build Wheel
2+
3+
on: [push, pull_request, workflow_dispatch]
4+
5+
env:
6+
dav1d_version: "1.5.3"
7+
libxml2_version: "v2.15.3"
8+
nv_version: "n13.0.19.0"
9+
libmfx_version: "master"
10+
libvpx_version: "v1.16.0"
11+
glslang_version: "16.3.0"
12+
BUILD_COMMANDS: |
13+
set -e
14+
JOBS=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu || echo 2)
15+
REPO_ROOT=$(pwd)
16+
17+
# Patch obuparse MinGW-w64 (UCRT64)
18+
if [[ "$PREFIX" == "/ucrt64" ]]; then
19+
sed -i 's|#define off_t __int64|// #define off_t __int64|g' obuparse/tools/obudump.c
20+
sed -i 's|#define fseeko _fseeki64|// #define fseeko _fseeki64|g' obuparse/tools/obudump.c
21+
sed -i 's|#define ftello _fseeki64|// #define ftello _fseeki64|g' obuparse/tools/obudump.c
22+
fi
23+
24+
# dav1d
25+
wget "https://code.videolan.org/videolan/dav1d/-/archive/$dav1d_version/dav1d-$dav1d_version.tar.bz2" -O - | tar xfvj -
26+
cd dav1d-"$dav1d_version"
27+
meson setup build -Dprefix="$PREFIX" -Denable_tools=false -Denable_examples=false -Denable_tests=false --default-library=static --buildtype=release --strip
28+
meson compile -C build
29+
$SUDO meson install -C build
30+
cd .. && rm -rf dav1d-"$dav1d_version"
31+
32+
# mfx_dispatch build (Linux/Windows only)
33+
if [[ "$EXTRA_FFMPEG_FLAGS" == *"--enable-libmfx"* ]]; then
34+
git clone -b "$libmfx_version" --depth 1 https://github.com/lu-zero/mfx_dispatch
35+
cd mfx_dispatch
36+
autoreconf -i
37+
CPPFLAGS="-O2 -s -pipe" ./configure --prefix="$PREFIX"
38+
make -j$(nproc)
39+
$SUDO make -j "$JOBS" install
40+
cd .. && rm -rf mfx_dispatch
41+
fi
42+
43+
# nv-codec-headers (Linux/Windows only)
44+
if [[ "$EXTRA_FFMPEG_FLAGS" == *"--enable-nvdec"* ]]; then
45+
git clone -b "$nv_version" --depth 1 https://git.videolan.org/git/ffmpeg/nv-codec-headers
46+
cd nv-codec-headers
47+
$SUDO make install PREFIX="$PREFIX"
48+
cd .. && rm -rf nv-codec-headers
49+
fi
50+
51+
# libxml2
52+
git clone -b "$libxml2_version" --depth 1 https://github.com/gnome/libxml2
53+
cd libxml2
54+
cmake -B build -G Ninja -DLIBXML2_WITH_TESTS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_HTML=ON -DLIBXML2_WITH_C14N=ON -DLIBXML2_WITH_CATALOG=OFF -DLIBXML2_WITH_DEBUG=OFF -DLIBXML2_WITH_DOCB=ON -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_ISO8859X=ON -DLIBXML2_WITH_ZLIB=ON -DLIBXML2_WITH_ICU=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_LEGACY=ON -DLIBXML2_WITH_MEM_DEBUG=OFF -DLIBXML2_WITH_MODULES=ON -DLIBXML2_WITH_OUTPUT=ON -DLIBXML2_WITH_PATTERN=ON -DLIBXML2_WITH_PUSH=ON -DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_READER=ON -DLIBXML2_WITH_REGEXPS=ON -DLIBXML2_WITH_RUN_DEBUG=OFF -DLIBXML2_WITH_SAX1=ON -DLIBXML2_WITH_SCHEMAS=ON -DLIBXML2_WITH_SCHEMATRON=ON -DLIBXML2_WITH_THREADS=ON -DLIBXML2_WITH_THREAD_ALLOC=OFF -DLIBXML2_WITH_TREE=ON -DLIBXML2_WITH_VALID=ON -DLIBXML2_WITH_WRITER=ON -DLIBXML2_WITH_XINCLUDE=ON -DLIBXML2_WITH_XPATH=ON -DLIBXML2_WITH_XPTR=ON -DCMAKE_C_FLAGS_RELEASE="-DIN_LIBXML" -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$PREFIX" -DCMAKE_INSTALL_PREFIX="$PREFIX" -D BUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DCMAKE_CL_SHOWINCLUDES_PREFIX="Note: including file:"
55+
ninja -C build
56+
$SUDO ninja -C build install
57+
cd .. && rm -rf libxml2
58+
59+
# libvpx
60+
git clone -b "$libvpx_version" --depth 1 https://github.com/webmproject/libvpx
61+
cd libvpx
62+
VPX_TARGET=""
63+
if [[ "$PREFIX" == "/ucrt64" ]]; then
64+
VPX_TARGET="--target=x86_64-win64-gcc"
65+
fi
66+
./configure --prefix="$PREFIX" $VPX_TARGET --disable-examples --disable-tools --disable-docs --disable-unit-tests --enable-vp9-highbitdepth --disable-debug-libs --disable-shared --as=nasm --enable-runtime-cpu-detect --disable-debug --disable-vp8 --disable-vp9-encoder --disable-postproc --disable-webm-io --disable-libyuv
67+
make -j "$JOBS"
68+
$SUDO make -j "$JOBS" install
69+
cd .. && rm -rf libvpx
70+
71+
# glslang
72+
git clone -b "$glslang_version" --depth 1 https://github.com/KhronosGroup/glslang
73+
cd glslang
74+
python3 ./update_glslang_sources.py
75+
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PREFIX" -DBUILD_SHARED_LIBS=OFF -DENABLE_OPT=OFF -DENABLE_HLSL=OFF -DENABLE_SPIRV=ON -DENABLE_GLSLANG_BINARIES=ON -DGLSLANG_TESTS=OFF
76+
ninja -C build
77+
$SUDO ninja -C build install
78+
cd .. && rm -rf glslang
79+
80+
# FFmpeg
81+
cd $REPO_ROOT/FFmpeg
82+
./configure --prefix="$PREFIX" --disable-muxers --disable-avdevice --disable-encoders --disable-filters --disable-devices --disable-network --enable-libdav1d --enable-zlib --enable-libxml2 --enable-libvpx --disable-programs --disable-doc --disable-debug --enable-gpl --enable-pic --disable-nvenc --enable-hwaccel=ffv1_vulkan --disable-bzlib --disable-iconv --disable-lzma --extra-cflags="-fPIC" --extra-ldflags="-fPIC" $EXTRA_FFMPEG_FLAGS || {
83+
echo "====================================================================="
84+
echo "❌ FFmpeg configure FAILED! Dumping ffbuild/config.log..."
85+
echo "====================================================================="
86+
cat ffbuild/config.log
87+
echo "====================================================================="
88+
exit 1
89+
}
90+
91+
make -j "$JOBS"
92+
$SUDO make -j "$JOBS" install
93+
cd ..
94+
95+
jobs:
96+
build-non-windows-wheel:
97+
runs-on: ${{ matrix.os }}
98+
99+
strategy:
100+
matrix:
101+
include:
102+
- os: ubuntu-latest
103+
arch: linux_x86_64
104+
- os: ubuntu-24.04-arm
105+
arch: linux_aarch64
106+
- os: macos-15-intel
107+
arch: macosx_x86_64
108+
- os: macos-15
109+
arch: macosx_arm64
110+
111+
steps:
112+
- name: Checkout
113+
uses: actions/checkout@v7
114+
with:
115+
fetch-depth: 0
116+
submodules: true
117+
118+
- uses: pypa/cibuildwheel@v4.1.0
119+
env:
120+
CIBW_BUILD: cp314-*
121+
CIBW_BUILD_FRONTEND: uv
122+
CIBW_ENVIRONMENT: >
123+
dav1d_version=${{ env.dav1d_version }}
124+
libxml2_version=${{ env.libxml2_version }}
125+
nv_version=${{ env.nv_version }}
126+
libmfx_version=${{ env.libmfx_version }}
127+
libvpx_version=${{ env.libvpx_version }}
128+
glslang_version=${{ env.glslang_version }}
129+
PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib64/pkgconfig
130+
CMAKE_ARGS="-DZLIB_USE_STATIC_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-fPIC -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_MODULE_LINKER_FLAGS=-Wl,-Bsymbolic"
131+
CIBW_ENVIRONMENT_MACOS: >
132+
MACOSX_DEPLOYMENT_TARGET=15
133+
CMAKE_ARGS="-DZLIB_USE_STATIC_LIBS=OFF"
134+
CIBW_BEFORE_ALL_LINUX: |
135+
set -e
136+
if command -v dnf >/dev/null 2>&1; then
137+
dnf install -y epel-release dnf-plugins-core
138+
dnf -y install wget curl git make gcc gcc-c++ cmake ninja-build nasm zlib-devel pkgconf-pkg-config diffutils python3-pip vulkan-headers vulkan-loader-devel autoconf automake libtool
139+
else
140+
apk add --no-cache wget curl git make gcc g++ cmake ninja nasm zlib-dev pkgconfig diffutils python3 py3-pip vulkan-headers vulkan-loader-dev autoconf automake libtool make
141+
fi &&
142+
pip3 install meson
143+
ARCH=$(uname -m)
144+
if [[ "$ARCH" == "x86_64" ]]; then
145+
export PREFIX=/usr SUDO="" EXTRA_FFMPEG_FLAGS="--enable-cuvid --enable-nvdec --enable-libmfx"
146+
else
147+
export PREFIX=/usr SUDO="" EXTRA_FFMPEG_FLAGS="--enable-cuvid --enable-nvdec"
148+
fi
149+
${{ env.BUILD_COMMANDS }}
150+
CIBW_BEFORE_ALL_MACOS: |
151+
brew install cmake meson nasm ninja pkg-config zlib wget vulkan-headers vulkan-loader autoconf automake libtool
152+
export PREFIX=/usr/local SUDO="sudo" EXTRA_FFMPEG_FLAGS="--disable-videotoolbox --disable-audiotoolbox"
153+
${{ env.BUILD_COMMANDS }}
154+
with:
155+
output-dir: dist
156+
extras: uv
157+
158+
- name: Test macOS Wheel
159+
if: runner.os == 'macOS'
160+
run: |
161+
export HOMEBREW_NO_REQUIRE_TAP_TRUST=1
162+
brew install vapoursynth
163+
BREW_PREFIX=$(brew --prefix)
164+
165+
python3 -m venv test_env
166+
source test_env/bin/activate
167+
168+
export LIBRARY_PATH="$BREW_PREFIX/lib:$LIBRARY_PATH"
169+
export PKG_CONFIG_PATH="$BREW_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH"
170+
export DYLD_FALLBACK_LIBRARY_PATH="$BREW_PREFIX/lib:$DYLD_FALLBACK_LIBRARY_PATH"
171+
172+
pip install --upgrade pip
173+
pip install vapoursynth
174+
pip install dist/*macosx*.whl
175+
176+
python3 -c "import vapoursynth, os; core = vapoursynth.core; plugins = [p.name for p in core.plugins()]; print('Successfully loaded plugins:', plugins); p_dir = os.path.join(os.path.dirname(vapoursynth.__file__), 'plugins'); print('Contents of plugins directory:', os.listdir(p_dir) if os.path.exists(p_dir) else 'DIR NOT FOUND'); assert any('lsmash' in p.lower() for p in plugins), 'lsmashsource plugin failed to autoload!'"
177+
178+
#- name: Test macOS Wheel
179+
# if: runner.os == 'macOS'
180+
# run: |
181+
# export HOMEBREW_NO_REQUIRE_TAP_TRUST=1
182+
# brew install vapoursynth
183+
# BREW_PREFIX=$(brew --prefix)
184+
#
185+
# python3 -m venv test_env
186+
# source test_env/bin/activate
187+
#
188+
# export LIBRARY_PATH="$BREW_PREFIX/lib:$LIBRARY_PATH"
189+
# export PKG_CONFIG_PATH="$BREW_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH"
190+
# export DYLD_FALLBACK_LIBRARY_PATH="$BREW_PREFIX/lib:$DYLD_FALLBACK_LIBRARY_PATH"
191+
#
192+
# pip install --upgrade pip
193+
# pip install vapoursynth
194+
# pip install dist/*macosx*.whl
195+
#
196+
# echo "Locating installed plugin..."
197+
# # Use Python to find the exact path pip installed the .so file to
198+
# PLUGIN_PATH=$(python3 -c "import vapoursynth, os; print(os.path.join(os.path.dirname(vapoursynth.__file__), 'plugins', 'liblsmashsource.so'))")
199+
# echo "Plugin located at: $PLUGIN_PATH"
200+
#
201+
# echo "--- Checking dynamic dependencies (otool) ---"
202+
# # This shows what the plugin THINKS it needs
203+
# otool -L "$PLUGIN_PATH"
204+
#
205+
# echo "--- Forcing macOS dyld to load the plugin (ctypes) ---"
206+
# # This will intentionally crash and print the EXACT missing library or architecture error
207+
# python3 -c "import ctypes; ctypes.CDLL('$PLUGIN_PATH')"
208+
209+
- name: Test Linux Wheel
210+
if: runner.os == 'Linux'
211+
run: |
212+
python3 -m venv test_env
213+
source test_env/bin/activate
214+
215+
pip install --upgrade pip
216+
pip install vapoursynth
217+
218+
pip install dist/*manylinux*.whl
219+
220+
echo "Testing VapourSynth Autoloading..."
221+
python3 -c "import vapoursynth; core = vapoursynth.core; plugins = [p.name for p in core.plugins()]; print('Successfully loaded plugins:', plugins); assert any('lsmash' in p.lower() for p in plugins), 'Plugin failed to autoload!'; print('✅ SUCCESS: Plugin autoloaded correctly!')"
222+
223+
- uses: actions/upload-artifact@v7.0.1
224+
with:
225+
name: lsmashsource-${{ matrix.arch }}
226+
path: dist
227+
228+
build-windows-wheel:
229+
runs-on: windows-latest
230+
defaults:
231+
run:
232+
shell: msys2 {0}
233+
steps:
234+
- uses: actions/checkout@v7
235+
with:
236+
fetch-depth: 0
237+
submodules: true
238+
- uses: msys2/setup-msys2@v2.32.0
239+
with:
240+
msystem: UCRT64
241+
update: true
242+
install: >-
243+
mingw-w64-ucrt-x86_64-cmake
244+
mingw-w64-ucrt-x86_64-gcc
245+
mingw-w64-ucrt-x86_64-meson
246+
mingw-w64-ucrt-x86_64-ninja
247+
mingw-w64-ucrt-x86_64-nasm
248+
mingw-w64-ucrt-x86_64-pkgconf
249+
mingw-w64-ucrt-x86_64-zlib
250+
mingw-w64-ucrt-x86_64-vulkan-headers
251+
mingw-w64-ucrt-x86_64-vulkan-loader
252+
mingw-w64-ucrt-x86_64-glslang
253+
mingw-w64-ucrt-x86_64-autotools
254+
mingw-w64-ucrt-x86_64-python
255+
mingw-w64-ucrt-x86_64-python-pip
256+
mingw-w64-ucrt-x86_64-python-wheel
257+
git
258+
make
259+
wget
260+
diffutils
261+
262+
- name: Build Dependencies and Plugin
263+
run: |
264+
export PREFIX=/ucrt64 SUDO="" EXTRA_FFMPEG_FLAGS="--enable-cuvid --enable-nvdec --enable-libmfx --enable-w32threads --pkg-config-flags=-static"
265+
${{ env.BUILD_COMMANDS }}
266+
267+
python -m pip install build scikit-build-core --break-system-packages
268+
export LDFLAGS="-static-libgcc -static-libstdc++ -static"
269+
python -m build --outdir dist
270+
271+
- name: Test Windows Wheel
272+
run: |
273+
echo "Creating isolated Python virtual environment..."
274+
python -m venv test_env
275+
source test_env/bin/activate
276+
277+
echo "Installing VapourSynth via pip..."
278+
pip install --upgrade pip
279+
pip install vapoursynth
280+
281+
echo "Installing the built wheel for testing..."
282+
pip install dist/*ucrt_gnu.whl
283+
284+
echo "Testing VapourSynth Autoloading on Windows..."
285+
python -c "import vapoursynth; core = vapoursynth.core; plugins = [p.name for p in core.plugins()]; print('Successfully loaded plugins:', plugins); assert any('lsmash' in p.lower() for p in plugins), 'Plugin failed to autoload!'; print('SUCCESS: Plugin autoloaded correctly!')"
286+
287+
- name: Retag Wheel for Distribution
288+
run: |
289+
echo "Test passed! Retagging wheel to win_amd64 for standard Windows users..."
290+
python -m wheel tags --platform-tag=win_amd64 --remove dist/*.whl
291+
292+
- uses: actions/upload-artifact@v7.0.1
293+
with:
294+
name: lsmashsource-sdist
295+
path: dist/*.tar.gz
296+
297+
- uses: actions/upload-artifact@v7.0.1
298+
with:
299+
name: lsmashsource-win_amd64
300+
path: dist/*.whl
301+
302+
publish:
303+
permissions:
304+
id-token: write
305+
needs: [build-non-windows-wheel, build-windows-wheel]
306+
if: startsWith(github.ref, 'refs/tags/')
307+
runs-on: ubuntu-latest
308+
309+
steps:
310+
- uses: actions/download-artifact@v8.0.1
311+
with:
312+
path: dist
313+
merge-multiple: true
314+
315+
- uses: pypa/gh-action-pypi-publish@release/v1

0 commit comments

Comments
 (0)