-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_macos.sh
More file actions
executable file
·180 lines (153 loc) · 6.31 KB
/
build_macos.sh
File metadata and controls
executable file
·180 lines (153 loc) · 6.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env bash
set -euo pipefail
# build_macos.sh
# macOS build helper for GitRepositoryManager (PyInstaller)
# Creates a virtualenv (if missing), installs deps and PyInstaller, then builds a macOS app bundle
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$DIR"
VENV_DIR=.venv
PYINSTALLER_VERSION=5.13.2
APP_MAIN=git_repository_manager.py
EXE_NAME=GitRepositoryManager
PKG_VERSION="1.0"
# safe_rm: attempt to remove a path, fix ownership with sudo if needed
safe_rm() {
local target="$1"
if [ -e "$target" ]; then
echo "Removing existing $target"
rm -rf "$target" 2>/dev/null || true
if [ -e "$target" ]; then
echo "Could not remove $target due to permissions. Trying to fix ownership (sudo may be required)..."
if command -v sudo >/dev/null 2>&1; then
sudo chown -R "$(id -u):$(id -g)" "$target" || true
sudo rm -rf "$target" || true
else
echo "sudo not available; please remove $target manually and re-run the script."
exit 1
fi
fi
fi
}
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment in $VENV_DIR..."
python3 -m venv "$VENV_DIR"
fi
# shellcheck source=/dev/null
source "$VENV_DIR/bin/activate"
echo "Upgrading pip and installing requirements..."
python -m pip install --upgrade pip
python -m pip install -r requirements.txt || true
echo "Installing PyInstaller (version $PYINSTALLER_VERSION)..."
python -m pip install "pyinstaller==$PYINSTALLER_VERSION"
echo
echo "Building (one-folder / directory mode - keeps app_data.json writable)..."
# On macOS and Linux use ':' as the add-data separator. This creates a dist/<name>/ folder.
pyinstaller --noconfirm --clean --name "$EXE_NAME" --windowed --add-data "app_data.json:." "$APP_MAIN"
if [ $? -ne 0 ]; then
echo "Directory build failed. Exiting with non-zero status."
exit 1
fi
echo
echo "Optional: build single-file executable (slower startup, not recommended if you need writable app_data.json)"
# Uncomment the next line to enable onefile builds
# pyinstaller --noconfirm --clean --onefile --name "$EXE_NAME" --windowed --add-data "app_data.json:." "$APP_MAIN"
echo
echo "Build complete."
echo "Directory build (one-folder): dist/$EXE_NAME/"
# macOS .app bundles may be created depending on flags; the executable will be inside the dist folder
# Prepare a minimal .app bundle wrapper if PyInstaller produced a console executable rather than a .app
APP_BUNDLE_NAME="$EXE_NAME.app"
APP_BUNDLE_DIR="dist/$APP_BUNDLE_NAME"
EXECUTABLE_PATH="dist/$EXE_NAME/$EXE_NAME"
if [ -f "$EXECUTABLE_PATH" ] && [ ! -d "$APP_BUNDLE_DIR" ]; then
echo "Creating minimal .app bundle wrapper: $APP_BUNDLE_DIR"
safe_rm "dist/$APP_BUNDLE_NAME"
safe_rm "dist/pkgroot"
mkdir -p "$APP_BUNDLE_DIR/Contents/MacOS"
mkdir -p "$APP_BUNDLE_DIR/Contents/Resources"
# Basic Info.plist — adjust CFBundleIdentifier if you have a reverse-domain identifier
cat > "$APP_BUNDLE_DIR/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>CFBundleName</key>
<string>$EXE_NAME</string>
<key>CFBundleDisplayName</key>
<string>$EXE_NAME</string>
<key>CFBundleIdentifier</key>
<string>com.example.$EXE_NAME</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleExecutable</key>
<string>$EXE_NAME</string>
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
</dict>
</plist>
EOF
# Copy the executable into the .app bundle
cp "$EXECUTABLE_PATH" "$APP_BUNDLE_DIR/Contents/MacOS/$EXE_NAME"
chmod +x "$APP_BUNDLE_DIR/Contents/MacOS/$EXE_NAME"
fi
# Create a macOS installer package (.pkg) using pkgbuild if available
PKGBUILD_BIN=$(command -v pkgbuild || true)
if [ -n "$PKGBUILD_BIN" ]; then
echo "Creating .pkg installer using pkgbuild..."
PKG_ID="com.example.$EXE_NAME"
PKG_VERSION="1.0"
PKG_OUT="dist/${EXE_NAME}-${PKG_VERSION}.pkg"
# pkgbuild expects a root directory containing the install destination layout.
TMP_PKG_ROOT="dist/pkgroot"
rm -rf "$TMP_PKG_ROOT"
mkdir -p "$TMP_PKG_ROOT/Applications"
if [ -d "$APP_BUNDLE_DIR" ]; then
cp -R "$APP_BUNDLE_DIR" "$TMP_PKG_ROOT/Applications/"
else
# If no .app bundle, install the dist folder into /usr/local/<EXE_NAME>
mkdir -p "$TMP_PKG_ROOT/usr/local/$EXE_NAME"
cp -R "dist/$EXE_NAME"/* "$TMP_PKG_ROOT/usr/local/$EXE_NAME/"
fi
# Build the package
pkgbuild --root "$TMP_PKG_ROOT" --identifier "$PKG_ID" --version "$PKG_VERSION" --install-location "/" "$PKG_OUT"
echo "Created installer: $PKG_OUT"
else
echo "pkgbuild not found — skipping .pkg creation. To create a .pkg installer, install Xcode command line tools which include pkgbuild."
fi
# Create a compressed DMG containing the .app for easy distribution
HDIUTIL_BIN=$(command -v hdiutil || true)
if [ -n "$HDIUTIL_BIN" ]; then
if [ -d "$APP_BUNDLE_DIR" ]; then
echo "Creating .dmg image containing $APP_BUNDLE_NAME..."
DMG_NAME="${EXE_NAME}-${PKG_VERSION}.dmg"
DMG_OUT="dist/$DMG_NAME"
DMG_STAGING="dist/dmg_staging"
safe_rm "$DMG_STAGING"
mkdir -p "$DMG_STAGING"
# Copy the .app into the staging area
cp -R "$APP_BUNDLE_DIR" "$DMG_STAGING/"
# Add a link to /Applications so users can drag-and-drop.
ln -s /Applications "$DMG_STAGING/Applications" || true
# Create a compressed read-only DMG
hdiutil create -volname "$EXE_NAME" -srcfolder "$DMG_STAGING" -ov -format UDZO "$DMG_OUT"
echo "Created DMG: $DMG_OUT"
# Cleanup staging
rm -rf "$DMG_STAGING"
else
echo "No .app bundle found at $APP_BUNDLE_DIR — skipping .dmg creation."
fi
else
echo "hdiutil not found — cannot create .dmg. This tool is available on macOS by default."
fi
echo
echo "Notes:"
cat <<'NOTES'
- This script creates a virtual environment in .venv and installs packages there.
- Use the directory (onedir) build to keep app_data.json next to the executable so the app can write settings.
- To codesign the produced app or binary, use the "codesign" tool or set signing options in a PyInstaller spec file.
Example (ad-hoc signing):
codesign --deep --force --verbose --sign - dist/$EXE_NAME/$EXE_NAME
For distribution on macOS (notarization), follow Apple's notarization flow.
- If you prefer to use the included PyInstaller spec file, run:
pyinstaller GitRepositoryManager.spec
NOTES