Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,29 @@ jobs:
path: BLine-*-Windows-Executable.zip
if-no-files-found: error

build-macos:
needs: check-release
if: needs.check-release.outputs.should_release == 'true'
runs-on: macos-14
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Build DMG
run: |
cd packaging
chmod +x build-dmg.sh
./build-dmg.sh

- name: Upload macOS DMG artifact
uses: actions/upload-artifact@v4
with:
name: BLine-macOS-DMG
path: BLine-*-macOS-arm64.dmg
if-no-files-found: error

create-release:
needs: [check-release, build-linux, build-windows]
needs: [check-release, build-linux, build-windows, build-macos]
if: needs.check-release.outputs.should_release == 'true'
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -173,6 +194,12 @@ jobs:
name: BLine-Windows-Executable
path: ./artifacts

- name: Download macOS DMG
uses: actions/download-artifact@v4
with:
name: BLine-macOS-DMG
path: ./artifacts

- name: Create Release
uses: softprops/action-gh-release@v1
with:
Expand All @@ -183,6 +210,9 @@ jobs:

### Downloads

**macOS (Apple Silicon):**
- `BLine-${{ needs.check-release.outputs.version }}-macOS-arm64.dmg` - Disk image

**Linux:**
- `BLine-x86_64.AppImage` - Single portable file

Expand All @@ -193,6 +223,11 @@ jobs:

### Installation

**macOS:**
1. Download `BLine-${{ needs.check-release.outputs.version }}-macOS-arm64.dmg`
2. Open the DMG and drag BLine.app to Applications
3. Launch from Applications

**Linux:**
1. Download `BLine-x86_64.AppImage`
2. Make it executable: `chmod +x BLine-x86_64.AppImage`
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ No installation or dependencies required!

#### macOS

macOS builds are not currently available as prebuilt binaries. See [Install from Source](#install-from-source) below.
**DMG (ARM Only)**
1. Download `BLine-${VERSION}-macOS-${ARCH}.dmg`
2. Open it
3. Drag Bline to your Applications folder
4. Run the Bline applications

---

Expand Down
67 changes: 67 additions & 0 deletions packaging/bline-macos.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for BLine macOS .app bundle

from pathlib import Path

project_dir = Path(SPECPATH).parent
assets_dir = project_dir / 'assets'
build_dir = Path(SPECPATH) / 'build'
icns_path = build_dir / 'BLine.icns'

a = Analysis(
[str(project_dir / 'main.py')],
pathex=[str(project_dir)],
binaries=[],
datas=[(str(assets_dir), 'assets')],
hiddenimports=[
'PySide6.QtCore',
'PySide6.QtGui',
'PySide6.QtWidgets',
'imageio',
'pyshortcuts',
'assets_rc',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)

pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='BLine',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(icns_path) if icns_path.exists() else None,
)

coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='BLine',
)

app = BUNDLE(
coll,
name='BLine.app',
icon=str(icns_path) if icns_path.exists() else None,
bundle_identifier=None,
)
105 changes: 105 additions & 0 deletions packaging/build-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/bash
set -e

# BLine macOS Build Script
# Creates a .app bundle with PyInstaller and packages it as a fancy .dmg

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="${SCRIPT_DIR}/build"
DIST_DIR="${SCRIPT_DIR}/dist"

echo "========================================"
echo "Building BLine for macOS"
echo "========================================"
echo ""

# Check for required tools
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 is required but not installed."; exit 1; }
command -v brew >/dev/null 2>&1 || { echo "Error: Homebrew is required. Install from https://brew.sh"; exit 1; }
if ! command -v create-dmg >/dev/null 2>&1; then
echo "Installing create-dmg..."
brew install create-dmg
fi

# Get version from pyproject.toml
VERSION=$(grep '^version = ' "${PROJECT_DIR}/pyproject.toml" | sed 's/version = "\(.*\)"/\1/')
ARCH=$(uname -m) # arm64/x86_64

echo "Version: $VERSION"
echo "Architecture: $ARCH"
echo ""

# Clean previous build
echo "Cleaning previous build..."
rm -rf "$BUILD_DIR" "$DIST_DIR"
mkdir -p "$BUILD_DIR"

# Generate .icns from rebel_logo.png
echo "Generating app icon..."
LOGO_SRC="${PROJECT_DIR}/assets/rebel_logo.png"
ICONSET_DIR="${BUILD_DIR}/BLine.iconset"
ICNS_PATH="${BUILD_DIR}/BLine.icns"
mkdir -p "$ICONSET_DIR"

for size in 16 32 64 128 256 512; do
sips -z $size $size "$LOGO_SRC" --out "${ICONSET_DIR}/icon_${size}x${size}.png" >/dev/null
double=$((size * 2))
sips -z $double $double "$LOGO_SRC" --out "${ICONSET_DIR}/icon_${size}x${size}@2x.png" >/dev/null
done

iconutil -c icns "$ICONSET_DIR" -o "$ICNS_PATH"

# Create a venv and install deps
echo "Creating virtual environment and installing dependencies..."
python3 -m venv "${BUILD_DIR}/venv"
source "${BUILD_DIR}/venv/bin/activate"

pip install --upgrade pip
pip install pyinstaller
pip install -e "$PROJECT_DIR"

# Build .app bundle with PyInstaller
echo "Building .app bundle with PyInstaller..."
cd "$SCRIPT_DIR"
pyinstaller --clean --noconfirm \
--distpath "$DIST_DIR" \
--workpath "${BUILD_DIR}/pyinstaller" \
bline-macos.spec

deactivate

APP_PATH="${DIST_DIR}/BLine.app"
if [ ! -d "$APP_PATH" ]; then
echo "Error: PyInstaller build failed — ${APP_PATH} not found."
exit 1
fi

# Create DMG
echo "Creating DMG..."
OUTPUT="${PROJECT_DIR}/BLine-${VERSION}-macOS-${ARCH}.dmg"
STAGING_DIR="${BUILD_DIR}/dmg-staging"

rm -f "$OUTPUT"
mkdir -p "$STAGING_DIR"
cp -r "$APP_PATH" "$STAGING_DIR/"

create-dmg \
--volname "BLine ${VERSION}" \
--volicon "$ICNS_PATH" \
--window-pos 200 150 \
--window-size 660 400 \
--icon-size 128 \
--icon "BLine.app" 180 185 \
--hide-extension "BLine.app" \
--app-drop-link 480 185 \
--no-internet-enable \
"$OUTPUT" \
"$STAGING_DIR/"

echo ""
echo "========================================"
echo "Build complete!"
echo "DMG created: $OUTPUT"
echo "========================================"