From 935ea4871bb646296d6645b2dcdc2bdef76a402c Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Sun, 19 Jul 2026 14:42:06 +0000
Subject: [PATCH 32/71] feat(macos): configure summary for the frontend +
 optional standalone .app bundle

1. Configuration summary now reports the native macOS frontend choice, the
   frontend ObjC++ compiler (with a warning if the default modern GCC is used
   instead of gcc-4.2), the ObjC++ standard, and the bundle setting.

2. CONTOUR_MACOS_BUNDLE=ON (default OFF) builds a self-contained Contour.app:
   the target becomes a MACOSX_BUNDLE with a minimal Info.plist and INSTALL_RPATH
   @executable_path/../Frameworks, and an install(CODE fixup_bundle) step copies
   every non-system dylib the binary needs (MacPorts libstdc++/libgcc_s, freetype,
   harfbuzz, fontconfig, libunicode, yaml-cpp, ...) into Contents/Frameworks and
   rewrites load paths, so the app runs with MacPorts deactivated. System
   frameworks are left as-is. Bundling the correct libstdc++/libgcc_s also makes
   the two-libstdc++ runtime issue moot for the bundled app. Default OFF keeps the
   plain bin/ install for normal MacPorts builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeG2jwYMX5gdvvtUnx3PJa
---
 CMakeLists.txt                   | 14 +++++++++++++-
 docs/macos-port.md               | 26 +++++++++++++++++++++++---
 src/contour_macos/CMakeLists.txt | 29 ++++++++++++++++++++++++++++-
 3 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 13d10dbc..f758aaa2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,6 +102,7 @@ option(CONTOUR_TESTING "Enables building of unittests for libterminal [default:
 option(CONTOUR_VERIFY_STATE "Enables expensive internal terminal-state invariant checks (Grid/Terminal verifyState) [default: ${MAINTAINER_MODE}]" ${MAINTAINER_MODE})
 option(CONTOUR_FRONTEND_GUI "Enables the Qt6 GUI frontend." ON)
 option(CONTOUR_FRONTEND_MACOS "Enables the native AppKit/CoreGraphics macOS frontend (no Qt)." OFF)
+option(CONTOUR_MACOS_BUNDLE "Build a standalone Contour.app that bundles its non-system dylibs, so it runs independently of MacPorts [default: OFF]." OFF)
 option(CONTOUR_COVERAGE "Builds with codecov [default: OFF]" OFF)
 option(CONTOUR_SANITIZE "Builds with Address sanitizer enabled [default: OFF]" "OFF")
 option(CONTOUR_STACKTRACE_ADDR2LINE "Uses addr2line to pretty-print SEGV stacktrace." ${ADDR2LINE_DEFAULT})
@@ -293,7 +294,18 @@ macro(ContourConfigurationSummary)
     message(STATUS "Build unit tests:                                   ${CONTOUR_TESTING}")
     message(STATUS "Verify terminal state invariants:                   ${CONTOUR_VERIFY_STATE}")
     message(STATUS "Enable with code coverage:                          ${CONTOUR_CODE_COVERAGE_ENABLED}")
-    message(STATUS "Build contour frontend GUI:                         ${CONTOUR_FRONTEND_GUI}")
+    message(STATUS "Build contour frontend GUI (Qt6):                   ${CONTOUR_FRONTEND_GUI}")
+    if(APPLE)
+        message(STATUS "Build native macOS frontend (AppKit, no Qt):        ${CONTOUR_FRONTEND_MACOS}")
+        if(CONTOUR_FRONTEND_MACOS)
+            if(CONTOUR_MACOS_OBJCXX_COMPILER)
+                message(STATUS "  Frontend ObjC++ compiler:                         ${CONTOUR_MACOS_OBJCXX_COMPILER} (-std=${CONTOUR_MACOS_OBJCXX_STD})")
+            else()
+                message(STATUS "  Frontend ObjC++ compiler:                         default OBJCXX (-std=${CONTOUR_MACOS_OBJCXX_STD})  [WARNING: modern GCC miscompiles this; set CONTOUR_MACOS_OBJCXX_COMPILER=/usr/bin/g++-4.2]")
+            endif()
+            message(STATUS "  Bundle dependencies (standalone .app):            ${CONTOUR_MACOS_BUNDLE}")
+        endif()
+    endif()
     message(STATUS "Build contour using mimalloc:                       ${CONTOUR_BUILD_WITH_MIMALLOC}")
     message(STATUS "Clang Tidy:                                         ${USING_TIDY_STRING}")
     message(STATUS "|> Enable performance metrics:                      ${CONTOUR_PERF_STATS}")
diff --git a/docs/macos-port.md b/docs/macos-port.md
index cd88329a..dd44e7e7 100644
--- a/docs/macos-port.md
+++ b/docs/macos-port.md
@@ -247,12 +247,32 @@ are a testing convenience; the primary target is 10.6 PowerPC.
   setup pass, no window/PTY), and `CMakeLists.txt` (an `INTERFACE` library `contour_macos`
   linking the engine libs, plus the `contour_macos_smoke` executable).
 
-Configure and build the current milestone with:
+Configure and build with:
 
 ```
-cmake -S . -B build -DCONTOUR_FRONTEND_MACOS=ON -DCONTOUR_TESTING=OFF
-cmake --build build --target contour_macos_smoke
+cmake -S . -B build -DCONTOUR_FRONTEND_MACOS=ON -DCONTOUR_TESTING=OFF \
+      -DCONTOUR_MACOS_OBJCXX_COMPILER=/usr/bin/g++-4.2
+cmake --build build --target contour
 ```
 
+`CONTOUR_MACOS_OBJCXX_COMPILER` is required on the target: modern GCC miscompiles the ObjC++
+frontend (it crashes the ObjC runtime at launch), so the two frontend `.mm` files must be
+built with Apple's `gcc-4.2` while the C++23 engine uses modern GCC. They link cleanly
+because the only thing crossing the compiler boundary is the plain-C `SessionBridge`.
+
+## Standalone app bundle (optional)
+
+`-DCONTOUR_MACOS_BUNDLE=ON` makes the install a self-contained `Contour.app`: at install
+time, CMake's `BundleUtilities::fixup_bundle` copies every non-system dylib the binary needs
+(MacPorts `libstdc++`/`libgcc_s`, freetype, harfbuzz, fontconfig, libunicode, yaml-cpp, …)
+into `Contents/Frameworks/` and rewrites the load paths to `@executable_path/../Frameworks`,
+so the app runs with MacPorts deactivated. System frameworks (AppKit/Foundation/
+ApplicationServices) are left as-is. Bundling the correct `libstdc++`/`libgcc_s` also makes
+the two-libstdc++ runtime issue (that the MacPorts DYLD wrapper otherwise papers over) moot
+for the bundled app. Default OFF: a plain MacPorts build installs a `bin/` binary whose
+runtime deps resolve through MacPorts. Caveat: `fixup_bundle` on the 10.6/PPC toolchain
+(otool/install_name_tool) is untested; deep or unusual dylib graphs can occasionally need a
+manual `install_name_tool` fix-up.
+
 The dependency graph pulled in here is the engine's own (freetype, harfbuzz, fontconfig,
 yaml-cpp, range-v3, ms-gsl, boxed-cpp, reflection-cpp, libunicode) plus CoreText — no Qt.
diff --git a/src/contour_macos/CMakeLists.txt b/src/contour_macos/CMakeLists.txt
index 78037dd3..7987bc99 100644
--- a/src/contour_macos/CMakeLists.txt
+++ b/src/contour_macos/CMakeLists.txt
@@ -137,5 +137,32 @@ Empty = use the modern default OBJCXX compiler.")
     )
 
     include(GNUInstallDirs)
-    install(TARGETS contour RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+
+    if(CONTOUR_MACOS_BUNDLE)
+        # --- Standalone Contour.app that carries its own non-system dylibs. ---
+        # Make the target a bundle with a minimal Info.plist, then at install time copy every
+        # non-system dylib the binary depends on (MacPorts libstdc++/libgcc_s, freetype,
+        # harfbuzz, fontconfig, libunicode, yaml-cpp, ...) into Contents/Frameworks and rewrite
+        # load paths to @executable_path-relative, so the app runs with MacPorts deactivated.
+        # System frameworks (AppKit/Foundation/ApplicationServices) are left as-is.
+        set_target_properties(contour PROPERTIES
+            MACOSX_BUNDLE ON
+            MACOSX_BUNDLE_BUNDLE_NAME "Contour"
+            MACOSX_BUNDLE_GUI_IDENTIFIER "org.contourterminal.Contour"
+            MACOSX_BUNDLE_BUNDLE_VERSION "${CONTOUR_VERSION_STRING}"
+            MACOSX_BUNDLE_SHORT_VERSION_STRING "${CONTOUR_VERSION}"
+            INSTALL_RPATH "@executable_path/../Frameworks")
+
+        install(TARGETS contour BUNDLE DESTINATION ".")
+
+        # fixup_bundle runs otool/install_name_tool on the installed .app and pulls in deps.
+        set(_bundle_app "\${CMAKE_INSTALL_PREFIX}/contour.app")
+        install(CODE "
+            include(BundleUtilities)
+            fixup_bundle(\"${_bundle_app}\" \"\" \"\")
+        " COMPONENT Runtime)
+    else()
+        # Plain binary in bin/ (the MacPorts default; runtime deps resolved via MacPorts).
+        install(TARGETS contour RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+    endif()
 endif()
