From 39677f4cc6205b36446fac684ac5de9611915826 Mon Sep 17 00:00:00 2001 From: David Worley Date: Wed, 7 May 2025 18:11:51 -0400 Subject: [PATCH] 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 --- Help/manual/cmake-properties.7.rst | 1 + Help/manual/cmake-variables.7.rst | 1 + Help/prop_tgt/AUTOMOC.rst | 9 +++ Help/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES.rst | 30 +++++++ .../dev/automoc-include-directories.rst | 7 ++ .../CMAKE_AUTOMOC_INCLUDE_DIRECTORIES.rst | 15 ++++ Source/cmQtAutoGenInitializer.cxx | 80 ++++++++++++++----- Source/cmTarget.cxx | 1 + .../AutoMocIncludeDirectories-check.cmake | 46 +++++++++++ .../Autogen_7/AutoMocIncludeDirectories.cmake | 16 ++++ Tests/RunCMake/Autogen_7/CMakeLists.txt | 3 + Tests/RunCMake/Autogen_7/RunCMakeTest.cmake | 10 +++ Tests/RunCMake/CMakeLists.txt | 2 +- 13 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 Help/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES.rst create mode 100644 Help/release/dev/automoc-include-directories.rst create mode 100644 Help/variable/CMAKE_AUTOMOC_INCLUDE_DIRECTORIES.rst create mode 100644 Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories-check.cmake create mode 100644 Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories.cmake create mode 100644 Tests/RunCMake/Autogen_7/CMakeLists.txt create mode 100644 Tests/RunCMake/Autogen_7/RunCMakeTest.cmake diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 106009ee85..a9bd928f1d 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -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 diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 51bd9dc66f..4cda7b4829 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -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 diff --git a/Help/prop_tgt/AUTOMOC.rst b/Help/prop_tgt/AUTOMOC.rst index 221ef47e4e..55e6d69457 100644 --- a/Help/prop_tgt/AUTOMOC.rst +++ b/Help/prop_tgt/AUTOMOC.rst @@ -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 target’s include directories. + :prop_sf:`SKIP_AUTOMOC`: Sources and headers can be excluded from ``AUTOMOC`` processing by setting this source file property. diff --git a/Help/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES.rst b/Help/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES.rst new file mode 100644 index 0000000000..a280ce18d2 --- /dev/null +++ b/Help/prop_tgt/AUTOMOC_INCLUDE_DIRECTORIES.rst @@ -0,0 +1,30 @@ +AUTOMOC_INCLUDE_DIRECTORIES +--------------------------- + +.. versionadded:: 4.1 + +Specifies zero or more include directories for AUTOMOC to pass explicitly to +the Qt Meta‑Object 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 `. + +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" + ) diff --git a/Help/release/dev/automoc-include-directories.rst b/Help/release/dev/automoc-include-directories.rst new file mode 100644 index 0000000000..d1c5cbee64 --- /dev/null +++ b/Help/release/dev/automoc-include-directories.rst @@ -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. diff --git a/Help/variable/CMAKE_AUTOMOC_INCLUDE_DIRECTORIES.rst b/Help/variable/CMAKE_AUTOMOC_INCLUDE_DIRECTORIES.rst new file mode 100644 index 0000000000..2146f00b1a --- /dev/null +++ b/Help/variable/CMAKE_AUTOMOC_INCLUDE_DIRECTORIES.rst @@ -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 Meta‑Object 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. diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx index 2e42602eca..acb64978df 100644 --- a/Source/cmQtAutoGenInitializer.cxx +++ b/Source/cmQtAutoGenInitializer.cxx @@ -723,33 +723,71 @@ bool cmQtAutoGenInitializer::InitMoc() // Moc includes { - SearchPathSanitizer const sanitizer(this->Makefile); - auto getDirs = - [this, &sanitizer](std::string const& cfg) -> std::vector { - // Get the include dirs for this target, without stripping the implicit - // include dirs off, see issue #13667. - std::vector 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 dirs = getDirs(cfg); - if (dirs == this->Moc.Includes.Default) { - continue; + // Build a single list of configs to iterate, whether single or multi + std::vector configs = this->MultiConfig + ? this->ConfigsList + : std::vector{ this->ConfigDefault }; + + for (auto const& cfg : configs) { + std::string eval = cge->Evaluate(this->LocalGen, cfg); + std::vector 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 { + // Get the include dirs for this target, without stripping the implicit + // include dirs off, see issue #13667. + std::vector 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 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 { diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index b49381724d..a17af0481e 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -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 }, diff --git a/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories-check.cmake b/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories-check.cmake new file mode 100644 index 0000000000..62b928233b --- /dev/null +++ b/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories-check.cmake @@ -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_. +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" : [ "" ] +# For long entries: +# "MOC_INCLUDES_Debug" : +# [ +# "" +# ], + +# 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() diff --git a/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories.cmake b/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories.cmake new file mode 100644 index 0000000000..5cb0691406 --- /dev/null +++ b/Tests/RunCMake/Autogen_7/AutoMocIncludeDirectories.cmake @@ -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" +) diff --git a/Tests/RunCMake/Autogen_7/CMakeLists.txt b/Tests/RunCMake/Autogen_7/CMakeLists.txt new file mode 100644 index 0000000000..2632ffa91f --- /dev/null +++ b/Tests/RunCMake/Autogen_7/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/Autogen_7/RunCMakeTest.cmake b/Tests/RunCMake/Autogen_7/RunCMakeTest.cmake new file mode 100644 index 0000000000..d835cda143 --- /dev/null +++ b/Tests/RunCMake/Autogen_7/RunCMakeTest.cmake @@ -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() diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 4d90048a12..c9becbe0b4 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -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) # /lib/cmake