name: Shader Binary Release
on:
push:
tags:
- 'v*'
permissions: write-all
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
shader_config:
- group: "media"
features: ""
gstreamer_required: true
shaders: "audiovis,computecolors,fft,matrix,pathtracing,scenecolor,voronoi,fluid,cnn,synth,blockgame,kuwahara,lego,gaussian"
- group: "no-media"
features: "--no-default-features"
gstreamer_required: false
shaders: "buddhabrot,lich,mandelbulb,sdvert,sinh,orbits,nebula,rorschach,tree,2dneuron,spiralchaos,cliffordcompute,volumepassage,jfa,system,tameimp,gaussian3d,physarum"
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
ext: ""
archive_ext: ".tar.gz"
- os: windows-latest
target: x86_64-pc-windows-msvc
ext: ".exe"
archive_ext: ".zip"
- os: macos-latest
target: x86_64-apple-darwin
ext: ""
archive_ext: ".tar.gz"
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install macOS dependencies
if: runner.os == 'macOS' && matrix.shader_config.gstreamer_required
run: |
# Download and install official GStreamer packages
GSTREAMER_VERSION="1.28.1"
curl -L "https://gstreamer.freedesktop.org/data/pkg/osx/$GSTREAMER_VERSION/gstreamer-1.0-$GSTREAMER_VERSION-universal.pkg" -o gstreamer.pkg
curl -L "https://gstreamer.freedesktop.org/data/pkg/osx/$GSTREAMER_VERSION/gstreamer-1.0-devel-$GSTREAMER_VERSION-universal.pkg" -o gstreamer-devel.pkg
sudo installer -pkg gstreamer.pkg -target /
sudo installer -pkg gstreamer-devel.pkg -target /
# Set environment variables for build and runtime
echo "PKG_CONFIG_PATH=/Library/Frameworks/GStreamer.framework/Versions/1.0/lib/pkgconfig" >> $GITHUB_ENV
echo "GST_PLUGIN_PATH=/Library/Frameworks/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0" >> $GITHUB_ENV
echo "DYLD_FALLBACK_LIBRARY_PATH=/Library/Frameworks/GStreamer.framework/Versions/1.0/lib" >> $GITHUB_ENV
- name: Install Linux dependencies
if: runner.os == 'Linux' && matrix.shader_config.gstreamer_required
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libudev-dev \
pkg-config \
build-essential \
libglib2.0-dev \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
patchelf
- name: Install Windows dependencies
if: runner.os == 'Windows' && matrix.shader_config.gstreamer_required
shell: pwsh
run: |
Write-Host "Starting GStreamer installation process..."
$gstVer = "1.28.1"
$tempDir = "C:\gst-temp"
$installDir = "C:\gstreamer"
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
# GStreamer 1.28+ uses Inno Setup installer (single exe, no separate devel package)
$installerUrl = "https://github.com/altunenes/cuneus/releases/download/dependencies/gstreamer-1.0-msvc-x86_64-$gstVer.exe"
Write-Host "Downloading GStreamer $gstVer installer..."
Invoke-WebRequest -Uri $installerUrl -OutFile "$tempDir\gstreamer.exe"
# Inno Setup silent flags
Write-Host "Installing GStreamer..."
$proc = Start-Process -FilePath "$tempDir\gstreamer.exe" `
-ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/DIR=$installDir" `
-Wait -PassThru
if ($proc.ExitCode -ne 0) {
Write-Host "Installation failed with exit code: $($proc.ExitCode)"; exit 1
}
Write-Host "Searching for GStreamer installation..."
# Dump directory tree to find where Inno Setup actually installed
Write-Host "=== Contents of C:\ ==="
Get-ChildItem "C:\" | Select-Object Name | Format-Table
if (Test-Path $installDir) {
Write-Host "=== Contents of $installDir (3 levels) ==="
Get-ChildItem $installDir -Recurse -Depth 3 | Select-Object FullName | Format-Table
}
Write-Host "=== Checking Program Files ==="
Get-ChildItem "C:\Program Files" | Where-Object { $_.Name -like "*gstreamer*" -or $_.Name -like "*GStreamer*" } | Format-Table
# Try known possible paths
$possiblePaths = @(
"$installDir\1.0\msvc_x86_64",
"$installDir\msvc_x86_64",
$installDir,
"C:\Program Files\GStreamer\1.0\msvc_x86_64",
"C:\Program Files (x86)\GStreamer\1.0\msvc_x86_64"
)
$expectedPath = $null
foreach ($p in $possiblePaths) {
if (Test-Path "$p\bin") {
Write-Host "Found GStreamer at: $p"
$expectedPath = $p
break
}
}
if ($null -eq $expectedPath) {
Write-Host "ERROR: Could not find GStreamer installation"
exit 1
}
# Verify core GStreamer DLL is present
$dllToCheck = "bin\gstreamer-1.0-0.dll"
$fullDllPath = Join-Path $expectedPath $dllToCheck
if (Test-Path $fullDllPath) {
Write-Host "Found: $dllToCheck"
} else {
Write-Host "Missing critical file: $dllToCheck in $expectedPath"
Get-ChildItem -Path $expectedPath -Recurse | Format-List
exit 1
}
Write-Host "Setting environment variables..."
$env:GSTREAMER_1_0_ROOT_MSVC_X86_64 = $expectedPath
$env:GST_PLUGIN_PATH = "$expectedPath\lib\gstreamer-1.0"
$env:PKG_CONFIG_PATH = "$expectedPath\lib\pkgconfig"
$env:Path = "$expectedPath\bin;" + $env:Path
"GSTREAMER_1_0_ROOT_MSVC_X86_64=$expectedPath" | Out-File -FilePath $env:GITHUB_ENV -Append
"GST_PLUGIN_PATH=$expectedPath\lib\gstreamer-1.0" | Out-File -FilePath $env:GITHUB_ENV -Append
"PKG_CONFIG_PATH=$expectedPath\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Append
"PATH=$expectedPath\bin;$env:Path" | Out-File -FilePath $env:GITHUB_ENV -Append
Write-Host "GStreamer installation completed successfully."
- name: Build binaries
shell: bash
run: |
echo "Building ${{ matrix.shader_config.group }} shaders with features: ${{ matrix.shader_config.features }}"
# CRITICAL: Set linker flags for macOS to allow install_name_tool modifications
# Without this flag, install_name_tool silently fails due to insufficient header padding
# This enables fixing @rpath entries to point to bundled GStreamer libraries
if [[ "${{ runner.os }}" == "macOS" ]]; then
export RUSTFLAGS="$RUSTFLAGS -C link-arg=-Wl,-headerpad_max_install_names"
fi
IFS=',' read -ra SHADERS <<< "${{ matrix.shader_config.shaders }}"
for shader in "${SHADERS[@]}"; do
echo "Building shader: $shader"
cargo build --release --example "$shader" ${{ matrix.shader_config.features }}
done
- name: Bundle GStreamer for media shaders
if: matrix.shader_config.gstreamer_required
shell: bash
run: |
echo "🔧 Creating GStreamer bundle for media shaders on ${{ runner.os }}..."
BUNDLE_DIR="gstreamer_bundle"
mkdir -p "$BUNDLE_DIR/lib"
mkdir -p "$BUNDLE_DIR/lib/gstreamer-1.0"
mkdir -p "$BUNDLE_DIR/etc/ssl/certs"
if [[ "${{ runner.os }}" == "macOS" ]]; then
echo " Bundling GStreamer for macOS..."
GSTREAMER_ROOT="/Library/Frameworks/GStreamer.framework/Versions/1.0"
LIB_EXT="dylib"
# Core libraries (macOS)
CORE_LIBS=(
"libgstreamer-1.0.0.dylib" "libgstbase-1.0.0.dylib" "libgstapp-1.0.0.dylib"
"libgstvideo-1.0.0.dylib" "libgstaudio-1.0.0.dylib" "libgstpbutils-1.0.0.dylib"
"libgsttag-1.0.0.dylib" "libgstfft-1.0.0.dylib" "libgstgl-1.0.0.dylib"
"libgstvulkan-1.0.0.dylib" "libgstcodecparsers-1.0.0.dylib" "libgstriff-1.0.0.dylib"
"libgstrtp-1.0.0.dylib" "libglib-2.0.0.dylib" "libgobject-2.0.0.dylib"
"libgio-2.0.0.dylib" "libgmodule-2.0.0.dylib" "libintl.8.dylib" "libffi.7.dylib"
"libpcre2-8.0.dylib" "liborc-0.4.0.dylib" "libz.1.dylib" "libbz2.1.dylib"
"libavformat.61.dylib" "libavutil.59.dylib" "libavcodec.61.dylib"
"libavfilter.10.dylib" "libswresample.5.dylib" "libSoundTouch.2.dylib" "libMoltenVK.dylib"
)
# Plugins (macOS)
PLUGINS=(
"libgstcoreelements.dylib" "libgstapp.dylib" "libgstplayback.dylib"
"libgsttypefindfunctions.dylib" "libgstvideoconvertscale.dylib" "libgstvideorate.dylib"
"libgstaudioconvert.dylib" "libgstaudioresample.dylib" "libgstvolume.dylib"
"libgstosxaudio.dylib" "libgstautodetect.dylib" "libgstaudiotestsrc.dylib"
"libgstaudiomixer.dylib" "libgstspectrum.dylib" "libgstsoundtouch.dylib"
"libgstaudioparsers.dylib" "libgstlibav.dylib" "libgstisomp4.dylib"
"libgstapplemedia.dylib" "libgstvideoparsersbad.dylib"
)
elif [[ "${{ runner.os }}" == "Windows" ]]; then
echo "Bundling GStreamer for Windows..."
GSTREAMER_ROOT="${GSTREAMER_1_0_ROOT_MSVC_X86_64:-C:/gstreamer/1.0/msvc_x86_64}"
LIB_EXT="dll"
# Core libraries (Windows) - these come from both lib/ and bin/ directories
CORE_LIBS=(
# GStreamer core (from lib/)
"gstreamer-1.0-0.dll" "gstbase-1.0-0.dll" "gstapp-1.0-0.dll"
"gstvideo-1.0-0.dll" "gstaudio-1.0-0.dll" "gstpbutils-1.0-0.dll"
"gsttag-1.0-0.dll" "gstfft-1.0-0.dll" "gstgl-1.0-0.dll"
"gstvulkan-1.0-0.dll" "gstcodecparsers-1.0-0.dll" "gstriff-1.0-0.dll"
"gstrtp-1.0-0.dll" "gstrtsp-1.0-0.dll" "gstsdp-1.0-0.dll"
"gstnet-1.0-0.dll" "gstcontroller-1.0-0.dll"
# GLib and dependencies (from lib/)
"glib-2.0-0.dll" "gobject-2.0-0.dll" "gio-2.0-0.dll"
"gmodule-2.0-0.dll" "gthread-2.0-0.dll"
"intl-8.dll" "ffi-7.dll" "pcre2-8-0.dll"
# Core dependencies (from lib/)
"orc-0.4-0.dll" "z.dll" "z-1.dll" "bz2.dll"
# Additional dependencies (from lib/)
"libwinpthread-1.dll" "libgcc_s_seh-1.dll" "libstdc++-6.dll"
"libiconv-2.dll" "libcharset-1.dll"
)
# CRITICAL: Additional codec libraries from bin/ directory
# These are essential for video processing and were missing!
BIN_CODEC_LIBS=(
"avformat-61.dll" "avcodec-61.dll" "avutil-59.dll"
"avfilter-10.dll" "swresample-5.dll" "swscale-8.dll"
"opus-0.dll" "vorbis-0.dll" "ogg-0.dll"
"mp3lame-0.dll" "libmpg123-1.dll"
)
# Plugins (Windows)
PLUGINS=(
# Core elements
"gstcoreelements.dll" "gstapp.dll" "gstplayback.dll"
"gsttypefindfunctions.dll" "gstvideoconvertscale.dll" "gstvideorate.dll"
"gstaudioconvert.dll" "gstaudioresample.dll" "gstvolume.dll"
"gstvideofilter.dll" "gstgio.dll"
# VIDEO DECODERS
"gstlibav.dll"
# Windows audio
"gstwasapi.dll" "gstwasapi2.dll" "gstdirectsound.dll"
"gstautodetect.dll" "gstaudiotestsrc.dll"
# Audio processing
"gstaudiomixer.dll" "gstspectrum.dll" "gstsoundtouch.dll"
"gstaudioparsers.dll"
# Container formats
"gstisomp4.dll" "gstvideoparsersbad.dll" "gstmatroska.dll"
"gstavi.dll" "gstflv.dll" "gstmpegtsdemux.dll" "gstasf.dll"
# Codec plugins
"gstvpx.dll" "gsttheora.dll" "gstmpg123.dll" "gstlame.dll"
"gstvorbis.dll" "gstogg.dll"
# Windows-specific video - for DirectShow support
"gstdirectshow.dll" "gstwinks.dll"
)
# Windows: Also create directories for helper executables
mkdir -p "$BUNDLE_DIR/bin"
mkdir -p "$BUNDLE_DIR/libexec/gstreamer-1.0"
elif [[ "${{ runner.os }}" == "Linux" ]]; then
echo " Bundling GStreamer for Linux..."
GSTREAMER_ROOT="/usr"
LIB_EXT="so"
# Core libraries (Linux)
CORE_LIBS=(
"libgstreamer-1.0.so.0" "libgstbase-1.0.so.0" "libgstapp-1.0.so.0"
"libgstvideo-1.0.so.0" "libgstaudio-1.0.so.0" "libgstpbutils-1.0.so.0"
"libgsttag-1.0.so.0" "libgstfft-1.0.so.0" "libgstgl-1.0.so.0"
"libgstvulkan-1.0.so.0" "libgstcodecparsers-1.0.so.0" "libgstriff-1.0.so.0"
"libgstrtp-1.0.so.0" "libglib-2.0.so.0" "libgobject-2.0.so.0"
"libgio-2.0.so.0" "libgmodule-2.0.so.0" "liborc-0.4.so.0"
)
# Plugins (Linux)
PLUGINS=(
"libgstcoreelements.so" "libgstapp.so" "libgstplayback.so"
"libgsttypefindfunctions.so" "libgstvideoconvert.so" "libgstvideorate.so"
"libgstaudioconvert.so" "libgstaudioresample.so" "libgstvolume.so"
"libgstpulseaudio.so" "libgstautodetect.so" "libgstaudiotestsrc.so"
"libgstaudiomixer.so" "libgstspectrum.so" "libgstsoundtouch.so"
"libgstaudioparsers.so" "libgstlibav.so" "libgstisomp4.so"
"libgstvideoparsersbad.so" "libgstv4l2.so"
)
fi
# Copy libraries
for lib in "${CORE_LIBS[@]}"; do
if [[ -f "$GSTREAMER_ROOT/lib/$lib" ]]; then
cp "$GSTREAMER_ROOT/lib/$lib" "$BUNDLE_DIR/lib/" 2>/dev/null || true
elif [[ -f "$GSTREAMER_ROOT/bin/$lib" ]]; then
cp "$GSTREAMER_ROOT/bin/$lib" "$BUNDLE_DIR/lib/" 2>/dev/null || true
fi
done
# Copy additional codec libraries from bin/ directory (Windows only)
if [[ "${{ runner.os }}" == "Windows" ]]; then
echo " Copying critical codec libraries from bin directory..."
echo " Checking GStreamer installation at: $GSTREAMER_ROOT"
echo " Expected bin directory: $GSTREAMER_ROOT/bin"
# Debug: List what's actually in the bin directory
if [[ -d "$GSTREAMER_ROOT/bin" ]]; then
echo " Bin directory exists. Contents:"
ls -la "$GSTREAMER_ROOT/bin" | head -20
echo " Total files in bin: $(ls -1 "$GSTREAMER_ROOT/bin" | wc -l)"
else
echo " Bin directory does not exist at: $GSTREAMER_ROOT/bin"
echo " Available directories in $GSTREAMER_ROOT:"
ls -la "$GSTREAMER_ROOT/" 2>/dev/null || echo "GSTREAMER_ROOT path invalid"
fi
for lib in "${BIN_CODEC_LIBS[@]}"; do
if [[ -f "$GSTREAMER_ROOT/bin/$lib" ]]; then
# Copy to bin directory so Windows restructuring can find them
cp "$GSTREAMER_ROOT/bin/$lib" "$BUNDLE_DIR/bin/" 2>/dev/null || true
echo " Copied codec to bin/: $lib"
else
echo " Missing codec: $lib (checked: $GSTREAMER_ROOT/bin/$lib)"
fi
done
fi
# Copy plugins with detailed logging
echo " Copying GStreamer plugins..."
echo " Plugin directory: $GSTREAMER_ROOT/lib/gstreamer-1.0"
# Debug: Check plugin directory
if [[ -d "$GSTREAMER_ROOT/lib/gstreamer-1.0" ]]; then
echo " Plugin directory exists"
echo " Total plugins available: $(ls -1 "$GSTREAMER_ROOT/lib/gstreamer-1.0"/*.dll 2>/dev/null | wc -l)"
# Check for critical plugins specifically
for critical in "gstlibav.dll" "gstmpegtsdemux.dll" "gstasf.dll"; do
if [[ -f "$GSTREAMER_ROOT/lib/gstreamer-1.0/$critical" ]]; then
echo " Found critical plugin: $critical"
else
echo " Critical plugin missing: $critical"
fi
done
else
echo " Plugin directory does not exist"
fi
for plugin in "${PLUGINS[@]}"; do
if [[ -f "$GSTREAMER_ROOT/lib/gstreamer-1.0/$plugin" ]]; then
cp "$GSTREAMER_ROOT/lib/gstreamer-1.0/$plugin" "$BUNDLE_DIR/lib/gstreamer-1.0/" 2>/dev/null || true
echo " Copied plugin: $plugin"
# Special check for critical macOS camera plugin
if [[ "$plugin" == "libgstapplemedia.dylib" ]]; then
echo " CRITICAL: macOS camera plugin copied successfully"
fi
else
echo " MISSING plugin: $plugin (checked: $GSTREAMER_ROOT/lib/gstreamer-1.0/$plugin)"
# Special warning for critical macOS camera plugin
if [[ "$plugin" == "libgstapplemedia.dylib" ]]; then
echo " CRITICAL ERROR: macOS camera plugin NOT FOUND - webcam will not work!"
fi
fi
done
# Verify critical plugins are present
if [[ "${{ runner.os }}" == "Windows" ]]; then
echo " Verifying critical plugins in bundle..."
critical_plugins=("gstlibav.dll" "gstmpegtsdemux.dll" "gstasf.dll")
for critical in "${critical_plugins[@]}"; do
if [[ -f "$BUNDLE_DIR/lib/gstreamer-1.0/$critical" ]]; then
echo " Critical plugin verified: $critical"
else
echo " CRITICAL PLUGIN MISSING: $critical"
fi
done
fi
# Copy SSL certificates
if [[ -f "$GSTREAMER_ROOT/etc/ssl/certs/ca-certificates.crt" ]]; then
cp "$GSTREAMER_ROOT/etc/ssl/certs/ca-certificates.crt" "$BUNDLE_DIR/etc/ssl/certs/"
elif [[ -f "/etc/ssl/certs/ca-certificates.crt" ]]; then
cp "/etc/ssl/certs/ca-certificates.crt" "$BUNDLE_DIR/etc/ssl/certs/"
fi
# Copy GStreamer helper binaries
if [[ "${{ runner.os }}" == "Windows" ]]; then
# For Windows, copy important executables and helpers
for exe in "gst-plugin-scanner.exe" "gst-inspect-1.0.exe" "gst-launch-1.0.exe"; do
if [[ -f "$GSTREAMER_ROOT/bin/$exe" ]]; then
cp "$GSTREAMER_ROOT/bin/$exe" "$BUNDLE_DIR/bin/" 2>/dev/null || true
fi
done
# Copy plugin scanner to libexec
if [[ -f "$GSTREAMER_ROOT/libexec/gstreamer-1.0/gst-plugin-scanner.exe" ]]; then
cp "$GSTREAMER_ROOT/libexec/gstreamer-1.0/gst-plugin-scanner.exe" "$BUNDLE_DIR/libexec/gstreamer-1.0/" 2>/dev/null || true
elif [[ -f "$GSTREAMER_ROOT/bin/gst-plugin-scanner.exe" ]]; then
cp "$GSTREAMER_ROOT/bin/gst-plugin-scanner.exe" "$BUNDLE_DIR/libexec/gstreamer-1.0/" 2>/dev/null || true
fi
else
# macOS and Linux
mkdir -p "$BUNDLE_DIR/libexec/gstreamer-1.0"
mkdir -p "$BUNDLE_DIR/bin"
# Copy plugin scanner
if [[ -f "$GSTREAMER_ROOT/libexec/gstreamer-1.0/gst-plugin-scanner" ]]; then
cp "$GSTREAMER_ROOT/libexec/gstreamer-1.0/gst-plugin-scanner" "$BUNDLE_DIR/libexec/gstreamer-1.0/"
chmod +x "$BUNDLE_DIR/libexec/gstreamer-1.0/gst-plugin-scanner"
fi
# Copy essential tools
for tool in "gst-inspect-1.0" "gst-typefind-1.0" "gst-launch-1.0"; do
if [[ -f "$GSTREAMER_ROOT/bin/$tool" ]]; then
cp "$GSTREAMER_ROOT/bin/$tool" "$BUNDLE_DIR/bin/"
chmod +x "$BUNDLE_DIR/bin/$tool"
fi
done
fi
echo " GStreamer bundle created: $(du -sh $BUNDLE_DIR | cut -f1)"
- name: Prepare release packages
shell: bash
run: |
IFS=',' read -ra SHADERS <<< "${{ matrix.shader_config.shaders }}"
for shader in "${SHADERS[@]}"; do
echo "Packaging shader: $shader"
# Create directory structure
mkdir -p "release/$shader/shaders"
# Copy the binary
cp "target/release/examples/$shader${{ matrix.ext }}" "release/$shader/"
# Copy shader files
cp "examples/shaders/$shader.wgsl" "release/$shader/shaders/"
# Embed GStreamer for media shaders (cross-platform)
if [[ "${{ matrix.shader_config.gstreamer_required }}" == "true" ]]; then
echo " Embedding GStreamer for $shader..."
# Copy GStreamer bundle
cp -r "gstreamer_bundle" "release/$shader/gstreamer"
# Fix library paths (platform-specific)
echo " Fixing library paths for ${{ runner.os }}..."
BINARY="release/$shader/$shader${{ matrix.ext }}"
BUNDLE_LIB_DIR="release/$shader/gstreamer/lib"
BUNDLE_PLUGIN_DIR="$BUNDLE_LIB_DIR/gstreamer-1.0"
echo "Debug: Binary path: $BINARY"
echo "Debug: Bundle lib dir: $BUNDLE_LIB_DIR"
echo "Debug: Bundle exists: $(test -d release/$shader/gstreamer && echo yes || echo no)"
if [[ "${{ runner.os }}" == "macOS" ]]; then
echo " Processing macOS library paths..."
# Fix main binary
echo "Fixing main binary: $BINARY"
if [[ -f "$BINARY" ]]; then
echo "Before fixing:"
otool -L "$BINARY" | grep -E "(@rpath|/Library/Frameworks/GStreamer.framework)" || echo "No problematic paths found"
# Create a temporary file with the dependencies to fix
TEMP_DEPS="/tmp/deps_to_fix_$$"
otool -L "$BINARY" 2>/dev/null | grep -E "(@rpath|/Library/Frameworks/GStreamer.framework)" | awk '{print $1}' > "$TEMP_DEPS"
while read -r dep; do
if [[ -n "$dep" ]]; then
lib_name=$(basename "$dep")
new_path="@executable_path/gstreamer/lib/$lib_name"
echo " Changing: $dep -> $new_path"
install_name_tool -change "$dep" "$new_path" "$BINARY" 2>/dev/null || echo " Failed to change $dep"
fi
done < "$TEMP_DEPS"
rm -f "$TEMP_DEPS"
echo "After fixing:"
otool -L "$BINARY" | grep -E "(@rpath|/Library/Frameworks/GStreamer.framework)" || echo "All paths fixed!"
else
echo " Binary not found: $BINARY"
fi
# Fix all libraries
echo "Fixing libraries in: $BUNDLE_LIB_DIR"
if [[ -d "$BUNDLE_LIB_DIR" ]]; then
for lib in "$BUNDLE_LIB_DIR"/*.dylib; do
if [[ -f "$lib" ]]; then
lib_name=$(basename "$lib")
echo " Processing library: $lib_name"
# Use a subshell to prevent script exit on errors
(
# Check if library is writable before attempting to modify
if [[ ! -w "$lib" ]]; then
echo " Skipping $lib_name (read-only)"
exit 0
fi
# Test if we can modify this library at all
if ! install_name_tool -id "@loader_path/$lib_name" "$lib" 2>/dev/null; then
echo " Skipping $lib_name (cannot modify - likely system/protected library)"
exit 0
fi
echo " Successfully modified ID for $lib_name"
# Fix dependencies
otool -L "$lib" 2>/dev/null | grep -E "(@rpath|/Library/Frameworks/GStreamer.framework)" | awk '{print $1}' | while read dep; do
if [[ -n "$dep" ]]; then
dep_lib_name=$(basename "$dep")
new_dep_path="@loader_path/$dep_lib_name"
if install_name_tool -change "$dep" "$new_dep_path" "$lib" 2>/dev/null; then
echo " Fixed dependency: $dep"
else
echo " Warning: Could not change dependency $dep in $lib_name"
fi
fi
done
) || echo " Error processing $lib_name, continuing..."
fi
done
else
echo " Library directory not found: $BUNDLE_LIB_DIR"
fi
# Fix all plugins
echo "Fixing plugins in: $BUNDLE_PLUGIN_DIR"
if [[ -d "$BUNDLE_PLUGIN_DIR" ]]; then
for plugin in "$BUNDLE_PLUGIN_DIR"/*.dylib; do
if [[ -f "$plugin" ]]; then
plugin_name=$(basename "$plugin")
echo " Processing plugin: $plugin_name"
# Use a subshell to prevent script exit on errors
(
# Try to set the plugin ID, skip if it fails
if ! install_name_tool -id "@loader_path/../$plugin_name" "$plugin" 2>/dev/null; then
echo " Skipping $plugin_name (cannot modify)"
exit 0
fi
echo " Successfully modified ID for $plugin_name"
# Fix dependencies
otool -L "$plugin" 2>/dev/null | grep -E "(@rpath|/Library/Frameworks/GStreamer.framework)" | awk '{print $1}' | while read dep; do
if [[ -n "$dep" ]]; then
dep_lib_name=$(basename "$dep")
new_dep_path="@loader_path/../$dep_lib_name"
if install_name_tool -change "$dep" "$new_dep_path" "$plugin" 2>/dev/null; then
echo " Fixed dependency: $dep"
else
echo " Warning: Could not change dependency $dep in $plugin_name"
fi
fi
done
) || echo " Error processing $plugin_name, continuing..."
fi
done
else
echo " Plugin directory not found: $BUNDLE_PLUGIN_DIR"
fi
elif [[ "${{ runner.os }}" == "Linux" ]]; then
# Linux: Use patchelf and $ORIGIN rpath
echo "Setting rpath for Linux binary..."
if command -v patchelf >/dev/null 2>&1; then
patchelf --set-rpath '$ORIGIN/gstreamer/lib' "$BINARY" 2>/dev/null || true
# Fix all libraries
for lib in "$BUNDLE_LIB_DIR"/*.so*; do
if [[ -f "$lib" ]]; then
patchelf --set-rpath '$ORIGIN' "$lib" 2>/dev/null || true
fi
done
# Fix all plugins
for plugin in "$BUNDLE_PLUGIN_DIR"/*.so; do
if [[ -f "$plugin" ]]; then
patchelf --set-rpath '$ORIGIN/..' "$plugin" 2>/dev/null || true
fi
done
else
echo " patchelf not available, library paths may not work correctly"
fi
elif [[ "${{ runner.os }}" == "Windows" ]]; then
# Windows: DLLs use current directory by default, so restructure
echo "Windows DLL bundling and structure optimization..."
# Copy DLLs to release directory root (alongside exe)
echo " Restructuring Windows GStreamer layout..."
# Copy all DLLs directly to exe directory (includes both core libs and codec libs)
echo " Copying all DLLs from lib/ to exe directory..."
cp "release/$shader/gstreamer/lib"/*.dll "release/$shader/" 2>/dev/null || true
# Copy codec DLLs from bin directory to exe directory
if [[ -d "release/$shader/gstreamer/bin" ]]; then
echo " Copying critical codec DLLs from bin directory to exe directory..."
# Explicitly copy the 12 required codec DLLs
for codec_dll in "avformat-61.dll" "avcodec-61.dll" "avutil-59.dll" \
"avfilter-10.dll" "swresample-5.dll" "swscale-8.dll" \
"libvpx-1.dll" "libopus-0.dll" "libvorbis-0.dll" \
"libogg-0.dll" "libmp3lame-0.dll" "libmpg123-0.dll"; do
if [[ -f "release/$shader/gstreamer/bin/$codec_dll" ]]; then
cp "release/$shader/gstreamer/bin/$codec_dll" "release/$shader/" 2>/dev/null || true
echo " Copied critical codec: $codec_dll"
else
echo " MISSING critical codec: $codec_dll"
fi
done
# Copy other executables from bin
cp "release/$shader/gstreamer/bin"/*.exe "release/$shader/" 2>/dev/null || true
else
echo " CRITICAL ERROR: No bin directory found in GStreamer bundle!"
fi
# Debug: Final count of DLLs in exe directory
echo " Final DLL count in exe directory:"
ls -la "release/$shader/"*.dll 2>/dev/null | wc -l | xargs echo " Total DLLs:"
# Keep the lib folder structure for plugins
mkdir -p "release/$shader/lib"
cp -r "release/$shader/gstreamer/lib/gstreamer-1.0" "release/$shader/lib/" 2>/dev/null || true
# Keep libexec for plugin scanner
if [[ -d "release/$shader/gstreamer/libexec" ]]; then
cp -r "release/$shader/gstreamer/libexec" "release/$shader/" 2>/dev/null || true
fi
# Keep etc for configs
if [[ -d "release/$shader/gstreamer/etc" ]]; then
cp -r "release/$shader/gstreamer/etc" "release/$shader/" 2>/dev/null || true
fi
# Remove the original gstreamer folder
rm -rf "release/$shader/gstreamer" 2>/dev/null || true
echo " Windows structure: exe + DLLs + lib/ + etc/ + libexec/"
fi
echo " GStreamer embedded and paths fixed"
fi
# Create appropriate README based on shader group
echo "$shader Shader" > "release/$shader/README.txt"
echo "Requirements:" >> "release/$shader/README.txt"
if [[ "${{ matrix.shader_config.gstreamer_required }}" == "true" ]]; then
echo "1. GStreamer is bundled!" >> "release/$shader/README.txt"
echo "2. The 'shaders' directory must remain in the same folder as the executable." >> "release/$shader/README.txt"
echo "3. Self-contained build with embedded media support (~80-100MB)." >> "release/$shader/README.txt"
if [[ "${{ runner.os }}" == "Windows" ]]; then
echo "" >> "release/$shader/README.txt"
echo "Windows Notes:" >> "release/$shader/README.txt"
echo "- If you see errors about missing DLLs, install Visual C++ Redistributable:" >> "release/$shader/README.txt"
echo " https://aka.ms/vs/17/release/vc_redist.x64.exe" >> "release/$shader/README.txt"
echo "- Use the .bat launcher for best compatibility" >> "release/$shader/README.txt"
fi
else
echo "1. just run the executable!" >> "release/$shader/README.txt"
echo "2. The 'shaders' directory must remain in the same folder as the executable." >> "release/$shader/README.txt"
fi
# Windows-specific: create .bat launcher
if [[ "${{ runner.os }}" == "Windows" ]]; then
# For Windows,
if [[ "${{ matrix.shader_config.gstreamer_required }}" == "true" ]]; then
echo "@echo off" > "release/$shader/run_$shader.bat"
echo "setlocal" >> "release/$shader/run_$shader.bat"
echo "" >> "release/$shader/run_$shader.bat"
echo "REM Ensure shader directory exists" >> "release/$shader/run_$shader.bat"
echo "if not exist shaders mkdir shaders" >> "release/$shader/run_$shader.bat"
echo "" >> "release/$shader/run_$shader.bat"
echo "REM Set working directory to script location" >> "release/$shader/run_$shader.bat"
echo "cd /d \"%~dp0\"" >> "release/$shader/run_$shader.bat"
echo "" >> "release/$shader/run_$shader.bat"
echo "REM Configure GStreamer environment" >> "release/$shader/run_$shader.bat"
echo "set GST_PLUGIN_PATH=%~dp0lib\\gstreamer-1.0" >> "release/$shader/run_$shader.bat"
echo "set GST_PLUGIN_SYSTEM_PATH=" >> "release/$shader/run_$shader.bat"
echo "set GST_REGISTRY=%~dp0gst-registry.bin" >> "release/$shader/run_$shader.bat"
echo "set GST_PLUGIN_SCANNER=%~dp0libexec\\gstreamer-1.0\\gst-plugin-scanner.exe" >> "release/$shader/run_$shader.bat"
echo "" >> "release/$shader/run_$shader.bat"
echo "REM Disable conflicting environment variables" >> "release/$shader/run_$shader.bat"
echo "set GSTREAMER_1_0_ROOT_MSVC_X86_64=" >> "release/$shader/run_$shader.bat"
echo "set GSTREAMER_1_0_ROOT_MINGW_X86_64=" >> "release/$shader/run_$shader.bat"
echo "" >> "release/$shader/run_$shader.bat"
echo "REM Run the shader" >> "release/$shader/run_$shader.bat"
echo "$shader.exe %*" >> "release/$shader/run_$shader.bat"
echo "endlocal" >> "release/$shader/run_$shader.bat"
else
# Simple launcher for non-media shaders
echo "@echo off" > "release/$shader/run_$shader.bat"
echo "if not exist shaders mkdir shaders" >> "release/$shader/run_$shader.bat"
echo "$shader.exe %*" >> "release/$shader/run_$shader.bat"
fi
fi
# Create launcher script for Linux/MacOS
if [[ "${{ runner.os }}" != "Windows" ]]; then
echo '#!/bin/bash' > "release/$shader/run_$shader.sh"
echo 'mkdir -p shaders' >> "release/$shader/run_$shader.sh"
# Add GStreamer environment setup for media shaders
if [[ "${{ matrix.shader_config.gstreamer_required }}" == "true" ]]; then
echo 'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' >> "release/$shader/run_$shader.sh"
echo 'export GST_PLUGIN_PATH="$SCRIPT_DIR/gstreamer/lib/gstreamer-1.0"' >> "release/$shader/run_$shader.sh"
echo 'export GST_PLUGIN_SYSTEM_PATH=""' >> "release/$shader/run_$shader.sh"
echo 'export GST_REGISTRY="$SCRIPT_DIR/gstreamer/registry.bin"' >> "release/$shader/run_$shader.sh"
# Set plugin scanner for macOS and Linux
if [[ "${{ runner.os }}" == "macOS" ]]; then
echo 'export GST_PLUGIN_SCANNER="$SCRIPT_DIR/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner"' >> "release/$shader/run_$shader.sh"
echo '# Disable system GStreamer to prevent conflicts' >> "release/$shader/run_$shader.sh"
echo 'export DYLD_LIBRARY_PATH="$SCRIPT_DIR/gstreamer/lib:$DYLD_LIBRARY_PATH"' >> "release/$shader/run_$shader.sh"
echo 'export DYLD_FALLBACK_LIBRARY_PATH="$SCRIPT_DIR/gstreamer/lib"' >> "release/$shader/run_$shader.sh"
echo 'unset PKG_CONFIG_PATH' >> "release/$shader/run_$shader.sh"
elif [[ "${{ runner.os }}" == "Linux" ]]; then
echo 'export GST_PLUGIN_SCANNER="$SCRIPT_DIR/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner"' >> "release/$shader/run_$shader.sh"
fi
# Linux-specific: Add library path
if [[ "${{ runner.os }}" == "Linux" ]]; then
echo 'export LD_LIBRARY_PATH="$SCRIPT_DIR/gstreamer/lib:$LD_LIBRARY_PATH"' >> "release/$shader/run_$shader.sh"
fi
fi
echo "./$shader \"\$@\"" >> "release/$shader/run_$shader.sh"
chmod +x "release/$shader/run_$shader.sh"
fi
done
- name: Create archives
shell: bash
run: |
cd release
IFS=',' read -ra SHADERS <<< "${{ matrix.shader_config.shaders }}"
for shader in "${SHADERS[@]}"; do
echo "Creating archive for: $shader"
if [[ "${{ runner.os }}" == "Windows" ]]; then
7z a "../$shader-${{ matrix.target }}${{ matrix.archive_ext }}" "$shader"
else
tar -czf "../$shader-${{ matrix.target }}${{ matrix.archive_ext }}" "$shader"
fi
done
- name: Upload artifacts
shell: bash
run: |
IFS=',' read -ra SHADERS <<< "${{ matrix.shader_config.shaders }}"
for shader in "${SHADERS[@]}"; do
echo "Uploading artifact for: $shader"
done
- name: Upload artifacts to GitHub
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.shader_config.group }}-${{ matrix.target }}
path: "*-${{ matrix.target }}${{ matrix.archive_ext }}"
release:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: artifacts/**/*
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
prerelease: false
generate_release_notes: true