Skip to content

Instantly share code, notes, and snippets.

@pathikrit
Created August 22, 2025 16:22
Show Gist options
  • Save pathikrit/c195ac50525157ddf763b8fd850d0c93 to your computer and use it in GitHub Desktop.
Save pathikrit/c195ac50525157ddf763b8fd850d0c93 to your computer and use it in GitHub Desktop.
Make a uv project into a mac app
#!/bin/bash
# Script to convert a uv project into a Mac (Apple Silicon) .app binary
set -e
# Configuration - Set these for your app!
APP_NAME="your-app-name"
BUNDLE_NAME="${APP_NAME}.app"
BUNDLE_ID="com.your-company.${APP_NAME}"
PYTHON_VERSION="3.11"
echo "🍎 Creating macOS app bundle for ${APP_NAME}..."
if [ -d "$BUNDLE_NAME" ]; then
echo "Removing existing ${BUNDLE_NAME}..."
rm -rf "$BUNDLE_NAME"
fi
echo "πŸ“ Creating app bundle structure..."
mkdir -p "${BUNDLE_NAME}/Contents/MacOS"
mkdir -p "${BUNDLE_NAME}/Contents/Resources"
echo "πŸš€ Creating launcher script..."
cat > "${BUNDLE_NAME}/Contents/MacOS/${APP_NAME}" << 'EOF'
#!/bin/bash
# Add logging for debugging
LOG_FILE="$HOME/Library/Logs/APP_NAME_PLACEHOLDER.log"
mkdir -p "$HOME/Library/Logs"
exec > "$LOG_FILE" 2>&1
echo "=== App Launch $(date) ==="
# Get the directory where the app bundle is located
BUNDLE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
RESOURCES_DIR="${BUNDLE_DIR}/Contents/Resources"
echo "Working from: $RESOURCES_DIR"
# Change to the resources directory (where our Python files are)
cd "$RESOURCES_DIR"
# Add homebrew to PATH - common missing piece in .app bundles
export PATH="/opt/homebrew/bin:$PATH"
# Check if uv is installed, if not try to install it
if ! command -v uv &> /dev/null; then
echo "uv not found, attempting to install..."
if command -v curl &> /dev/null; then
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.cargo/bin:$PATH"
else
echo "Error: uv is required but not installed, and curl is not available to install it."
exit 1
fi
fi
echo "About to run: uv run --python PYTHON_VERSION app.py"
# Find a Python installation that has tkinter (for GUI apps)
echo "Finding Python with tkinter support..."
PYTHON_CMD=""
for python_path in "/Library/Frameworks/Python.framework/Versions/PYTHON_VERSION/bin/python3.PYTHON_VERSION" "/usr/bin/python3" "pythonPYTHON_VERSION" "python3"; do
if command -v "$python_path" >/dev/null 2>&1; then
if "$python_path" -c "import tkinter" >/dev/null 2>&1; then
PYTHON_CMD="$python_path"
echo "Found Python with tkinter: $PYTHON_CMD"
break
fi
fi
done
if [ -z "$PYTHON_CMD" ]; then
echo "No Python installation with tkinter found"
echo "Try: brew install python-tk@PYTHON_VERSION"
exit 1
fi
# Run the app with the Python that has tkinter
echo "Running with Python: $PYTHON_CMD"
echo "Platform info:"
uname -a
echo "Architecture before: $(arch)"
# Force native ARM64 execution - multiple approaches
export ARCHFLAGS="-arch arm64"
export CPPFLAGS="-I/opt/homebrew/include"
export LDFLAGS="-L/opt/homebrew/lib"
# Check if we're in Rosetta and force native mode
if [ "$(arch)" = "i386" ]; then
echo "Detected Rosetta mode, forcing ARM64..."
exec arch -arm64e /bin/bash -c "
cd '$RESOURCES_DIR'
export PATH='/opt/homebrew/bin:$PATH'
export ARCHFLAGS='-arch arm64'
echo 'Architecture after arch command: '\$(arch)
exec uv run --python '$PYTHON_CMD' app.py
"
else
echo "Already in ARM64 mode"
echo "Running: uv run --python $PYTHON_CMD app.py"
exec uv run --python "$PYTHON_CMD" app.py
fi
EOF
# Replace placeholders in the launcher script with actual values
sed -i '' "s/PYTHON_VERSION/${PYTHON_VERSION}/g" "${BUNDLE_NAME}/Contents/MacOS/${APP_NAME}"
sed -i '' "s/APP_NAME_PLACEHOLDER/${APP_NAME}/g" "${BUNDLE_NAME}/Contents/MacOS/${APP_NAME}"
chmod +x "${BUNDLE_NAME}/Contents/MacOS/${APP_NAME}"
echo "πŸ“‹ Copying Python files..."
cp pyproject.toml "${BUNDLE_NAME}/Contents/Resources/"
cp *.py "${BUNDLE_NAME}/Contents/Resources/"
echo "πŸ“„ Creating Info.plist..."
cat > "${BUNDLE_NAME}/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>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${APP_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>${BUNDLE_ID}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${APP_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSArchitecturePriority</key>
<array>
<string>arm64</string>
</array>
<key>LSRequiresNativeExecution</key>
<true/>
</dict>
</plist>
EOF
echo "πŸ”’ Setting permissions..."
chmod -R 755 "${BUNDLE_NAME}"
echo "βœ… ${BUNDLE_NAME} created successfully!"
echo ""
echo "πŸ“¦ Your app bundle is ready at: $(pwd)/${BUNDLE_NAME}"
echo ""
echo "πŸš€ To test: Double-click ${BUNDLE_NAME} in Finder"
echo "πŸ› Debug log: cat ~/Library/Logs/${APP_NAME}.log"
echo ""
echo "πŸ“‹ To distribute:"
echo " 1. Zip the ${BUNDLE_NAME} folder"
echo " 2. Send the zip file to users"
echo " 3. Users should unzip and double-click the .app"
echo ""
echo "πŸ’‘ Note: Users may see a security warning on first run."
echo " They can right-click the app and select 'Open' to bypass it."
echo ""
echo "πŸ“‹ Requirements for users:"
echo " - macOS 12.0+ on Apple Silicon (M1/M2/M3)"
echo " - Python ${PYTHON_VERSION} with tkinter: brew install python-tk@${PYTHON_VERSION}"
echo " - uv will be auto-installed if not present"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment