Browse Source

pchreuse: defer target existence enforcement to generation time

Now that generation can work with any way the state gets to the way it
is, just do the target enforcement at generation time. This allows PCH
reuse targets to be declared before or after targets which use them.

Also update `cmLocalGenerator` to use the methods now that they reliably
provide values rather than parallel construction.
Ben Boeckel 4 months ago
parent
commit
f78f592b78

+ 78 - 12
Source/cmGeneratorTarget.cxx

@@ -1296,6 +1296,8 @@ std::string cmGeneratorTarget::GetCompilePDBName(
     return components.prefix + pdbName + ".pdb";
   }
 
+  // If the target is PCH-reused, we need a stable name for the PDB file so
+  // that reusing targets can construct a stable name for it.
   if (this->PchReused) {
     NameComponents const& components = GetFullNameInternalComponents(
       config, cmStateEnums::RuntimeBinaryArtifact);
@@ -2811,12 +2813,45 @@ std::string cmGeneratorTarget::GetClangTidyExportFixesDirectory(
   return cmSystemTools::CollapseFullPath(path);
 }
 
+struct CycleWatcher
+{
+  CycleWatcher(bool& flag)
+    : Flag(flag)
+  {
+    this->Flag = true;
+  }
+  ~CycleWatcher() { this->Flag = false; }
+  bool& Flag;
+};
+
 cmGeneratorTarget const* cmGeneratorTarget::GetPchReuseTarget() const
 {
+  if (this->ComputingPchReuse) {
+    // TODO: Get the full cycle.
+    if (!this->PchReuseCycleDetected) {
+      this->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Circular PCH reuse target involving '", this->GetName(),
+                 '\''));
+    }
+    this->PchReuseCycleDetected = true;
+    return nullptr;
+  }
+  CycleWatcher watch(this->ComputingPchReuse);
+  (void)watch;
   cmValue pchReuseFrom = this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
   if (!pchReuseFrom) {
     return nullptr;
   }
+  cmGeneratorTarget const* generatorTarget =
+    this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
+  if (!generatorTarget) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat(
+        "Target \"", *pchReuseFrom, "\" for the \"", this->GetName(),
+        R"(" target's "PRECOMPILE_HEADERS_REUSE_FROM" property does not exist.)"));
+  }
   if (this->GetProperty("PRECOMPILE_HEADERS").IsOn()) {
     this->Makefile->IssueMessage(
       MessageType::FATAL_ERROR,
@@ -2824,16 +2859,43 @@ cmGeneratorTarget const* cmGeneratorTarget::GetPchReuseTarget() const
                this->GetName(), "\")\n"));
   }
 
-  // Guaranteed to exist because `SetProperty` does a target lookup.
-  return this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
+  if (generatorTarget) {
+    if (auto const* recurseReuseTarget =
+          generatorTarget->GetPchReuseTarget()) {
+      return recurseReuseTarget;
+    }
+  }
+  return generatorTarget;
 }
 
 cmGeneratorTarget* cmGeneratorTarget::GetPchReuseTarget()
 {
+  if (this->ComputingPchReuse) {
+    // TODO: Get the full cycle.
+    if (!this->PchReuseCycleDetected) {
+      this->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Circular PCH reuse target involving '", this->GetName(),
+                 '\''));
+    }
+    this->PchReuseCycleDetected = true;
+    return nullptr;
+  }
+  CycleWatcher watch(this->ComputingPchReuse);
+  (void)watch;
   cmValue pchReuseFrom = this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
   if (!pchReuseFrom) {
     return nullptr;
   }
+  cmGeneratorTarget* generatorTarget =
+    this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
+  if (!generatorTarget) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat(
+        "Target \"", *pchReuseFrom, "\" for the \"", this->GetName(),
+        R"(" target's "PRECOMPILE_HEADERS_REUSE_FROM" property does not exist.)"));
+  }
   if (this->GetProperty("PRECOMPILE_HEADERS").IsOn()) {
     this->Makefile->IssueMessage(
       MessageType::FATAL_ERROR,
@@ -2841,8 +2903,12 @@ cmGeneratorTarget* cmGeneratorTarget::GetPchReuseTarget()
                this->GetName(), "\")\n"));
   }
 
-  // Guaranteed to exist because `SetProperty` does a target lookup.
-  return this->GetGlobalGenerator()->FindGeneratorTarget(*pchReuseFrom);
+  if (generatorTarget) {
+    if (auto* recurseReuseTarget = generatorTarget->GetPchReuseTarget()) {
+      return recurseReuseTarget;
+    }
+  }
+  return generatorTarget;
 }
 
 std::vector<std::string> cmGeneratorTarget::GetPchArchs(
@@ -2873,6 +2939,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
   }
   cmGeneratorTarget const* generatorTarget = this;
   cmGeneratorTarget const* reuseTarget = this->GetPchReuseTarget();
+  bool const haveReuseTarget = reuseTarget && reuseTarget != this;
   if (reuseTarget) {
     generatorTarget = reuseTarget;
   }
@@ -2882,7 +2949,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
   if (inserted.second) {
     std::vector<BT<std::string>> const headers =
       this->GetPrecompileHeaders(config, language);
-    if (headers.empty() && !reuseTarget) {
+    if (headers.empty() && !haveReuseTarget) {
       return std::string();
     }
     std::string& filename = inserted.first->second;
@@ -2905,7 +2972,7 @@ std::string cmGeneratorTarget::GetPchHeader(std::string const& config,
                languageToExtension.at(language));
 
     std::string const filename_tmp = cmStrCat(filename, ".tmp");
-    if (!reuseTarget) {
+    if (!haveReuseTarget) {
       cmValue pchPrologue =
         this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
       cmValue pchEpilogue =
@@ -2981,6 +3048,7 @@ std::string cmGeneratorTarget::GetPchSource(std::string const& config,
 
     cmGeneratorTarget const* generatorTarget = this;
     cmGeneratorTarget const* reuseTarget = this->GetPchReuseTarget();
+    bool const haveReuseTarget = reuseTarget && reuseTarget != this;
     if (reuseTarget) {
       generatorTarget = reuseTarget;
     }
@@ -3009,7 +3077,7 @@ std::string cmGeneratorTarget::GetPchSource(std::string const& config,
     }
 
     std::string const filename_tmp = cmStrCat(filename, ".tmp");
-    if (!reuseTarget) {
+    if (!haveReuseTarget) {
       {
         cmGeneratedFileStream file(filename_tmp);
         file << "/* generated by CMake */\n";
@@ -4467,11 +4535,9 @@ bool cmGeneratorTarget::ComputePDBOutputDir(std::string const& kind,
     }
   }
   if (out.empty()) {
-    // A target which is PCH-reused must have a stable PDB output directory so
-    // that the PDB can be stably referred to when consuming the PCH file.
-    if (this->PchReused) {
-      out = cmStrCat(this->GetLocalGenerator()->GetCurrentBinaryDirectory(),
-                     '/', this->GetName(), ".dir/");
+    // Compile output should always have a path.
+    if (kind == "COMPILE_PDB"_s) {
+      out = this->GetSupportDirectory();
     } else {
       return false;
     }

+ 2 - 0
Source/cmGeneratorTarget.h

@@ -1522,6 +1522,8 @@ private:
   };
   mutable std::map<std::string, InfoByConfig> Configs;
   bool PchReused = false;
+  mutable bool ComputingPchReuse = false;
+  mutable bool PchReuseCycleDetected = false;
 };
 
 class cmGeneratorTarget::TargetPropertyEntry

+ 40 - 59
Source/cmLocalGenerator.cxx

@@ -2779,8 +2779,8 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
           continue;
         }
 
-        cmValue ReuseFrom =
-          target->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
+        auto* reuseTarget = target->GetPchReuseTarget();
+        bool const haveReuseTarget = reuseTarget && reuseTarget != target;
 
         auto* pch_sf = this->Makefile->GetOrCreateSource(
           pchSource, false, cmSourceFileLocationKind::Known);
@@ -2789,7 +2789,7 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
         pch_sf->SetProperty("CXX_SCAN_FOR_MODULES", "0");
 
         if (!this->GetGlobalGenerator()->IsXcode()) {
-          if (!ReuseFrom) {
+          if (!haveReuseTarget) {
             target->AddSource(pchSource, true);
           }
 
@@ -2797,14 +2797,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
 
           // Exclude the pch files from linking
           if (this->Makefile->IsOn("CMAKE_LINK_PCH")) {
-            if (!ReuseFrom) {
+            if (!haveReuseTarget) {
               pch_sf->AppendProperty(
                 "OBJECT_OUTPUTS",
                 cmStrCat("$<$<CONFIG:", config, ">:", pchFile, '>'));
             } else {
-              auto* reuseTarget =
-                this->GlobalGenerator->FindGeneratorTarget(*ReuseFrom);
-
               if (this->Makefile->IsOn("CMAKE_PCH_COPY_COMPILE_PDB")) {
 
                 std::string const compilerId =
@@ -2850,11 +2847,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
                 }
 
                 if (editAndContinueDebugInfo || msvc2008OrLess) {
-                  this->CopyPchCompilePdb(config, lang, target, *ReuseFrom,
-                                          reuseTarget, { ".pdb", ".idb" });
+                  this->CopyPchCompilePdb(config, lang, target, reuseTarget,
+                                          { ".pdb", ".idb" });
                 } else if (programDatabaseDebugInfo) {
-                  this->CopyPchCompilePdb(config, lang, target, *ReuseFrom,
-                                          reuseTarget, { ".pdb" });
+                  this->CopyPchCompilePdb(config, lang, target, reuseTarget,
+                                          { ".pdb" });
                 }
               }
 
@@ -2906,18 +2903,11 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
 
 void cmLocalGenerator::CopyPchCompilePdb(
   std::string const& config, std::string const& language,
-  cmGeneratorTarget* target, std::string const& ReuseFrom,
-  cmGeneratorTarget* reuseTarget, std::vector<std::string> const& extensions)
+  cmGeneratorTarget* target, cmGeneratorTarget* reuseTarget,
+  std::vector<std::string> const& extensions)
 {
-  std::string const pdb_prefix =
-    this->GetGlobalGenerator()->IsMultiConfig() ? cmStrCat(config, '/') : "";
-
-  std::string const target_compile_pdb_dir =
-    cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
-             target->GetName(), ".dir/");
-
-  std::string const copy_script =
-    cmStrCat(target_compile_pdb_dir, "copy_idb_pdb_", config, ".cmake");
+  std::string const copy_script = cmStrCat(target->GetSupportDirectory(),
+                                           "/copy_idb_pdb_", config, ".cmake");
   cmGeneratedFileStream file(copy_script);
 
   file << "# CMake generated file\n";
@@ -2926,28 +2916,37 @@ void cmLocalGenerator::CopyPchCompilePdb(
        << "# by mspdbsrv. The foreach retry loop is needed to make sure\n"
        << "# the pdb file is ready to be copied.\n\n";
 
+  auto configGenex = [&](cm::string_view expr) -> std::string {
+    if (this->GetGlobalGenerator()->IsMultiConfig()) {
+      return cmStrCat("$<$<CONFIG:", config, ">:", expr, '>');
+    }
+    return std::string(expr);
+  };
+
+  std::vector<std::string> outputs;
+  auto replaceExtension = [](std::string const& path,
+                             std::string const& ext) -> std::string {
+    auto const dir = cmSystemTools::GetFilenamePath(path);
+    auto const base = cmSystemTools::GetFilenameWithoutLastExtension(path);
+    if (dir.empty()) {
+      return cmStrCat(base, ext);
+    }
+    return cmStrCat(dir, '/', base, ext);
+  };
   for (auto const& extension : extensions) {
     std::string const from_file =
-      cmStrCat(reuseTarget->GetLocalGenerator()->GetCurrentBinaryDirectory(),
-               '/', ReuseFrom, ".dir/${PDB_PREFIX}", ReuseFrom, extension);
-
-    std::string const to_dir =
-      cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
-               target->GetName(), ".dir/${PDB_PREFIX}");
-
-    std::string const to_file = cmStrCat(to_dir, ReuseFrom, extension);
-
-    std::string dest_file = to_file;
-
-    std::string const& prefix = target->GetSafeProperty("PREFIX");
-    if (!prefix.empty()) {
-      dest_file = cmStrCat(to_dir, prefix, ReuseFrom, extension);
-    }
+      replaceExtension(reuseTarget->GetCompilePDBPath(config), extension);
+    std::string const to_dir = target->GetCompilePDBDirectory(config);
+    std::string const to_file = cmStrCat(
+      replaceExtension(reuseTarget->GetCompilePDBName(config), extension),
+      '/');
+    std::string const dest_file = cmStrCat(to_dir, to_file);
 
     file << "foreach(retry RANGE 1 30)\n";
     file << "  if (EXISTS \"" << from_file << "\" AND (NOT EXISTS \""
          << dest_file << "\" OR NOT \"" << dest_file << "\" IS_NEWER_THAN \""
          << from_file << "\"))\n";
+    file << "    file(MAKE_DIRECTORY \"" << to_dir << "\")\n";
     file << "    execute_process(COMMAND ${CMAKE_COMMAND} -E copy";
     file << " \"" << from_file << "\""
          << " \"" << to_dir << "\" RESULT_VARIABLE result "
@@ -2956,10 +2955,6 @@ void cmLocalGenerator::CopyPchCompilePdb(
          << "      execute_process(COMMAND ${CMAKE_COMMAND}"
          << " -E sleep 1)\n"
          << "    else()\n";
-    if (!prefix.empty()) {
-      file << "  file(REMOVE \"" << dest_file << "\")\n";
-      file << "  file(RENAME \"" << to_file << "\" \"" << dest_file << "\")\n";
-    }
     file << "      break()\n"
          << "    endif()\n";
     file << "  elseif(NOT EXISTS \"" << from_file << "\")\n"
@@ -2967,29 +2962,18 @@ void cmLocalGenerator::CopyPchCompilePdb(
          << " -E sleep 1)\n"
          << "  endif()\n";
     file << "endforeach()\n";
+    outputs.push_back(configGenex(dest_file));
   }
 
-  auto configGenex = [&](cm::string_view expr) -> std::string {
-    if (this->GetGlobalGenerator()->IsMultiConfig()) {
-      return cmStrCat("$<$<CONFIG:", config, ">:", expr, '>');
-    }
-    return std::string(expr);
-  };
-
-  cmCustomCommandLines commandLines = cmMakeSingleCommandLine(
-    { configGenex(cmSystemTools::GetCMakeCommand()),
-      configGenex(cmStrCat("-DPDB_PREFIX=", pdb_prefix)), configGenex("-P"),
-      configGenex(copy_script) });
+  cmCustomCommandLines commandLines =
+    cmMakeSingleCommandLine({ configGenex(cmSystemTools::GetCMakeCommand()),
+                              configGenex("-P"), configGenex(copy_script) });
 
   auto const comment =
     cmStrCat("Copying PDB for PCH reuse from ", reuseTarget->GetName(),
              " for ", target->GetName());
   ;
 
-  std::vector<std::string> outputs;
-  outputs.push_back(configGenex(
-    cmStrCat(target_compile_pdb_dir, pdb_prefix, ReuseFrom, ".pdb")));
-
   auto cc = cm::make_unique<cmCustomCommand>();
   cc->SetCommandLines(commandLines);
   cc->SetComment(comment.c_str());
@@ -3011,9 +2995,6 @@ void cmLocalGenerator::CopyPchCompilePdb(
       target->AddSource(copy_rule->ResolveFullPath());
     }
   }
-
-  target->Target->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
-                              target_compile_pdb_dir);
 }
 
 cm::optional<std::string> cmLocalGenerator::GetMSVCDebugFormatName(

+ 0 - 1
Source/cmLocalGenerator.h

@@ -640,7 +640,6 @@ private:
   void CopyPchCompilePdb(std::string const& config,
                          std::string const& language,
                          cmGeneratorTarget* target,
-                         std::string const& ReuseFrom,
                          cmGeneratorTarget* reuseTarget,
                          std::vector<std::string> const& extensions);
 

+ 3 - 17
Source/cmTarget.cxx

@@ -2148,24 +2148,10 @@ void cmTarget::SetProperty(std::string const& prop, cmValue value)
       return;
     }
   } else if (prop == propPRECOMPILE_HEADERS_REUSE_FROM) {
-    auto* reusedTarget = this->impl->Makefile->GetCMakeInstance()
-                           ->GetGlobalGenerator()
-                           ->FindTarget(value);
-    if (!reusedTarget) {
-      std::string const e(
-        "PRECOMPILE_HEADERS_REUSE_FROM set with non existing target");
-      this->impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
-      return;
-    }
-
-    std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
-    if (reusedFrom.empty()) {
-      reusedFrom = *value;
+    this->impl->Properties.SetProperty(prop, value);
+    if (value) {
+      this->AddUtility(*value, false, this->impl->Makefile);
     }
-
-    this->impl->Properties.SetProperty(prop, reusedFrom);
-
-    this->AddUtility(reusedFrom, false, this->impl->Makefile);
   } else if (prop == propC_STANDARD || prop == propCXX_STANDARD ||
              prop == propCUDA_STANDARD || prop == propHIP_STANDARD ||
              prop == propOBJC_STANDARD || prop == propOBJCXX_STANDARD) {

+ 12 - 1
Tests/PDBDirectoryAndName/CMakeLists.txt

@@ -76,16 +76,27 @@ endif()
 
 set(pdbs "")
 foreach(t ${my_targets})
+  set(with_compile 0)
   get_property(pdb_name TARGET ${t} PROPERTY PDB_NAME)
   get_property(pdb_dir TARGET ${t} PROPERTY PDB_OUTPUT_DIRECTORY)
   if(NOT pdb_name)
+    set(with_compile 1)
     get_property(pdb_name TARGET ${t} PROPERTY COMPILE_PDB_NAME)
   endif()
   if(NOT pdb_dir)
     get_property(pdb_dir TARGET ${t} PROPERTY COMPILE_PDB_OUTPUT_DIRECTORY)
   endif()
   if(NOT pdb_dir)
-    set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR})
+    if (NOT with_compile)
+      set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR})
+    elseif (CMAKE_GENERATOR MATCHES "Ninja" OR
+            CMAKE_GENERATOR MATCHES "Makefiles")
+      set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${t}.dir)
+    elseif (CMAKE_GENERATOR MATCHES "Visual Studio")
+      set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/${t}.dir)
+    else ()
+      set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/${t}.dir)
+    endif ()
   endif()
   if (pdb_dir MATCHES "\\$<.*>")
     # Skip per-configuration subdirectory if the value contained

+ 2 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseConsistency-build-stderr.txt

@@ -0,0 +1,2 @@
+((Classic Intel)?Warning #672: the command line options do not match those used[^
+]*)?

+ 12 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseConsistency.cmake

@@ -54,12 +54,24 @@ file(WRITE ${CMAKE_BINARY_DIR}/message.hxx [=[
 const char* message();
 ]=])
 
+add_library(pch_before_reuse_pch ${CMAKE_BINARY_DIR}/message.cxx)
+target_precompile_headers(pch_before_reuse_pch PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
+target_precompile_headers(pch_before_reuse_pch REUSE_FROM pch-generator)
+set_property(TARGET pch_before_reuse_pch PROPERTY PRECOMPILE_HEADERS_REUSE_FROM)
+target_include_directories(pch_before_reuse_pch PRIVATE ${CMAKE_BINARY_DIR})
+
 add_library(pch_before_reuse_reuse ${CMAKE_BINARY_DIR}/message.cxx)
 target_precompile_headers(pch_before_reuse_reuse PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
 target_precompile_headers(pch_before_reuse_reuse REUSE_FROM pch-generator)
 set_property(TARGET pch_before_reuse_reuse PROPERTY PRECOMPILE_HEADERS "")
 target_include_directories(pch_before_reuse_reuse PRIVATE ${CMAKE_BINARY_DIR})
 
+add_library(reuse_before_pch_pch ${CMAKE_BINARY_DIR}/message.cxx)
+target_precompile_headers(reuse_before_pch_pch REUSE_FROM pch-generator)
+target_precompile_headers(reuse_before_pch_pch PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")
+set_property(TARGET reuse_before_pch_pch PROPERTY PRECOMPILE_HEADERS_REUSE_FROM)
+target_include_directories(reuse_before_pch_pch PRIVATE ${CMAKE_BINARY_DIR})
+
 add_library(reuse_before_pch_reuse ${CMAKE_BINARY_DIR}/message.cxx)
 target_precompile_headers(reuse_before_pch_reuse REUSE_FROM pch-generator)
 target_precompile_headers(reuse_before_pch_reuse PRIVATE "${CMAKE_BINARY_DIR}/string.hxx")

+ 2 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseDeclarationOrder-build-stderr.txt

@@ -0,0 +1,2 @@
+((Classic Intel)?Warning #672: the command line options do not match those used[^
+]*)?

+ 59 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseDeclarationOrder.cmake

@@ -0,0 +1,59 @@
+enable_language(CXX)
+
+if(CMAKE_CXX_COMPILE_OPTIONS_USE_PCH)
+  add_definitions(-DHAVE_PCH_SUPPORT)
+endif()
+
+######################################################################
+
+file(WRITE ${CMAKE_BINARY_DIR}/message.cxx [=[
+#include "message.hxx"
+
+#ifndef HAVE_PCH_SUPPORT
+  #include "string.hxx"
+#endif
+
+const char* message()
+{
+  static std::string greeting("hi there");
+  return greeting.c_str();
+}
+]=])
+
+file(WRITE ${CMAKE_BINARY_DIR}/message.hxx [=[
+const char* message();
+]=])
+
+add_library(reuse_decl_order ${CMAKE_BINARY_DIR}/message.cxx)
+target_precompile_headers(reuse_decl_order REUSE_FROM pch-generator)
+target_include_directories(reuse_decl_order PRIVATE ${CMAKE_BINARY_DIR})
+
+######################################################################
+
+file(WRITE ${CMAKE_BINARY_DIR}/pch.cxx [=[
+void nothing()
+{
+}
+]=])
+
+file(WRITE ${CMAKE_BINARY_DIR}/string.hxx [=[
+#include <string.h>
+
+namespace std {
+  struct string
+  {
+    char storage[20];
+
+    string(const char* s) {
+      strcpy(storage, s);
+    }
+
+    const char* c_str() const {
+      return storage;
+    }
+  };
+}
+]=])
+
+add_library(pch-generator ${CMAKE_BINARY_DIR}/pch.cxx)
+target_precompile_headers(pch-generator PRIVATE ${CMAKE_BINARY_DIR}/string.hxx)

+ 1 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseFromCycle-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseFromCycle-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error in CMakeLists.txt:
+  Circular PCH reuse target involving 'first'

+ 10 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseFromCycle.cmake

@@ -0,0 +1,10 @@
+enable_language(C)
+
+add_library(first foo.c)
+target_precompile_headers(first REUSE_FROM third)
+
+add_library(second foo.c)
+target_precompile_headers(second REUSE_FROM first)
+
+add_library(third foo.c)
+target_precompile_headers(third REUSE_FROM second)

+ 2 - 0
Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake

@@ -32,7 +32,9 @@ run_test(PchReuseFromSubdir)
 run_build_verbose(PchReuseFromIgnoreOwnProps)
 run_build_verbose(PchReuseFromUseUpdatedProps)
 run_build_verbose(PchReuseConsistency)
+run_cmake(PchReuseFromCycle)
 run_cmake(PchMultilanguage)
+run_build_verbose(PchReuseDeclarationOrder)
 if(RunCMake_GENERATOR MATCHES "Make|Ninja")
   run_cmake(PchWarnInvalid)