Created
August 22, 2025 16:22
-
-
Save pathikrit/c195ac50525157ddf763b8fd850d0c93 to your computer and use it in GitHub Desktop.
Make a uv project into a mac app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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