浏览代码

Precompile Headers: Add REUSE_FROM signature

Add the ability to share precompiled headers artifacts between
targets.

Fixes: #19659
Cristian Adam 6 年之前
父节点
当前提交
729d997f10

+ 14 - 0
Help/command/target_precompile_headers.rst

@@ -9,9 +9,23 @@ Add a list of header files to precompile.
     <INTERFACE|PUBLIC|PRIVATE> [header1...]
     <INTERFACE|PUBLIC|PRIVATE> [header1...]
     [<INTERFACE|PUBLIC|PRIVATE> [header2...] ...])
     [<INTERFACE|PUBLIC|PRIVATE> [header2...] ...])
 
 
+  target_precompile_headers(<target> REUSE_FROM <other_target>)
+
 Adds header files to :prop_tgt:`PRECOMPILE_HEADERS` or
 Adds header files to :prop_tgt:`PRECOMPILE_HEADERS` or
 :prop_tgt:`INTERFACE_PRECOMPILE_HEADERS` target properties.
 :prop_tgt:`INTERFACE_PRECOMPILE_HEADERS` target properties.
 
 
+The second signature will reuse an already precompiled header file artefact
+from another target. This is done by setting the
+:prop_tgt:`PRECOMPILE_HEADERS_REUSE_FROM` to ``<other_target>`` value.
+The ``<other_target>`` will become a dependency of ``<target>``.
+
+.. note::
+
+  The second signature will require the same set of compiler options,
+  compiler flags, compiler definitions for both ``<target>``, and
+  ``<other_target>``. Compilers (e.g. GCC) will issue a warning if the
+  precompiled header file cannot be used (``-Winvalid-pch``).
+
 Precompiling header files can speed up compilation by creating a partially
 Precompiling header files can speed up compilation by creating a partially
 processed version of some header files, and then using that version during
 processed version of some header files, and then using that version during
 compilations rather than repeatedly parsing the original headers.
 compilations rather than repeatedly parsing the original headers.

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -298,6 +298,7 @@ Properties on Targets
    /prop_tgt/PDB_OUTPUT_DIRECTORY
    /prop_tgt/PDB_OUTPUT_DIRECTORY
    /prop_tgt/POSITION_INDEPENDENT_CODE
    /prop_tgt/POSITION_INDEPENDENT_CODE
    /prop_tgt/PRECOMPILE_HEADERS
    /prop_tgt/PRECOMPILE_HEADERS
+   /prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM
    /prop_tgt/PREFIX
    /prop_tgt/PREFIX
    /prop_tgt/PRIVATE_HEADER
    /prop_tgt/PRIVATE_HEADER
    /prop_tgt/PROJECT_LABEL
    /prop_tgt/PROJECT_LABEL

+ 7 - 0
Help/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM.rst

@@ -0,0 +1,7 @@
+PRECOMPILE_HEADERS_REUSE_FROM
+-----------------------------
+
+Target from which to reuse the precomipled headers build artifact.
+
+See the second signature of :command:`target_precompile_headers` command
+for more detailed information.

+ 10 - 3
Modules/Platform/Windows-MSVC.cmake

@@ -333,10 +333,17 @@ macro(__windows_compiler_msvc lang)
   set(CMAKE_LINK_PCH ON)
   set(CMAKE_LINK_PCH ON)
   if(MSVC_VERSION GREATER_EQUAL 1910)
   if(MSVC_VERSION GREATER_EQUAL 1910)
     # VS 2017 or greater
     # VS 2017 or greater
-    set(CMAKE_PCH_PROLOGUE "#pragma system_header")
+    if (NOT ${CMAKE_${lang}_COMPILER_ID} STREQUAL "Clang")
+        set(CMAKE_PCH_PROLOGUE "#pragma system_header")
+    else()
+        set(CMAKE_PCH_PROLOGUE "#pragma clang system_header")
+    endif()
+  endif()
+  if (NOT ${CMAKE_${lang}_COMPILER_ID} STREQUAL "Clang")
+    set(CMAKE_PCH_COPY_COMPILE_PDB ON)
   endif()
   endif()
-  set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH /Yu<PCH_HEADER> /FI<PCH_HEADER>)
-  set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH /Yc<PCH_HEADER> /FI<PCH_HEADER>)
+  set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH /Yu<PCH_HEADER> /Fp<PCH_FILE> /FI<PCH_HEADER>)
+  set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH /Yc<PCH_HEADER> /Fp<PCH_FILE> /FI<PCH_HEADER>)
 
 
   if("x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xMSVC")
   if("x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xMSVC")
     set(_CMAKE_${lang}_IPO_SUPPORTED_BY_CMAKE YES)
     set(_CMAKE_${lang}_IPO_SUPPORTED_BY_CMAKE YES)

+ 1 - 0
Source/cmCommonTargetGenerator.cxx

@@ -171,6 +171,7 @@ std::string cmCommonTargetGenerator::ComputeTargetCompilePDB() const
   if (this->GeneratorTarget->GetType() > cmStateEnums::OBJECT_LIBRARY) {
   if (this->GeneratorTarget->GetType() > cmStateEnums::OBJECT_LIBRARY) {
     return compilePdbPath;
     return compilePdbPath;
   }
   }
+
   compilePdbPath =
   compilePdbPath =
     this->GeneratorTarget->GetCompilePDBPath(this->GetConfigName());
     this->GeneratorTarget->GetCompilePDBPath(this->GetConfigName());
   if (compilePdbPath.empty()) {
   if (compilePdbPath.empty()) {

+ 90 - 41
Source/cmGeneratorTarget.cxx

@@ -35,6 +35,7 @@
 #include "cmRange.h"
 #include "cmRange.h"
 #include "cmSourceFile.h"
 #include "cmSourceFile.h"
 #include "cmSourceFileLocation.h"
 #include "cmSourceFileLocation.h"
+#include "cmSourceFileLocationKind.h"
 #include "cmState.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
@@ -3371,57 +3372,67 @@ std::string cmGeneratorTarget::GetPchHeader(const std::string& config,
     }
     }
     std::string& filename = inserted.first->second;
     std::string& filename = inserted.first->second;
 
 
+    const cmGeneratorTarget* generatorTarget = this;
+    const char* pchReuseFrom =
+      generatorTarget->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
+    if (pchReuseFrom) {
+      generatorTarget =
+        this->GetGlobalGenerator()->FindGeneratorTarget(pchReuseFrom);
+    }
+
     if (this->GetGlobalGenerator()->IsMultiConfig()) {
     if (this->GetGlobalGenerator()->IsMultiConfig()) {
-      filename =
-        cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), "/");
+      filename = cmStrCat(
+        generatorTarget->LocalGenerator->GetCurrentBinaryDirectory(), "/");
     } else {
     } else {
       // For GCC we need to have the header file .h[xx]
       // For GCC we need to have the header file .h[xx]
       // next to the .h[xx].gch file
       // next to the .h[xx].gch file
-      filename = this->ObjectDirectory;
+      filename = generatorTarget->ObjectDirectory;
     }
     }
 
 
-    filename = cmStrCat(filename, "CMakeFiles/", this->GetName(),
+    filename = cmStrCat(filename, "CMakeFiles/", generatorTarget->GetName(),
                         ".dir/cmake_pch", ((language == "C") ? ".h" : ".hxx"));
                         ".dir/cmake_pch", ((language == "C") ? ".h" : ".hxx"));
 
 
     const std::string filename_tmp = cmStrCat(filename, ".tmp");
     const std::string filename_tmp = cmStrCat(filename, ".tmp");
-    {
+    if (!pchReuseFrom) {
       auto pchPrologue = this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
       auto pchPrologue = this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
       auto pchEpilogue = this->Makefile->GetDefinition("CMAKE_PCH_EPILOGUE");
       auto pchEpilogue = this->Makefile->GetDefinition("CMAKE_PCH_EPILOGUE");
 
 
-      cmGeneratedFileStream file(
-        filename_tmp, false,
-        this->GetGlobalGenerator()->GetMakefileEncoding());
-      file << "/* generated by CMake */\n\n";
-      if (pchPrologue) {
-        file << pchPrologue << "\n";
-      }
-      if (this->GetGlobalGenerator()->IsXcode()) {
-        file << "#ifndef CMAKE_SKIP_PRECOMPILE_HEADERS\n";
-      }
-      if (language == "CXX") {
-        file << "#ifdef __cplusplus\n";
-      }
-      for (auto const& header_bt : headers) {
-        if (header_bt.Value.empty()) {
-          continue;
+      {
+        cmGeneratedFileStream file(
+          filename_tmp, false,
+          this->GetGlobalGenerator()->GetMakefileEncoding());
+        file << "/* generated by CMake */\n\n";
+        if (pchPrologue) {
+          file << pchPrologue << "\n";
         }
         }
-        if (header_bt.Value[0] == '<' || header_bt.Value[0] == '"') {
-          file << "#include " << header_bt.Value << "\n";
-        } else {
-          file << "#include \"" << header_bt.Value << "\"\n";
+        if (this->GetGlobalGenerator()->IsXcode()) {
+          file << "#ifndef CMAKE_SKIP_PRECOMPILE_HEADERS\n";
+        }
+        if (language == "CXX") {
+          file << "#ifdef __cplusplus\n";
+        }
+        for (auto const& header_bt : headers) {
+          if (header_bt.Value.empty()) {
+            continue;
+          }
+          if (header_bt.Value[0] == '<' || header_bt.Value[0] == '\"') {
+            file << "#include " << header_bt.Value << "\n";
+          } else {
+            file << "#include \"" << header_bt.Value << "\"\n";
+          }
+        }
+        if (language == "CXX") {
+          file << "#endif // __cplusplus\n";
+        }
+        if (this->GetGlobalGenerator()->IsXcode()) {
+          file << "#endif // CMAKE_SKIP_PRECOMPILE_HEADERS\n";
+        }
+        if (pchEpilogue) {
+          file << pchEpilogue << "\n";
         }
         }
       }
       }
-      if (language == "CXX") {
-        file << "#endif // __cplusplus\n";
-      }
-      if (this->GetGlobalGenerator()->IsXcode()) {
-        file << "#endif // CMAKE_SKIP_PRECOMPILE_HEADERS\n";
-      }
-      if (pchEpilogue) {
-        file << pchEpilogue << "\n";
-      }
+      cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
     }
     }
-    cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
   }
   }
   return inserted.first->second;
   return inserted.first->second;
 }
 }
@@ -3440,8 +3451,18 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
       return std::string();
       return std::string();
     }
     }
     std::string& filename = inserted.first->second;
     std::string& filename = inserted.first->second;
-    filename = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(),
-                        "/CMakeFiles/", this->GetName(), ".dir/cmake_pch");
+
+    const cmGeneratorTarget* generatorTarget = this;
+    const char* pchReuseFrom =
+      generatorTarget->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
+    if (pchReuseFrom) {
+      generatorTarget =
+        this->GetGlobalGenerator()->FindGeneratorTarget(pchReuseFrom);
+    }
+
+    filename =
+      cmStrCat(generatorTarget->LocalGenerator->GetCurrentBinaryDirectory(),
+               "/CMakeFiles/", generatorTarget->GetName(), ".dir/cmake_pch");
 
 
     // For GCC the source extension will be tranformed into .h[xx].gch
     // For GCC the source extension will be tranformed into .h[xx].gch
     if (!this->Makefile->IsOn("CMAKE_LINK_PCH")) {
     if (!this->Makefile->IsOn("CMAKE_LINK_PCH")) {
@@ -3449,12 +3470,40 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
     } else {
     } else {
       filename += ((language == "C") ? ".c" : ".cxx");
       filename += ((language == "C") ? ".c" : ".cxx");
     }
     }
+
     const std::string filename_tmp = cmStrCat(filename, ".tmp");
     const std::string filename_tmp = cmStrCat(filename, ".tmp");
-    {
-      cmGeneratedFileStream file(filename_tmp);
-      file << "/* generated by CMake */\n";
+    if (!pchReuseFrom) {
+      {
+        cmGeneratedFileStream file(filename_tmp);
+        file << "/* generated by CMake */\n";
+      }
+      cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
     }
     }
-    cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
+  }
+  return inserted.first->second;
+}
+
+std::string cmGeneratorTarget::GetPchFileObject(const std::string& config,
+                                                const std::string& language)
+{
+  if (language != "C" && language != "CXX") {
+    return std::string();
+  }
+  const auto inserted =
+    this->PchObjectFiles.insert(std::make_pair(language + config, ""));
+  if (inserted.second) {
+    const std::string pchSource = this->GetPchSource(config, language);
+    if (pchSource.empty()) {
+      return std::string();
+    }
+    std::string& filename = inserted.first->second;
+
+    this->AddSource(pchSource, true);
+
+    auto pchSf = this->Makefile->GetOrCreateSource(
+      pchSource, false, cmSourceFileLocationKind::Known);
+
+    filename = cmStrCat(this->ObjectDirectory, this->GetObjectName(pchSf));
   }
   }
   return inserted.first->second;
   return inserted.first->second;
 }
 }

+ 3 - 0
Source/cmGeneratorTarget.h

@@ -462,6 +462,8 @@ public:
                            const std::string& language) const;
                            const std::string& language) const;
   std::string GetPchSource(const std::string& config,
   std::string GetPchSource(const std::string& config,
                            const std::string& language) const;
                            const std::string& language) const;
+  std::string GetPchFileObject(const std::string& config,
+                               const std::string& language);
 
 
   bool IsSystemIncludeDirectory(const std::string& dir,
   bool IsSystemIncludeDirectory(const std::string& dir,
                                 const std::string& config,
                                 const std::string& config,
@@ -880,6 +882,7 @@ private:
   mutable std::set<std::string> LinkImplicitNullProperties;
   mutable std::set<std::string> LinkImplicitNullProperties;
   mutable std::map<std::string, std::string> PchHeaders;
   mutable std::map<std::string, std::string> PchHeaders;
   mutable std::map<std::string, std::string> PchSources;
   mutable std::map<std::string, std::string> PchSources;
+  mutable std::map<std::string, std::string> PchObjectFiles;
 
 
   void ExpandLinkItems(std::string const& prop, std::string const& value,
   void ExpandLinkItems(std::string const& prop, std::string const& value,
                        std::string const& config,
                        std::string const& config,

+ 108 - 5
Source/cmLocalGenerator.cxx

@@ -4,7 +4,9 @@
 
 
 #include "cmAlgorithms.h"
 #include "cmAlgorithms.h"
 #include "cmComputeLinkInformation.h"
 #include "cmComputeLinkInformation.h"
+#include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmCustomCommandGenerator.h"
+#include "cmCustomCommandLines.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpressionEvaluationFile.h"
 #include "cmGeneratorExpressionEvaluationFile.h"
@@ -2255,23 +2257,124 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target,
     return;
     return;
   }
   }
 
 
+  const char* pchReuseFrom =
+    target->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
+
   auto pch_sf = this->Makefile->GetOrCreateSource(
   auto pch_sf = this->Makefile->GetOrCreateSource(
     pchSource, false, cmSourceFileLocationKind::Known);
     pchSource, false, cmSourceFileLocationKind::Known);
   std::string pchFile = pchHeader;
   std::string pchFile = pchHeader;
 
 
   if (!this->GetGlobalGenerator()->IsXcode()) {
   if (!this->GetGlobalGenerator()->IsXcode()) {
+    if (!pchReuseFrom) {
+      target->AddSource(pchSource, true);
+    }
+
     // Exclude the pch files from linking
     // Exclude the pch files from linking
     if (this->Makefile->IsOn("CMAKE_LINK_PCH")) {
     if (this->Makefile->IsOn("CMAKE_LINK_PCH")) {
-      cmSystemTools::ReplaceString(pchFile, (lang == "C" ? ".h" : ".hxx"),
-                                   pchExtension);
-      pch_sf->SetProperty("OBJECT_OUTPUTS", pchFile.c_str());
+
+      auto replaceExtension = [](const std::string& str,
+                                 const std::string& ext) -> std::string {
+        auto dot_pos = str.rfind('.');
+        std::string result;
+        if (dot_pos != std::string::npos) {
+          result = str.substr(0, dot_pos);
+        }
+        result += ext;
+        return result;
+      };
+
+      if (!pchReuseFrom) {
+        std::string pchSourceObj = target->GetPchFileObject(config, lang);
+
+        pchFile = replaceExtension(pchSourceObj, pchExtension);
+        pch_sf->SetProperty("OBJECT_OUTPUTS", pchFile.c_str());
+      } else {
+        auto reuseTarget =
+          this->GlobalGenerator->FindGeneratorTarget(pchReuseFrom);
+
+        if (this->Makefile->IsOn("CMAKE_PCH_COPY_COMPILE_PDB")) {
+
+          const std::string pdb_prefix =
+            this->GetGlobalGenerator()->IsMultiConfig()
+            ? cmStrCat(this->GlobalGenerator->GetCMakeCFGIntDir(), "/")
+            : "";
+
+          const std::string target_compile_pdb_dir =
+            cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(),
+                     "/", target->GetName(), ".dir/");
+
+          const std::string copy_script =
+            cmStrCat(target_compile_pdb_dir, "copy_idb_pdb.cmake");
+          cmGeneratedFileStream file(copy_script);
+
+          file << "# CMake generated file\n";
+          for (auto extension : { ".pdb", ".idb" }) {
+            const std::string from_file = cmStrCat(
+              reuseTarget->GetLocalGenerator()->GetCurrentBinaryDirectory(),
+              "/", pchReuseFrom, ".dir/${PDB_PREFIX}", pchReuseFrom,
+              extension);
+
+            const std::string to_dir = cmStrCat(
+              target->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/",
+              target->GetName(), ".dir/${PDB_PREFIX}");
+
+            file << "if (EXISTS \"" << from_file << "\")\n";
+            file << "  file(COPY \"" << from_file << "\""
+                 << " DESTINATION \"" << to_dir << "\")\n";
+            file << "endif()\n";
+          }
+
+          cmCustomCommandLines commandLines;
+          cmCustomCommandLine currentLine;
+          currentLine.push_back(cmSystemTools::GetCMakeCommand());
+          currentLine.push_back(cmStrCat("-DPDB_PREFIX=", pdb_prefix));
+          currentLine.push_back("-P");
+          currentLine.push_back(copy_script);
+          commandLines.push_back(std::move(currentLine));
+
+          const std::string no_main_dependency;
+          const std::vector<std::string> no_deps;
+          const char* no_message = "";
+          const char* no_current_dir = nullptr;
+          std::vector<std::string> no_byproducts;
+
+          std::vector<std::string> outputs;
+          outputs.push_back(cmStrCat(target_compile_pdb_dir, pdb_prefix,
+                                     pchReuseFrom, ".pdb"));
+
+          if (this->GetGlobalGenerator()->IsMultiConfig()) {
+            this->Makefile->AddCustomCommandToTarget(
+              target->GetName(), outputs, no_deps, commandLines,
+              cmTarget::PRE_BUILD, no_message, no_current_dir);
+          } else {
+            cmImplicitDependsList no_implicit_depends;
+            cmSourceFile* copy_rule = this->Makefile->AddCustomCommandToOutput(
+              outputs, no_byproducts, no_deps, no_main_dependency,
+              no_implicit_depends, commandLines, no_message, no_current_dir);
+
+            if (copy_rule) {
+              target->AddSource(copy_rule->ResolveFullPath());
+            }
+          }
+
+          target->Target->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
+                                      target_compile_pdb_dir.c_str());
+        }
+
+        std::string pchSourceObj = reuseTarget->GetPchFileObject(config, lang);
+
+        // Link to the pch object file
+        target->Target->SetProperty(
+          "LINK_FLAGS",
+          this->ConvertToOutputFormat(pchSourceObj, SHELL).c_str());
+
+        pchFile = replaceExtension(pchSourceObj, pchExtension);
+      }
     } else {
     } else {
       pchFile += pchExtension;
       pchFile += pchExtension;
       pch_sf->SetProperty("PCH_EXTENSION", pchExtension.c_str());
       pch_sf->SetProperty("PCH_EXTENSION", pchExtension.c_str());
     }
     }
 
 
-    target->AddSource(pchSource, true);
-
     for (auto& str : { std::ref(useOptionList), std::ref(createOptionList) }) {
     for (auto& str : { std::ref(useOptionList), std::ref(createOptionList) }) {
       cmSystemTools::ReplaceString(str, "<PCH_HEADER>", pchHeader);
       cmSystemTools::ReplaceString(str, "<PCH_HEADER>", pchHeader);
       cmSystemTools::ReplaceString(str, "<PCH_FILE>", pchFile);
       cmSystemTools::ReplaceString(str, "<PCH_FILE>", pchFile);

+ 44 - 0
Source/cmTarget.cxx

@@ -1085,6 +1085,7 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
   MAKE_STATIC_PROP(COMPILE_FEATURES);
   MAKE_STATIC_PROP(COMPILE_FEATURES);
   MAKE_STATIC_PROP(COMPILE_OPTIONS);
   MAKE_STATIC_PROP(COMPILE_OPTIONS);
   MAKE_STATIC_PROP(PRECOMPILE_HEADERS);
   MAKE_STATIC_PROP(PRECOMPILE_HEADERS);
+  MAKE_STATIC_PROP(PRECOMPILE_HEADERS_REUSE_FROM);
   MAKE_STATIC_PROP(CUDA_PTX_COMPILATION);
   MAKE_STATIC_PROP(CUDA_PTX_COMPILATION);
   MAKE_STATIC_PROP(EXPORT_NAME);
   MAKE_STATIC_PROP(EXPORT_NAME);
   MAKE_STATIC_PROP(IMPORTED_GLOBAL);
   MAKE_STATIC_PROP(IMPORTED_GLOBAL);
@@ -1231,6 +1232,41 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
       << impl->Name << "\")\n";
       << impl->Name << "\")\n";
     impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
     impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
     return;
     return;
+  } else if (prop == propPRECOMPILE_HEADERS_REUSE_FROM) {
+    if (this->GetProperty("PRECOMPILE_HEADERS")) {
+      std::ostringstream e;
+      e << "PRECOMPILE_HEADERS property is already set on target (\""
+        << impl->Name << "\")\n";
+      impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return;
+    }
+    auto reusedTarget =
+      impl->Makefile->GetCMakeInstance()->GetGlobalGenerator()->FindTarget(
+        value);
+    if (!reusedTarget) {
+      const std::string e(
+        "PRECOMPILE_HEADERS_REUSE_FROM set with non existing target");
+      impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
+      return;
+    }
+
+    std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
+    if (reusedFrom.empty()) {
+      reusedFrom = value;
+    }
+
+    impl->Properties.SetProperty(prop, reusedFrom.c_str());
+
+    reusedTarget->SetProperty("COMPILE_PDB_NAME", reusedFrom.c_str());
+    reusedTarget->SetProperty("COMPILE_PDB_OUTPUT_DIRECTORY",
+                              cmStrCat(reusedFrom, ".dir/").c_str());
+
+    for (auto p : { "COMPILE_PDB_NAME", "PRECOMPILE_HEADERS",
+                    "INTERFACE_PRECOMPILE_HEADERS" }) {
+      this->SetProperty(p, reusedTarget->GetProperty(p));
+    }
+
+    this->AddUtility(reusedFrom, impl->Makefile);
   } else {
   } else {
     impl->Properties.SetProperty(prop, value);
     impl->Properties.SetProperty(prop, value);
   }
   }
@@ -1308,6 +1344,14 @@ void cmTarget::AppendProperty(const std::string& prop, const char* value,
       impl->LinkDirectoriesBacktraces.push_back(lfbt);
       impl->LinkDirectoriesBacktraces.push_back(lfbt);
     }
     }
   } else if (prop == "PRECOMPILE_HEADERS") {
   } else if (prop == "PRECOMPILE_HEADERS") {
+    if (this->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM")) {
+      std::ostringstream e;
+      e << "PRECOMPILE_HEADERS_REUSE_FROM property is already set on target "
+           "(\""
+        << impl->Name << "\")\n";
+      impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return;
+    }
     if (value && *value) {
     if (value && *value) {
       impl->PrecompileHeadersEntries.emplace_back(value);
       impl->PrecompileHeadersEntries.emplace_back(value);
       cmListFileBacktrace lfbt = impl->Makefile->GetBacktrace();
       cmListFileBacktrace lfbt = impl->Makefile->GetBacktrace();

+ 1 - 1
Source/cmTargetPrecompileHeadersCommand.cxx

@@ -10,7 +10,7 @@
 bool cmTargetPrecompileHeadersCommand::InitialPass(
 bool cmTargetPrecompileHeadersCommand::InitialPass(
   std::vector<std::string> const& args, cmExecutionStatus&)
   std::vector<std::string> const& args, cmExecutionStatus&)
 {
 {
-  return this->HandleArguments(args, "PRECOMPILE_HEADERS");
+  return this->HandleArguments(args, "PRECOMPILE_HEADERS", PROCESS_REUSE_FROM);
 }
 }
 
 
 void cmTargetPrecompileHeadersCommand::HandleMissingTarget(
 void cmTargetPrecompileHeadersCommand::HandleMissingTarget(

+ 13 - 0
Source/cmTargetPropCommandBase.cxx

@@ -65,6 +65,19 @@ bool cmTargetPropCommandBase::HandleArguments(
     ++argIndex;
     ++argIndex;
   }
   }
 
 
+  if ((flags & PROCESS_REUSE_FROM) && args[argIndex] == "REUSE_FROM") {
+    if (args.size() != 3) {
+      this->SetError("called with incorrect number of arguments");
+      return false;
+    }
+    ++argIndex;
+
+    this->Target->SetProperty("PRECOMPILE_HEADERS_REUSE_FROM",
+                              args[argIndex].c_str());
+
+    ++argIndex;
+  }
+
   this->Property = prop;
   this->Property = prop;
 
 
   while (argIndex < args.size()) {
   while (argIndex < args.size()) {

+ 4 - 3
Source/cmTargetPropCommandBase.h

@@ -17,9 +17,10 @@ class cmTargetPropCommandBase : public cmCommand
 public:
 public:
   enum ArgumentFlags
   enum ArgumentFlags
   {
   {
-    NO_FLAGS = 0,
-    PROCESS_BEFORE = 1,
-    PROCESS_SYSTEM = 2
+    NO_FLAGS = 0x0,
+    PROCESS_BEFORE = 0x1,
+    PROCESS_SYSTEM = 0x2,
+    PROCESS_REUSE_FROM = 0x3
   };
   };
 
 
   bool HandleArguments(std::vector<std::string> const& args,
   bool HandleArguments(std::vector<std::string> const& args,

+ 20 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseFrom.cmake

@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.15)
+project(PchReuseFrom C)
+
+add_library(empty empty.c)
+target_precompile_headers(empty PUBLIC
+  <stdio.h>
+  <string.h>
+)
+target_include_directories(empty PUBLIC include)
+
+add_library(foo foo.c)
+target_include_directories(foo PUBLIC include)
+target_precompile_headers(foo REUSE_FROM empty)
+
+add_executable(foobar foobar.c)
+target_link_libraries(foobar foo )
+set_target_properties(foobar PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM foo)
+
+enable_testing()
+add_test(NAME foobar COMMAND foobar)

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

@@ -0,0 +1,2 @@
+^(|Warning #670: precompiled header file [^
+]* was not generated in this directory)$

+ 18 - 0
Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir.cmake

@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.15)
+project(PchReuseFromSubdir C)
+
+add_library(empty empty.c)
+target_precompile_headers(empty PUBLIC
+  <stdio.h>
+  <string.h>
+)
+target_include_directories(empty PUBLIC include)
+
+add_library(foo foo.c)
+target_include_directories(foo PUBLIC include)
+target_precompile_headers(foo REUSE_FROM empty)
+
+subdirs(subdir)
+
+enable_testing()
+add_test(NAME foobar COMMAND foobar)

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

@@ -16,3 +16,5 @@ run_cmake(DisabledPch)
 run_test(PchInterface)
 run_test(PchInterface)
 run_cmake(PchPrologueEpilogue)
 run_cmake(PchPrologueEpilogue)
 run_test(SkipPrecompileHeaders)
 run_test(SkipPrecompileHeaders)
+run_test(PchReuseFrom)
+run_test(PchReuseFromSubdir)

+ 3 - 0
Tests/RunCMake/PrecompileHeaders/empty.c

@@ -0,0 +1,3 @@
+void nothing()
+{
+}

+ 1 - 1
Tests/RunCMake/PrecompileHeaders/include/foo.h

@@ -1,6 +1,6 @@
 #ifndef foo_h
 #ifndef foo_h
 #define foo_h
 #define foo_h
 
 
-extern int foo();
+int foo(void);
 
 
 #endif
 #endif

+ 3 - 0
Tests/RunCMake/PrecompileHeaders/subdir/CMakeLists.txt

@@ -0,0 +1,3 @@
+add_executable(foobar ../foobar.c)
+target_link_libraries(foobar foo )
+set_target_properties(foobar PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM foo)