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>
This commit is contained in:
Joerg Bornemann 2020-03-12 16:16:55 +01:00
parent 6f5c91e985
commit bfcf36d459
4 changed files with 299 additions and 1 deletions

View File

@ -287,6 +287,7 @@ qt_copy_or_install(FILES
cmake/QtCompilerFlags.cmake cmake/QtCompilerFlags.cmake
cmake/QtCompilerOptimization.cmake cmake/QtCompilerOptimization.cmake
cmake/QtFeature.cmake cmake/QtFeature.cmake
cmake/QtFinishPrlFile.cmake
cmake/QtFindWrapHelper.cmake cmake/QtFindWrapHelper.cmake
cmake/QtFindWrapConfigExtra.cmake.in cmake/QtFindWrapConfigExtra.cmake.in
cmake/QtFileConfigure.txt.in cmake/QtFileConfigure.txt.in

View File

@ -661,7 +661,6 @@ QT_VERSION = ${PROJECT_VERSION}
QT_MAJOR_VERSION = ${PROJECT_VERSION_MAJOR} QT_MAJOR_VERSION = ${PROJECT_VERSION_MAJOR}
QT_MINOR_VERSION = ${PROJECT_VERSION_MINOR} QT_MINOR_VERSION = ${PROJECT_VERSION_MINOR}
QT_PATCH_VERSION = ${PROJECT_VERSION_PATCH} QT_PATCH_VERSION = ${PROJECT_VERSION_PATCH}
CONFIG -= link_prl # we do not create prl files right now
CONFIG += ${config_entries} 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}") qt_clone_property_for_configs(${target} ARCHIVE_OUTPUT_DIRECTORY "${possible_configs}")
endfunction() 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 # 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 # consists of a library, public header files, private header files and configurable
# features. # features.
@ -2059,7 +2141,159 @@ set(QT_CMAKE_EXPORT_NAMESPACE ${QT_CMAKE_EXPORT_NAMESPACE})")
endif() endif()
qt_describe_module(${target}) 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() endfunction()
function(qt_export_tools module_name) function(qt_export_tools module_name)

View File

@ -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}")

View File

@ -470,3 +470,9 @@ qt_create_tools_config_files()
if (ANDROID) if (ANDROID)
qt_modules_process_android_dependencies() qt_modules_process_android_dependencies()
endif() endif()
# Install prl files
qt_install(DIRECTORY "${PROJECT_BINARY_DIR}/${INSTALL_LIBDIR}/"
DESTINATION ${INSTALL_LIBDIR}
FILES_MATCHING PATTERN "*.prl"
)