1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-18 17:31:57 +08:00

AUTOMOC: Add option to specify moc include directories explicitly

Add a `AUTOMOC_INCLUDE_DIRECTORIES` target property and a corresponding
`CMAKE_AUTOMOC_INCLUDE_DIRECTORIES` variable to initialize it.
This is useful for targets that do not need moc to search include
directories from all dependencies.

Closes: #26414
This commit is contained in:
David Worley
2025-05-07 18:11:51 -04:00
parent dd835c8b2b
commit 39677f4cc6
13 changed files with 199 additions and 22 deletions

View File

@@ -145,6 +145,7 @@ Properties on Targets
/prop_tgt/AUTOMOC_COMPILER_PREDEFINES
/prop_tgt/AUTOMOC_DEPEND_FILTERS
/prop_tgt/AUTOMOC_EXECUTABLE
/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES
/prop_tgt/AUTOMOC_MACRO_NAMES
/prop_tgt/AUTOMOC_MOC_OPTIONS
/prop_tgt/AUTOMOC_PATH_PREFIX

View File

@@ -427,6 +427,7 @@ Variables that Control the Build
/variable/CMAKE_AUTOMOC
/variable/CMAKE_AUTOMOC_COMPILER_PREDEFINES
/variable/CMAKE_AUTOMOC_DEPEND_FILTERS
/variable/CMAKE_AUTOMOC_INCLUDE_DIRECTORIES
/variable/CMAKE_AUTOMOC_MACRO_NAMES
/variable/CMAKE_AUTOMOC_MOC_OPTIONS
/variable/CMAKE_AUTOMOC_PATH_PREFIX

View File

@@ -34,6 +34,11 @@ At configuration time, a list of header files that should be scanned by
and adds these to the scan list.
- The :prop_tgt:`AUTOMOC_INCLUDE_DIRECTORIES` target property may be set
to explicitly tell ``moc`` what include directories to search. If not
set, the default is to use the include directories from the target and
its transitive closure of dependencies.
At build time, CMake scans each unknown or modified header file from the
list and searches for
@@ -222,6 +227,10 @@ defining file name filters in this target property.
Compiler pre definitions for ``moc`` are written to the ``moc_predefs.h`` file.
The generation of this file can be enabled or disabled in this target property.
:prop_tgt:`AUTOMOC_INCLUDE_DIRECTORIES`:
Specifies one or more include directories for ``AUTOMOC`` to pass explicitly to ``moc``
instead of automatically discovering a targets include directories.
:prop_sf:`SKIP_AUTOMOC`:
Sources and headers can be excluded from ``AUTOMOC`` processing by
setting this source file property.

View File

@@ -0,0 +1,30 @@
AUTOMOC_INCLUDE_DIRECTORIES
---------------------------
.. versionadded:: 4.1
Specifies zero or more include directories for AUTOMOC to pass explicitly to
the Qt MetaObject Compiler (``moc``) instead of automatically discovering a
target's include directories.
When this property is set on a target, only the directories listed here will be
used by :prop_tgt:`AUTOMOC`, and any other include paths will be ignored.
This property may contain :manual:`generator expressions <cmake-generator-expressions(7)>`.
All directory paths in the final evaluated result **must be absolute**. If any
non-absolute paths are present after generator expression evaluation,
configuration will fail with an error.
See also the :variable:`CMAKE_AUTOMOC_INCLUDE_DIRECTORIES` variable, which can
be used to initialize this property on all targets.
Example
^^^^^^^
.. code-block:: cmake
add_library(myQtLib ...)
set_property(TARGET myQtLib PROPERTY AUTOMOC_INCLUDE_DIRECTORIES
"${CMAKE_CURRENT_SOURCE_DIR}/include/myQtLib"
)

View File

@@ -0,0 +1,7 @@
automoc-include-directories
---------------------------
* The :prop_tgt:`AUTOMOC_INCLUDE_DIRECTORIES` target property and associated
:variable:`CMAKE_AUTOMOC_INCLUDE_DIRECTORIES` variable were added to
override the automatic discovery of moc includes from a target's transitive
include directories.

View File

@@ -0,0 +1,15 @@
CMAKE_AUTOMOC_INCLUDE_DIRECTORIES
---------------------------------
.. versionadded:: 4.1
Specifies zero or more include directories for AUTOMOC to pass explicitly to
the Qt MetaObject Compiler (``moc``) instead of automatically discovering
each target's include directories.
The directories listed here will replace any include paths discovered from
target properties such as :prop_tgt:`INCLUDE_DIRECTORIES`.
This variable is used to initialize the :prop_tgt:`AUTOMOC_INCLUDE_DIRECTORIES`
property on all the targets. See that target property for additional
information.

View File

@@ -723,33 +723,71 @@ bool cmQtAutoGenInitializer::InitMoc()
// Moc includes
{
SearchPathSanitizer const sanitizer(this->Makefile);
auto getDirs =
[this, &sanitizer](std::string const& cfg) -> std::vector<std::string> {
// Get the include dirs for this target, without stripping the implicit
// include dirs off, see issue #13667.
std::vector<std::string> dirs;
bool const appendImplicit = (this->QtVersion.Major >= 5);
this->LocalGen->GetIncludeDirectoriesImplicit(
dirs, this->GenTarget, "CXX", cfg, false, appendImplicit);
return sanitizer(dirs);
};
// If the property AUTOMOC_INCLUDE_DIRECTORIES is set on the target,
// use its value for moc include paths instead of gathering all
// include directories from the target.
cmValue autoIncDirs =
this->GenTarget->GetProperty("AUTOMOC_INCLUDE_DIRECTORIES");
if (autoIncDirs) {
cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
cmGeneratorExpression ge(*this->Makefile->GetCMakeInstance(), lfbt);
auto cge = ge.Parse(*autoIncDirs);
// Other configuration settings
if (this->MultiConfig) {
for (std::string const& cfg : this->ConfigsList) {
std::vector<std::string> dirs = getDirs(cfg);
if (dirs == this->Moc.Includes.Default) {
continue;
// Build a single list of configs to iterate, whether single or multi
std::vector<std::string> configs = this->MultiConfig
? this->ConfigsList
: std::vector<std::string>{ this->ConfigDefault };
for (auto const& cfg : configs) {
std::string eval = cge->Evaluate(this->LocalGen, cfg);
std::vector<std::string> incList = cmList(eval);
// Validate absolute paths
for (auto const& path : incList) {
if (!cmGeneratorExpression::StartsWithGeneratorExpression(path) &&
!cmSystemTools::FileIsFullPath(path)) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("AUTOMOC_INCLUDE_DIRECTORIES: path '", path,
"' is not absolute."));
return false;
}
}
if (this->MultiConfig) {
this->Moc.Includes.Config[cfg] = std::move(incList);
} else {
this->Moc.Includes.Default = std::move(incList);
}
this->Moc.Includes.Config[cfg] = std::move(dirs);
}
} else {
// Default configuration include directories
this->Moc.Includes.Default = getDirs(this->ConfigDefault);
// Otherwise, discover include directories from the target for moc.
SearchPathSanitizer const sanitizer(this->Makefile);
auto getDirs = [this, &sanitizer](
std::string const& cfg) -> std::vector<std::string> {
// Get the include dirs for this target, without stripping the implicit
// include dirs off, see issue #13667.
std::vector<std::string> dirs;
bool const appendImplicit = (this->QtVersion.Major >= 5);
this->LocalGen->GetIncludeDirectoriesImplicit(
dirs, this->GenTarget, "CXX", cfg, false, appendImplicit);
return sanitizer(dirs);
};
// Other configuration settings
if (this->MultiConfig) {
for (std::string const& cfg : this->ConfigsList) {
std::vector<std::string> dirs = getDirs(cfg);
if (dirs == this->Moc.Includes.Default) {
continue;
}
this->Moc.Includes.Config[cfg] = std::move(dirs);
}
} else {
// Default configuration include directories
this->Moc.Includes.Default = getDirs(this->ConfigDefault);
}
}
}
// Moc compile definitions
{
auto getDefs = [this](std::string const& cfg) -> std::set<std::string> {

View File

@@ -381,6 +381,7 @@ TargetProperty const StaticTargetProperties[] = {
// ---- moc
{ "AUTOMOC"_s, IC::CanCompileSources },
{ "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources },
{ "AUTOMOC_INCLUDE_DIRECTORIES"_s, IC::CanCompileSources },
{ "AUTOMOC_MACRO_NAMES"_s, IC::CanCompileSources },
{ "AUTOMOC_MOC_OPTIONS"_s, IC::CanCompileSources },
{ "AUTOMOC_PATH_PREFIX"_s, IC::CanCompileSources },

View File

@@ -0,0 +1,46 @@
# Read the JSON file into a variable
set(autogenInfoFilePath "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/foo_autogen.dir/AutogenInfo.json")
if(NOT IS_READABLE "${autogenInfoFilePath}")
set(RunCMake_TEST_FAILED "Expected autogen info file missing:\n \"${autogenInfoFilePath}\"")
return()
endif()
file(READ "${autogenInfoFilePath}" jsonRaw)
# If multi-config generator, we are looking for MOC_INCLUDES_<CONFIG>.
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(mocKey "MOC_INCLUDES_Debug") # Pick one arbitrarily (they will all be the same in this test)
# If single-config generator, we are looking for MOC_INCLUDES.
else()
set(mocKey "MOC_INCLUDES")
endif()
string(JSON actualValue GET "${jsonRaw}" "${mocKey}")
# The format of the MOC_INCLUDES entries in AutogenInfo.json depends on how long the paths are.
# For short entries:
# "MOC_INCLUDES" : [ "<SHORT_PATH>" ]
# For long entries:
# "MOC_INCLUDES_Debug" :
# [
# "<SOME_PARTICULARLY_LONG_PATH>"
# ],
# Also, paths given to AUTOMOC_INCLUDE_DIRECTORIES must be absolute paths.
# The code uses SystemTools::FileIsFullPath() to verify this, and it accepts
# a forward slash at the beginning for both Windows (network path) and UNIX platforms.
# Therefore, for the simplicity of this test, use a dummy value "/pass".
# Strip the JSON format around the string for a true before/after comparison.
string(REPLACE "[ \"" "" actualValue ${actualValue})
string(REPLACE "\" ]" "" actualValue ${actualValue})
# Final pass/fail comparison.
set(expectedValue "/pass")
if (NOT actualValue STREQUAL expectedValue)
set(RunCMake_TEST_FAILED "AUTOMOC_INCLUDE_DIRECTORIES override property not honored.")
string(APPEND RunCMake_TEST_FAILURE_MESSAGE
"Expected MOC_INCLUDES in AutogenInfo.json to have ${expectedValue} but found ${actualValue}."
)
endif()

View File

@@ -0,0 +1,16 @@
enable_language(CXX)
set(CMAKE_CXX_STANDARD 11)
find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
# Create a test library with an arbitrary include directory to later override with the property
add_library(foo STATIC ../Autogen_common/example.cpp)
target_include_directories(foo PRIVATE ../Autogen_common/example.h)
# Set AUTOMOC_INCLUDE_DIRECTORIES with a test value to verify it replaces the above include directory
# in AutogenInfo.json's MOC_INCLUDES list.
# See comments in the -check.cmake counterpart for more information about this test.
set_target_properties(foo PROPERTIES
AUTOMOC ON
AUTOMOC_INCLUDE_DIRECTORIES "/pass"
)

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@@ -0,0 +1,10 @@
include(RunCMake)
if (DEFINED with_qt_version)
set(RunCMake_TEST_OPTIONS
-Dwith_qt_version=${with_qt_version}
"-DQt${with_qt_version}_DIR:PATH=${Qt${with_qt_version}_DIR}"
"-DCMAKE_PREFIX_PATH:STRING=${CMAKE_PREFIX_PATH}"
)
run_cmake(AutoMocIncludeDirectories)
endif()

View File

@@ -291,7 +291,7 @@ if(CMake_TEST_APPLE_SILICON)
add_RunCMake_test(AppleSilicon)
endif()
set(want_NoQt_test TRUE)
set(autogen_test_number 1 2 3 4 5 6)
set(autogen_test_number 1 2 3 4 5 6 7)
if(CMake_TEST_Qt6 AND Qt6Widgets_FOUND)
# Work around Qt6 not finding sibling dependencies without CMAKE_PREFIX_PATH
cmake_path(GET Qt6_DIR PARENT_PATH base_dir) # <base>/lib/cmake