Files
SageTube/.github/workflows/build-macos.yml
T
oop7 bf9e11d08f macOS build: trim .app bundle and packaging tweaks
Improve macOS CI packaging by trimming unnecessary files from the built .app and adjusting build options. Changes include:

- Enable cx_Freeze silent build and run Python with -OO for bdist_mac.
- Add zip_include_packages to ensure key packages are bundled inside zip distributions.
- Exclude additional stdlib modules (pydoc, doctest, email) to reduce size.
- New workflow step to locate the generated .app and remove screenshots, Qt translations, unused PySide6 plugins and other Qt artifacts (Qt6Web*, Qt6Pdf*), with safer path detection and logging.
- Remove noisy post-build directory listings and simplify DMG handling/log messages; make .app and .dmg discovery more robust.

These changes aim to produce smaller, cleaner artifacts and more reliable CI packaging for macOS builds.
2026-02-03 14:41:19 +02:00

312 lines
9.8 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"
# 1. Remove screenshots
if [ -d "$app_path/Contents/Resources/lib/assets/branding/screenshots" ]; then
rm -rf "$app_path/Contents/Resources/lib/assets/branding/screenshots"
echo "Removed screenshots folder"
fi
# 2. Remove unused Qt translations
# In macOS .app, Qt libs might be in Contents/MacOS/PySide6 or similar
# Search for translations folder within the app bundle
translations=$(find "$app_path" -type d -name "translations" | grep "PySide6" || true)
if [ -n "$translations" ]; then
rm -rf "$translations"
echo "Removed Qt translations at $translations"
fi
# 3. Remove unused Qt plugins (matching Windows logic)
plugins_dir=$(find "$app_path" -type d -name "plugins" | grep "PySide6" | head -n1 || true)
if [ -n "$plugins_dir" ]; then
echo "Cleaning plugins in $plugins_dir"
for plugin in designer pdf svg sql help qml quick webengine bluetooth opengl printsupport test xml; do
if [ -d "$plugins_dir/$plugin" ]; then
rm -rf "$plugins_dir/$plugin"
echo "Removed plugin: $plugin"
fi
done
fi
# 4. Remove other unused components if found
find "$app_path" -name "Qt6Web*" -delete
find "$app_path" -name "Qt6Pdf*" -delete
echo "Cleaned additional Qt files"
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 }}