Browse Source

Merge topic 'automoc-specific-include-dirs'

39677f4cc6 AUTOMOC: Add option to specify moc include directories explicitly

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Acked-by: Osyotr <[email protected]>
Merge-request: !10640
Brad King 5 months ago
parent
commit
fc9e9d1196

+ 1 - 0
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

+ 1 - 0
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

+ 9 - 0
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.

+ 30 - 0
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 <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"
+  )

+ 7 - 0
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.

+ 15 - 0
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.

+ 60 - 22
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<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;
+    // 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);
+
+      // 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> {

+ 1 - 0
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 },

+ 46 - 0
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_<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()

+ 16 - 0
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"
+)

+ 3 - 0
Tests/RunCMake/Autogen_7/CMakeLists.txt

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

+ 10 - 0
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()

+ 1 - 1
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)  # <base>/lib/cmake