diff --git a/.github/workflows/build-macos-installer.yml b/.github/workflows/build-macos-installer.yml index a065c2aa..bba81d93 100644 --- a/.github/workflows/build-macos-installer.yml +++ b/.github/workflows/build-macos-installer.yml @@ -59,7 +59,7 @@ jobs: run: | python3 -m pip install --upgrade pip pip3 install pyinstaller - pip3 install -e . + pip3 install . - name: Get version id: get_version diff --git a/docs/MACOS_INSTALL.md b/docs/MACOS_INSTALL.md index 20168c72..2ab47ace 100644 --- a/docs/MACOS_INSTALL.md +++ b/docs/MACOS_INSTALL.md @@ -30,11 +30,11 @@ sudo installer -pkg RamaLama-VERSION-macOS-Installer.pkg -target / The installer places files in: - `/usr/local/bin/ramalama` - Main executable -- `/usr/local/share/ramalama/` - Configuration files - `/usr/local/share/man/` - Man pages - `/usr/local/share/bash-completion/` - Bash completions - `/usr/local/share/fish/` - Fish completions - `/usr/local/share/zsh/` - Zsh completions +- `/Applications/ramalama.app` - App bundle containing configuration files, resources, Python dependencies, etc. ### Verify Installation @@ -124,8 +124,10 @@ To remove RamaLama: # Remove the executable sudo rm /usr/local/bin/ramalama +# Remove the app bundle +sudo rm -rf /Applications/ramalama.app + # Remove configuration and data files (optional) -sudo rm -rf /usr/local/share/ramalama rm -rf ~/.local/share/ramalama rm -rf ~/.config/ramalama diff --git a/logos/ICNS/ramalama.icns b/logos/ICNS/ramalama.icns new file mode 100644 index 00000000..74a65b45 Binary files /dev/null and b/logos/ICNS/ramalama.icns differ diff --git a/ramalama.spec b/ramalama.spec index f57c33cf..2c639740 100644 --- a/ramalama.spec +++ b/ramalama.spec @@ -15,8 +15,6 @@ from pathlib import Path sys.path.insert(0, str(Path.cwd() / 'ramalama')) from version import version as get_version -block_cipher = None - # Get the project root directory project_root = Path.cwd() @@ -80,28 +78,21 @@ a = Analysis( 'pandas', 'PIL', ], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, noarchive=False, ) -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, - a.binaries, - a.zipfiles, - a.datas, [], + exclude_binaries=True, name='ramalama', debug=False, bootloader_ignore_signals=False, strip=False, upx=False, # Disabled for Apple Silicon compatibility - upx_exclude=[], - runtime_tmpdir=None, console=True, disable_windowed_traceback=False, argv_emulation=False, @@ -110,11 +101,20 @@ exe = EXE( entitlements_file=None, ) -# Create app bundle for macOS -app = BUNDLE( +coll = COLLECT( exe, + a.binaries, + a.datas, + strip=False, + upx=False, + upx_exclude=[], + name='ramalama', +) + +app = BUNDLE( + coll, name='ramalama.app', - icon=None, + icon='logos/ICNS/ramalama.icns', bundle_identifier='com.github.containers.ramalama', version=app_version, info_plist={ @@ -123,8 +123,7 @@ app = BUNDLE( 'CFBundleIdentifier': 'com.github.containers.ramalama', 'CFBundleVersion': app_version, 'CFBundleShortVersionString': app_version, - 'NSHumanReadableCopyright': 'Copyright © 2024 The Containers Organization', + 'NSHumanReadableCopyright': 'Copyright © 2026 The Containers Organization', 'CFBundleExecutable': 'ramalama', }, ) - diff --git a/scripts/build_macos_pkg.sh b/scripts/build_macos_pkg.sh index 742cb6df..53e4ef5e 100755 --- a/scripts/build_macos_pkg.sh +++ b/scripts/build_macos_pkg.sh @@ -57,24 +57,18 @@ rm -rf "$PROJECT_ROOT/dist" rm -rf "$PROJECT_ROOT/build" mkdir -p "$BUILD_DIR" -# Build standalone executable with PyInstaller -echo "Building standalone executable..." +# Build app bundle with PyInstaller +echo "Building app bundle..." cd "$PROJECT_ROOT" pyinstaller ramalama.spec --clean --noconfirm -# Verify build - PyInstaller creates either a single file or directory -BUILT_EXECUTABLE="" -if [ -f "$PROJECT_ROOT/dist/ramalama" ]; then - BUILT_EXECUTABLE="$PROJECT_ROOT/dist/ramalama" - echo "Found single-file executable: $BUILT_EXECUTABLE" -elif [ -f "$PROJECT_ROOT/dist/ramalama/ramalama" ]; then - BUILT_EXECUTABLE="$PROJECT_ROOT/dist/ramalama/ramalama" - echo "Found executable in bundle: $BUILT_EXECUTABLE" -elif [ -d "$PROJECT_ROOT/dist/ramalama.app" ]; then - BUILT_EXECUTABLE="$PROJECT_ROOT/dist/ramalama.app/Contents/MacOS/ramalama" - echo "Found app bundle: $PROJECT_ROOT/dist/ramalama.app" +# Verify build - PyInstaller creates a .app directory +BUNDLE_DIR="$PROJECT_ROOT/dist/ramalama.app" +BUILT_EXECUTABLE="$BUNDLE_DIR/Contents/MacOS/ramalama" +if [ -d "$BUNDLE_DIR" ]; then + echo "Found app bundle: $BUNDLE_DIR" else - echo "Error: PyInstaller build failed - no executable found in dist/" + echo "Error: PyInstaller build failed - no bundle found in dist/" echo "Contents of dist/:" ls -la "$PROJECT_ROOT/dist/" || true exit 1 @@ -86,46 +80,24 @@ if [ ! -x "$BUILT_EXECUTABLE" ]; then exit 1 fi -echo "Standalone executable built successfully: $BUILT_EXECUTABLE" +echo "App bundle built successfully: $BUNDLE_DIR" # Create package structure echo "Creating package structure..." PKG_ROOT="$BUILD_DIR/package-root" mkdir -p "$PKG_ROOT/usr/local/bin" -mkdir -p "$PKG_ROOT/usr/local/share/ramalama" mkdir -p "$PKG_ROOT/usr/local/share/man/man1" mkdir -p "$PKG_ROOT/usr/local/share/man/man5" mkdir -p "$PKG_ROOT/usr/local/share/man/man7" mkdir -p "$PKG_ROOT/usr/local/share/bash-completion/completions" mkdir -p "$PKG_ROOT/usr/local/share/fish/vendor_completions.d" mkdir -p "$PKG_ROOT/usr/local/share/zsh/site-functions" +mkdir -p "$PKG_ROOT/Applications" -# Copy executable -echo "Copying executable..." -if [ -d "$PROJECT_ROOT/dist/ramalama.app" ]; then - # For app bundle, copy the executable from inside - cp "$BUILT_EXECUTABLE" "$PKG_ROOT/usr/local/bin/ramalama" -else - # For single file or directory bundle - cp "$BUILT_EXECUTABLE" "$PKG_ROOT/usr/local/bin/ramalama" -fi -chmod +x "$PKG_ROOT/usr/local/bin/ramalama" - -# Verify the copy worked -if [ ! -x "$PKG_ROOT/usr/local/bin/ramalama" ]; then - echo "Error: Failed to copy executable to package root" - exit 1 -fi - -# Copy configuration files -echo "Copying configuration files..." -cp "$PROJECT_ROOT/shortnames/shortnames.conf" "$PKG_ROOT/usr/local/share/ramalama/" -cp "$PROJECT_ROOT/docs/ramalama.conf" "$PKG_ROOT/usr/local/share/ramalama/" - -# Copy inference spec files -mkdir -p "$PKG_ROOT/usr/local/share/ramalama/inference" -cp "$PROJECT_ROOT"/inference-spec/schema/*.json "$PKG_ROOT/usr/local/share/ramalama/inference/" -cp "$PROJECT_ROOT"/inference-spec/engines/* "$PKG_ROOT/usr/local/share/ramalama/inference/" +# Copy bundle +echo "Copying bundle..." +cp -a "$BUNDLE_DIR" "$PKG_ROOT/Applications" +ln -s ../../../Applications/ramalama.app/Contents/MacOS/ramalama "$PKG_ROOT/usr/local/bin/ramalama" # Copy man pages echo "Copying documentation..." @@ -205,6 +177,7 @@ pkgbuild \ --version "$VERSION" \ --scripts "$SCRIPTS_DIR" \ --install-location "/" \ + --component-plist "$SCRIPT_DIR/macos-installer/ramalama-pkg.plist" \ "$PKG_OUTPUT" # Copy installer resource files from templates @@ -243,4 +216,3 @@ echo "To distribute:" echo " 1. Sign the package (recommended for public distribution)" echo " 2. Upload to GitHub Releases" echo " 3. Optionally notarize with Apple" - diff --git a/scripts/macos-installer/ramalama-pkg.plist b/scripts/macos-installer/ramalama-pkg.plist new file mode 100644 index 00000000..57328f36 --- /dev/null +++ b/scripts/macos-installer/ramalama-pkg.plist @@ -0,0 +1,30 @@ + + + + + + BundleHasStrictIdentifier + + BundleIsRelocatable + + BundleIsVersionChecked + + BundleOverwriteAction + upgrade + RootRelativeBundlePath + Applications/ramalama.app + + + BundleHasStrictIdentifier + + BundleIsRelocatable + + BundleIsVersionChecked + + BundleOverwriteAction + upgrade + RootRelativeBundlePath + Applications/ramalama.app/Contents/Frameworks/Python.framework + + +