Skip to content

Instantly share code, notes, and snippets.

@ahalekelly
Created November 17, 2025 02:30
Show Gist options
  • Select an option

  • Save ahalekelly/c270b7a3584c398af74b103065e1eb5e to your computer and use it in GitHub Desktop.

Select an option

Save ahalekelly/c270b7a3584c398af74b103065e1eb5e to your computer and use it in GitHub Desktop.
Create Mac App using py-app-standalone
#!/bin/bash
# Example build script showing how to wrap py-app-standalone output into macOS .app bundles
# This provides instant startup times with native macOS application experience
set -e # Exit on error
echo "Building standalone executables..."
echo "Platform: $(uname -s)"
echo ""
# Check if uv is installed
if ! command -v uv &> /dev/null; then
echo "Error: uv is not installed. Please install it first:"
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
exit 1
fi
echo "Using uv version: $(uv --version)"
echo ""
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf py-standalone
# Build the standalone distribution from local package
echo "Building standalone Python environment from local package..."
uvx py-app-standalone . --source-only
# On macOS, create .app bundles
if [[ "$OSTYPE" == "darwin"* ]]; then
echo ""
echo "Creating macOS .app bundles..."
# Find the Python installation directory
PYTHON_DIR=$(find py-standalone -maxdepth 1 -name "cpython-*" -type d | head -1)
if [ -z "$PYTHON_DIR" ]; then
echo "Error: Could not find Python installation directory"
exit 1
fi
# Function to create .app bundle
# This wraps the py-app-standalone output in a native macOS application structure
# Benefits:
# - Native macOS experience (can be launched from Finder, appears in Dock, etc.)
# - Instant startup (no extraction like PyInstaller --onefile)
# - No Gatekeeper issues with nested executables (single entry point)
create_app_bundle() {
local APP_NAME="$1" # Display name (e.g., "My App")
local EXECUTABLE_NAME="$2" # Entry point script name from pyproject.toml [project.scripts]
local BUNDLE_ID="$3" # Reverse DNS bundle identifier (e.g., "com.example.myapp")
echo "Creating $APP_NAME.app..."
# Create .app structure (standard macOS application bundle layout)
mkdir -p "dist/$APP_NAME.app/Contents/MacOS"
mkdir -p "dist/$APP_NAME.app/Contents/Resources"
# Copy the entire py-standalone directory into Resources
# This includes the Python interpreter, libraries, and all dependencies
cp -R py-standalone "dist/$APP_NAME.app/Contents/Resources/"
# Create launcher script that executes the py-app-standalone entry point
cat > "dist/$APP_NAME.app/Contents/MacOS/$APP_NAME" << 'EOF'
#!/bin/bash
# Get the directory where this script is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Run the actual Python executable (entry point from pyproject.toml [project.scripts])
exec "$DIR/../Resources/py-standalone/cpython-"*"/bin/EXEC_NAME" "$@"
EOF
# Replace EXEC_NAME with actual executable name
sed -i '' "s/EXEC_NAME/$EXECUTABLE_NAME/g" "dist/$APP_NAME.app/Contents/MacOS/$APP_NAME"
chmod +x "dist/$APP_NAME.app/Contents/MacOS/$APP_NAME"
# Create Info.plist (macOS application metadata)
cat > "dist/$APP_NAME.app/Contents/Info.plist" << EOF
<?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>CFBundleExecutable</key>
<string>$APP_NAME</string>
<key>CFBundleIdentifier</key>
<string>$BUNDLE_ID</string>
<key>CFBundleName</key>
<string>$APP_NAME</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
EOF
}
# Create dist directory
mkdir -p dist
# Create .app bundle for the entry point defined in pyproject.toml [project.scripts]
# Example: If you have [project.scripts] with:
# my-app = "mypackage.main:run"
# Then create bundle like:
create_app_bundle "My App" "my-app" "com.example.myapp"
echo ""
echo "macOS .app bundle created in dist/"
fi
echo ""
echo "Build complete!"
echo ""
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS .app bundle is located in:"
echo " ./dist/"
echo ""
echo "Available applications:"
ls -d dist/*.app
echo ""
echo "Double-click the .app file to launch, or run:"
echo " open 'dist/My App.app'"
else
echo "The standalone executables are located in:"
echo " ./py-standalone/cpython-*/bin/"
echo ""
echo "Available executables:"
find py-standalone -name "my-app"
echo ""
echo "You can move the entire py-standalone directory to any compatible system."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment