Fixed the macOS launcher errors you hit:

suppressed noisy defaults read failures for missing preference keys,

        fixed unsafe/empty test expressions that caused test: argument expected,

        corrected the gettext app domain from APP=name to APP=zoitechat.

    Improved launcher behavior for architecture mismatches (Bad CPU type in executable) by detecting exit 126 and printing a clear hint plus file output for ZoiteChat-bin.

    Updated osx/makebundle.sh so bundling is more Intel/Apple Silicon friendly:

        dynamically resolves/writes prefix and prefix:enchant (Homebrew Intel vs Apple Silicon layouts),

        still adapts enchant data path (share/enchant vs share/enchant-2),

        prints bundled binary architecture after bundling so mismatches are immediately visible.

    Extended macOS debugging docs with a dedicated “Bad CPU type in executable” section and explicit Intel (x86_64) build commands.
This commit is contained in:
2026-02-19 10:06:49 -07:00
parent 8a1dcee8e1
commit cf2d5e5778
4 changed files with 208 additions and 19 deletions

146
osx/DEBUGGING.md Normal file
View File

@@ -0,0 +1,146 @@
# Debugging ZoiteChat on macOS (Xcode + CLI)
If the unsigned `.app` launches but does nothing (or immediately exits), use the steps below to get actionable logs and a debugger session.
## 1) Build with debug symbols
ZoiteChat uses Meson. Build a debug configuration first so LLDB can show useful backtraces.
```bash
meson setup build-macos-debug --buildtype=debug
meson compile -C build-macos-debug
```
This project default is `debugoptimized`, but a full `debug` build is better while diagnosing crashes/startup issues.
## 2) Bundle the app and verify deployment target
Use the existing bundle script:
```bash
cd osx
./makebundle.sh
```
The generated `Info.plist` should keep:
- `LSMinimumSystemVersion = 11.0`
That is the projects explicit minimum runtime target for macOS 11+.
## 3) Sanity-check the Mach-O binary in the bundle
Confirm architecture(s), deployment target, and linked libraries:
```bash
file ZoiteChat.app/Contents/MacOS/ZoiteChat-bin
otool -l ZoiteChat.app/Contents/MacOS/ZoiteChat-bin | rg -n "LC_BUILD_VERSION|minos|sdk"
otool -L ZoiteChat.app/Contents/MacOS/ZoiteChat-bin
```
For widest compatibility, build universal (`arm64` + `x86_64`) or build separately per arch and test each on matching hosts.
## 4) Ad-hoc sign for local debugging
Unsigned GUI binaries can fail in unhelpful ways because of hardened runtime/quarantine/Gatekeeper interactions. For local debugging, ad-hoc sign the app:
```bash
xattr -dr com.apple.quarantine ZoiteChat.app
codesign --force --deep --sign - ZoiteChat.app
codesign --verify --deep --strict --verbose=2 ZoiteChat.app
```
## 5) Launch from Terminal first (before Xcode)
Direct launch exposes stdout/stderr and validates the launcher environment:
```bash
./ZoiteChat.app/Contents/MacOS/ZoiteChat
```
The launcher script sets GTK/Pango/GDK environment variables. If direct launch fails, capture that output first.
## 6) Debug with LLDB (reliable baseline)
Debug the real executable inside the bundle:
```bash
lldb -- ZoiteChat.app/Contents/MacOS/ZoiteChat-bin
(lldb) run
(lldb) bt
```
If it exits instantly, set breakpoints on startup entry points (for example `main`) and re-run.
## 7) Debug with Xcode (if you prefer GUI)
Xcode works best when opening the executable directly instead of importing build scripts.
1. **File → Open…** and select `ZoiteChat.app/Contents/MacOS/ZoiteChat-bin`.
2. In **Product → Scheme → Edit Scheme…**
- Executable: `ZoiteChat-bin`
- Working directory: repo root (or `osx/`)
- Add environment variables equivalent to the launcher if needed.
3. Run under debugger.
If Xcode “runs” but no UI appears, compare its environment to `osx/launcher.sh` and copy missing GTK-related variables into the scheme.
## 8) Capture macOS crash diagnostics
Even if no dialog appears, macOS usually logs termination reasons:
```bash
log stream --style compact --predicate 'process == "ZoiteChat-bin"'
```
Also check:
- `~/Library/Logs/DiagnosticReports/`
## 9) Frequent root causes for “silent” startup failures
- Missing GTK runtime libraries in app bundle (`otool -L` shows unresolved paths).
- Incorrect `@rpath`/install names after bundling.
- Running under Rosetta mismatch (x86_64 binary with arm64-only deps or vice versa).
- Quarantine/signature issues on unsigned artifacts.
- Missing `GDK_PIXBUF_MODULE_FILE`, `GTK_IM_MODULE_FILE`, or schema paths.
### "Bad CPU type in executable"
This means the bundled `ZoiteChat-bin` architecture does not match the Mac you are running on.
- Intel Mac requires `x86_64`
- Apple Silicon requires `arm64` (or Rosetta + `x86_64`)
Check the binary quickly:
```bash
file ZoiteChat.app/Contents/MacOS/ZoiteChat-bin
```
Build for Intel explicitly when needed:
```bash
export CFLAGS="-arch x86_64"
export LDFLAGS="-arch x86_64"
meson setup build-macos-intel --buildtype=debug
meson compile -C build-macos-intel
```
If your dependency stack supports it, build universal (`arm64` + `x86_64`) and verify with `lipo -info`.
## 10) Recommended compatibility settings for macOS 11+
- Keep `LSMinimumSystemVersion` at `11.0`.
- Build on the oldest macOS SDK/toolchain that still supports your dependencies, or explicitly set:
```bash
export MACOSX_DEPLOYMENT_TARGET=11.0
```
- Verify with `otool -l` that `minos` is actually `11.0` in the final executable.
---
If you want, we can add an Xcode scheme file to this repo that mirrors `osx/launcher.sh` so “Run” in Xcode behaves exactly like launching the app bundle from Finder/Terminal.

View File

@@ -1,13 +1,13 @@
#!/bin/sh #!/usr/bin/env bash
if test "x$GTK_DEBUG_LAUNCHER" != x; then if test "x$GTK_DEBUG_LAUNCHER" != x; then
set -x set -x
fi fi
if test "x$GTK_DEBUG_GDB" != x; then if test "x$GTK_DEBUG_GDB" != x; then
EXEC="gdb --args" EXEC_PREFIX=(gdb --args)
else else
EXEC=exec EXEC_PREFIX=()
fi fi
name=`basename "$0"` name=`basename "$0"`
@@ -50,7 +50,7 @@ export OPENSSL_CONF="/System/Library/OpenSSL/openssl.cnf"
export ZOITECHAT_LIBDIR="$bundle_lib/zoitechat/plugins" export ZOITECHAT_LIBDIR="$bundle_lib/zoitechat/plugins"
APP=name APP=zoitechat
I18NDIR="$bundle_data/locale" I18NDIR="$bundle_data/locale"
# Set the locale-related variables appropriately: # Set the locale-related variables appropriately:
unset LANG LC_MESSAGES LC_MONETARY LC_COLLATE unset LANG LC_MESSAGES LC_MONETARY LC_COLLATE
@@ -58,7 +58,7 @@ unset LANG LC_MESSAGES LC_MONETARY LC_COLLATE
# Has a language ordering been set? # Has a language ordering been set?
# If so, set LC_MESSAGES and LANG accordingly; otherwise skip it. # If so, set LC_MESSAGES and LANG accordingly; otherwise skip it.
# First step uses sed to clean off the quotes and commas, to change - to _, and change the names for the chinese scripts from "Hans" to CN and "Hant" to TW. # First step uses sed to clean off the quotes and commas, to change - to _, and change the names for the chinese scripts from "Hans" to CN and "Hant" to TW.
APPLELANGUAGES=`defaults read .GlobalPreferences AppleLanguages | sed -En -e 's/\-/_/' -e 's/Hant/TW/' -e 's/Hans/CN/' -e 's/[[:space:]]*\"?([[:alnum:]_]+)\"?,?/\1/p' ` APPLELANGUAGES=`defaults read .GlobalPreferences AppleLanguages 2>/dev/null | sed -En -e 's/\-/_/' -e 's/Hant/TW/' -e 's/Hans/CN/' -e 's/[[:space:]]*\"?([[:alnum:]_]+)\"?,?/\1/p' `
if test "$APPLELANGUAGES"; then if test "$APPLELANGUAGES"; then
# A language ordering exists. # A language ordering exists.
# Test, item per item, to see whether there is an corresponding locale. # Test, item per item, to see whether there is an corresponding locale.
@@ -89,26 +89,26 @@ fi
unset APPLELANGUAGES L unset APPLELANGUAGES L
# If we didn't get a language from the language list, try the Collation preference, in case it's the only setting that exists. # If we didn't get a language from the language list, try the Collation preference, in case it's the only setting that exists.
APPLECOLLATION=`defaults read .GlobalPreferences AppleCollationOrder` APPLECOLLATION=`defaults read .GlobalPreferences AppleCollationOrder 2>/dev/null || true`
if test -z ${LANG} -a -n $APPLECOLLATION; then if test -z "${LANG:-}" -a -n "${APPLECOLLATION:-}"; then
if test -f "$I18NDIR/${APPLECOLLATION:0:2}/LC_MESSAGES/$APP.mo"; then if test -f "$I18NDIR/${APPLECOLLATION:0:2}/LC_MESSAGES/$APP.mo"; then
export LANG=${APPLECOLLATION:0:2} export LANG=${APPLECOLLATION:0:2}
fi fi
fi fi
if test ! -z $APPLECOLLATION; then if test -n "${APPLECOLLATION:-}"; then
export LC_COLLATE=$APPLECOLLATION export LC_COLLATE=$APPLECOLLATION
fi fi
unset APPLECOLLATION unset APPLECOLLATION
# Continue by attempting to find the Locale preference. # Continue by attempting to find the Locale preference.
APPLELOCALE=`defaults read .GlobalPreferences AppleLocale` APPLELOCALE=`defaults read .GlobalPreferences AppleLocale 2>/dev/null || true`
if test -f "$I18NDIR/${APPLELOCALE:0:5}/LC_MESSAGES/$APP.mo"; then if test -f "$I18NDIR/${APPLELOCALE:0:5}/LC_MESSAGES/$APP.mo"; then
if test -z $LANG; then if test -z "${LANG:-}"; then
export LANG="${APPLELOCALE:0:5}" export LANG="${APPLELOCALE:0:5}"
fi fi
elif test -z $LANG -a -f "$I18NDIR/${APPLELOCALE:0:2}/LC_MESSAGES/$APP.mo"; then elif test -z "${LANG:-}" -a -f "$I18NDIR/${APPLELOCALE:0:2}/LC_MESSAGES/$APP.mo"; then
export LANG="${APPLELOCALE:0:2}" export LANG="${APPLELOCALE:0:2}"
fi fi
@@ -116,20 +116,20 @@ fi
#5-character locale to avoid the "Locale not supported by C library" #5-character locale to avoid the "Locale not supported by C library"
#warning from Gtk -- even though Gtk will translate with a #warning from Gtk -- even though Gtk will translate with a
#two-character code. #two-character code.
if test -n $LANG; then if test -n "${LANG:-}"; then
#If the language code matches the applelocale, then that's the message #If the language code matches the applelocale, then that's the message
#locale; otherwise, if it's longer than two characters, then it's #locale; otherwise, if it's longer than two characters, then it's
#probably a good message locale and we'll go with it. #probably a good message locale and we'll go with it.
if test $LANG == ${APPLELOCALE:0:5} -o $LANG != ${LANG:0:2}; then if test "$LANG" = "${APPLELOCALE:0:5}" -o "$LANG" != "${LANG:0:2}"; then
export LC_MESSAGES=$LANG export LC_MESSAGES=$LANG
#Next try if the Applelocale is longer than 2 chars and the language #Next try if the Applelocale is longer than 2 chars and the language
#bit matches $LANG #bit matches $LANG
elif test $LANG == ${APPLELOCALE:0:2} -a $APPLELOCALE > ${APPLELOCALE:0:2}; then elif test "$LANG" = "${APPLELOCALE:0:2}" -a "$APPLELOCALE" \> "${APPLELOCALE:0:2}"; then
export LC_MESSAGES=${APPLELOCALE:0:5} export LC_MESSAGES=${APPLELOCALE:0:5}
#Fail. Get a list of the locales in $PREFIX/share/locale that match #Fail. Get a list of the locales in $PREFIX/share/locale that match
#our two letter language code and pick the first one, special casing #our two letter language code and pick the first one, special casing
#english to set en_US #english to set en_US
elif test $LANG == "en"; then elif test "$LANG" = "en"; then
export LC_MESSAGES="en_US" export LC_MESSAGES="en_US"
else else
LOC=`find $PREFIX/share/locale -name $LANG???` LOC=`find $PREFIX/share/locale -name $LANG???`
@@ -181,4 +181,18 @@ if /bin/expr "x$1" : '^x-psn_' > /dev/null; then
shift 1 shift 1
fi fi
$EXEC "$bundle_contents/MacOS/$name-bin" "$@" $EXTRA_ARGS BIN_PATH="$bundle_contents/MacOS/$name-bin"
if test ${#EXEC_PREFIX[@]} -gt 0; then
"${EXEC_PREFIX[@]}" "$BIN_PATH" "$@" $EXTRA_ARGS
else
"$BIN_PATH" "$@" $EXTRA_ARGS
fi
status=$?
if test "$status" -eq 126; then
echo "error: $BIN_PATH could not execute on this Mac (possible architecture mismatch)." >&2
if command -v file >/dev/null 2>&1; then
file "$BIN_PATH" >&2 || true
fi
echo "hint: build ZoiteChat for this architecture (x86_64 on Intel, arm64 on Apple Silicon) or as a universal binary." >&2
fi
exit "$status"

View File

@@ -36,9 +36,29 @@ rm -f ./*.app.zip
# - some have no share-level config dir at all # - some have no share-level config dir at all
# Keep the bundle definition in sync with what's actually available so # Keep the bundle definition in sync with what's actually available so
# gtk-mac-bundler doesn't fail on a missing source path. # gtk-mac-bundler doesn't fail on a missing source path.
ENCHANT_PREFIX_PATH="${ENCHANT_PREFIX:-}"
if [ -z "$ENCHANT_PREFIX_PATH" ] && command -v brew >/dev/null 2>&1; then # Resolve package-manager prefix dynamically so Intel (/usr/local) and
ENCHANT_PREFIX_PATH="$(brew --prefix enchant 2>/dev/null || true)" # Apple Silicon (/opt/homebrew) hosts both bundle correctly.
BUNDLE_PREFIX="${BUNDLE_PREFIX:-}"
if [ -z "$BUNDLE_PREFIX" ] && command -v brew >/dev/null 2>&1; then
BUNDLE_PREFIX="$(brew --prefix 2>/dev/null || true)"
fi
if [ -z "$BUNDLE_PREFIX" ]; then
BUNDLE_PREFIX="/usr/local"
fi
ENCHANT_PREFIX_DEFAULT="${BUNDLE_PREFIX}/opt/enchant"
ENCHANT_PREFIX_PATH="${ENCHANT_PREFIX:-$ENCHANT_PREFIX_DEFAULT}"
perl -0pi -e 's|(<prefix\s+name="default">)[^<]+(</prefix>)|$1'"$BUNDLE_PREFIX"'$2|s' "$BUNDLE_DEF"
perl -0pi -e 's|(<prefix\s+name="enchant">)[^<]+(</prefix>)|$1'"$ENCHANT_PREFIX_PATH"'$2|s' "$BUNDLE_DEF"
if command -v brew >/dev/null 2>&1; then
BREW_ENCHANT_PREFIX="$(brew --prefix enchant 2>/dev/null || true)"
if [ -n "$BREW_ENCHANT_PREFIX" ]; then
ENCHANT_PREFIX_PATH="$BREW_ENCHANT_PREFIX"
perl -0pi -e 's|(<prefix\s+name="enchant">)[^<]+(</prefix>)|$1'"$ENCHANT_PREFIX_PATH"'$2|s' "$BUNDLE_DEF"
fi
fi fi
if [ -n "$ENCHANT_PREFIX_PATH" ]; then if [ -n "$ENCHANT_PREFIX_PATH" ]; then
@@ -72,5 +92,10 @@ if [ ! -d "$APP_NAME" ]; then
exit 1 exit 1
fi fi
if command -v file >/dev/null 2>&1; then
echo "Bundled binary architecture:"
file "$APP_NAME/Contents/MacOS/ZoiteChat-bin" || true
fi
echo "Compressing bundle" echo "Compressing bundle"
zip -9rXq "./ZoiteChat-$(git describe --tags).app.zip" "./$APP_NAME" zip -9rXq "./ZoiteChat-$(git describe --tags).app.zip" "./$APP_NAME"

View File

@@ -52,3 +52,7 @@ provide binary packages linked to the OpenSSL libraries, provided that
all other requirements of the GPL are met. all other requirements of the GPL are met.
See file COPYING for details. See file COPYING for details.
</sub> </sub>
## macOS debugging
If you are troubleshooting local macOS build/run/debug issues (including Xcode setup), see `osx/DEBUGGING.md`.