Skip to content

Added runtime test

Added runtime test #22

Workflow file for this run

name: macOS DMG
on:
workflow_dispatch:
push:
branches: [master]
tags: ["[0-9]*.[0-9]*.[0-9]*"]
pull_request:
paths:
- ".github/workflows/macos-dmg.yml"
- "meson.build"
- "data/**"
- "src/**"
- "macos/**"
jobs:
build-dmg:
strategy:
matrix:
os: [macos-13, macos-14]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Show runner info
run: |
uname -a
sw_vers
sysctl -n machdep.cpu.brand_string || true
- name: Setup Homebrew and dependencies
run: |
brew update-reset
brew install meson ninja pkg-config glib gtk4 libadwaita pygobject3 gtksourceview5 [email protected]
brew --version
brew list --versions
- name: Build gtk-mac-bundler from source
run: |
git clone https://gitlab.gnome.org/GNOME/gtk-mac-bundler.git
cd gtk-mac-bundler
make
sudo make install
which gtk-mac-bundler
gtk-mac-bundler --version || true
- name: Build blueprint-compiler from git (v0.18.0)
run: |
set -euxo pipefail
git clone --depth 1 --branch v0.18.0 https://gitlab.gnome.org/GNOME/blueprint-compiler.git
cd blueprint-compiler
meson setup build --prefix "$PWD/../bp_prefix" --buildtype=release
meson compile -C build
meson install -C build
echo "$GITHUB_WORKSPACE/bp_prefix/bin" >> "$GITHUB_PATH"
- name: Build project (Meson)
run: |
set -euxo pipefail
PY_BIN="$(brew --prefix [email protected])/bin/python3"
echo "Using Python: ${PY_BIN}"
export PYTHON="${PY_BIN}"
meson setup build --prefix "$PWD/stage" --buildtype=release
meson compile -C build
meson install -C build
- name: Bundle app with gtk-mac-bundler
run: |
mkdir -p macos
BREW_PREFIX="$(brew --prefix)"
export BREW_PREFIX
echo "BREW_PREFIX=${BREW_PREFIX}" >> "$GITHUB_ENV"
echo "Detected Homebrew prefix: $BREW_PREFIX"
# Write Info.plist required by gtk-mac-bundler
cat > macos/Info.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>io.github.BuddySirJava.SSH-Studio</string>
<key>CFBundleName</key>
<string>SSH Studio</string>
<key>CFBundleExecutable</key>
<string>ssh-studio</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>SSHS</string>
</dict>
</plist>
PLIST
# Bundle description for gtk-mac-bundler
cat > macos/ssh-studio.bundle << 'XML'
<?xml version="1.0"?>
<app-bundle>
<meta>
<prefix>${env:BREW_PREFIX}</prefix>
<destination overwrite="yes">${project}/../build/ssh-studio.bundle</destination>
<gtk>gtk4</gtk>
</meta>
<id>io.github.BuddySirJava.SSH-Studio</id>
<name>SSH Studio</name>
<version>1.0.0</version>
<icon>${project}/../data/media/icon_512.png</icon>
<plist>${env:GITHUB_WORKSPACE}/macos/Info.plist</plist>
<main-binary dest="${bundle}/Contents/MacOS/${name}">${project}/../stage/bin/ssh-studio</main-binary>
<data dest="${bundle}/Contents/Resources/share/io.github.BuddySirJava.SSH-Studio/ssh-studio-resources.gresource">${project}/../stage/share/io.github.BuddySirJava.SSH-Studio/ssh-studio-resources.gresource</data>
<data dest="${bundle}/Contents/Resources/src">${project}/../src</data>
</app-bundle>
XML
- name: Run gtk-mac-bundler
run: |
set -euxo pipefail
BREW_PREFIX="$(brew --prefix)"
export BREW_PREFIX
gtk-mac-bundler macos/ssh-studio.bundle
- name: Build self-contained .app (vendor Python + GTK)
run: |
set -euxo pipefail
APP="dist/SSH Studio.app"
APPROOT="$APP/Contents"
MACOS="$APPROOT/MacOS"
RES="$APPROOT/Resources"
FRAMEWORKS="$APPROOT/Frameworks"
mkdir -p "$MACOS" "$RES/python" "$FRAMEWORKS"
# 1) Copy your Python sources
rsync -a src/ "$RES/python/ssh_studio/"
# 2) Copy compiled GResource from Meson install
APP_ID="io.github.BuddySirJava.SSH-Studio"
install -m 0644 "stage/share/$APP_ID/ssh-studio-resources.gresource" "$RES/"
# 3) Vendor Python.framework
BREW_PREFIX="$(brew --prefix)"
rsync -a "$BREW_PREFIX/Frameworks/Python.framework" "$FRAMEWORKS/"
# 4) Vendor GTK & friends (most-used libs)
for p in glib gtk4 libadwaita gtksourceview5 gdk-pixbuf pango cairo harfbuzz fribidi graphite2 libpng jpeg libtiff libepoxy libffi gettext; do
if [ -d "$BREW_PREFIX/opt/$p/lib" ]; then
mkdir -p "$FRAMEWORKS/$p/lib"
rsync -a "$BREW_PREFIX/opt/$p/lib/" "$FRAMEWORKS/$p/lib/"
fi
if [ -d "$BREW_PREFIX/opt/$p/lib/girepository-1.0" ]; then
mkdir -p "$RES/girepository-1.0"
rsync -a "$BREW_PREFIX/opt/$p/lib/girepository-1.0/" "$RES/girepository-1.0/"
fi
if [ -d "$BREW_PREFIX/opt/$p/share" ]; then
mkdir -p "$RES/share/$p"
rsync -a "$BREW_PREFIX/opt/$p/share/" "$RES/share/"
fi
done
# 4b) Vendor PyGObject (gi) and PyCairo into bundled Python path
for SITE in \
"$BREW_PREFIX/lib/python3.13/site-packages" \
"$BREW_PREFIX/opt/pygobject3/lib/python3.13/site-packages" \
"$BREW_PREFIX/opt/py3cairo/lib/python3.13/site-packages"; do
if [ -d "$SITE/gi" ]; then
rsync -a "$SITE/gi" "$RES/python/"
fi
if [ -d "$SITE/cairo" ]; then
rsync -a "$SITE/cairo" "$RES/python/"
fi
done
# 5) Schemas needed by GSettings
mkdir -p "$RES/share/glib-2.0/schemas"
rsync -a "$BREW_PREFIX/opt/glib/share/glib-2.0/schemas/" "$RES/share/glib-2.0/schemas/"
glib-compile-schemas "$RES/share/glib-2.0/schemas"
# 6) Launcher that sets env so app uses bundled runtimes
cat > "$MACOS/ssh-studio" <<'SH'
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
RES="$SCRIPT_DIR/../Resources"
FW="$SCRIPT_DIR/../Frameworks"
export PYTHONHOME="$FW/Python.framework/Versions/3.13"
export PYTHONPATH="$RES/python"
export DYLD_FALLBACK_LIBRARY_PATH="$FW:$FW/glib/lib:$FW/gtk4/lib:$FW/libadwaita/lib:$FW/pango/lib:$FW/cairo/lib:$FW/gtksourceview5/lib"
export GI_TYPELIB_PATH="$RES/girepository-1.0"
export XDG_DATA_DIRS="$RES/share"
export GSETTINGS_SCHEMA_DIR="$RES/share/glib-2.0/schemas"
export GTK_DATA_PREFIX="$RES"
# Register GResource then run app
"$PYTHONHOME/bin/python3" - <<'PY'
import os, sys
from gi.repository import Gio
res_path = os.path.join(os.path.dirname(__file__), '..', 'Resources', 'ssh-studio-resources.gresource')
Gio.resources_register(Gio.Resource.load(res_path))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'Resources', 'python'))
from ssh_studio import main as _main
sys.exit(_main.main())
PY
SH
chmod 0755 "$MACOS/ssh-studio"
# 7) Minimal Info.plist (if you’re not generating it already)
cat > "$APPROOT/Info.plist" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleIdentifier</key><string>io.github.BuddySirJava.SSH-Studio</string>
<key>CFBundleName</key><string>SSH Studio</string>
<key>CFBundleExecutable</key><string>ssh-studio</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
</dict></plist>
PLIST
- name: List .app contents
run: |
set -euxo pipefail
echo 'gtk-mac-bundler app:' || true
ls -R build/ssh-studio.bundle/SSH\ Studio.app/Contents || true
echo 'self-contained app:' || true
ls -R 'dist/SSH Studio.app/Contents' || true
- name: Smoke test self-contained app
run: |
set -euxo pipefail
LAUNCHER='dist/SSH Studio.app/Contents/MacOS/ssh-studio'
test -x "$LAUNCHER"
# CLI help should exit quickly without requiring a GUI session
"$LAUNCHER" --help || true
# Also try launching briefly, then kill
"$LAUNCHER" &
sleep 2 || true
pkill -f "SSH Studio" || true
pkill -f "ssh-studio" || true
- name: Create DMG
run: |
set -euxo pipefail
VER=$(sed -n "s/.*version: '\([^']*\)'.*/\1/p" meson.build | head -n1)
ARCH=$(uname -m)
mkdir -p dmgroot
# Prefer self-contained app for distribution
cp -R "dist/SSH Studio.app" "dmgroot/SSH Studio.app"
ln -s /Applications dmgroot/Applications
hdiutil create -volname "SSH Studio" -srcfolder dmgroot -ov -fs HFS+ "ssh-studio-${VER}-${ARCH}.dmg"
- name: Mount DMG and smoke test app
run: |
set -euxo pipefail
DMG="$(ls -1 ssh-studio-*.dmg | head -n1)"
MNT="$(mktemp -d /tmp/sshstudio.XXXX)"
hdiutil attach "$DMG" -mountpoint "$MNT" -nobrowse -quiet
ls -la "$MNT"
LAUNCHER="$MNT/SSH Studio.app/Contents/MacOS/ssh-studio"
test -x "$LAUNCHER"
# CLI help should execute; ignore non-zero in case GTK prints warnings
"$LAUNCHER" --help || true
# Briefly launch then terminate
"$LAUNCHER" &
sleep 2 || true
pkill -f "SSH Studio" || true
pkill -f "ssh-studio" || true
hdiutil detach "$MNT" -quiet || true
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ssh-studio-dmg-${{ matrix.os }}
path: |
*.dmg