aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/Qt6QmlMacros.cmake
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2023-04-19 13:59:19 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2023-08-28 13:47:42 +0200
commitba3cc0a094ca5187523df962f2cd9437cd822aee (patch)
treef061e211de3da6db5c8a670d4c0ae079715b61c3 /src/qml/Qt6QmlMacros.cmake
parent4ded3cb8a6d7590dffbd3dfdd7a6c1956c5a222c (diff)
CMake: Reference all qml resources in static qml plugin constructor
Previously, when a static library was made into a qml module, we added resources containing the qml files, qmldir, etc, into separate object libraries rather than directly into the static library. We did this to ensure that the linker doesn't discard these resources if they are not explicitly initialized with Q_INIT_RESOURCE in user code. This proves to be a problem for projects that link directly to the static libraries instead of using target names, because the object libraries that contain the resources are never referenced in the cmake project and thus not linked in. This tends to happen in projects that ship their own static qml modules without accompanying target information. To improve the situation, move all the resources back into the backing libraries, and reference the resources inside the static qml plugin constructor. This way a project that wishes to link to libraries directly can do the following and expect a working project: - link to the static backing library - link to the static qml plugin - add Q_IMPORT_QML_PLUGIN(PluginName) in their project code. This will ensure all resources are kept and initialized. To implement that, use the new macros introduced in QtCore's qtsymbolmacros.h which generate code to ensure symbols are not discarded. It's based on the same idea we use to keep the type registration function: a volatile reference to the symbol to keep. Add a test that confirms all this works. Fixes: QTBUG-110243 Change-Id: I00a127ef17bc852e360fb7a2dc58ee20522424cd Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Diffstat (limited to 'src/qml/Qt6QmlMacros.cmake')
-rw-r--r--src/qml/Qt6QmlMacros.cmake284
1 files changed, 247 insertions, 37 deletions
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake
index 334ac5f763..d9ac0d9b6e 100644
--- a/src/qml/Qt6QmlMacros.cmake
+++ b/src/qml/Qt6QmlMacros.cmake
@@ -589,6 +589,14 @@ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details."
PREFIX "${prefix}"
OUTPUT_TARGETS resource_targets
)
+
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_qmldir_resource_name "${qmldir_resource_name}")
+ set_property(TARGET ${target} APPEND PROPERTY
+ _qt_qml_module_sanitized_resource_names "${sanitized_qmldir_resource_name}")
+
list(APPEND output_targets ${resource_targets})
# If we are adding the same file twice, we need a different resource
# name for the second one. It has the same QT_RESOURCE_ALIAS but a
@@ -805,6 +813,37 @@ macro(_qt_internal_genex_getjoinedproperty var target property item_prefix glue)
set(${var} "$<${have_${var}}:${item_prefix}$<JOIN:${${var}},${glue}${item_prefix}>>")
endmacro()
+
+# Creates a genex that will call a c++ macro on each of the list values.
+# Handles empty lists.
+macro(_qt_internal_genex_get_list_joined_with_macro var input_list macro_name)
+ set(${var} "${input_list}")
+ set(have_${var} "$<BOOL:${${var}}>")
+
+ set(_macro_begin "${macro_name}(")
+ set(_macro_end ");")
+ set(_macro_glue "${_macro_end}\n${_macro_begin}")
+
+ string(JOIN "" ${var}
+ "${_macro_begin}"
+ "$<JOIN:${${var}},${_macro_glue}>"
+ "${_macro_end}")
+
+ set(${var} "$<${have_${var}}:${${var}}>")
+
+ unset(_macro_begin)
+ unset(_macro_end)
+ unset(_macro_glue)
+endmacro()
+
+# Reads a target property that contains a list of values and creates a genex that will
+# call a c++ macro on each of the values.
+# Handles empty properties.
+macro(_qt_internal_genex_get_property_joined_with_macro var target property macro_name)
+ _qt_internal_genex_getproperty(${var} ${target} ${property})
+ _qt_internal_genex_get_list_joined_with_macro("${var}" "${${var}}" "${macro_name}")
+endmacro()
+
macro(_qt_internal_genex_getoption var target property)
set(${var} "$<BOOL:$<TARGET_PROPERTY:${target},${property}>>")
endmacro()
@@ -1024,15 +1063,20 @@ function(_qt_internal_propagate_qmlcache_object_lib
set(${output_generated_target} "${resource_target}" PARENT_SCOPE)
endfunction()
-function(_qt_internal_target_enable_qmlcachegen target output_targets_var qmlcachegen)
-
- set(output_targets)
+function(_qt_internal_target_enable_qmlcachegen target qmlcachegen)
set_target_properties(${target} PROPERTIES _qt_cachegen_set_up TRUE)
get_target_property(target_binary_dir ${target} BINARY_DIR)
set(qmlcache_dir ${target_binary_dir}/.rcc/qmlcache)
set(qmlcache_resource_name qmlcache_${target})
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_qmlcache_resource_name "${qmlcache_resource_name}")
+ set_target_properties(${target} PROPERTIES _qt_cachegen_sanitized_resource_name
+ "${sanitized_qmlcache_resource_name}")
+
# INTEGRITY_SYMBOL_UNIQUENESS
# The cache loader file name has to be unique, because the Integrity compiler uses the file name
# for the generation of the translation unit static constructor symbol name.
@@ -1101,25 +1145,11 @@ function(_qt_internal_target_enable_qmlcachegen target output_targets_var qmlcac
endif()
# TODO: Probably need to reject ${target} being an object library as unsupported
- get_target_property(target_type ${target} TYPE)
- if(target_type STREQUAL "STATIC_LIBRARY")
- set(extra_conditions "")
- _qt_internal_propagate_qmlcache_object_lib(
- ${target}
- "${qmlcache_loader_cpp}"
- "${extra_conditions}"
- output_target)
-
- list(APPEND output_targets ${output_target})
- else()
- target_sources(${target} PRIVATE "${qmlcache_loader_cpp}")
- target_link_libraries(${target} PRIVATE
- ${QT_CMAKE_EXPORT_NAMESPACE}::QmlPrivate
- ${QT_CMAKE_EXPORT_NAMESPACE}::Core
- )
- endif()
-
- set(${output_targets_var} ${output_targets} PARENT_SCOPE)
+ target_sources(${target} PRIVATE "${qmlcache_loader_cpp}")
+ target_link_libraries(${target} PRIVATE
+ ${QT_CMAKE_EXPORT_NAMESPACE}::QmlPrivate
+ ${QT_CMAKE_EXPORT_NAMESPACE}::Core
+ )
endfunction()
# We cannot defer writing out the qmldir file to generation time because the
@@ -1578,6 +1608,136 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
endfunction()
endif()
+# Get extern declaration for types registration function.
+function(_qt_internal_qml_get_types_extern_declaration register_types_function_name out_var)
+ _qt_internal_genex_get_list_joined_with_macro(
+ content
+ "${register_types_function_name}"
+ "QT_DECLARE_EXTERN_SYMBOL_VOID"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference the types registration function in the plugin constructor.
+# Ensures the symbol is not discarded when linking.
+function(_qt_internal_qml_get_types_keep_reference register_types_function_name out_var)
+ _qt_internal_genex_get_list_joined_with_macro(
+ content
+ "${register_types_function_name}"
+ "QT_KEEP_SYMBOL"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get extern declaration for cachegen resource.
+function(_qt_internal_qml_get_cachegen_extern_resource_declaration target out_var)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_cachegen_sanitized_resource_name"
+ "QT_DECLARE_EXTERN_RESOURCE"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference the cachegen resource in the plugin constructor.
+# Ensures the resource is not discarded when linking.
+function(_qt_internal_qml_get_cachegen_resource_keep_reference target out_var)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_cachegen_sanitized_resource_name"
+ "QT_KEEP_RESOURCE"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get extern declaration for a regular resource.
+function(_qt_internal_qml_get_resource_extern_declarations target out_var)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_qml_module_sanitized_resource_names"
+ "QT_DECLARE_EXTERN_RESOURCE"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference regular resources in the plugin constructor.
+# Ensures the resources are not discarded when linking.
+function(_qt_internal_qml_get_resource_keep_references target out_var)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_qml_module_sanitized_resource_names"
+ "QT_KEEP_RESOURCE"
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get 3 different code blocks that should be inserted into various locations of the generated
+# qml plugin cpp file. The code blocks ensure that if a target links to a plugin backed by a static
+# library and initializes the plugin using Q_IMPORT_PLUGIN, none of the resources or type
+# registration functions in the backing library are discarded by the linker.
+function(_qt_internal_qml_get_symbols_to_keep
+ target
+ backing_target_type
+ register_types_function_name
+ out_var_intro
+ out_var_intro_namespaced
+ out_var_constructor)
+ set(intro_content "")
+ set(intro_namespaced_content "")
+
+ # External symbol declarations.
+ _qt_internal_qml_get_types_extern_declaration("${register_types_function_name}" output)
+ string(APPEND intro_namespaced_content "${output}")
+
+ if(backing_target_type STREQUAL "STATIC_LIBRARY")
+ _qt_internal_qml_get_cachegen_extern_resource_declaration(${target} output)
+ string(APPEND intro_content "${output}")
+
+ _qt_internal_qml_get_resource_extern_declarations(${target} output)
+ string(APPEND intro_content "${output}")
+ endif()
+
+
+ # Reference the external symbols in the plugin constructor to prevent the linker from
+ # discarding the symbols.
+ # The types symbol is an exported symbol, so we always reference it.
+ set(constructor_content "")
+
+ _qt_internal_qml_get_types_keep_reference("${register_types_function_name}" output)
+ string(APPEND constructor_content "${output}")
+
+ # Only reference the resources if the backing library is static.
+ # If the backing library is shared, the symbols will not be found at link time because they
+ # are not exported symbols.
+ if(backing_target_type STREQUAL "STATIC_LIBRARY")
+ _qt_internal_qml_get_cachegen_resource_keep_reference(${target} output)
+ string(APPEND constructor_content "${output}")
+
+ _qt_internal_qml_get_resource_keep_references(${target} output)
+ string(APPEND constructor_content "${output}")
+ endif()
+
+ set(${out_var_intro} "${intro_content}" PARENT_SCOPE)
+ set(${out_var_intro_namespaced} "${intro_namespaced_content}" PARENT_SCOPE)
+ set(${out_var_constructor} "${constructor_content}" PARENT_SCOPE)
+endfunction()
+
function(qt6_add_qml_plugin target)
set(args_option
STATIC
@@ -1653,6 +1813,10 @@ function(qt6_add_qml_plugin target)
endif()
endif()
+ if(arg_BACKING_TARGET AND TARGET "${arg_BACKING_TARGET}")
+ get_target_property(backing_type ${arg_BACKING_TARGET} TYPE)
+ endif()
+
if(TARGET ${target})
# Plugin target already exists. Perform a few sanity checks, but we
# otherwise trust that the target is appropriate for use as a plugin.
@@ -1700,7 +1864,6 @@ function(qt6_add_qml_plugin target)
if(TARGET "${arg_BACKING_TARGET}")
# Ensure that the plugin type we create will be compatible with the
# type of backing target we were given
- get_target_property(backing_type ${arg_BACKING_TARGET} TYPE)
if(backing_type STREQUAL "STATIC_LIBRARY")
if(lib_type STREQUAL "")
set(lib_type STATIC)
@@ -1862,29 +2025,69 @@ function(qt6_add_qml_plugin target)
set(qt_qml_plugin_moc_include_name "${generated_cpp_file_name_base}.moc")
set(qt_qml_plugin_intro "")
set(qt_qml_plugin_outro "")
+ set(qt_qml_plugin_constructor_content "")
+
+ # Get the target that contains the resource names.
+ set(target_with_prop "${target}")
+ if(NOT arg_BACKING_TARGET STREQUAL "" AND TARGET "${arg_BACKING_TARGET}")
+ set(target_with_prop "${arg_BACKING_TARGET}")
+ endif()
+
+ _qt_internal_qml_get_symbols_to_keep(
+ "${target_with_prop}"
+ "${backing_type}"
+ "${register_types_function_name}"
+ extra_intro_content
+ extra_intro_namespaced_content
+ extra_costructor_content
+ )
+
+ string(APPEND qt_qml_plugin_intro "${extra_intro_content}\n\n")
+
if (arg_NAMESPACE)
- string(APPEND qt_qml_plugin_intro "namespace ${arg_NAMESPACE} {\n\n")
+ string(APPEND qt_qml_plugin_intro "namespace ${arg_NAMESPACE} {\n")
string(APPEND qt_qml_plugin_outro "} // namespace ${arg_NAMESPACE}")
endif()
- string(APPEND qt_qml_plugin_intro "extern void ${register_types_function_name}();\nQ_GHS_KEEP_REFERENCE(${register_types_function_name})")
+ string(APPEND qt_qml_plugin_intro "${extra_intro_namespaced_content}")
- # Indenting here is deliberately different so as to make the generated
- # file have sensible indenting
- set(qt_qml_plugin_constructor_content
- "volatile auto registration = &${register_types_function_name};
- Q_UNUSED(registration)"
- )
+ string(APPEND qt_qml_plugin_constructor_content "${extra_costructor_content}")
- set(generated_cpp_file
- "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name_base}.cpp"
+ # Configure file from the template.
+ set(generated_cpp_file_in
+ "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name_base}_in.cpp"
)
configure_file(
"${__qt_qml_macros_module_base_dir}/Qt6QmlPluginTemplate.cpp.in"
- "${generated_cpp_file}"
+ "${generated_cpp_file_in}"
@ONLY
)
- target_sources(${target} PRIVATE "${generated_cpp_file}")
+
+ get_target_property(template_generated ${target} _qt_qml_plugin_template_generated)
+
+ if(NOT template_generated)
+ # Generate the file to allow adding generator expressions.
+ set(generated_cpp_file
+ "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name_base}.cpp"
+ )
+ file(GENERATE OUTPUT "${generated_cpp_file}"
+ INPUT "${generated_cpp_file_in}"
+ )
+
+ # We can't rely on policy CMP0118 since user project controls it
+ set(scope_args)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set(scope_args TARGET_DIRECTORY ${target})
+ endif()
+ set_source_files_properties("${generated_cpp_file}" ${scope_args}
+ PROPERTIES GENERATED TRUE
+ )
+
+ # Because the add_qml_plugin function might be called multiple times on the same target,
+ # Only generate and add the cpp file once.
+ set_target_properties(${target} PROPERTIES _qt_qml_plugin_template_generated TRUE)
+ target_sources(${target} PRIVATE "${generated_cpp_file}")
+ endif()
# The generated cpp file expects to include its moc-ed output file.
set_target_properties(${target} PROPERTIES AUTOMOC TRUE)
@@ -2285,8 +2488,7 @@ function(qt6_target_qml_sources target)
# after we know there will be at least one file to compile.
get_target_property(is_cachegen_set_up ${target} _qt_cachegen_set_up)
if(NOT is_cachegen_set_up)
- _qt_internal_target_enable_qmlcachegen(${target} resource_target ${qmlcachegen})
- list(APPEND output_targets ${resource_target})
+ _qt_internal_target_enable_qmlcachegen(${target} ${qmlcachegen})
endif()
# We ensured earlier that arg_PREFIX always ends with "/"
@@ -2424,6 +2626,14 @@ function(qt6_target_qml_sources target)
)
math(EXPR counter "${counter} + 1")
set_target_properties(${target} PROPERTIES QT_QML_MODULE_RAW_QML_SETS ${counter})
+
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_resource_name "${resource_name}")
+ set_property(TARGET ${target}
+ APPEND PROPERTY _qt_qml_module_sanitized_resource_names "${sanitized_resource_name}")
+
list(APPEND output_targets ${resource_targets})
if(arg_OUTPUT_TARGETS)