From ac74b60c9c1101288eb2c558420ba69f675a2ee2 Mon Sep 17 00:00:00 2001 From: Alexey Edelev Date: Wed, 3 Aug 2022 17:46:15 +0200 Subject: [PATCH] Add function to add and compile executables at configure time qt_internal_add_configure_time_executable compiles the executable at configure time and exposes it to the CMake source tree. This is useful when need to run a small C++ program at configure time. Task-number: QTBUG-87480 Change-Id: I031efe797c8afa0721d75b46d4f36f67276bf46e Reviewed-by: Alexandru Croitor --- cmake/QtBaseGlobalTargets.cmake | 1 + ...QtConfigureTimeExecutableCMakeLists.txt.in | 21 +++ cmake/QtExecutableHelpers.cmake | 163 ++++++++++++++++++ cmake/QtTargetHelpers.cmake | 20 ++- cmake/QtToolHelpers.cmake | 42 +++++ 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 cmake/QtConfigureTimeExecutableCMakeLists.txt.in diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake index 70a738b8351..ba56ea17041 100644 --- a/cmake/QtBaseGlobalTargets.cmake +++ b/cmake/QtBaseGlobalTargets.cmake @@ -223,6 +223,7 @@ qt_copy_or_install(FILES cmake/QtCompilerFlags.cmake cmake/QtCompilerOptimization.cmake cmake/QtConfigDependencies.cmake.in + cmake/QtConfigureTimeExecutableCMakeLists.txt.in cmake/QtDeferredDependenciesHelpers.cmake cmake/QtDbusHelpers.cmake cmake/QtDocsHelpers.cmake diff --git a/cmake/QtConfigureTimeExecutableCMakeLists.txt.in b/cmake/QtConfigureTimeExecutableCMakeLists.txt.in new file mode 100644 index 00000000000..fcf30d6d381 --- /dev/null +++ b/cmake/QtConfigureTimeExecutableCMakeLists.txt.in @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.16) + +project(@configure_time_target@ LANGUAGES CXX) + +set(packages "@packages@") +set(defines @defines@) +set(compile_options @compile_options@) +set(link_options @link_options@) + +foreach(package IN LISTS packages) + find_package(${package} REQUIRED) +endforeach() + +add_executable(@configure_time_target@ @win32@ @macosx_bundle@ @sources@) +set_target_properties(@configure_time_target@ PROPERTIES + INCLUDE_DIRECTORIES "@include_directories@" +) + +target_compile_options(@configure_time_target@ PRIVATE ${compile_options}) +target_compile_definitions(@configure_time_target@ PRIVATE ${defines}) +target_link_options(@configure_time_target@ PRIVATE ${link_options}) diff --git a/cmake/QtExecutableHelpers.cmake b/cmake/QtExecutableHelpers.cmake index 78e0c671af9..61f4c005d12 100644 --- a/cmake/QtExecutableHelpers.cmake +++ b/cmake/QtExecutableHelpers.cmake @@ -295,3 +295,166 @@ Q_IMPORT_PLUGIN($) endforeach() endfunction() + +# This function compiles the target at configure time the very first time and creates the custom +# ${target}_build that re-runs compilation at build time if necessary. The resulting executable is +# imported under the provided target name. This function should only be used to compile tiny +# executables with system dependencies only. +# One-value Arguments: +# CMAKELISTS_TEMPLATE +# The CMakeLists.txt templated that is used to configure the project +# for an executable. By default the predefined template from the Qt installation is used. +# INSTALL_DIRECTORY +# installation directory of the executable. Ignored if NO_INSTALL is set. +# OUTPUT_NAME +# the output name of an executable +# CONFIG +# the name of configuration that tool needs to be build with. +# Multi-value Arguments: +# PACKAGES +# list of system packages are required to successfully build the project. +# INCLUDES +# list of include directories are required to successfully build the project. +# DEFINES +# list of definitions are required to successfully build the project. +# COMPILE_OPTIONS +# list of compiler options are required to successfully build the project. +# LINK_OPTIONS +# list of linker options are required to successfully build the project. +# SOURCES +# list of project sources. +# CMAKE_FLAGS +# specify flags of the form -DVAR:TYPE=VALUE to be passed to the cmake command-line used to +# drive the test build. +# Options: +# WIN32 +# reflects the corresponding add_executable argument. +# MACOSX_BUNDLE +# reflects the corresponding add_executable argument. +# NO_INSTALL +# avoids installing the tool. +function(qt_internal_add_configure_time_executable target) + set(one_value_args + CMAKELISTS_TEMPLATE + INSTALL_DIRECTORY + OUTPUT_NAME + CONFIG + ) + set(multi_value_args + PACKAGES + INCLUDES + DEFINES + COMPILE_OPTIONS + LINK_OPTIONS + SOURCES + CMAKE_FLAGS + ) + set(option_args WIN32 MACOSX_BUNDLE NO_INSTALL) + cmake_parse_arguments(arg "${option_args}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + set(target_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/configure_time_bins") + if(arg_CONFIG) + set(CMAKE_TRY_COMPILE_CONFIGURATION "${arg_CONFIG}") + endif() + + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config AND CMAKE_TRY_COMPILE_CONFIGURATION) + set(configuration_path "${CMAKE_TRY_COMPILE_CONFIGURATION}/") + set(config_build_arg "--config" "${CMAKE_TRY_COMPILE_CONFIGURATION}") + endif() + + set(configure_time_target "${target}") + if(arg_OUTPUT_NAME) + set(configure_time_target "${arg_OUTPUT_NAME}") + endif() + set(target_binary "${configure_time_target}${CMAKE_EXECUTABLE_SUFFIX}") + set(target_binary_path + "${target_binary_dir}/${configuration_path}${target_binary}") + get_filename_component(target_binary_path "${target_binary_path}" ABSOLUTE) + + if(NOT DEFINED arg_SOURCES) + message(FATAL_ERROR "No SOURCES given to target: ${target}") + endif() + set(sources "${arg_SOURCES}") + + # Timestamp file is required because CMake ignores 'add_custom_command' if we use only the + # binary file as the OUTPUT. + set(timestamp_file "${target_binary_path}_timestamp") + add_custom_command(OUTPUT "${target_binary_path}" "${timestamp_file}" + COMMAND + ${CMAKE_COMMAND} --build "${target_binary_dir}" ${config_build_arg} + COMMAND + ${CMAKE_COMMAND} -E touch "${timestamp_file}" + DEPENDS + ${sources} + COMMENT + "Compiling ${target}" + VERBATIM + ) + + add_custom_target(${target}_build ALL + DEPENDS + "${target_binary_path}" + "${timestamp_file}" + ) + + if(NOT EXISTS "${target_binary_path}") + foreach(arg IN LISTS multi_value_args) + string(TOLOWER "${arg}" template_arg_name) + set(${template_arg_name} "") + if(DEFINED arg_${arg}) + set(${template_arg_name} "${arg_${arg}}") + endif() + endforeach() + + foreach(arg IN LISTS option_args) + string(TOLOWER "${arg}" template_arg_name) + set(${template_arg_name} "") + if(arg_${arg}) + set(${template_arg_name} "${arg}") + endif() + endforeach() + + file(MAKE_DIRECTORY "${target_binary_dir}") + set(template "${QT_CMAKE_DIR}/QtConfigureTimeExecutableCMakeLists.txt.in") + if(DEFINED arg_CMAKELISTS_TEMPLATE) + set(template "${arg_CMAKELISTS_TEMPLATE}") + endif() + + set(cmake_flags_arg) + if(arg_CMAKE_FLAGS) + set(cmake_flags_arg CMAKE_FLAGS ${arg_CMAKE_FLAGS}) + endif() + configure_file("${template}" "${target_binary_dir}/CMakeLists.txt" @ONLY) + try_compile(result + "${target_binary_dir}" + "${target_binary_dir}" + ${target} + ${cmake_flags_arg} + OUTPUT_VARIABLE try_compile_output + ) + + if(NOT result) + message(FATAL_ERROR "Unable to build ${target}: ${try_compile_output}") + endif() + endif() + + add_executable(${target} IMPORTED GLOBAL) + add_executable(${QT_CMAKE_EXPORT_NAMESPACE}::${target} ALIAS ${target}) + set_target_properties(${target} PROPERTIES + _qt_internal_configure_time_target TRUE + IMPORTED_LOCATION "${target_binary_path}") + + if(NOT arg_NO_INSTALL) + set(install_dir "${INSTALL_BINDIR}") + if(arg_INSTALL_DIRECTORY) + set(install_dir "${arg_INSTALL_DIRECTORY}") + endif() + set_target_properties(${target} PROPERTIES + _qt_internal_configure_time_target_install_location + "${install_dir}/${target_binary}" + ) + qt_path_join(target_install_dir ${QT_INSTALL_DIR} ${install_dir}) + qt_copy_or_install(PROGRAMS "${target_binary_path}" DESTINATION "${target_install_dir}") + endif() +endfunction() diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake index 7e1f7cdc2d5..e215e7d021a 100644 --- a/cmake/QtTargetHelpers.cmake +++ b/cmake/QtTargetHelpers.cmake @@ -508,6 +508,24 @@ endif() set(properties_retrieved TRUE) + get_target_property(is_configure_time_target ${target} _qt_internal_configure_time_target) + if(is_configure_time_target) + get_target_property(configure_time_target_install_location ${target} + _qt_internal_configure_time_target_install_location) + if(configure_time_target_install_location) + string(APPEND content " +# Import configure-time executable ${full_target} +if(NOT TARGET ${full_target}) + add_executable(${full_target} IMPORTED) + set_property(TARGET ${full_target} APPEND PROPERTY IMPORTED_CONFIGURATIONS ${default_cfg}) + set_target_properties(${full_target} PROPERTIES IMPORTED_LOCATION_${uc_default_cfg} + \"$\\{PACKAGE_PREFIX_DIR}/${configure_time_target_install_location}\") + set_property(TARGET ${full_target} PROPERTY IMPORTED_GLOBAL TRUE) +endif() +\n") + endif() + endif() + # Non-prefix debug-and-release builds: add check for the existence of the debug binary of # the target. It is not built by default. if(NOT QT_WILL_INSTALL AND QT_FEATURE_debug_and_release) @@ -521,7 +539,7 @@ if(NOT EXISTS \"$\\{_qt_imported_location}\") list(REMOVE_ITEM _qt_imported_configs DEBUG) set_property(TARGET ${full_target} PROPERTY IMPORTED_CONFIGURATIONS $\\{_qt_imported_configs}) set_property(TARGET ${full_target} PROPERTY IMPORTED_LOCATION_DEBUG) -endif()\n\n") +endif()\n") endif() endif() diff --git a/cmake/QtToolHelpers.cmake b/cmake/QtToolHelpers.cmake index adcac6fbe20..f79ae2f400b 100644 --- a/cmake/QtToolHelpers.cmake +++ b/cmake/QtToolHelpers.cmake @@ -605,3 +605,45 @@ function(qt_internal_find_tool out_var target_name tools_target) endif() set(${out_var} "TRUE" PARENT_SCOPE) endfunction() + +# This function adds an internal tool that should be compiled at configure time. +# TOOLS_TARGET +# Specifies the module this tool belongs to. The Qt6${TOOLS_TARGET}Tools module +# will then expose targets for this tool. Ignored if NO_INSTALL is set. +function(qt_internal_add_configure_time_tool target_name) + set(one_value_args INSTALL_DIRECTORY TOOLS_TARGET) + set(multi_value_args) + set(option_args NO_INSTALL) + cmake_parse_arguments(arg "${option_args}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + qt_internal_find_tool(will_build_tools ${target_name} "${arg_TOOLS_TARGET}") + if(NOT will_build_tools) + return() + endif() + + qt_tool_target_to_name(name ${target_name}) + set(extra_args "") + if(arg_NO_INSTALL OR NOT arg_TOOLS_TARGET) + list(APPEND extra_args "NO_INSTALL") + else() + set(install_dir "${INSTALL_BINDIR}") + if(arg_INSTALL_DIRECTORY) + set(install_dir "${arg_INSTALL_DIRECTORY}") + endif() + set(extra_args "INSTALL_DIRECTORY" "${install_dir}") + endif() + + qt_internal_add_configure_time_executable(${target_name} + OUTPUT_NAME ${name} + ${extra_args} + ${arg_UNPARSED_ARGUMENTS} + ) + + if(NOT arg_NO_INSTALL AND arg_TOOLS_TARGET) + qt_internal_add_targets_to_additional_targets_export_file( + TARGETS ${target_name} + TARGET_EXPORT_NAMES ${QT_CMAKE_EXPORT_NAMESPACE}::${name} + EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${arg_TOOLS_TARGET}Tools + ) + endif() +endfunction()