From: Manuel Nickschas Date: Sun, 3 Jan 2021 13:48:36 +0000 (+0100) Subject: cmake: Modernize (and fix) deployment on macOS X-Git-Tag: 0.14-rc2~32 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=082cb8c8eb6db90cbb2166a0098874e76d5c6ad9 cmake: Modernize (and fix) deployment on macOS For the past decade or so, we have used a bunch of self-written python and bash scripts for creating packages for macOS. These have not aged well, and recently several workarounds had to be hacked in to keep the machinery somewhat working at all. Still, invoking the scripts at build time rather than install time caused a race condition where sometimes not all the packages would be created in CI. To break the camel's back, deploying dependencies no longer worked correctly and broke the packages completely in 0.14-rc1. Fix this by modernizing the whole deployment process and related parts of the build system, replacing the custom scripts by relying on Qt's and CMake's own tooling instead. Some workarounds still need to be added to that to make everything work correctly (neither tool can deal correctly with QtWebEngine, for some reason, and CMake's Info.plist template lacks functionality), but the main part of the work is now delegated to official tooling, everything properly happend at install time avoiding race conditions in the build process, and we can remove a bunch of decade-old and hardly maintained custom code. Also adapt the CI configuration to use -DBUNDLE instead of the old -DDEPLOY, and remove the explicit setting of qmake's path too, as it is no longer needed now. --- diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0dae8e4..2bcfc196 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -142,24 +142,25 @@ jobs: mkdir build cd build && cmake $GITHUB_WORKSPACE \ -GNinja \ + -DWANT_CORE=ON \ + -DWANT_QTCLIENT=ON \ + -DWANT_MONO=ON \ -DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \ + -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/bundles \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING=ON \ -DFATAL_WARNINGS=OFF \ - -DDEPLOY=ON \ - -DENABLE_SHARED=OFF + -DENABLE_SHARED=OFF \ + -DBUNDLE=ON \ - name: Build - run: | - # Deploy scripts require qmake in the path - export PATH=$PATH:/usr/local/opt/qt5/bin - cd build && ninja + run: cd build && ninja - name: Run tests run: cd build && ctest - name: Install - run: cd build && DESTDIR=$GITHUB_WORKSPACE/image ninja install + run: cd build && ninja install - name: Print ccache stats run: ccache -s @@ -168,7 +169,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: macOS - path: ${{ github.workspace }}/build/*.dmg + path: ${{ github.workspace }}/bundles/*.dmg # ------------------------------------------------------------------------------------------------------------------------------------------ build-windows: diff --git a/CMakeLists.txt b/CMakeLists.txt index cebb61b0..dfdaaebe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,10 @@ if (APPLE) find_library(CARBON_LIBRARY Carbon) mark_as_advanced(CARBON_LIBRARY) link_libraries(${CARBON_LIBRARY}) + + # Whether to enable the creation of bundles and DMG images + cmake_dependent_option(BUNDLE "Create bundles and DMG images" OFF "APPLE" OFF) + add_feature_info(BUNDLE BUNDLE "Create bundles and DMG images") endif() # Always embed on Windows or OSX; never embed when enabling KDE integration @@ -101,14 +105,13 @@ if (WIN32 OR APPLE) set(EMBED_DEFAULT ON) endif() cmake_dependent_option(EMBED_DATA "Embed icons and translations into the binaries instead of installing them" ${EMBED_DEFAULT} - "NOT WIN32;NOT WITH_KDE" ${EMBED_DEFAULT}) + "NOT WIN32;NOT WITH_KDE" ${EMBED_DEFAULT}) if (NOT EMBED_DEFAULT) add_feature_info(EMBED_DATA EMBED_DATA "Embed icons and translations in the binaries instead of installing them") endif() -# The following options are not for end-user consumption, so don't list them in the feature summary +# The following option is not for end-user consumption, so don't list it in the feature summary option(FATAL_WARNINGS "Make compile warnings fatal (most useful for CI builds)" OFF) -cmake_dependent_option(DEPLOY "Add required libs to bundle resources and create a dmg" OFF "APPLE" OFF) # List of authenticators and the cmake flags to build them # (currently that's just LDAP, but more can be added here). diff --git a/ChangeLog b/ChangeLog index c395764d..223e9403 100644 --- a/ChangeLog +++ b/ChangeLog @@ -47,6 +47,7 @@ NOTE: Database schema format change, no downgrade possible! * Support building shared libraries via the ENABLE_SHARED CMake option (defaults to on) * Introduce support for (and a small selection of) unit tests via the BUILD_TESTING CMake option * Use Github Actions as CI system, replacing Travis and Appveyor +* Revamp bundle/DMG creation on macOS * Many smaller fixes * Improve documentation and UI help * Update translations diff --git a/cmake/FinalizeBundle.cmake.in b/cmake/FinalizeBundle.cmake.in new file mode 100644 index 00000000..bce8c072 --- /dev/null +++ b/cmake/FinalizeBundle.cmake.in @@ -0,0 +1,63 @@ +include(BundleUtilities) + +# After the relevant targets, support files, as well as plugins have already been installed into the bundle structure, +# the bundle must still be made standalone by copying the required frameworks and making them position-independent. +# This is generally called "fixing up" the bundle. +# +# Principally there are two ways to do that: Qt's official macdeployqt tool, and CMake's BundleUtilities. +# +# Some frameworks, in particular QtWebEngineCore, come with nested bundles. In order for them to work correctly, the Frameworks directory +# from the main bundle must be symlinked into the nested bundle, otherwise dependencies cannot be resolved. +# Neither macdeployqt (shockingly) nor BundleUtilities can handle this properly. The former simply ignores nested bundles and thus +# does not fix them up at all. The latter scans for additional binaries and tries fixing them up, but there is no way to inject +# creation of the Frameworks symlink between the copy and the fixup steps. +# +# The working solution implemented here is to first run macdeployqt (which also handles some Qt-specific quirks), then symlink Frameworks +# into the nested bundles (if any), then use BundleUtilities to perform the remaining fixups and verify the bundle. + +# Since we're in the install phase, DESTDIR might be set +set(BUNDLE_PATH "$ENV{DESTDIR}@BUNDLE_PATH@") +set(DMG_PATH "$ENV{DESTDIR}@DMG_PATH@") + +# First, use Qt's official tool, macdeployqt, for deploying the needed Qt Frameworks into the bundle +message(STATUS "Deploying Qt Frameworks in bundle \"${BUNDLE_PATH}\"") +execute_process( + # Don't deploy plugins - we've already installed the selection relevant for our target! + COMMAND @MACDEPLOYQT_EXECUTABLE@ "${BUNDLE_PATH}" -verbose=1 -no-plugins + RESULT_VARIABLE result +) +if(NOT result EQUAL 0) + message(FATAL_ERROR "Deploying Qt Frameworks failed.") +endif() + +# Scan for nested bundles and symlink the main bundle's Frameworks directory into them +message(STATUS "Checking for nested bundles") +execute_process( + COMMAND find "${BUNDLE_PATH}" -mindepth 1 -type d -name "*.app" + OUTPUT_VARIABLE nested_bundles + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(nested_bundles) + string(REPLACE "\n" ";" nested_bundles ${nested_bundles}) + foreach(nested_bundle IN LISTS nested_bundles) + message(STATUS "Symlinking Frameworks into nested bundle \"${nested_bundle}\"") + file(RELATIVE_PATH path "${nested_bundle}/Contents" "${BUNDLE_PATH}/Contents/Frameworks") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${path}" "${nested_bundle}/Contents/Frameworks") + endforeach() +else() + message("Checking for nested bundles - none found") +endif() + +# Now fixup the whole thing using CMake's own tooling, which (unlike macdeployqt) will take care of any additional internal executables +message(STATUS "Fixing up bundle...") +fixup_bundle("${BUNDLE_PATH}" "" "") + +# Create the DMG image +message(STATUS "Creating DMG image...") +execute_process( + COMMAND hdiutil create "${DMG_PATH}" -srcfolder "${BUNDLE_PATH}" -format "UDBZ" -fs "HFS+" -volname "Quassel IRC" + RESULT_VARIABLE result +) +if(NOT result EQUAL 0) + message(FATAL_ERROR "Creating DMG image failed.") +endif() diff --git a/scripts/build/Info.plist b/cmake/MacOSXBundleInfo.plist.in similarity index 81% rename from scripts/build/Info.plist rename to cmake/MacOSXBundleInfo.plist.in index c773a3f5..76700789 100644 --- a/scripts/build/Info.plist +++ b/cmake/MacOSXBundleInfo.plist.in @@ -5,11 +5,11 @@ CFBundleDevelopmentRegion English CFBundleExecutable - %(BUNDLE_NAME)s + @BUNDLE_NAME@ CFBundleGetInfoString Quassel IRC Client CFBundleIconFile - %(ICON_FILE)s + quassel.icns CFBundleIdentifier org.quassel-irc.client CFBundleInfoDictionaryVersion @@ -19,22 +19,22 @@ CFBundlePackageType APPL CFBundleShortVersionString - %(BUNDLE_VERSION)s + @QUASSEL_MAJOR@.@QUASSEL_MINOR@.@QUASSEL_PATCH@ CFBundleSignature ???? CFBundleVersion - %(BUNDLE_VERSION)s + @QUASSEL_MAJOR@.@QUASSEL_MINOR@.@QUASSEL_PATCH@ + LSMinimumSystemVersion + @QMAKE_MACOSX_DEPLOYMENT_TARGET@ LSRequiresCarbon - NSPrincipalClass - NSApplication NSHighResolutionCapable NSHumanReadableCopyright © 2005-2020, Quassel IRC Team + NSPrincipalClass + NSApplication NSSupportsAutomaticGraphicsSwitching - LSMinimumSystemVersion - %(QT_MACOSX_DEPLOYMENT_TARGET)s diff --git a/cmake/QuasselMacros.cmake b/cmake/QuasselMacros.cmake index f6d4ea69..077e59a2 100644 --- a/cmake/QuasselMacros.cmake +++ b/cmake/QuasselMacros.cmake @@ -90,6 +90,143 @@ function(quassel_add_module _module) set(TARGET ${target} PARENT_SCOPE) endfunction() +################################################################################################### +# Adds an executable target for Quassel. +# +# quassel_add_executable( COMPONENT [SOURCES src1 src2...] [LIBRARIES lib1 lib2...]) +# +# This function supports the creation of either of the three hardcoded executable targets: Core, Client, and Mono. +# Given sources and libraries are added to the target. +# +# On macOS, the creation of bundles and corresponding DMG files is supported and can be enabled by setting the +# BUNDLE option to ON. +# +function(quassel_add_executable _target) + set(options) + set(oneValueArgs COMPONENT) + set(multiValueArgs SOURCES LIBRARIES) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Set up some hard-coded data based on the component to be built + if(ARG_COMPONENT STREQUAL "Core") + set(DEFINE BUILD_CORE) + set(WIN32 FALSE) + set(BUNDLE_NAME "Quassel Core") + elseif(ARG_COMPONENT STREQUAL "Client") + set(DEFINE BUILD_QTUI) + set(WIN32 TRUE) + set(BUNDLE_NAME "Quassel Client") + elseif(ARG_COMPONENT STREQUAL "Mono") + set(DEFINE BUILD_MONO) + set(WIN32 TRUE) + set(BUNDLE_NAME "Quassel") + else() + message(FATAL_ERROR "quassel_executable requires a COMPONENT argument with one of the values 'Core', 'Client' or 'Mono'") + endif() + + add_executable(${_target} ${ARG_SOURCES}) + set_property(TARGET ${_target} APPEND PROPERTY COMPILE_DEFINITIONS ${DEFINE}) + set_target_properties(${_target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + WIN32_EXECUTABLE ${WIN32} # Ignored on non-Windows platforms + ) + target_link_libraries(${_target} PUBLIC ${ARG_LIBRARIES}) # Link publicly, so plugin detection for bundles work + + # Prepare bundle creation on macOS + if(APPLE AND BUNDLE) + set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}.app") + set(DMG_PATH "${CMAKE_INSTALL_PREFIX}/Quassel${ARG_COMPONENT}_MacOSX-x86_64_${QUASSEL_VERSION_STRING}.dmg") + + # Generate an appropriate Info.plist + set(BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/Info_${ARG_COMPONENT}.plist") + configure_file(${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in ${BUNDLE_INFO_PLIST} @ONLY) + + # Set some bundle-specific properties + set_target_properties(${_target} PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${BUNDLE_INFO_PLIST}" + OUTPUT_NAME "${BUNDLE_NAME}" + ) + endif() + + # Install main target; this will also create an initial bundle skeleton if appropriate + install(TARGETS ${_target} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + BUNDLE DESTINATION ${CMAKE_INSTALL_PREFIX} # Ignored when not creating a bundle + COMPONENT ${ARG_COMPONENT} + ) + + # Once the bundle skeleton has been created and the main executable installed, finalize bundle creation and build DMGs + if(APPLE AND BUNDLE) + # We cannot rely on Qt's macdeployqt for deploying plugins, because it will unconditionally install a bunch of unneeded ones, + # dragging in unwanted dependencies. + # Instead, transitively determine all Qt modules that the Quassel executable links against, and deploy only the plugins belonging + # to those modules. + # macdeployqt will take care of fixing up dependencies afterwards. + function(find_transitive_link_deps target var) + if(TARGET ${target}) + get_target_property(libs ${target} LINK_LIBRARIES) + if(libs) + foreach(lib IN LISTS libs) + if(NOT lib IN_LIST ${var}) + list(APPEND ${var} ${lib}) + find_transitive_link_deps(${lib} ${var}) + endif() + endforeach() + endif() + set(${var} ${${var}} PARENT_SCOPE) + endif() + endfunction() + + find_transitive_link_deps(${_target} link_deps) + # TODO CMake 3.6: use list(FILTER...) + foreach(dep IN LISTS link_deps) + if(${dep} MATCHES "^Qt5::.*") + list(APPEND qt_deps ${dep}) + endif() + endforeach() + + foreach(module IN LISTS qt_deps) + string(REPLACE "::" "" module ${module}) + foreach(plugin ${${module}_PLUGINS}) + install( + FILES $ + DESTINATION ${BUNDLE_PATH}/Contents/PlugIns/$ + COMPONENT ${ARG_COMPONENT} + ) + endforeach() + endforeach() + + # Generate iconset and deploy it as well as a qt.conf enabling plugins + add_dependencies(${_target} MacOsIcons) + install( + FILES ${CMAKE_SOURCE_DIR}/data/qt.conf ${CMAKE_BINARY_DIR}/pics/quassel.icns + DESTINATION ${BUNDLE_PATH}/Contents/Resources + COMPONENT ${ARG_COMPONENT} + ) + + # Determine the location of macdeployqt. Not available directly via CMake, so look for it in qmake's bindir... + get_target_property(QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) + get_filename_component(qt_bin_dir ${QMAKE_EXECUTABLE} DIRECTORY) + find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS ${qt_bin_dir} REQUIRED) + + # Generate and invoke post-install script, finalizing the bundle and creating a DMG image + #set(BUNDLE_PATH $ENV{DESTDIR}/${BUNDLE_PATH}) + configure_file(${CMAKE_SOURCE_DIR}/cmake/FinalizeBundle.cmake.in ${CMAKE_BINARY_DIR}/FinalizeBundle_${ARG_COMPONENT}.cmake @ONLY) + install(CODE " + execute_process( + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/FinalizeBundle_${ARG_COMPONENT}.cmake + RESULT_VARIABLE result + ) + if(NOT result EQUAL 0) + message(FATAL_ERROR \"Finalizing bundle failed.\") + endif() + " + COMPONENT ${ARG_COMPONENT} + ) + endif() +endfunction() + ################################################################################################### # Provides a library that contains data files as a Qt resource (.qrc). # diff --git a/data/qt.conf b/data/qt.conf new file mode 100644 index 00000000..02408feb --- /dev/null +++ b/data/qt.conf @@ -0,0 +1,4 @@ +[Paths] +Plugins = PlugIns +Imports = Resources/qml +Qml2Imports = Resources/qml diff --git a/scripts/build/macosx_DeployApp.py b/scripts/build/macosx_DeployApp.py deleted file mode 100755 index edcb13c6..00000000 --- a/scripts/build/macosx_DeployApp.py +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/python -# -*- coding: iso-8859-1 -*- - -################################################################################ -# # -# 2008 June 27th by Marcus 'EgS' Eggenberger # -# # -# The author disclaims copyright to this source code. # -# This Python Script is in the PUBLIC DOMAIN. # -# # -################################################################################ - -# ============================== -# Imports -# ============================== -import sys -import os -import os.path - -from subprocess import Popen, PIPE - -# Handling Qt properties -import macosx_qt - -# ============================== -# Constants -# ============================== -QT_CONFIG = """[Paths] - Plugins = plugins -""" - -QT_CONFIG_NOBUNDLE = """[Paths] - Prefix = ../ - Plugins = plugins -""" - - -class InstallQt(object): - def __init__(self, appdir, bundle=True, requestedPlugins=[], skipInstallQtConf=False): - self.appDir = appdir - self.bundle = bundle - self.frameworkDir = self.appDir + "/Frameworks" - self.pluginDir = self.appDir + "/plugins" - self.executableDir = self.appDir - if bundle: - self.executableDir += "/MacOS" - - self.installedFrameworks = set() - - self.findFrameworkPath() - - executables = [self.executableDir + "/" + executable for executable in os.listdir(self.executableDir)] - for executable in executables: - self.resolveDependancies(executable) - - self.findPluginsPath() - self.installPlugins(requestedPlugins) - if not skipInstallQtConf: - self.installQtConf() - - def findFrameworkPath(self): - self.sourceFrameworkPath = macosx_qt.qtProperty('QT_INSTALL_LIBS') - - def findPluginsPath(self): - self.sourcePluginsPath = macosx_qt.qtProperty('QT_INSTALL_PLUGINS') - - def findPlugin(self, pluginname): - qmakeProcess = Popen('find %s -name %s' % (self.sourcePluginsPath, pluginname), shell=True, stdout=PIPE, stderr=PIPE) - result = qmakeProcess.stdout.read().strip() - qmakeProcess.stdout.close() - qmakeProcess.wait() - if not result: - raise OSError - return result - - def installPlugins(self, requestedPlugins): - try: - os.mkdir(self.pluginDir) - except: - pass - - for plugin in requestedPlugins: - if not plugin.isalnum(): - print "Skipping library '%s'..." % plugin - continue - - pluginName = "lib%s.dylib" % plugin - pluginSource = '' - try: - pluginSource = self.findPlugin(pluginName) - except OSError: - print "WARNING: Requested library does not exist: '%s'" % plugin - continue - - pluginSubDir = os.path.dirname(pluginSource) - pluginSubDir = pluginSubDir.replace(self.sourcePluginsPath, '').strip('/') - try: - os.mkdir("%s/%s" % (self.pluginDir, pluginSubDir)) - except OSError: - pass - - os.system('cp "%s" "%s/%s"' % (pluginSource, self.pluginDir, pluginSubDir)) - - self.resolveDependancies("%s/%s/%s" % (self.pluginDir, pluginSubDir, pluginName)) - - def installQtConf(self): - qtConfName = self.appDir + "/qt.conf" - qtConfContent = QT_CONFIG_NOBUNDLE - if self.bundle: - qtConfContent = QT_CONFIG - qtConfName = self.appDir + "/Resources/qt.conf" - - qtConf = open(qtConfName, 'w') - qtConf.write(qtConfContent) - qtConf.close() - - def resolveDependancies(self, obj): - # obj must be either an application binary or a framework library - # print "resolving deps for:", obj - for framework, lib in self.determineDependancies(obj): - self.installFramework(framework) - self.changeDylPath(obj, framework, lib) - - def installFramework(self, framework): - # skip if framework is already installed. - if framework in self.installedFrameworks: - return - - self.installedFrameworks.add(framework) - - # if the Framework-Folder is a Symlink we are in a Helper-Process ".app" (e.g. in QtWebEngine), - # in this case skip copying/installing on existing folders - skipExisting = False; - if os.path.islink(self.frameworkDir): - skipExisting = True; - - # ensure that the framework directory exists - try: - os.mkdir(self.frameworkDir) - except: - pass - - if not framework.startswith('/'): - framework = "%s/%s" % (self.sourceFrameworkPath, framework) - - frameworkname = framework.split('/')[-1] - localframework = self.frameworkDir + "/" + frameworkname - - # Framework already installed in previous run ... see above - if skipExisting and os.path.isdir(localframework): - return - - # Copy Framework - os.system('cp -R "%s" "%s"' % (framework, self.frameworkDir)) - - # De-Myllify - os.system('find "%s" -name *debug* -exec rm -f {} \;' % localframework) - os.system('find "%s" -name Headers -exec rm -rf {} \; 2>/dev/null' % localframework) - - # Install new Lib ID and Change Path to Frameworks for the Dynamic linker - for lib in os.listdir(localframework + "/Versions/Current"): - lib = "%s/Versions/Current/%s" % (localframework, lib) - otoolProcess = Popen('otool -D "%s"' % lib, shell=True, stdout=PIPE, stderr=PIPE) - try: - libname = [line for line in otoolProcess.stdout][1].strip() - except: - libname = '' - otoolProcess.stdout.close() - if otoolProcess.wait() == 1: # we found some Resource dir or similar -> skip - continue - frameworkpath, libpath = libname.split(frameworkname) - if self.bundle: - newlibname = "@executable_path/../%s%s" % (frameworkname, libpath) - else: - newlibname = "@executable_path/%s%s" % (frameworkname, libpath) - # print 'install_name_tool -id "%s" "%s"' % (newlibname, lib) - os.system('chmod +w "%s"' % (lib)) - os.system('install_name_tool -id "%s" "%s"' % (newlibname, lib)) - - self.resolveDependancies(lib) - - def determineDependancies(self, app): - otoolPipe = Popen('otool -L "%s"' % app, shell=True, stdout=PIPE).stdout - otoolOutput = [line for line in otoolPipe] - otoolPipe.close() - libs = [line.split()[0] for line in otoolOutput[1:] if ("Qt" in line or "phonon" in line) and "@executable_path" not in line] - frameworks = [lib[:lib.find(".framework") + len(".framework")] for lib in libs] - frameworks = [framework[framework.rfind('/') + 1:] for framework in frameworks] - return zip(frameworks, libs) - - def changeDylPath(self, obj, framework, lib): - newlibname = framework + lib.split(framework)[1] - if self.bundle: - newlibname = "@executable_path/../Frameworks/%s" % newlibname - else: - newlibname = "@executable_path/Frameworks/%s" % newlibname - - # print 'install_name_tool -change "%s" "%s" "%s"' % (lib, newlibname, obj) - os.system('chmod +w "%s"' % (lib)) - os.system('chmod +w "%s"' % (obj)) - os.system('install_name_tool -change "%s" "%s" "%s"' % (lib, newlibname, obj)) - -if __name__ == "__main__": - if len(sys.argv) < 2: - print "Wrong Argument Count (Syntax: %s [--nobundle] [--plugins=plugin1,plugin2,...] $TARGET_APP)" % sys.argv[0] - sys.exit(1) - else: - bundle = True - plugins = [] - offset = 1 - - while offset < len(sys.argv) and sys.argv[offset].startswith("--"): - if sys.argv[offset] == "--nobundle": - bundle = False - - if sys.argv[offset].startswith("--plugins="): - plugins = sys.argv[offset].split('=')[1].split(',') - - offset += 1 - - targetDir = sys.argv[offset] - if bundle: - targetDir += "/Contents" - - InstallQt(targetDir, bundle, plugins) - - if bundle: - webenginetarget = targetDir + '/Frameworks/QtWebEngineCore.framework/Helpers/QtWebEngineProcess.app/Contents' - - if os.path.isdir(webenginetarget): - os.system('ln -s ../../../../../../ "%s"/Frameworks' % webenginetarget) - InstallQt(webenginetarget, bundle, [], True) diff --git a/scripts/build/macosx_makePackage.sh b/scripts/build/macosx_makePackage.sh deleted file mode 100755 index e9342dd5..00000000 --- a/scripts/build/macosx_makePackage.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -# Don't consider packaging a success if any commands fail -# See http://redsymbol.net/articles/unofficial-bash-strict-mode/ -set -euo pipefail - -myname=$0 -if [ -s "$myname" ] && [ -x "$myname" ]; then - # $myname is already a valid file name - - mypath=$myname -else - case "$myname" in - /*) exit 1;; # absolute path - do not search PATH - *) - # Search all directories from the PATH variable. Take - # care to interpret leading and trailing ":" as meaning - # the current directory; the same is true for "::" within - # the PATH. - - # Replace leading : with . in PATH, store in p - p=${PATH/#:/.:} - # Replace trailing : with . - p=${p//%:/:.} - # Replace :: with :.: - p=${p//::/:.:} - # Temporary input field separator, see FAQ #1 - OFS=$IFS IFS=: - # Split the path on colons and loop through each of them - for dir in $p; do - [ -f "$dir/$myname" ] || continue # no file - [ -x "$dir/$myname" ] || continue # not executable - mypath=$dir/$myname - break # only return first matching file - done - # Restore old input field separator - IFS=$OFS - ;; - esac -fi - -if [ ! -f "$mypath" ]; then - echo >&2 "cannot find full path name: $myname" - exit 1 -fi - -SCRIPTDIR=$(dirname $mypath) -QUASSEL_VERSION=$(git describe) -BUILDTYPE=$1 - -# check the working dir -# Default to "." using Bash default-value syntax -WORKINGDIR="${2:-.}" -WORKINGDIR="${WORKINGDIR}/" -PACKAGETMPDIR="${WORKINGDIR}PACKAGE_TMP_DIR_${BUILDTYPE}" -QUASSEL_DMG="Quassel${BUILDTYPE}_MacOSX-x86_64_${QUASSEL_VERSION}.dmg" - -# Default to null string -if [[ -z ${3:-} ]]; then - ADDITIONAL_PLUGINS="" -else - # Options provided, append to list - ADDITIONAL_PLUGINS=",$3" -fi - -echo "ADDITIONAL_PLUGINS: ${ADDITIONAL_PLUGINS}" - -mkdir $PACKAGETMPDIR -case $BUILDTYPE in -"Client") - cp -r ${WORKINGDIR}Quassel\ Client.app ${PACKAGETMPDIR}/ - ${SCRIPTDIR}/macosx_DeployApp.py --plugins=qcocoa,qgenericbearer,qcorewlanbearer,qmacstyle${ADDITIONAL_PLUGINS} "${PACKAGETMPDIR}/Quassel Client.app" - ;; -"Core") - cp ${WORKINGDIR}quasselcore ${PACKAGETMPDIR}/ - ${SCRIPTDIR}/macosx_DeployApp.py --nobundle --plugins=qsqlite,qsqlpsql${ADDITIONAL_PLUGINS} ${PACKAGETMPDIR} - ;; -"Mono") - cp -r ${WORKINGDIR}Quassel.app ${PACKAGETMPDIR}/ - ${SCRIPTDIR}/macosx_DeployApp.py --plugins=qsqlite,qsqlpsql,qcocoa,qgenericbearer,qcorewlanbearer,qmacstyle${ADDITIONAL_PLUGINS} "${PACKAGETMPDIR}/Quassel.app" - ;; -*) - echo >&2 "Valid parameters are \"Client\", \"Core\", or \"Mono\"." - rmdir ${PACKAGETMPDIR} - exit 1 - ;; -esac - -echo "Creating macOS disk image with hdiutil: 'Quassel ${BUILDTYPE} - ${QUASSEL_VERSION}'" - -# Modern macOS versions support APFS, however default to HFS+ for now in order -# to ensure old macOS versions can parse the package and display the warning -# about being out of date. This mirrors the approach taken by Qt's macdeployqt -# tool. In the future if this isn't needed, just remove "-fs HFS+" to revert -# to default. -# -# See https://doc.qt.io/qt-5/macos-deployment.html - -# hdiutil seems to have a bit of a reputation for failing to create disk images -# for various reasons. -# -# If you've come here to see why on earth your macOS build is failing despite -# making changes entirely unrelated to macOS, you have my sympathy. -# -# There are two main approaches: -# -# 1. Let hdiutil calculate a size automatically -# -# 2. Separately calculate the size with a margin of error, then specify this -# to hdiutil during disk image creation. -# -# Both seem to have caused issues, but in recent tests, option #1 seemed more -# reliable. -# -# Option 1: - -hdiutil create -srcfolder ${PACKAGETMPDIR} -format UDBZ -fs HFS+ -volname "Quassel ${BUILDTYPE} - ${QUASSEL_VERSION}" "${WORKINGDIR}${QUASSEL_DMG}" >/dev/null - -# If hdiutil changes over time and fails often, you can try the other option. -# -# Option 2: -# -#PACKAGESIZE_MARGIN="1.1" -#PACKAGESIZE=$(echo "$(du -ms ${PACKAGETMPDIR} | cut -f1) * $PACKAGESIZE_MARGIN" | bc) -#echo "PACKAGESIZE: $PACKAGESIZE MB" -#hdiutil create -srcfolder ${PACKAGETMPDIR} -format UDBZ -fs HFS+ -size ${PACKAGESIZE}M -volname "Quassel ${BUILDTYPE} - ${QUASSEL_VERSION}" "${WORKINGDIR}${QUASSEL_DMG}" >/dev/null - - -# Regardless of choice, clean up the packaging temporary directory -rm -rf ${PACKAGETMPDIR} diff --git a/scripts/build/macosx_makebundle.py b/scripts/build/macosx_makebundle.py deleted file mode 100755 index 85742a06..00000000 --- a/scripts/build/macosx_makebundle.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/python -# -*- coding: iso-8859-1 -*- - -################################################################################ -# # -# 2008 June 27th by Marcus 'EgS' Eggenberger # -# # -# The author disclaims copyright to this source code. # -# This Python Script is in the PUBLIC DOMAIN. # -# # -################################################################################ - -# ============================== -# Imports -# ============================== -import os -import os.path -import sys -import commands - -# Handling Qt properties -import macosx_qt - -# ============================== -# Constants -# ============================== -if len(sys.argv) < 2: - sys.exit(1) - -SOURCE_DIR = sys.argv[1] - -if len(sys.argv) < 4: - BUNDLE_NAME = "Quassel Client" - EXE_NAME = "quasselclient" -else: - EXE_NAME = sys.argv[3] - BUNDLE_NAME = sys.argv[2] - -# make the dir of the exe the target dir -if(os.path.dirname(EXE_NAME)): - CONTENTS_DIR = os.path.dirname(EXE_NAME) + "/" -CONTENTS_DIR += BUNDLE_NAME + ".app/Contents/" - -BUNDLE_VERSION = commands.getoutput("git --git-dir=" + SOURCE_DIR + "/.git/ describe") -ICONSET_FOLDER = "pics/iconset/" - - -def createBundle(): - try: - os.makedirs(CONTENTS_DIR + "MacOS") - os.makedirs(CONTENTS_DIR + "Resources") - except: - pass - - -def copyFiles(exeFile, iconset): - os.system("cp %s %sMacOs/%s" % (exeFile, CONTENTS_DIR.replace(' ', '\ '), BUNDLE_NAME.replace(' ', '\ '))) - os.system("cp -r %s/%s %s/Resources/quassel.iconset/" % (SOURCE_DIR, iconset, CONTENTS_DIR.replace(' ', '\ '))) - - -def createPlist(bundleName, bundleVersion): - templateFile = file(SOURCE_DIR + "/scripts/build/Info.plist", 'r') - template = templateFile.read() - templateFile.close() - - # Get the minimum macOS deployment version - QT_MACOSX_DEPLOYMENT_TARGET = macosx_qt.qtMakespec('QMAKE_MACOSX_DEPLOYMENT_TARGET') - # Keep in sync with QMAKE_MACOSX_DEPLOYMENT_TARGET - # See https://doc.qt.io/qt-5/macos.html - if QT_MACOSX_DEPLOYMENT_TARGET is None: - # Something went wrong - sys.exit("Could not determine 'QMAKE_MACOSX_DEPLOYMENT_TARGET', check build scripts") - print("Qt macOS deployment target (minimum version): %s" % QT_MACOSX_DEPLOYMENT_TARGET) - - plistFile = file(CONTENTS_DIR + "Info.plist", 'w') - plistFile.write(template % {"BUNDLE_NAME": bundleName, - "ICON_FILE": "quassel.icns", - "BUNDLE_VERSION": bundleVersion, - "QT_MACOSX_DEPLOYMENT_TARGET": QT_MACOSX_DEPLOYMENT_TARGET}) - plistFile.close() - -def convertIconset(): - os.system("iconutil -c icns %s/Resources/quassel.iconset" % CONTENTS_DIR.replace(' ', '\ ')) - os.system("rm -R %s/Resources/quassel.iconset" % CONTENTS_DIR.replace(' ', '\ ')) - -if __name__ == "__main__": - createBundle() - createPlist(BUNDLE_NAME, BUNDLE_VERSION) - copyFiles(EXE_NAME, ICONSET_FOLDER) - convertIconset() diff --git a/scripts/build/macosx_qt.py b/scripts/build/macosx_qt.py deleted file mode 100755 index 3955364e..00000000 --- a/scripts/build/macosx_qt.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/python -# -*- coding: iso-8859-1 -*- - -################################################################################ -# # -# 2008 June 27th by Marcus 'EgS' Eggenberger # -# # -# The author disclaims copyright to this source code. # -# This Python Script is in the PUBLIC DOMAIN. # -# # -################################################################################ - -# ============================== -# Imports -# ============================== -import os -from subprocess import Popen, PIPE - -# ============================== -# Global Functions -# ============================== -def qtProperty(qtProperty): - """ - Query persistent property of Qt via qmake - """ - VALID_PROPERTIES = ['QT_INSTALL_PREFIX', - 'QT_INSTALL_DATA', - 'QT_INSTALL_DOCS', - 'QT_INSTALL_HEADERS', - 'QT_INSTALL_LIBS', - 'QT_INSTALL_BINS', - 'QT_INSTALL_PLUGINS', - 'QT_INSTALL_IMPORTS', - 'QT_INSTALL_TRANSLATIONS', - 'QT_INSTALL_CONFIGURATION', - 'QT_INSTALL_EXAMPLES', - 'QT_INSTALL_DEMOS', - 'QMAKE_MKSPECS', - 'QMAKE_VERSION', - 'QT_VERSION' - ] - if qtProperty not in VALID_PROPERTIES: - return None - - qmakeProcess = Popen('qmake -query %s' % qtProperty, shell=True, stdout=PIPE, stderr=PIPE) - result = qmakeProcess.stdout.read().strip() - qmakeProcess.stdout.close() - qmakeProcess.wait() - return result - -def qtMakespec(qtMakespec): - """ - Query a Makespec value of Qt via qmake - """ - - VALID_PROPERTIES = ['QMAKE_MACOSX_DEPLOYMENT_TARGET', - ] - if qtMakespec not in VALID_PROPERTIES: - return None - - # QMAKE_MACOSX_DEPLOYMENT_TARGET sadly cannot be queried in the traditional way - # - # Inspired by https://code.qt.io/cgit/pyside/pyside-setup.git/tree/qtinfo.py?h=5.6 - # Simplified, no caching, etc, as we're just looking for the macOS version. - # If a cleaner solution is desired, look into license compatibility in - # order to simply copy the above code. - - current_dir = os.getcwd() - qmakeFakeProjectFile = os.path.join(current_dir, "qmake_empty_project.txt") - qmakeStashFile = os.path.join(current_dir, ".qmake.stash") - # Make an empty file - open(qmakeFakeProjectFile, 'a').close() - - qmakeProcess = Popen('qmake -E %s' % qmakeFakeProjectFile, shell=True, stdout=PIPE, stderr=PIPE) - result = qmakeProcess.stdout.read().strip() - qmakeProcess.stdout.close() - qmakeProcess.wait() - - # Clean up temporary files - try: - os.remove(qmakeFakeProjectFile) - except OSError: - pass - try: - os.remove(qmakeStashFile) - except OSError: - pass - - # Result should be like this: - # PROPERTY = VALUE\n - result_list = result.splitlines() - # Clear result so if nothing matches, nothing is returned - result = None - # Search keys - for line in result_list: - if not '=' in line: - # Ignore lines without '=' - continue - - # Find property = value - parts = line.split('=', 1) - prop = parts[0].strip() - value = parts[1].strip() - if (prop == qtMakespec): - result = value - break - - return result diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 130144b5..042b1c0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(common) -add_subdirectory(main) if (BUILD_CORE) add_subdirectory(core) endif() @@ -12,3 +11,5 @@ endif() if (BUILD_TESTING) add_subdirectory(test) endif() + +add_subdirectory(main) diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index b5aa9636..a3ad83d6 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -1,13 +1,3 @@ -# Convenience function to avoid boilerplate -function(setup_executable _target _define) - set_target_properties(${_target} PROPERTIES - COMPILE_FLAGS ${_define} - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} - ) - target_link_libraries(${_target} PRIVATE ${ARGN}) - install(TARGETS ${_target} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -endfunction() - if (HAVE_UMASK) set_property(SOURCE main.cpp APPEND PROPERTY COMPILE_DEFINITIONS HAVE_UMASK) endif() @@ -33,54 +23,23 @@ if(WIN32) endif() endif() - # Build the executables if (WANT_CORE) - add_executable(quasselcore main.cpp ${WIN_RC}) - setup_executable(quasselcore -DBUILD_CORE Qt5::Core Quassel::Core) + quassel_add_executable(quasselcore COMPONENT Core SOURCES main.cpp ${WIN_RC} LIBRARIES Qt5::Core Quassel::Core) endif() if (WANT_QTCLIENT) - add_executable(quasselclient WIN32 main.cpp ${WIN_RC}) - setup_executable(quasselclient -DBUILD_QTUI Qt5::Core Qt5::Gui Quassel::QtUi) + set(libs Qt5::Core Qt5::Gui Quassel::QtUi) if (WITH_KDE) - target_link_libraries(quasselclient PRIVATE KF5::CoreAddons) + list(APPEND libs KF5::CoreAddons) endif() + quassel_add_executable(quasselclient COMPONENT Client SOURCES main.cpp ${WIN_RC} LIBRARIES ${libs}) endif() if (WANT_MONO) - add_executable(quassel WIN32 main.cpp monoapplication.cpp ${WIN_RC}) - setup_executable(quassel -DBUILD_MONO Qt5::Core Qt5::Gui Quassel::Core Quassel::QtUi) + set(libs Qt5::Core Qt5::Gui Quassel::Core Quassel::QtUi) if (WITH_KDE) - target_link_libraries(quassel PRIVATE KF5::CoreAddons) - endif() -endif() - -# Build bundles for MacOSX -if (APPLE) - if (WANT_QTCLIENT) - add_custom_command(TARGET quasselclient POST_BUILD - COMMAND ${CMAKE_SOURCE_DIR}/scripts/build/macosx_makebundle.py - ${CMAKE_SOURCE_DIR} "Quassel Client" ${CMAKE_BINARY_DIR}/quasselclient) - endif() - if (WANT_MONO) - add_custom_command(TARGET quassel POST_BUILD - COMMAND ${CMAKE_SOURCE_DIR}/scripts/build/macosx_makebundle.py - ${CMAKE_SOURCE_DIR} "Quassel" ${CMAKE_BINARY_DIR}/quassel) - endif() - - if (DEPLOY) - if (WANT_QTCLIENT) - add_custom_command(TARGET quasselclient POST_BUILD WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/build/macosx_makePackage.sh Client ${CMAKE_BINARY_DIR} qsvgicon) - endif() - if (WANT_CORE) - add_custom_command(TARGET quasselcore POST_BUILD WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/build/macosx_makePackage.sh Core ${CMAKE_BINARY_DIR}) - endif() - if (WANT_MONO) - add_custom_command(TARGET quassel POST_BUILD WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/build/macosx_makePackage.sh Mono ${CMAKE_BINARY_DIR} qsvgicon) - endif() + list(APPEND libs KF5::CoreAddons) endif() + quassel_add_executable(quassel COMPONENT Mono SOURCES main.cpp monoapplication.cpp ${WIN_RC} LIBRARIES ${libs}) endif()