Skip to content

Commit

Permalink
CMake: Allow installation of example sources into the Qt prefix
Browse files Browse the repository at this point in the history
In Qt 5 times, if Qt was configured with -make examples, running
make install would not only build and install the example binaries,
but would also install the example sources into the prefix.
Installation of example sources was not implemented when the Qt 6
build system has switched to using CMake.

There is still a use case for it though, mainly for Qt Creator, which
only shows the examples of a Qt kit if the sources are available.

In contrast to Qt 5, in Qt 6 we will not install example sources
by default. It will be opt in.

To enable installation of examples sources, configure with

 configure -make examples -install-examples-sources

or

 cmake -DQT_BUILD_EXAMPLES=ON -DQT_INSTALL_EXAMPLES_SOURCES=ON

The -make examples part is required, otherwise
-install-examples-sources has no effect.

All example sources can be installed by calling
 cmake --install . --component examples_sources
in the qt repo build directory.

In a top-level build, per-repo installation can be done using
 cmake --install . --component examples_sources_<repo_name>
where repo_name could be 'qtbase'.

A single example's source can be installed by calling
 cmake --install . --component examples_sources_<subdir_name>
where subdir_name is the subdirectory name of the example, e.g.
'gallery'.

Implement installation of example sources by hooking into the
qt_internal_add_example command.
This means that all examples in all repos need to be added via
qt_internal_add_example instead of add_subdirectory, to ensure the
sources are installed. The majority of repos already use it.

For testing purposes one can configure with
-DQT_BUILD_EXAMPLES=ON -DQT_INSTALL_EXAMPLES_SOURCES=ON
-DQT_INTERNAL_NO_CONFIGURE_EXAMPLES=ON to allow testing installation
of examples sources without building them.

Take into account an additional variable called
QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX to allow installation of
example sources into a location different from the example binaries.

As a cleanup, the NAME option that could previously be passed to
qt_internal_add_example_external_project has been removed.
That's because it's never used anywhere and could not have worked
anyway because qt_internal_add_example_in_tree never handled it.

Pick-to: 6.6
Fixes: QTBUG-112135
Change-Id: I52aa5ec643ff7e212276c88d8dd2dfecdbdbeb0d
Reviewed-by: Alexey Edelev <[email protected]>
  • Loading branch information
alcroito committed Aug 16, 2023
1 parent 120fc82 commit 121f7f3
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 52 deletions.
209 changes: 157 additions & 52 deletions cmake/QtBuildInternals/QtBuildInternalsConfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1000,19 +1000,118 @@ set(CMAKE_INSTALL_PREFIX \"\${_qt_internal_examples_cmake_install_prefix_backup}
set(CMAKE_UNITY_BUILD ${QT_UNITY_BUILD})
endmacro()

# Allows building an example either as an ExternalProject or in-tree with the Qt build.
# Also allows installing the example sources.
function(qt_internal_add_example subdir)
if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD)
qt_internal_add_example_in_tree(${ARGV})
# Pre-compute unique example name based on the subdir, in case of target name clashes.
qt_internal_get_example_unique_name(unique_example_name "${subdir}")

# QT_INTERNAL_NO_CONFIGURE_EXAMPLES is not meant to be used by Qt builders, it's here for faster
# testing of the source installation code path for build system engineers.
if(NOT QT_INTERNAL_NO_CONFIGURE_EXAMPLES)
if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD)
qt_internal_add_example_in_tree("${subdir}")
else()
qt_internal_add_example_external_project("${subdir}"
NAME "${unique_example_name}")
endif()
endif()

if(QT_INSTALL_EXAMPLES_SOURCES)
string(TOLOWER ${PROJECT_NAME} project_name_lower)

qt_internal_install_example_sources("${subdir}"
NAME "${unique_example_name}"
REPO_NAME "${project_name_lower}")
endif()
endfunction()

# Gets the install prefix where an example should be installed.
# Used for computing the final installation path.
function(qt_internal_get_example_install_prefix out_var)
# Allow customizing the installation path of the examples. Will be used in CI.
if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX)
set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}")
else()
qt_internal_add_example_external_project(${ARGV})
set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}")
endif()
file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)
set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE)
endfunction()

# Use old non-ExternalProject approach, aka build in-tree with the Qt build.
function(qt_internal_add_example_in_tree subdir)
# Gets the install prefix where an example's sources should be installed.
# Used for computing the final installation path.
function(qt_internal_get_examples_sources_install_prefix out_var)
# Allow customizing the installation path of the examples source specifically.
if(QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX)
set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX}")
else()
qt_internal_get_example_install_prefix(qt_example_install_prefix)
endif()
file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)
set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE)
endfunction()

# Gets the relative path of an example, relative to the current repo's examples source dir.
# QT_EXAMPLE_BASE_DIR is meant to be already set in a parent scope.
function(qt_internal_get_example_rel_path out_var subdir)
file(RELATIVE_PATH example_rel_path
"${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")
set(${out_var} "${example_rel_path}" PARENT_SCOPE)
endfunction()

# Gets the install path where an example should be installed.
function(qt_internal_get_example_install_path out_var subdir)
qt_internal_get_example_install_prefix(qt_example_install_prefix)
qt_internal_get_example_rel_path(example_rel_path "${subdir}")
set(example_install_path "${qt_example_install_prefix}/${example_rel_path}")

set(${out_var} "${example_install_path}" PARENT_SCOPE)
endfunction()

# Gets the install path where an example's sources should be installed.
function(qt_internal_get_examples_sources_install_path out_var subdir)
qt_internal_get_examples_sources_install_prefix(qt_example_install_prefix)
qt_internal_get_example_rel_path(example_rel_path "${subdir}")
set(example_install_path "${qt_example_install_prefix}/${example_rel_path}")

set(${out_var} "${example_install_path}" PARENT_SCOPE)
endfunction()

# Get the unique name of an example project based on its subdir or explicitly given name.
# Makes the name unique by appending a short sha1 hash of the relative path of the example
# if a target of the same name already exist.
function(qt_internal_get_example_unique_name out_var subdir)
qt_internal_get_example_rel_path(example_rel_path "${subdir}")

set(name "${subdir}")

# qtdeclarative has calls like qt_internal_add_example(imagine/automotive)
# so passing a nested subdirectory. Custom targets (and thus ExternalProjects) can't contain
# slashes, so extract the last part of the path to be used as a name.
if(name MATCHES "/")
string(REPLACE "/" ";" exploded_path "${name}")
list(POP_BACK exploded_path last_dir)
if(NOT last_dir)
message(FATAL_ERROR "Example subdirectory must have a name.")
else()
set(name "${last_dir}")
endif()
endif()

# Likely a clash with an example subdir ExternalProject custom target of the same name in a
# top-level build.
if(TARGET "${name}")
string(SHA1 rel_path_hash "${example_rel_path}")
string(SUBSTRING "${rel_path_hash}" 0 4 short_hash)
set(name "${name}-${short_hash}")
endif()

set(${out_var} "${name}" PARENT_SCOPE)
endfunction()

# Use old non-ExternalProject approach, aka build in-tree with the Qt build.
function(qt_internal_add_example_in_tree subdir)
# Unset the default CMAKE_INSTALL_PREFIX that's generated in
# ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
# so we can override it with a different value in
Expand All @@ -1026,15 +1125,8 @@ unset(CMAKE_INSTALL_PREFIX)

# Override the install prefix in the subdir cmake_install.cmake, so that
# relative install(TARGETS DESTINATION) calls in example projects install where we tell them to.
# Allow customizing the installation path of the examples. Will be used in CI.
if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX)
set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}")
else()
set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}")
endif()
file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)

set(CMAKE_INSTALL_PREFIX "${qt_example_install_prefix}/${example_rel_path}")
qt_internal_get_example_install_path(example_install_path "${subdir}")
set(CMAKE_INSTALL_PREFIX "${example_install_path}")

# Make sure unclean example projects have their INSTALL_EXAMPLEDIR set to "."
# Won't have any effect on example projects that don't use INSTALL_EXAMPLEDIR.
Expand All @@ -1044,7 +1136,7 @@ unset(CMAKE_INSTALL_PREFIX)
# TODO: Remove once all repositories use qt_internal_add_example instead of add_subdirectory.
set(QT_INTERNAL_SET_EXAMPLE_INSTALL_DIR_TO_DOT ON)

add_subdirectory(${subdir} ${ARGN})
add_subdirectory(${subdir})
endfunction()

function(qt_internal_add_example_external_project subdir)
Expand All @@ -1054,33 +1146,6 @@ function(qt_internal_add_example_external_project subdir)

cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}")

file(RELATIVE_PATH example_rel_path
"${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")

if(NOT arg_NAME)
set(arg_NAME "${subdir}")

# qtdeclarative has calls like qt_internal_add_example(imagine/automotive)
# so passing a nested subdirectory. Custom targets (and thus ExternalProjects) can't contain
# slashes, so extract the last part of the path to be used as a name.
if(arg_NAME MATCHES "/")
string(REPLACE "/" ";" exploded_path "${arg_NAME}")
list(POP_BACK exploded_path last_dir)
if(NOT last_dir)
message(FATAL_ERROR "Example subdirectory must have a name.")
else()
set(arg_NAME "${last_dir}")
endif()
endif()
endif()

# Likely a clash with an example subdir ExternalProject custom target of the same name.
if(TARGET "${arg_NAME}")
string(SHA1 rel_path_hash "${example_rel_path}")
string(SUBSTRING "${rel_path_hash}" 0 4 short_hash)
set(arg_NAME "${arg_NAME}-${short_hash}")
endif()

# TODO: Fix example builds when using Conan / install prefixes are different for each repo.
if(QT_SUPERBUILD OR QtBase_BINARY_DIR)
# When doing a top-level build or when building qtbase,
Expand Down Expand Up @@ -1301,15 +1366,7 @@ function(qt_internal_add_example_external_project subdir)
# example_source_dir, use _qt_internal_override_example_install_dir_to_dot to ensure
# INSTALL_EXAMPLEDIR does not interfere.

# Allow customizing the installation path of the examples. Will be used in CI.
if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX)
set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}")
else()
set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}")
endif()
file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix)

set(example_install_prefix "${qt_example_install_prefix}/${example_rel_path}")
qt_internal_get_example_install_path(example_install_path "${subdir}")

set(ep_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}")

Expand All @@ -1324,7 +1381,7 @@ function(qt_internal_add_example_external_project subdir)
PREFIX "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep"
STAMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep/stamp"
BINARY_DIR "${ep_binary_dir}"
INSTALL_DIR "${example_install_prefix}"
INSTALL_DIR "${example_install_path}"
INSTALL_COMMAND ""
${build_command}
TEST_COMMAND ""
Expand Down Expand Up @@ -1378,6 +1435,54 @@ execute_process(

endfunction()

function(qt_internal_install_example_sources subdir)
set(options "")
set(single_args NAME REPO_NAME)
set(multi_args "")

cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${single_args}" "${multi_args}")

qt_internal_get_examples_sources_install_path(example_install_path "${subdir}")

# The trailing slash is important to avoid duplicate nested directory names.
set(example_source_dir "${subdir}/")

# Allow controlling whether sources should be part of the default install target.
if(QT_INSTALL_EXAMPLES_SOURCES_BY_DEFAULT)
set(exclude_from_all "")
else()
set(exclude_from_all "EXCLUDE_FROM_ALL")
endif()

# Create an install component for all example sources. Can also be part of the default
# install target if EXCLUDE_FROM_ALL is not passed.
install(
DIRECTORY "${example_source_dir}"
DESTINATION "${example_install_path}"
COMPONENT "examples_sources"
USE_SOURCE_PERMISSIONS
${exclude_from_all}
)

# Also create a specific install component just for this repo's examples.
install(
DIRECTORY "${example_source_dir}"
DESTINATION "${example_install_path}"
COMPONENT "examples_sources_${arg_REPO_NAME}"
USE_SOURCE_PERMISSIONS
EXCLUDE_FROM_ALL
)

# Also create a specific install component just for the current example's sources.
install(
DIRECTORY "${example_source_dir}"
DESTINATION "${example_install_path}"
COMPONENT "examples_sources_${arg_NAME}"
USE_SOURCE_PERMISSIONS
EXCLUDE_FROM_ALL
)
endfunction()

if ("STANDALONE_TEST" IN_LIST Qt6BuildInternals_FIND_COMPONENTS)
include(${CMAKE_CURRENT_LIST_DIR}/QtStandaloneTestTemplateProject/Main.cmake)
if (NOT PROJECT_VERSION_MAJOR)
Expand Down
1 change: 1 addition & 0 deletions cmake/QtProcessConfigureArgs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ endif()

drop_input(make)
drop_input(nomake)
translate_boolean_input(install-examples-sources QT_INSTALL_EXAMPLES_SOURCES)

check_qt_build_parts(nomake)
check_qt_build_parts(make)
Expand Down
3 changes: 3 additions & 0 deletions cmake/QtSetup.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ enable_testing()

option(QT_BUILD_EXAMPLES "Build Qt examples" OFF)
option(QT_BUILD_EXAMPLES_BY_DEFAULT "Should examples be built as part of the default 'all' target." ON)
option(QT_INSTALL_EXAMPLES_SOURCES "Install example sources" OFF)
option(QT_INSTALL_EXAMPLES_SOURCES_BY_DEFAULT
"Install example sources as part of the default 'install' target" ON)

# FIXME: Support prefix builds as well QTBUG-96232
if(QT_WILL_INSTALL)
Expand Down
1 change: 1 addition & 0 deletions cmake/configure-cmake-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ The following table describes the mapping of configure options to CMake argument
| | | build them separately, after configuration. |
| -nomake <part> | -DQT_BUILD_TESTS=OFF | A way to turn off tools explicitly is missing. |
| | -DQT_BUILD_EXAMPLES=OFF | |
| -install-examples-sources | -DQT_INSTALL_EXAMPLES_SOURCES=ON | |
| -no-gui | -DFEATURE_gui=OFF | |
| -no-widgets | -DFEATURE_widgets=OFF | |
| -no-dbus | -DFEATURE_dbus=OFF | |
Expand Down
3 changes: 3 additions & 0 deletions config_help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ Component selection:
[default: libs and examples, also tools if not
cross-building, also tests if -developer-build]
-nomake <part> ....... Exclude <part> from the list of parts to be built.
-install-examples-sources Installs examples source code into the Qt prefix
Only possible when -make examples is also passed
[no]
-gui ................. Build the Qt GUI module and dependencies [yes]
-widgets ............. Build the Qt Widgets module and dependencies [yes]
-no-dbus ............. Do not build the Qt D-Bus module
Expand Down
7 changes: 7 additions & 0 deletions configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,13 @@ qt_configure_add_summary_entry(ARGS "sanitize_fuzzer_no_link")
qt_configure_add_summary_entry(ARGS "sanitize_undefined")
qt_configure_end_summary_section() # end of "Sanitizers" section
qt_configure_add_summary_build_parts("Build parts")
if(QT_INSTALL_EXAMPLES_SOURCES)
set(_examples_sources_entry_message "yes")
else()
set(_examples_sources_entry_message "no")
endif()
qt_configure_add_summary_entry(ARGS "Install examples sources" TYPE "message"
MESSAGE "${_examples_sources_entry_message}")
qt_configure_add_summary_entry(
ARGS "appstore-compliant"
CONDITION APPLE OR ANDROID OR WIN32
Expand Down
1 change: 1 addition & 0 deletions qt_cmdline.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ qt_commandline_option(ltcg TYPE boolean)
qt_commandline_option(intelcet TYPE boolean)
qt_commandline_option(make TYPE addString VALUES examples libs tests tools
benchmarks manual-tests minimal-static-tests)
qt_commandline_option(install-examples-sources TYPE boolean)
qt_commandline_option(mips_dsp TYPE boolean)
qt_commandline_option(mips_dspr2 TYPE boolean)
qt_commandline_option(nomake TYPE addString VALUES examples tests tools benchmarks
Expand Down

0 comments on commit 121f7f3

Please sign in to comment.