Files
SageTube/.github/workflows/build-macos.yml
T
oop7 c11a4a0280 Enhance macOS build lib cleanup
Update the macOS build workflow to robustly locate and clean multiple library directories inside the .app bundle. Instead of a single lib path, the script now finds all 'lib' dirs containing PySide6 or ytsage and iterates over them. Cleanup expanded to handle alternate PySide6 paths (PySide6/Qt/translations, PySide6/Qt/plugins), more plugin/imageformat variants (.dylib/.so), additional Qt library name patterns (Qt6*, framework-style names) and Qt6DBus, and removal of common build tools/modules. Adds logging and a warning when no library dirs are found.
2026-02-03 15:58:23 +02:00

349 lines
12 KiB
YAML

name: Build macOS Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version name for the release (e.g., 1.0.0)'
required: true
type: string
workflow_call:
inputs:
version:
description: 'Version name for the release (e.g., 1.0.0)'
required: true
type: string
permissions:
contents: write
env:
PYTHON_VERSION: '3.13'
jobs:
build-macos:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get version
id: get_version
shell: bash
run: |
version="${{ inputs.version || github.event.inputs.version }}"
echo "Extracted version: $version"
echo "VERSION=$version" >> "$GITHUB_OUTPUT"
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: |
venv
~/.cache/pip
~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Create virtual environment and install dependencies
shell: bash
run: |
python -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install --no-cache-dir .
pip install --no-cache-dir cx_Freeze dmgbuild
- name: Prepare build variables
shell: bash
run: |
version="${{ steps.get_version.outputs.VERSION }}"
echo "VERSION=$version" >> "$GITHUB_ENV"
arch="$(uname -m)" # arm64 or x86_64
echo "ARCH=$arch" >> "$GITHUB_ENV"
if [ "$arch" = "arm64" ]; then
echo "ARCH_SUFFIX=arm64" >> "$GITHUB_ENV"
else
echo "ARCH_SUFFIX=x64" >> "$GITHUB_ENV"
fi
echo "Prepared build variables for version: $version on $(uname -a)"
- name: Create cx_Freeze setup script
shell: bash
run: |
# Create entry point script
cat > ytsage_entry.py <<'ENTRY'
from ytsage.main import main
if __name__ == "__main__":
main()
ENTRY
cat > setup_cxfreeze.py <<'PY'
import os
from cx_Freeze import setup, Executable
version = os.environ.get("VERSION", "0.0.0")
build_exe_options = dict(
optimize=2,
silent=True,
packages=[
"ytsage",
"PySide6.QtCore",
"PySide6.QtGui",
"PySide6.QtWidgets",
"PySide6.QtMultimedia",
"PySide6.QtNetwork",
"requests",
"PIL",
"packaging",
"markdown",
"loguru",
"setuptools",
],
zip_include_packages=[
"PySide6",
"shiboken6",
"requests",
"PIL",
"packaging",
],
excludes=[
"PySide6.QtBluetooth",
"PySide6.QtOpenGL",
"PySide6.QtPrintSupport",
"PySide6.QtSvg",
"PySide6.QtTest",
"PySide6.QtXml",
"PySide6.QtSql",
"PySide6.QtHelp",
"PySide6.QtQml",
"PySide6.QtQuick",
"PySide6.QtWebEngineCore",
"PIL.ImageDraw",
"PIL.ImageFont",
"numpy",
"scipy",
"wx",
"pandas",
"tkinter",
"yt_dlp",
"unittest",
"test",
"tests",
"pydoc",
"doctest",
"email",
],
include_files=[
("ytsage/assets/Icon", "lib/assets/Icon"),
("ytsage/assets/sound", "lib/assets/sound"),
("ytsage/languages", "lib/languages"),
("branding/icons", "lib/assets/branding/icons"),
],
)
executables = [
Executable(
script="ytsage_entry.py",
target_name=f"YTSage-v{version}",
icon="branding/icons/icon.icns",
)
]
setup(
name="YTSage",
version=version,
description="YTSage",
options={
"build_exe": build_exe_options,
"bdist_mac": {
"iconfile": "branding/icons/icon.icns",
"bundle_name": f"YTSage-v{version}",
},
"bdist_dmg": {
"volume_label": f"YTSage v{version}",
"applications_shortcut": True,
"format": "UDZO",
"filesystem": "HFS+",
"default_view": "icon-view",
},
},
executables=executables,
)
PY
- name: Build .app bundle (bdist_mac)
shell: bash
run: |
source venv/bin/activate
python -OO setup_cxfreeze.py bdist_mac
- name: Trim unnecessary files from App bundle
shell: bash
run: |
version="${{ steps.get_version.outputs.VERSION }}"
# Locate the built .app
app_path=""
for cand in "dist/YTSage-v${version}.app" "build/dist/YTSage-v${version}.app" "build/YTSage-v${version}.app"; do
if [ -d "$cand" ]; then app_path="$cand"; break; fi
done
if [ -z "$app_path" ]; then
app_path=$(ls -d dist/*.app build/dist/*.app build/*.app 2>/dev/null | head -n1 || true)
fi
if [ -n "$app_path" ]; then
echo "Processing App Bundle at: $app_path"
# Determine Lib directory (standard location vs customized)
# We look for all 'lib' directories that contain 'PySide6' or 'ytsage' to be thorough
# resulting in potentially multiple lib dirs to clean (e.g. MacOS/lib and Resources/lib)
lib_dirs=$(find "$app_path/Contents" -type d -name "lib" | while read -r d; do if [ -d "$d/ytsage" ] || [ -d "$d/PySide6" ]; then echo "$d"; fi; done)
if [ -z "$lib_dirs" ]; then
echo "Warning: No valid Library directory found. Skipping deep trim."
fi
for lib_dir in $lib_dirs; do
echo "Processing Lib Directory: $lib_dir"
# 1. Remove screenshots
rm -rf "$lib_dir/assets/branding/screenshots"
echo "Removed screenshots folder"
# 2. Remove unused Qt translations (Path varies: PySide6/translations or PySide6/Qt/translations)
rm -rf "$lib_dir/PySide6/translations"
rm -rf "$lib_dir/PySide6/Qt/translations"
echo "Removed Qt translations"
# 3. Remove unused Qt plugins
# Path varies: PySide6/plugins or PySide6/Qt/plugins
for plugins_base in "$lib_dir/PySide6/plugins" "$lib_dir/PySide6/Qt/plugins"; do
if [ -d "$plugins_base" ]; then
for plugin in designer pdf svg sql help qml quick webengine bluetooth opengl printsupport test xml; do
if [ -d "$plugins_base/$plugin" ]; then
rm -rf "$plugins_base/$plugin"
echo "Removed plugin folder: $plugin from $plugins_base"
fi
done
# 3.1. Explicitly remove specific input contexts and image formats
rm -rf "$plugins_base/platforminputcontexts"
rm -f "$plugins_base/imageformats/libqpdf.dylib" "$plugins_base/imageformats/libqsvg.dylib"
rm -f "$plugins_base/imageformats/libqpdf.so" "$plugins_base/imageformats/libqsvg.so" # just in case
rm -f "$plugins_base/iconengines/libqsvgicon.dylib"
fi
done
# 4. Remove any duplicate or unnecessary Qt Libraries (DLLs/dylibs/frameworks)
# On macOS, these often appear as files with no extension (e.g. 'QtQml') or .dylib
# We remove both variants and also the 'Qt6' prefixed ones just in case
echo "Removing bloat Qt libraries..."
# Standard patterns (Linux/Windows style)
find "$lib_dir" -name "*Qt6Web*" -delete
find "$lib_dir" -name "*Qt6Pdf*" -delete
find "$lib_dir" -name "*Qt6Qml*" -delete
find "$lib_dir" -name "*Qt6Quick*" -delete
find "$lib_dir" -name "*Qt6VirtualKeyboard*" -delete
find "$lib_dir" -name "*Qt6OpenGL*" -delete
find "$lib_dir" -name "*Qt6DBus*" -delete
# macOS Framework style (No '6' in name usually, e.g. QtQml, QtQuick)
# Be careful not to delete QtQuickWidgets if used (though usually safe if Quick is gone)
# We use known specific lists to avoid over-deleting
for bloat in QtQml QtQmlMeta QtQmlModels QtQmlWorkerScript QtQuick QtQuickControls2 QtQuickTemplates2 QtWebEngine QtWebEngineCore QtVirtualKeyboard QtVirtualKeyboardQml QtOpenGL QtOpenGLWidgets QtPdf QtPdfWidgets QtDBus; do
find "$lib_dir" -maxdepth 1 -name "$bloat" -delete
find "$lib_dir" -maxdepth 1 -name "${bloat}.*" -delete # Matches versions or extensions
done
# 5. Remove build tools and unused python modules
for tool in setuptools wheel pkg_resources _distutils_hack curses _pyrepl; do
rm -rf "$lib_dir/$tool"
echo "Removed build tool/module: $tool"
done
done
else
echo "Error: Could not find .app bundle to trim!"
exit 1
fi
- name: Build DMG (bdist_dmg)
shell: bash
run: |
source venv/bin/activate
python setup_cxfreeze.py bdist_dmg
- name: Package and prepare release artifacts (.app.zip and .dmg)
shell: bash
run: |
version="${{ steps.get_version.outputs.VERSION }}"
echo "Preparing artifacts for version: $version"
mkdir -p artifacts
# Find the .app bundle
app_path=""
for cand in "dist/YTSage-v${version}.app" "build/dist/YTSage-v${version}.app" "build/YTSage-v${version}.app"; do
if [ -d "$cand" ]; then app_path="$cand"; break; fi
done
if [ -z "$app_path" ]; then
app_path=$(ls -d dist/*.app build/dist/*.app build/*.app 2>/dev/null | head -n1 || true)
fi
if [ -n "$app_path" ] && [ -d "$app_path" ]; then
app_base="$(basename "$app_path")"
app_parent="$(dirname "$app_path")"
(cd "$app_parent" && zip -r "${GITHUB_WORKSPACE}/artifacts/YTSage-v${version}-${ARCH_SUFFIX}.app.zip" "$app_base")
echo "Created: artifacts/YTSage-v${version}-${ARCH_SUFFIX}.app.zip"
else
echo "Warning: .app bundle not found in dist/ or build/"
fi
# Try to find the generated DMG
dmg_src=""
for cand in "dist/YTSage-v${version}.dmg"; do
if [ -f "$cand" ]; then dmg_src="$cand"; break; fi
done
if [ -z "$dmg_src" ]; then
dmg_src=$(ls dist/*.dmg build/*.dmg build/dist/*.dmg 2>/dev/null | head -n1 || true)
fi
if [ -n "$dmg_src" ] && [ -f "$dmg_src" ]; then
cp "$dmg_src" "artifacts/YTSage-v${version}-${ARCH_SUFFIX}.dmg"
echo "Copied DMG to artifacts"
else
echo "Warning: No DMG found"
fi
echo "Final artifacts:"
ls -lh artifacts || true
- name: Create/Update draft release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.get_version.outputs.VERSION }}
name: YTSage v${{ steps.get_version.outputs.VERSION }}
draft: true
prerelease: false
append_body: true
fail_on_unmatched_files: false
body: |
# YTSage v${{ steps.get_version.outputs.VERSION }}
**Release Date**: ${{ github.event.head_commit.timestamp }}
files: |
artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}