From a7acde8447823f9cd5f25aae569a1ec3a0174073 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Thu, 16 Aug 2018 01:25:43 +0200 Subject: [PATCH] cmake: Add support for generating .qrc files Most .qrc files we use just map the (partial) contents of a directory, so that can be autogenerated. Provide CMake functions that support this endeavour to remove the need of regenerating resource files if directory contents changes. Since RCC is stupid, some hacks are required to make this work, so we can't use either autorcc nor qt5_add_resource(); we must invoke RCC manually. Clever use of targets and custom commands as well as external CMake script invocation ensures that the .qrc files are always up to date even if files change, while avoiding useless rebuilds (as is currently still the case for i18n). --- cmake/GenerateQrc.cmake | 37 ++++++++++++ cmake/QuasselMacros.cmake | 116 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 cmake/GenerateQrc.cmake diff --git a/cmake/GenerateQrc.cmake b/cmake/GenerateQrc.cmake new file mode 100644 index 00000000..4614e494 --- /dev/null +++ b/cmake/GenerateQrc.cmake @@ -0,0 +1,37 @@ +# Generates a .qrc file named ${QRC_FILE} with path prefix ${PREFIX}, containing +# all files matching the glob ${PATTERNS} in the current working directory. +# This script is intended to be executed using the cmake -P syntax, so the +# arguments we're interested in start at ARGV3. + +set(QRC_FILE ${CMAKE_ARGV3}) +set(PREFIX ${CMAKE_ARGV4}) +set(PATTERNS ${CMAKE_ARGV5}) + +# Find all files matching PATTERNS in the current working directory +if (PATTERNS) + file(GLOB_RECURSE files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${PATTERNS}) +endif() + +# Generate a temporary file first, so we don't touch the real thing unless something changed +set(qrc_tmp ${QRC_FILE}.tmp) +file(WRITE ${qrc_tmp} "\n\n\n") +foreach(file ${files}) + # Record the timestamp of last modification so changes are detected + file(TIMESTAMP ${file} timestamp) + file(APPEND ${qrc_tmp} " ${file}\n") +endforeach() +file(APPEND ${qrc_tmp} "\n\n") + +# Check if the newly generated file has the same contents (including timestamps) as the existing one. +# If the files are the same, don't touch the original to avoid useless rebuilds. +if (EXISTS ${QRC_FILE}) + file(MD5 ${QRC_FILE} orig_sum) + file(MD5 ${qrc_tmp} tmp_sum) + if (NOT orig_sum STREQUAL tmp_sum) + file(RENAME ${qrc_tmp} ${QRC_FILE}) + else() + file(REMOVE ${qrc_tmp}) + endif() +else() + file(RENAME ${qrc_tmp} ${QRC_FILE}) +endif() diff --git a/cmake/QuasselMacros.cmake b/cmake/QuasselMacros.cmake index 08eaaa8c..318defb8 100644 --- a/cmake/QuasselMacros.cmake +++ b/cmake/QuasselMacros.cmake @@ -6,6 +6,8 @@ # For details see the accompanying COPYING-CMAKE-SCRIPTS file. ################################################################################################### +include(CMakeParseArguments) + ################################################################################################### # Adds a library target for a Quassel module. # @@ -43,6 +45,120 @@ function(quassel_add_module _module) set(TARGET ${target} PARENT_SCOPE) endfunction() +################################################################################################### +# Provides a library that contains data files as a Qt resource (.qrc). +# +# quassel_add_resource(QrcName +# [BASEDIR basedir] +# [PREFIX prefix] +# PATTERNS pattern1 pattern2... +# [DEPENDS dep1 dep2...] +# ) +# +# The first parameter is the CamelCased name of the resource; the library target will be called +# "Quassel::Resource::QrcName". The library provides a Qt resource named "qrcname" (lowercased QrcName) +# containing the files matching PATTERNS relative to BASEDIR (by default, the current source dir). +# The resource prefix can be set by giving the PREFIX argument. +# Additional target dependencies can be specified with DEPENDS. +# +function(quassel_add_resource _name) + set(options ) + set(oneValueArgs BASEDIR PREFIX) + set(multiValueArgs DEPENDS PATTERNS) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT ARG_BASEDIR) + set(ARG_BASEDIR ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + get_filename_component(basedir ${ARG_BASEDIR} REALPATH) + + string(TOLOWER ${_name} lower_name) + + set(qrc_target quassel-qrc-${lower_name}) + set(qrc_file ${lower_name}.qrc) + set(qrc_src qrc_${lower_name}.cpp) + set(qrc_filepath ${CMAKE_CURRENT_BINARY_DIR}/${qrc_file}) + set(qrc_srcpath ${CMAKE_CURRENT_BINARY_DIR}/${qrc_src}) + + # This target will always be built, so the qrc file will always be freshly generated. + # That way, changes to the glob result are always taken into account. + add_custom_target(${qrc_target} VERBATIM + COMMENT "Generating ${qrc_file}" + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/GenerateQrc.cmake "${qrc_filepath}" "${ARG_PREFIX}" "${ARG_PATTERNS}" + DEPENDS ${ARG_DEPENDS} + BYPRODUCTS ${qrc_filepath} + WORKING_DIRECTORY ${basedir} + ) + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${qrc_filepath}) + + # RCC sucks and expects the data files relative to the qrc file, with no way to configure it differently. + # Only when reading from stdin ("-") it takes the working directory as a base, so we have to use this if + # we want to use generated qrc files (which obviously cannot be placed in the source directory). + # Since neither autorcc nor qt5_add_resources() support this, we have to invoke rcc manually :( + # + # 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) + set(cat_cmd type) + else() + set(cat_cmd cat) + endif() + add_custom_command(VERBATIM + COMMENT "Generating ${qrc_src}" + COMMAND ${cat_cmd} "$" + | "$>" --name "${lower_name}" --output "$" - + DEPENDS ${qrc_target} + MAIN_DEPENDENCY ${qrc_filepath} + OUTPUT ${qrc_srcpath} + WORKING_DIRECTORY ${basedir} + ) + + # Generate library target that can be referenced elsewhere + quassel_add_module(Resource::${_name}) + target_sources(${TARGET} PRIVATE ${qrc_srcpath}) + set_target_properties(${TARGET} PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) + + # Set variable for referencing the target from outside + set(RESOURCE_TARGET ${TARGET} PARENT_SCOPE) +endfunction() + +################################################################################################### +# target_link_if_exists(Target +# [PUBLIC dep1 dep2...] +# [PRIVATE dep3 dep4...] +# ) +# +# Convenience function to add dependencies to a target only if they exist. This is useful when +# handling targets that are conditionally created, e.g. resource libraries depending on -DEMBED_DATA. +# +# NOTE: In order to link a given target, it must already have been created, i.e its subdirectory +# must already have been added. This is also true for globally visible ALIAS targets that +# can otherwise be linked to regardless of creation order; "if (TARGET...)" does not +# support handling this case correctly. +# +function(target_link_if_exists _target) + set(options ) + set(oneValueArgs ) + set(multiValueArgs PUBLIC PRIVATE) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (ARG_PUBLIC) + foreach(dep ${ARG_PUBLIC}) + if (TARGET ${dep}) + target_link_libraries(${_target} PUBLIC ${dep}) + endif() + endforeach() + endif() + + if (ARG_PRIVATE) + foreach(dep ${ARG_PRIVATE}) + if (TARGET ${dep}) + target_link_libraries(${_target} PRIVATE ${dep}) + endif() + endforeach() + endif() +endfunction() + ###################################### # Macros for dealing with translations ###################################### -- 2.20.1