Files
SageTube/.github/workflows/build-macos.yml
T
oop7 ac11cbd7bb Simplify macOS .app lib trimming workflow
Replace complex multi-dir detection with a single find+while loop to identify library dirs and verify Python lib layout (asyncio/PySide6/ytsage). Consolidate cleanup steps: recursively remove PySide6 translations, normalize plugin path detection and remove specific plugin folders/files via find, and simplify removal of unwanted Qt modules using broader find patterns. Remove some verbose checks and add a final sanity listing of remaining lib contents. These changes make the trimming more robust to different .app layouts and reduce duplicate logic.
2026-02-03 16:14:59 +02:00

333 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"
# Simplified discovery of library directories
# We search for 'lib' folders and then verify if they look like the python library folder
find "$app_path/Contents" -type d -name "lib" | while read -r lib_dir; do
# Check if this lib dir is a python library dir (contains asyncio or PySide6 or ytsage)
if [ -d "$lib_dir/asyncio" ] || [ -d "$lib_dir/PySide6" ] || [ -d "$lib_dir/ytsage" ]; then
echo "Cleaning Library Directory: $lib_dir"
# 1. Remove screenshots
rm -rf "$lib_dir/assets/branding/screenshots"
# 2. Remove unused Qt translations (Recursively find 'translations' folder inside PySide6)
find "$lib_dir" -type d -path "*/PySide6/*/translations" -exec rm -rf {} + 2>/dev/null || true
find "$lib_dir" -type d -path "*/PySide6/translations" -exec rm -rf {} + 2>/dev/null || true
# 3. Remove unused Qt plugins
# Determine plugins path
plugins_path=""
if [ -d "$lib_dir/PySide6/plugins" ]; then plugins_path="$lib_dir/PySide6/plugins"; fi
if [ -d "$lib_dir/PySide6/Qt/plugins" ]; then plugins_path="$lib_dir/PySide6/Qt/plugins"; fi
if [ -n "$plugins_path" ]; then
echo "Found plugins at: $plugins_path"
for plugin in designer pdf svg sql help qml quick webengine bluetooth opengl printsupport test xml; do
rm -rf "$plugins_path/$plugin"
done
# Specific cleanups
rm -rf "$plugins_path/platforminputcontexts"
find "$plugins_path" -name "libqpdf.*" -delete
find "$plugins_path" -name "libqsvg.*" -delete
find "$plugins_path" -name "libqsvgicon.*" -delete
fi
# 4. Remove bloat Qt libraries / Frameworks
# Iterate specifically over unwanted Qt modules
# Matches both 'QtQml' (file) and 'QtQml.abi3.so' etc
for bloat in QtQml QtQmlMeta QtQmlModels QtQmlWorkerScript QtQuick QtQuickControls2 QtQuickTemplates2 QtWebEngine QtWebEngineCore QtVirtualKeyboard QtVirtualKeyboardQml QtOpenGL QtOpenGLWidgets QtPdf QtPdfWidgets QtDBus; do
find "$lib_dir" -maxdepth 2 -name "$bloat" -delete
find "$lib_dir" -maxdepth 2 -name "${bloat}.*" -delete
find "$lib_dir" -maxdepth 2 -name "*$bloat*" -delete
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"
done
fi
done
# Final sanity verification
echo "Trim complete. Remaining contents of lib (first 2 levels):"
find "$app_path/Contents" -type d -name "lib" -exec ls -R {} \; | head -n 50 || true
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 }}