From d8acb36e8d6123bb6b35cb4f4733a709a4425e98 Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Wed, 10 Jul 2024 16:11:32 +0100 Subject: [PATCH] Resolve Qt framework path with `install_name_tool` (#7379) * Add comment about EXC_BAD_ACCESS * Optional certificate install * Improve log output * Move team ID arg * Change position of arg * Set PATH in .zshrc and use bash command substitution * Simplified macdeployqt find logic * Formatting * Minor tweaks to Qt cmake config * Resolve framework path with install_name_tool * Update ChangeLog --- .env.example | 26 +++++----- ChangeLog | 1 + cspell.json | 2 + scripts/install_deps.py | 2 +- scripts/lib/mac.py | 107 ++++++++++++++++++++++++++++++---------- src/gui/CMakeLists.txt | 37 +++++++------- 6 files changed, 117 insertions(+), 58 deletions(-) diff --git a/.env.example b/.env.example index bc8eb65c7..95691653b 100644 --- a/.env.example +++ b/.env.example @@ -25,22 +25,22 @@ # [Windows] Password for the PFX code signing certificate # WINDOWS_PFX_PASSWORD="super-secret-password" +# [macOS] Certificate ID for the Developer ID Application code signing certificate +# APPLE_CODESIGN_ID="Developer ID Application: Acme Inc (ABC123XYZ9)" + +# [macOS] Base64 encoded P12 code signing certificate +# APPLE_P12_CERTIFICATE="very-long-base64-encoded-string" + +# [macOS] Password for the P12 code signing certificate +# APPLE_P12_PASSWORD="super-secret-password" + +# [macOS] Apple Team ID +# https://developer.apple.com/account/#/membership +# APPLE_TEAM_ID="ABC123XYZ9" + # [macOS] Apple ID used to notarize the app # APPLE_NOTARY_USER="example@example.com" # [macOS] App-specific password for the Apple ID # https://support.apple.com/en-gb/102654 # APPLE_NOTARY_PASSWORD="super-secret-password" - -# [macOS] Apple Team ID -# https://developer.apple.com/account/#/membership -# APPLE_TEAM_ID="ABC123XYZ9" - -# [macOS] Certificate ID for the Developer ID Application code signing certificate -# APPLE_CODESIGN_ID="Developer ID Application: Acme Inc (ABC123XYZ9)" - -# [macOS] Password for the P12 code signing certificate -# APPLE_P12_PASSWORD="super-secret-password" - -# [macOS] Base64 encoded P12 code signing certificate -# APPLE_P12_CERTIFICATE="very-long-base64-encoded-string" diff --git a/ChangeLog b/ChangeLog index dbbac564b..0042ebdee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -46,6 +46,7 @@ Enhancements: - #7374 Add missing DEB and RPM dependencies - #7376 Automated weekly build of Docker images for Linux runners - #7378 Improve workflow triggers to ensure correct run time +- #7379 Resolve Qt framework path with `install_name_tool` - #7380 Add `qt6-qpa-plugins` Qt dependency for Debian # 1.14.6 diff --git a/cspell.json b/cspell.json index 6d9602fb4..e46f60f85 100644 --- a/cspell.json +++ b/cspell.json @@ -7,12 +7,14 @@ "aqtinstall", "codesign", "codesigning", + "contribs", "distros", "dmgbuild", "dotenv", "gdrive", "keychain", "Keychains", + "LLDB", "macdeployqt", "msvc", "notarytool", diff --git a/scripts/install_deps.py b/scripts/install_deps.py index 83cba1aad..c8515972a 100755 --- a/scripts/install_deps.py +++ b/scripts/install_deps.py @@ -106,7 +106,7 @@ class Dependencies: cmd_utils.run(command, shell=True, print_cmd=True) if not self.ci_env: - mac.set_cmake_prefix_env_var(self.config.get_os_value("qt-prefix-command")) + mac.set_env_vars(self.config.get_os_value("qt-prefix-command")) def linux(self): """Installs dependencies on Linux.""" diff --git a/scripts/lib/mac.py b/scripts/lib/mac.py index 3ede4ca4d..8ef2be16b 100644 --- a/scripts/lib/mac.py +++ b/scripts/lib/mac.py @@ -4,7 +4,10 @@ import lib.cmd_utils as cmd_utils import lib.env as env from lib.certificate import Certificate -cmake_env_var = "CMAKE_PREFIX_PATH" +path_env = "PATH" +cmake_env = "CMAKE_PREFIX_PATH" +cert_p12_env = "APPLE_P12_CERTIFICATE" +notary_user_env = "APPLE_NOTARY_USER" shell_rc = "~/.zshrc" dist_dir = "dist" product_name = "Synergy 1" @@ -21,35 +24,80 @@ keychain_path = "/Library/Keychains/System.keychain" def set_env_var(name, value): text = f'export {name}="${name}:{value}"' file = os.path.expanduser(shell_rc) - with open(file, "r") as f: - if text in f.read(): - return + if os.path.exists(file): + with open(file, "r") as f: + if text in f.read(): + return print(f"Setting environment variable: {name}={name}") with open(file, "a") as f: - f.write(f"\n{text}") + f.write(f"\n{text}\n") print(f"Appended to {shell_rc}: {text}") -def set_cmake_prefix_env_var(cmake_prefix_command): - result = cmd_utils.run( - cmake_prefix_command, get_output=True, shell=True, print_cmd=True - ) - cmake_prefix = result.stdout.strip() - set_env_var(cmake_env_var, cmake_prefix) +def set_env_vars(cmake_prefix_command): + cmd_sub = f"$({cmake_prefix_command})" + set_env_var(path_env, cmd_sub) + set_env_var(cmake_env, cmd_sub) def package(filename_base): - codesign_id = env.get_env("APPLE_CODESIGN_ID") - cert_base64 = env.get_env("APPLE_P12_CERTIFICATE") - cert_password = env.get_env("APPLE_P12_PASSWORD") + """ + Package the application for macOS. + The app bundle must be signed, or an error will occur: + > EXC_BAD_ACCESS (SIGKILL (Code Signature Invalid)) + An "Apple Development" certificate is sufficient for local development. + """ + + ( + codesign_id, + cert_base64, + cert_password, + notary_user, + notary_password, + notary_team_id, + ) = package_env_vars() + + if cert_base64: + install_certificate(cert_base64, cert_password) + else: + print(f"Skipped certificate installation, env var {cert_p12_env} not set") build_bundle() - install_certificate(cert_base64, cert_password) - assert_certificate_installed(codesign_id) sign_bundle(codesign_id) dmg_path = build_dmg(filename_base) - notarize_package(dmg_path) + + if notary_user: + notarize_package(dmg_path, notary_user, notary_password, notary_team_id) + else: + print(f"Skipped notarization, env var {notary_user_env} not set") + + +def package_env_vars(): + codesign_id = env.get_env("APPLE_CODESIGN_ID") + cert_base64 = env.get_env(cert_p12_env, required=False) + notary_user = env.get_env(notary_user_env, required=False) + + if notary_user: + notary_password = env.get_env("APPLE_NOTARY_PASSWORD") + notary_team_id = env.get_env("APPLE_TEAM_ID") + else: + notary_password = None + notary_team_id = None + + if cert_base64: + cert_password = env.get_env("APPLE_P12_PASSWORD") + else: + cert_password = None + + return ( + codesign_id, + cert_base64, + cert_password, + notary_user, + notary_password, + notary_team_id, + ) def build_bundle(): @@ -57,9 +105,19 @@ def build_bundle(): # cmake build install target should run macdeployqt cmd_utils.run("cmake --build build --target install", shell=True, print_cmd=True) + bundle_bin_path = "Contents/MacOS/synergy" + cmd_utils.run( + 'install_name_tool -add_rpath "@executable_path/../Frameworks" ' + f"{app_path}/{bundle_bin_path}", + shell=True, + print_cmd=True, + ) + def sign_bundle(codesign_id): print(f"Signing bundle {app_path}...") + + assert_certificate_installed(codesign_id) cmd_utils.run( [ codesign_path, @@ -75,6 +133,8 @@ def sign_bundle(codesign_id): def assert_certificate_installed(codesign_id): + print(f"Checking certificate: {codesign_id}") + installed = cmd_utils.run( "security find-identity -v -p codesigning", get_output=True, @@ -146,15 +206,10 @@ def install_certificate(cert_base64, cert_password): ) -def notarize_package(dmg_path): +def notarize_package(dmg_path, user, password, team_id): print(f"Notarizing package {dmg_path}...") notary_tool = NotaryTool() - notary_tool.store_credentials( - env.get_env("APPLE_NOTARY_USER"), - env.get_env("APPLE_NOTARY_PASSWORD"), - env.get_env("APPLE_TEAM_ID"), - ) - + notary_tool.store_credentials(user, password, team_id) notary_tool.submit_and_wait(dmg_path) @@ -186,10 +241,10 @@ class NotaryTool: notarytool_path, "store-credentials", "notarytool-password", - "--apple-id", - user, "--team-id", team_id, + "--apple-id", + user, "--password", password, ] diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4712938e5..a599a6a9a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,6 +1,6 @@ find_package( Qt6 - COMPONENTS Core Widgets Network Core5Compat + COMPONENTS Core5Compat Core Widgets Network REQUIRED) set(CMAKE_AUTOMOC ON) @@ -17,13 +17,6 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND GUI_SOURCE_FILES ${GUI_MAC_SOURCE_FILES}) endif() -# Retrieve the absolute path to qmake and then use that path to find the -# binaries -get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) -get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) -find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") -find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}") - if(NOT ENABLE_LICENSING) list(REMOVE_ITEM GUI_SOURCE_FILES ${ACTIVATION_FILES}) list(REMOVE_ITEM GUI_UI_FILES ${ACTIVATION_FILES}) @@ -51,17 +44,23 @@ if(WIN32) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - install(TARGETS synergy DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR}) - install( - CODE "MESSAGE (\"Running macdeployqt to install frameworks in bundle\")") - install( - CODE "execute_process(COMMAND ${MACDEPLOYQT_EXECUTABLE} ${SYNERGY_BUNDLE_APP_DIR} -always-overwrite)" - ) -elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - install(TARGETS synergy DESTINATION bin) -endif() -if(WIN32) + find_program(MACDEPLOYQT_BIN macdeployqt6) + message(STATUS "Found macdeployqt6: ${MACDEPLOYQT_BIN}") + + set(MACDEPLOYQT_CMD + "${MACDEPLOYQT_BIN} ${SYNERGY_BUNDLE_APP_DIR} -always-overwrite") + + install(TARGETS synergy DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR}) + install(CODE "MESSAGE (\"Running: ${MACDEPLOYQT_CMD}\")") + install(CODE "execute_process(COMMAND ${MACDEPLOYQT_CMD})") + +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + + install(TARGETS synergy DESTINATION bin) + +elseif(WIN32) + if(Qt6_FOUND AND WIN32 AND TARGET Qt6::qmake @@ -83,6 +82,7 @@ if(WIN32) ${imported_location}) endif() endif() + if(TARGET Qt6::windeployqt) # execute windeployqt in a tmp directory after build add_custom_command( @@ -92,4 +92,5 @@ if(WIN32) COMMAND Qt6::windeployqt "$/$") endif() + endif()