diff options
author | Joerg Bornemann <joerg.bornemann@qt.io> | 2020-03-12 16:16:55 +0100 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@qt.io> | 2020-04-17 20:42:10 +0200 |
commit | bfcf36d459c7a36a92dd6f9127e6513f7a08277e (patch) | |
tree | 971b021ae9b51046af28512f6e4c69d289fdfe2e | |
parent | 6f5c91e98599f1c79085ea7bb819a6e2d3411053 (diff) |
CMake: Generate qmake .prl files
This commit also adds a qt_finalize_module function that is called for
every Qt module after all link dependencies have been added.
Change-Id: I489d188d05e368208a8a62828bb12fb395df54bc
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
-rw-r--r-- | cmake/QtBaseGlobalTargets.cmake | 1 | ||||
-rw-r--r-- | cmake/QtBuild.cmake | 236 | ||||
-rw-r--r-- | cmake/QtFinishPrlFile.cmake | 57 | ||||
-rw-r--r-- | cmake/QtPostProcess.cmake | 6 |
4 files changed, 299 insertions, 1 deletions
diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake index ac1472bb3c..9cd3ad72cb 100644 --- a/cmake/QtBaseGlobalTargets.cmake +++ b/cmake/QtBaseGlobalTargets.cmake @@ -287,6 +287,7 @@ qt_copy_or_install(FILES cmake/QtCompilerFlags.cmake cmake/QtCompilerOptimization.cmake cmake/QtFeature.cmake + cmake/QtFinishPrlFile.cmake cmake/QtFindWrapHelper.cmake cmake/QtFindWrapConfigExtra.cmake.in cmake/QtFileConfigure.txt.in diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake index ce0c116fd4..f2a22c50b8 100644 --- a/cmake/QtBuild.cmake +++ b/cmake/QtBuild.cmake @@ -661,7 +661,6 @@ QT_VERSION = ${PROJECT_VERSION} QT_MAJOR_VERSION = ${PROJECT_VERSION_MAJOR} QT_MINOR_VERSION = ${PROJECT_VERSION_MINOR} QT_PATCH_VERSION = ${PROJECT_VERSION_PATCH} -CONFIG -= link_prl # we do not create prl files right now CONFIG += ${config_entries} " ) @@ -1558,6 +1557,89 @@ function(qt_handle_multi_config_output_dirs target) qt_clone_property_for_configs(${target} ARCHIVE_OUTPUT_DIRECTORY "${possible_configs}") endfunction() +# Add a finalizer function for the current CMake list file. +# +# You may add up to nine arguments that are passed to the finalizer. +# A finalizer that is registered with qt_add_list_file_finalizer(foo bar baz) +# will be called with nine arguments: foo(bar baz IGNORE IGNORE IGNORE...), +# because CMake's handling of empty list elements is a cruel joke. +# +# For CMake < 3.18 the function qt_watch_current_list_dir must know about the finalizer. +function(qt_add_list_file_finalizer func) + set_property(GLOBAL APPEND + PROPERTY QT_LIST_FILE_FINALIZER_FILES "${CMAKE_CURRENT_LIST_FILE}") + set_property(GLOBAL APPEND + PROPERTY QT_LIST_FILE_FINALIZER_FUNCS ${func}) + foreach(i RANGE 1 9) + set(arg "${ARGV${i}}") + if(i GREATER_EQUAL ARGC) + set(arg "IGNORE") + endif() + set_property(GLOBAL APPEND + PROPERTY QT_LIST_FILE_FINALIZER_ARGV${i} "${arg}") + endforeach() +endfunction() + +# Watcher function for the variable CMAKE_CURRENT_LIST_DIR. +# This is the driver of the finalizer facility. +function(qt_watch_current_list_dir variable access value current_list_file stack) + if(NOT access STREQUAL "MODIFIED_ACCESS") + # We are only interested in modifications of CMAKE_CURRENT_LIST_DIR. + return() + endif() + list(GET stack -1 stack_top) + if(stack_top STREQUAL current_list_file) + # If the top of the stack equals the current list file then + # we're entering a file. We're not interested in this case. + return() + endif() + get_property(files GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_FILES) + if(NOT files) + return() + endif() + get_property(funcs GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_FUNCS) + foreach(i RANGE 1 9) + get_property(args${i} GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_ARGV${i}) + endforeach() + list(LENGTH files n) + set(i 0) + while(i LESS n) + list(GET files ${i} file) + if(file STREQUAL stack_top) + list(GET funcs ${i} func) + foreach(k RANGE 1 9) + list(GET args${k} ${i} a${k}) + endforeach() + # We've found a file we're looking for. Call the finalizer. + if(${CMAKE_VERSION} VERSION_LESS "3.18.0") + # Make finalizer known functions here: + if(func STREQUAL "qt_generate_prl_file") + qt_generate_prl_file(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9}) + else() + message(FATAL_ERROR "qt_watch_current_list_dir doesn't know about ${func}. Consider adding it.") + endif() + else() + cmake_command(INVOKE ${func} ${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9}) + endif() + list(REMOVE_AT files ${i}) + list(REMOVE_AT funcs ${i}) + foreach(k RANGE 1 9) + list(REMOVE_AT args${k} ${i}) + endforeach() + math(EXPR n "${n} - 1") + else() + math(EXPR i "${i} + 1") + endif() + endwhile() + set_property(GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_FILES ${files}) + set_property(GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_FUNCS ${funcs}) + foreach(i RANGE 1 9) + set_property(GLOBAL PROPERTY QT_LIST_FILE_FINALIZER_ARGV${i} "${args${i}}") + endforeach() +endfunction() + +variable_watch(CMAKE_CURRENT_LIST_DIR qt_watch_current_list_dir) + # This is the main entry function for creating a Qt module, that typically # consists of a library, public header files, private header files and configurable # features. @@ -2059,7 +2141,159 @@ set(QT_CMAKE_EXPORT_NAMESPACE ${QT_CMAKE_EXPORT_NAMESPACE})") endif() qt_describe_module(${target}) + qt_add_list_file_finalizer(qt_generate_prl_file ${target}) +endfunction() + +# Add libraries to variable ${out_libs_var} in a way that duplicates +# are added at the end. This ensures the library order needed for the +# linker. +function(qt_merge_libs out_libs_var) + foreach(dep ${ARGN}) + list(REMOVE_ITEM ${out_libs_var} ${dep}) + list(APPEND ${out_libs_var} ${dep}) + endforeach() + set(${out_libs_var} ${${out_libs_var}} PARENT_SCOPE) +endfunction() + +# Collects the library dependencies of a target. +# This takes into account transitive usage requirements. +function(qt_collect_libs target out_var) + set(collected ${ARGN}) + if(target IN_LIST collected) + return() + endif() + list(APPEND collected ${target}) + if(NOT TARGET qt_collect_libs_dict) + add_library(qt_collect_libs_dict INTERFACE IMPORTED GLOBAL) + endif() + get_target_property(libs qt_collect_libs_dict INTERFACE_${target}) + if(NOT libs) + unset(libs) + get_target_property(target_libs ${target} INTERFACE_LINK_LIBRARIES) + if(NOT target_libs) + unset(target_libs) + endif() + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "STATIC_LIBRARY") + get_target_property(link_libs ${target} LINK_LIBRARIES) + if(link_libs) + list(APPEND target_libs ${link_libs}) + endif() + endif() + foreach(lib ${target_libs}) + # Cannot use $<TARGET_POLICY:...> in add_custom_command. + # Check the policy now, and replace the generator expression with the value. + while(lib MATCHES "\\$<TARGET_POLICY:([^>]+)>") + cmake_policy(GET ${CMAKE_MATCH_1} value) + if(value STREQUAL "NEW") + set(value "TRUE") + else() + set(value "FALSE") + endif() + string(REPLACE "${CMAKE_MATCH_0}" "${value}" lib "${lib}") + endwhile() + + # Fix up $<TARGET_PROPERTY:FOO> expressions that refer to the "current" target. + # Those cannot be used with add_custom_command. + while(lib MATCHES "\\$<TARGET_PROPERTY:([^,>]+)>") + string(REPLACE "${CMAKE_MATCH_0}" "$<TARGET_PROPERTY:${target},${CMAKE_MATCH_1}>" + lib "${lib}") + endwhile() + + if(lib MATCHES "^\\$<TARGET_OBJECTS:") + # Skip object files. + continue() + elseif(lib MATCHES "^\\$<LINK_ONLY:(.*)>$") + set(lib_target ${CMAKE_MATCH_1}) + else() + set(lib_target ${lib}) + endif() + + if(TARGET ${lib_target}) + if ("${lib_target}" MATCHES "^Qt::(.*)") + # If both, Qt::Foo and Foo targets exist, prefer the target name without + # namespace. Which one is preferred doesn't really matter. This code exists to + # avoid ending up with both, Qt::Foo and Foo in our dependencies. + set(namespaceless_lib_target "${CMAKE_MATCH_1}") + if(TARGET namespaceless_lib_target) + set(lib_target ${namespaceless_lib_target}) + endif() + endif() + get_target_property(lib_target_type ${lib_target} TYPE) + if(lib_target_type STREQUAL "INTERFACE_LIBRARY") + qt_collect_libs(${lib_target} lib_libs ${collected}) + if(lib_libs) + qt_merge_libs(libs ${lib_libs}) + set(is_module 0) + endif() + else() + qt_merge_libs(libs "$<TARGET_FILE:${lib_target}>") + qt_collect_libs(${lib_target} lib_libs ${collected}) + if(lib_libs) + qt_merge_libs(libs ${lib_libs}) + endif() + endif() + else() + qt_merge_libs(libs "${lib_target}") + endif() + endforeach() + set_target_properties(qt_collect_libs_dict PROPERTIES INTERFACE_${target} "${libs}") + endif() + set(${out_var} ${libs} PARENT_SCOPE) +endfunction() + +# Generate a qmake .prl file for the given target +function(qt_generate_prl_file target) + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "INTERFACE_LIBRARY") + return() + endif() + + unset(prl_libs) + qt_collect_libs(${target} prl_libs) + + unset(prl_config) + if(target_type STREQUAL "STATIC_LIBRARY") + list(APPEND prl_config static) + elseif(target_type STREQUAL "SHARED_LIBRARY") + list(APPEND prl_config shared) + endif() + if (NOT target_type STREQUAL "INTERFACE_LIBRARY") + get_target_property(is_fw ${target} FRAMEWORK) + if(is_fw) + list(APPEND prl_config lib_bundle) + endif() + endif() + list(JOIN prl_config " " prl_config) + + # Generate a preliminary .prl file that contains absolute paths to all libraries + set(prl_file_name "$<TARGET_FILE_PREFIX:${target}>$<TARGET_FILE_BASE_NAME:${target}>.prl") + file(GENERATE + OUTPUT "${prl_file_name}" + CONTENT + "QMAKE_PRL_BUILD_DIR = ${CMAKE_CURRENT_BINARY_DIR} +QMAKE_PRL_TARGET = $<TARGET_FILE_NAME:${target}> +QMAKE_PRL_CONFIG = ${prl_config} +QMAKE_PRL_VERSION = ${PROJECT_VERSION} +QMAKE_PRL_LIBS_FOR_CMAKE = ${prl_libs} +" + ) + + # Add a custom command that prepares the .prl file for installation + qt_path_join(qt_build_libdir ${QT_BUILD_DIR} ${INSTALL_LIBDIR}) + qt_path_join(prl_file_path "${qt_build_libdir}" "${prl_file_name}") + set(library_suffixes ${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_STATIC_LIBRARY_SUFFIX}) + add_custom_command( + TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -DIN_FILE=${prl_file_name} -DOUT_FILE=${prl_file_path} + "-DLIBRARY_SUFFIXES=${library_suffixes}" + -DQT_BUILD_LIBDIR=${qt_build_libdir} + -P "${QT_CMAKE_DIR}/QtFinishPrlFile.cmake" + VERBATIM + ) + # Installation of the .prl file happens globally elsewhere, + # because we have no clue here what the actual file name is. endfunction() function(qt_export_tools module_name) diff --git a/cmake/QtFinishPrlFile.cmake b/cmake/QtFinishPrlFile.cmake new file mode 100644 index 0000000000..2edc8bdd9c --- /dev/null +++ b/cmake/QtFinishPrlFile.cmake @@ -0,0 +1,57 @@ +# Finish a preliminary .prl file. +# +# - Replaces occurrences of the build libdir with $$[QT_INSTALL_LIBDIR/get]. +# - Strips version number suffixes from absolute paths, because qmake's lflag +# merging does not handle them correctly. +# +# This file is to be used in CMake script mode with the following variables set: +# IN_FILE: path to the preliminary .prl file +# OUT_FILE: path to the final .prl file that's going to be installed +# QT_BUILD_LIBDIR: path to Qt's libdir when building (those paths get replaced) +# LIBRARY_SUFFIXES: list of known library extensions, e.g. .so;.a on Linux + +function(strip_library_version_suffix out_var file_path) + get_filename_component(dir "${file_path}" DIRECTORY) + get_filename_component(basename "${file_path}" NAME_WE) + get_filename_component(ext "${file_path}" EXT) + foreach(libsuffix ${LIBRARY_SUFFIXES}) + if(ext MATCHES "^${libsuffix}(\\.[0-9]+)+") + set(ext ${libsuffix}) + break() + endif() + endforeach() + set(${out_var} "${dir}/${basename}${ext}" PARENT_SCOPE) +endfunction() + +file(STRINGS "${IN_FILE}" lines) +set(content "") +foreach(line ${lines}) + if(line MATCHES "^QMAKE_PRL_LIBS_FOR_CMAKE = (.*)") + unset(adjusted_libs) + foreach(lib ${CMAKE_MATCH_1}) + if("${lib}" STREQUAL "") + continue() + endif() + if(IS_ABSOLUTE "${lib}") + strip_library_version_suffix(lib "${lib}") + file(RELATIVE_PATH relative_lib "${QT_BUILD_LIBDIR}" "${lib}") + if(IS_ABSOLUTE "${relative_lib}" OR (relative_lib MATCHES "^\\.\\.")) + list(APPEND adjusted_libs "${lib}") + else() + list(APPEND adjusted_libs "$$[QT_INSTALL_LIBS/get]/${relative_lib}") + endif() + else() + if(NOT lib MATCHES "^-l") + string(PREPEND lib "-l") + endif() + list(APPEND adjusted_libs "${lib}") + endif() + endforeach() + list(JOIN adjusted_libs " " adjusted_libs_for_qmake) + string(APPEND content "QMAKE_PRL_LIBS = ${adjusted_libs_for_qmake}\n") + string(APPEND content "QMAKE_PRL_LIBS_FOR_CMAKE = ${adjusted_libs}\n") + else() + string(APPEND content "${line}\n") + endif() +endforeach() +file(WRITE "${OUT_FILE}" "${content}") diff --git a/cmake/QtPostProcess.cmake b/cmake/QtPostProcess.cmake index 798fd947f1..9ad7515e53 100644 --- a/cmake/QtPostProcess.cmake +++ b/cmake/QtPostProcess.cmake @@ -470,3 +470,9 @@ qt_create_tools_config_files() if (ANDROID) qt_modules_process_android_dependencies() endif() + +# Install prl files +qt_install(DIRECTORY "${PROJECT_BINARY_DIR}/${INSTALL_LIBDIR}/" + DESTINATION ${INSTALL_LIBDIR} + FILES_MATCHING PATTERN "*.prl" +) |