X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=cmake%2FQuasselMacros.cmake;h=9ba0003a8d42fae4987e2313b58c41e1106d943c;hp=e282506125efed172f62f61503b6abf0ec8c9ec6;hb=HEAD;hpb=1e37a9de70d5ff524fe9d01e715f6dbcdfa9ba06 diff --git a/cmake/QuasselMacros.cmake b/cmake/QuasselMacros.cmake index e2825061..9ba0003a 100644 --- a/cmake/QuasselMacros.cmake +++ b/cmake/QuasselMacros.cmake @@ -1,28 +1,43 @@ # This file contains various functions and macros useful for building Quassel. # -# (C) 2014-2018 by the Quassel Project +# (C) 2014-2022 by the Quassel Project # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. ################################################################################################### include(CMakeParseArguments) +include(GenerateExportHeader) include(QuasselCompileFeatures) ################################################################################################### # Adds a library target for a Quassel module. # -# It expects the (CamelCased) module name as a parameter, and derives various +# quassel_add_module(Module [STATIC] [EXPORT]) +# +# The function expects the (CamelCased) module name as a parameter, and derives various # strings from it. For example, quassel_add_module(Client) produces # - a library target named quassel_client with output name (lib)quassel-client(.so) # - an alias target named Quassel::Client in global scope # +# If the optional argument STATIC is given, or the ENABLE_SHARED option is OFF, +# a static library is built; otherwise a shared library is created. For shared +# libraries, an install rule is also added. +# +# To generate an export header for the library, specify EXPORT. The header will be named +# ${module}-export.h (where ${module} is the lower-case name of the module). +# # The function exports the TARGET variable which can be used in the current scope # for setting source files, properties, link dependencies and so on. # To refer to the target outside of the current scope, e.g. for linking, use # the alias name. # function(quassel_add_module _module) + set(options EXPORT STATIC NOINSTALL) + set(oneValueArgs ) + set(multiValueArgs ) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + # Derive target, alias target, output name from the given module name set(alias "Quassel::${_module}") set(target ${alias}) @@ -30,9 +45,7 @@ function(quassel_add_module _module) string(REPLACE "::" "_" target ${target}) string(REPLACE "_" "-" output_name ${target}) - # On Windows, building shared libraries requires export headers. - # Let's bother with that later. - if (WIN32) + if (ARG_STATIC OR NOT ENABLE_SHARED) set(buildmode STATIC) else() set(buildmode SHARED) @@ -53,14 +66,167 @@ function(quassel_add_module _module) VERSION ${QUASSEL_MAJOR}.${QUASSEL_MINOR}.${QUASSEL_PATCH} ) - if (buildmode STREQUAL "SHARED") - install(TARGETS ${target} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + if (buildmode STREQUAL "SHARED" AND NOT ${ARG_NOINSTALL}) + install(TARGETS ${target} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + endif() + + if (ARG_EXPORT) + string(TOLOWER ${_module} lower_module) + string(TOUPPER ${_module} upper_module) + string(REPLACE "::" "-" header_base ${lower_module}) + string(REPLACE "::" "_" macro_base ${upper_module}) + generate_export_header(${target} + BASE_NAME ${macro_base} + EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/export/${header_base}-export.h + ) + target_include_directories(${target} PUBLIC ${CMAKE_BINARY_DIR}/export) endif() # Export the target name for further use 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). # @@ -114,7 +280,7 @@ function(quassel_add_resource _name) # # On Windows, input redirection apparently doesn't work, however piping does. Use this for all platforms for # consistency, accommodating for the fact that the 'cat' equivalent on Windows is 'type'. - if (WIN32) + if (WIN32 AND NOT MSYS) set(cat_cmd type) else() set(cat_cmd cat) @@ -129,8 +295,9 @@ function(quassel_add_resource _name) WORKING_DIRECTORY ${basedir} ) - # Generate library target that can be referenced elsewhere - quassel_add_module(Resource::${_name}) + # Generate library target that can be referenced elsewhere. Force static, because + # we can't easily export symbols from the generated sources. + quassel_add_module(Resource::${_name} STATIC) target_sources(${TARGET} PRIVATE ${qrc_srcpath}) set_target_properties(${TARGET} PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) @@ -138,6 +305,64 @@ function(quassel_add_resource _name) set(RESOURCE_TARGET ${TARGET} PARENT_SCOPE) endfunction() +################################################################################################### +# Adds a unit test case +# +# quassel_add_test(TestName +# [LIBRARIES lib1 lib2...] +# ) +# +# The test name is given in CamelCase as first and mandatory parameter. The corresponding source file +# is expected the lower-cased test name plus the .cpp extension. +# The test case is automatically linked against Qt5::Test, GMock, Quassel::Common and +# Quassel::Test::Main, which contains the main function. This main function also instantiates a +# QCoreApplication, so the event loop can be used in test cases. +# +# Additional libraries can be given using the LIBRARIES argument. +# +# Test cases should include testglobal.h, which transitively includes the GTest/GMock headers and +# exports the main function. +# +# The compiled test case binary is located in the unit/ directory in the build directory. +# +function(quassel_add_test _target) + set(options ) + set(oneValueArgs ) + set(multiValueArgs LIBRARIES) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + string(TOLOWER ${_target} lower_target) + set(srcfile ${lower_target}.cpp) + + list(APPEND ARG_LIBRARIES + Qt5::Test + Quassel::Common + Quassel::Test::Global + Quassel::Test::Main + ) + + if (WIN32) + # On Windows, tests need to be built in the same directory that contains the libraries + set(output_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + else() + # On other platforms, separate the test cases out + set(output_dir "${CMAKE_BINARY_DIR}/unit") + endif() + + add_executable(${_target} ${srcfile}) + set_target_properties(${_target} PROPERTIES + OUTPUT_NAME ${_target} + RUNTIME_OUTPUT_DIRECTORY "${output_dir}" + ) + target_link_libraries(${_target} PUBLIC ${ARG_LIBRARIES}) + + add_test( + NAME ${_target} + COMMAND $ + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + ) +endfunction() + ################################################################################################### # target_link_if_exists(Target # [PUBLIC dep1 dep2...] @@ -174,3 +399,23 @@ function(target_link_if_exists _target) endforeach() endif() endfunction() + +################################################################################################### +# process_cmake_cxx_flags() +# +# Append the options declared CMAKE_CXX_FLAGS and CMAKE_CXX_FLAGS_ to the global +# compile options. +# Unset the variables afterwards to avoid duplication. +# +function(process_cmake_cxx_flags) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + set(cxx_flags "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${upper_build_type}}") + if(CMAKE_VERSION VERSION_LESS 3.12) + separate_arguments(sep_cxx_flags UNIX_COMMAND ${cxx_flags}) + add_compile_options(${sep_cxx_flags}) + else() + add_compile_options("SHELL:${cxx_flags}") + endif() + set(CMAKE_CXX_FLAGS "" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS_${upper_build_type} "" PARENT_SCOPE) +endfunction()