-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.sh
More file actions
executable file
·397 lines (341 loc) · 12.4 KB
/
build.sh
File metadata and controls
executable file
·397 lines (341 loc) · 12.4 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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/bin/bash
set -euo pipefail
# Function to print messages with a timestamp
log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') - $1"
}
# Function to detect the operating system
detect_os() {
case "$(uname -s)" in
Darwin)
echo "macos"
;;
Linux)
echo "linux"
;;
*)
echo "unsupported"
;;
esac
}
# Set the name of the application
NAME="PyRDPConnect"
# Determine the operating system
OS=$(detect_os)
if [ "$OS" == "unsupported" ]; then
log "Unsupported operating system. Exiting."
exit 1
fi
# Determine arch and whether we should use system PyQt5 (APT) on Linux ARM
ARCH="$(uname -m)"
USE_SYSTEM_PYQT=0
if [ "$OS" = "linux" ] && { [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "armv7l" ] || [ "$ARCH" = "armhf" ]; }; then
USE_SYSTEM_PYQT=1
fi
# Path to a vendored FreeRDP binary (set on macOS; Linux uses --add-data and resolves at runtime)
FREERDP_BIN=""
# Create a directory to store the final output based on the OS
FINAL_DIR="dist/$OS"
if [ -d "$FINAL_DIR" ]; then
rm -rf "$FINAL_DIR"
fi
mkdir -p "$FINAL_DIR"
# Require python3.11 on PATH
PYTHON_BIN="$(command -v python3.11 || true)"
if [ -z "$PYTHON_BIN" ]; then
log "python3.11 not found. On macOS, run: brew install python@3.11"
exit 1
fi
# (Re)create venv if missing or wrong version
NEED_RECREATE=0
if [ ! -x "env/bin/python" ]; then
NEED_RECREATE=1
else
VENV_VER="$(env/bin/python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' || echo unknown)"
if [ "$VENV_VER" != "3.11" ]; then
NEED_RECREATE=1
fi
fi
if [ "$NEED_RECREATE" -eq 1 ]; then
log "Creating fresh Python 3.11 virtual environment..."
rm -rf env
if [ "$USE_SYSTEM_PYQT" -eq 1 ]; then
# allow APT-installed PyQt5 to be visible in the venv
"$PYTHON_BIN" -m venv --system-site-packages env
else
"$PYTHON_BIN" -m venv env
fi
fi
# Activate venv
# shellcheck disable=SC1091
source env/bin/activate
# Double-check version (hard fail if not 3.11)
ACTIVE_VER="$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
if [ "$ACTIVE_VER" != "3.11" ]; then
log "Active Python is $ACTIVE_VER, expected 3.11. Aborting."
exit 1
fi
log "Using Python $(python -V)"
# Ensure that pip is updated
log "Updating pip..."
python -m pip install --upgrade pip wheel
log "Installing build dependencies..."
# PyInstaller 6.9+ supports 3.11 well; lock to <7 to avoid future surprizes.
# PyQt5 5.15.x is stable for Qt5 on macOS/Linux; lock <6.
python -m pip install "pyinstaller>=6.9,<7" "sip>=6.9,<7"
if [ "$USE_SYSTEM_PYQT" -eq 1 ]; then
# Ensure system PyQt5 (and QtSvg) are present
if command -v apt-get >/dev/null 2>&1; then
log "Installing system PyQt5 via APT (requires sudo)..."
sudo apt-get update
sudo apt-get install -y python3-pyqt5 python3-pyqt5.qtsvg
else
log "ERROR: APT not found; cannot install system PyQt5. Install PyQt5 manually or switch to a distro with APT."
exit 1
fi
# ensure system dist-packages are visible inside the venv (Debian/RPi)
SYS_PYTHON="$(command -v python3)"
# Collect all system "dist-packages" dirs that might contain APT's PyQt5
SYS_DIST_DIRS="$("$SYS_PYTHON" - <<'PY'
import site, sys
paths=set()
# Prefer entries that actually end with dist-packages
for p in getattr(site, 'getsitepackages', lambda: [])() or []:
if p.endswith('dist-packages'):
paths.add(p)
up = getattr(site, 'getusersitepackages', lambda: None)()
if up and str(up).endswith('dist-packages'):
paths.add(up)
# Common Debian/RPi locations
for c in ('/usr/lib/python3/dist-packages', '/usr/local/lib/python3/dist-packages'):
paths.add(c)
print('\\n'.join(sorted(paths)))
PY
)"
# Path to *this venv's* site-packages
VENV_SITE_PKGS="$(python - <<'PY'
import sysconfig
print(sysconfig.get_paths()["purelib"])
PY
)"
# Write a .pth pointing to each system dist-packages dir
: > "$VENV_SITE_PKGS/_system_dist_packages.pth"
while IFS= read -r d; do
[ -n "$d" ] && echo "$d" >> "$VENV_SITE_PKGS/_system_dist_packages.pth"
done <<< "$SYS_DIST_DIRS"
# Hard guarantee via sitecustomize.py
cat > "$VENV_SITE_PKGS/sitecustomize.py" <<'PY'
import sys
NEED = [
'/usr/lib/python3/dist-packages',
'/usr/local/lib/python3/dist-packages',
]
for p in NEED:
if p not in sys.path:
sys.path.append(p)
PY
# Sanity check (now should succeed)
python - <<'PY'
import sys
try:
import PyQt5, PyQt5.QtCore, PyQt5.QtWidgets, PyQt5.QtSvg
print("OK: System PyQt5 detected at:", PyQt5.__file__)
except Exception as e:
print("sys.path =", sys.path)
raise SystemExit(f"PyQt5 missing after setup: {e}")
PY
else
# Non-ARM / macOS etc: keep using PyPI wheels
python -m pip install "PyQt5>=5.15,<6"
fi
# Optional tools you had; keeping them only if you need them:
python -m pip install PySide6-Addons
# python -m pip install importlib PySide6-Addons
# Check if the .spec file exists
SPEC_FILE="$NAME.spec"
ICON_FILE="src/icons/icon.icns"
# Cleanup: Remove the leftover dist/$NAME directory on macOS
log "Cleaning up..."
if [ -d "dist/$NAME" ]; then
rm -rf "dist/$NAME"
fi
if [ -f "$SPEC_FILE" ]; then
rm -f "$SPEC_FILE"
fi
log "Verifying required PyQt5 modules are present..."
python - <<'PY'
from importlib.util import find_spec
missing = [m for m in ("PyQt5", "PyQt5.QtSvg") if find_spec(m) is None]
if missing:
raise SystemExit(f"Missing modules before build: {missing}")
print("Qt check passed.")
PY
log ".spec file not found. Generating a new one with PyInstaller..."
if [ "$OS" == "macos" ]; then
pyinstaller --windowed --name "$NAME" src/PyRDPConnect.py
elif [ "$OS" == "linux" ]; then
# Linux build: bundle data and hidden import directly
pyinstaller \
--onefile \
--name "$NAME" \
--hidden-import PyQt5.QtSvg \
--add-data "src/app:app" \
--add-data "src/styles:styles" \
--add-data "src/icons:icons" \
--add-data "src/img:img" \
--add-data "src/freerdp/linux:freerdp/linux" \
src/PyRDPConnect.py
fi
# Ensure the spec file now exists
if [ ! -f "$SPEC_FILE" ]; then
log "Failed to create .spec file. Exiting."
exit 1
fi
log "Generated .spec file: $SPEC_FILE"
# Update the .spec file to include the custom icon, data files, and hidden imports
log "Updating the .spec file to include the custom icon, data files, and hidden imports..."
if [ "$OS" == "macos" ]; then
sed -i '' "s|icon=None|icon='$ICON_FILE'|g" $SPEC_FILE
sed -i '' "/Analysis/s/(.*)/\0, hiddenimports=['PyQt5.QtSvg']/" $SPEC_FILE
sed -i '' "/a.datas +=/a \\
datas=[('src/styles', 'styles'), ('src/icons', 'icons'), ('src/app', 'app'), ('src/img', 'img')],
" $SPEC_FILE
elif [ "$OS" == "linux" ]; then
sed -i "s|icon=None|icon='$ICON_FILE'|g" $SPEC_FILE
sed -i "/Analysis/s/(.*)/\0, hiddenimports=['PyQt5.QtSvg']/" $SPEC_FILE
sed -i "/a.datas +=/a \\
datas=[('src/styles', 'styles'), ('src/icons', 'icons'), ('src/app', 'app'), ('src/img', 'img')],
" $SPEC_FILE
fi
# Build the project with PyInstaller using the updated .spec file
log "Building the project with PyInstaller..."
pyinstaller --noconfirm $SPEC_FILE
# Copy resources into the appropriate location
if [ "$OS" == "macos" ]; then
APP_ROOT="dist/$NAME.app/Contents"
APP_MACOS="$APP_ROOT/MacOS"
APP_RES="$APP_ROOT/Resources"
log "Creating app resource directories..."
mkdir -p "$APP_RES/styles" "$APP_RES/img" "$APP_RES/icons"
cp -R src/styles/* "$APP_RES/styles/"
cp -R src/img/* "$APP_RES/img/"
cp -R src/icons/* "$APP_RES/icons/"
# ---- Bundle FreeRDP as PyRDPConnect expects (Resources/freerdp/macos/...) ----
VENDOR_DIR_SRC="src/freerdp/macos"
VENDOR_DIR_DST="$APP_RES/freerdp/macos"
mkdir -p "$VENDOR_DIR_DST"
log "Copying FreeRDP tree to Resources..."
rsync -a "$VENDOR_DIR_SRC/" "$VENDOR_DIR_DST/"
FREERDP_BIN="$VENDOR_DIR_DST/xfreerdp"
LIB_DIR="$VENDOR_DIR_DST/lib"
X11_DIR="$VENDOR_DIR_DST/x11" # optional (if you vendored X11 libs)
PLUGINS_DIR="$VENDOR_DIR_DST/plugins" # optional
chmod +x "$FREERDP_BIN"
# ---- Patch rpaths + rewrite absolute Homebrew refs -> @rpath/<name> ----
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
add_rpath_if_missing() {
local bin="$1" r="$2"
if ! otool -l "$bin" | awk '/LC_RPATH/{getline; print $2}' | grep -qx "$r"; then
install_name_tool -add_rpath "$r" "$bin" 2>/dev/null || true
fi
}
# We want dyld to look inside ../lib and ../x11 relative to xfreerdp
add_rpath_if_missing "$FREERDP_BIN" "@loader_path/../lib"
if [ -d "$X11_DIR" ]; then
add_rpath_if_missing "$FREERDP_BIN" "@loader_path/../x11"
fi
# For each lib we ship, set id to @rpath/<base> and rewrite any Homebrew absolute deps to @rpath/<base>
patch_one_file() {
local file="$1"
# set its own id (for dylibs)
if [[ "$file" == *.dylib ]]; then
install_name_tool -id "@rpath/$(basename "$file")" "$file" 2>/dev/null || true
fi
# rewrite deps
otool -L "$file" | awk 'NR>1{print $1}' | while read -r dep; do
[ -z "$dep" ] && continue
case "$dep" in
/System/*|/usr/lib/*) continue ;; # keep system libs
esac
if echo "$dep" | grep -Eq "^$BREW_PREFIX/(opt|Cellar)/"; then
base="$(basename "$dep")"
install_name_tool -change "$dep" "@rpath/$base" "$file" 2>/dev/null || true
fi
done
}
log "Patching bundled dylibs..."
if [ -d "$LIB_DIR" ]; then
find "$LIB_DIR" -type f -name "*.dylib" -print0 | while IFS= read -r -d '' f; do
chmod u+w "$f"
patch_one_file "$f"
done
fi
if [ -d "$X11_DIR" ]; then
find "$X11_DIR" -type f -name "*.dylib" -print0 | while IFS= read -r -d '' f; do
chmod u+w "$f"
patch_one_file "$f"
done
fi
log "Patching xfreerdp to use @rpath for Homebrew deps..."
patch_one_file "$FREERDP_BIN"
# Ad-hoc sign so dyld doesn’t complain
log "Ad-hoc codesigning bundled libs and app..."
if [ -d "$LIB_DIR" ]; then
find "$LIB_DIR" -type f -name "*.dylib" -exec codesign --force --timestamp=none -s - {} \;
fi
if [ -d "$X11_DIR" ]; then
find "$X11_DIR" -type f -name "*.dylib" -exec codesign --force --timestamp=none -s - {} \;
fi
[ -f "$FREERDP_BIN" ] && codesign --force --timestamp=none -s - "$FREERDP_BIN"
codesign --force --deep --timestamp=none -s - "dist/$NAME.app"
log "Verify xfreerdp linkage (should show @rpath -> ../lib and ../x11):"
otool -L "$FREERDP_BIN" | sed 's/^/ /'
else
log "Moving the executable to the $FINAL_DIR directory..."
mv "dist/$NAME" "$FINAL_DIR/"
fi
# Verify the vendored version (macOS and linux)
expect_major="3" # adjust if you vendor 2.x
if [ -x "${FREERDP_BIN:-}" ]; then
vend_ver="$("$FREERDP_BIN" +version 2>/dev/null | head -n1 | grep -Eo '[0-9]+\.[0-9]+(\.[0-9]+)?' || true)"
if [ -z "$vend_ver" ]; then
log "WARN: Could not detect vendored FreeRDP version from +version"
else
vmaj="${vend_ver%%.*}"
if [ "$vmaj" != "$expect_major" ]; then
log "ERROR: Vendored FreeRDP major version is $vmaj, expected $expect_major"
exit 1
fi
log "Vendored FreeRDP version: $vend_ver"
fi
fi
# On Linux, fail fast if vendored xfreerdp has unresolved deps
if [ "$OS" = "linux" ] && [ -x "${FREERDP_BIN:-}" ]; then
if command -v ldd >/dev/null 2>&1; then
if ldd "$FREERDP_BIN" | grep -q "not found"; then
log "ERROR: Missing shared libraries for vendored xfreerdp:"
ldd "$FREERDP_BIN" | grep "not found" || true
exit 1
fi
else
log "WARN: ldd not available; skipping shared-library check."
fi
fi
# Move the built application or executable to the appropriate directory
if [ "$OS" == "macos" ]; then
log "Moving the .app bundle to the $FINAL_DIR directory..."
mv "dist/$NAME.app" "$FINAL_DIR/"
# Create a DMG image
log "Creating a DMG image for macOS..."
DMG_NAME="$FINAL_DIR/$NAME.dmg"
hdiutil create "$DMG_NAME" -volname "$NAME" -srcfolder "$FINAL_DIR/$NAME.app" -ov -format UDZO
log "DMG image created at $DMG_NAME"
fi
# Cleanup: Remove the leftover dist/$NAME directory on macOS
if [ -d "dist/$NAME" ]; then
log "Cleaning up the dist directory..."
rm -rf "dist/$NAME"
fi
log "Build completed successfully."
# Deactivate the virtual environment
deactivate