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 <alexandru.croitor@qt.io>
This commit is contained in:
Alexey Edelev 2022-08-03 17:46:15 +02:00
parent f0a40991fa
commit ac74b60c9c
5 changed files with 246 additions and 1 deletions

View File

@ -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

View File

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

View File

@ -295,3 +295,166 @@ Q_IMPORT_PLUGIN($<JOIN:${class_names},)\nQ_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()

View File

@ -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()

View File

@ -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()