From 935ea4871bb646296d6645b2dcdc2bdef76a402c Mon Sep 17 00:00:00 2001 From: Sergey Fedorov 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) 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()