From 729d997f1073c7a177da5b46b073a08b95adfa74 Mon Sep 17 00:00:00 2001
From: Cristian Adam <cristian.adam@gmail.com>
Date: Fri, 30 Aug 2019 16:21:19 +0200
Subject: Precompile Headers: Add REUSE_FROM signature

Add the ability to share precompiled headers artifacts between
targets.

Fixes: #19659
---
 Help/command/target_precompile_headers.rst         |  14 +++
 Help/manual/cmake-properties.7.rst                 |   1 +
 Help/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM.rst    |   7 ++
 Modules/Platform/Windows-MSVC.cmake                |  13 +-
 Source/cmCommonTargetGenerator.cxx                 |   1 +
 Source/cmGeneratorTarget.cxx                       | 131 ++++++++++++++-------
 Source/cmGeneratorTarget.h                         |   3 +
 Source/cmLocalGenerator.cxx                        | 113 +++++++++++++++++-
 Source/cmTarget.cxx                                |  44 +++++++
 Source/cmTargetPrecompileHeadersCommand.cxx        |   2 +-
 Source/cmTargetPropCommandBase.cxx                 |  13 ++
 Source/cmTargetPropCommandBase.h                   |   7 +-
 .../RunCMake/PrecompileHeaders/PchReuseFrom.cmake  |  20 ++++
 .../PchReuseFromSubdir-build-stderr.txt            |   2 +
 .../PrecompileHeaders/PchReuseFromSubdir.cmake     |  18 +++
 .../RunCMake/PrecompileHeaders/RunCMakeTest.cmake  |   2 +
 Tests/RunCMake/PrecompileHeaders/empty.c           |   3 +
 Tests/RunCMake/PrecompileHeaders/include/foo.h     |   2 +-
 .../PrecompileHeaders/subdir/CMakeLists.txt        |   3 +
 19 files changed, 345 insertions(+), 54 deletions(-)
 create mode 100644 Help/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM.rst
 create mode 100644 Tests/RunCMake/PrecompileHeaders/PchReuseFrom.cmake
 create mode 100644 Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir-build-stderr.txt
 create mode 100644 Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir.cmake
 create mode 100644 Tests/RunCMake/PrecompileHeaders/empty.c
 create mode 100644 Tests/RunCMake/PrecompileHeaders/subdir/CMakeLists.txt

diff --git a/Help/command/target_precompile_headers.rst b/Help/command/target_precompile_headers.rst
index 3e28265..7d36b11 100644
--- a/Help/command/target_precompile_headers.rst
+++ b/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> [header2...] ...])
 
+  target_precompile_headers(<target> REUSE_FROM <other_target>)
+
 Adds header files to :prop_tgt:`PRECOMPILE_HEADERS` or
 :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
 processed version of some header files, and then using that version during
 compilations rather than repeatedly parsing the original headers.
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 19afb7d..0928abe 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -298,6 +298,7 @@ Properties on Targets
    /prop_tgt/PDB_OUTPUT_DIRECTORY
    /prop_tgt/POSITION_INDEPENDENT_CODE
    /prop_tgt/PRECOMPILE_HEADERS
+   /prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM
    /prop_tgt/PREFIX
    /prop_tgt/PRIVATE_HEADER
    /prop_tgt/PROJECT_LABEL
diff --git a/Help/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM.rst b/Help/prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM.rst
new file mode 100644
index 0000000..d740303
--- /dev/null
+++ b/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.
diff --git a/Modules/Platform/Windows-MSVC.cmake b/Modules/Platform/Windows-MSVC.cmake
index 6ddcaa3..34f5d03 100644
--- a/Modules/Platform/Windows-MSVC.cmake
+++ b/Modules/Platform/Windows-MSVC.cmake
@@ -333,10 +333,17 @@ macro(__windows_compiler_msvc lang)
   set(CMAKE_LINK_PCH ON)
   if(MSVC_VERSION GREATER_EQUAL 1910)
     # 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()
-  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")
     set(_CMAKE_${lang}_IPO_SUPPORTED_BY_CMAKE YES)
diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx
index 33286ad..19a096b 100644
--- a/Source/cmCommonTargetGenerator.cxx
+++ b/Source/cmCommonTargetGenerator.cxx
@@ -171,6 +171,7 @@ std::string cmCommonTargetGenerator::ComputeTargetCompilePDB() const
   if (this->GeneratorTarget->GetType() > cmStateEnums::OBJECT_LIBRARY) {
     return compilePdbPath;
   }
+
   compilePdbPath =
     this->GeneratorTarget->GetCompilePDBPath(this->GetConfigName());
   if (compilePdbPath.empty()) {
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 2ca5706..3a321c5 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -35,6 +35,7 @@
 #include "cmRange.h"
 #include "cmSourceFile.h"
 #include "cmSourceFileLocation.h"
+#include "cmSourceFileLocationKind.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -3371,57 +3372,67 @@ std::string cmGeneratorTarget::GetPchHeader(const std::string& config,
     }
     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()) {
-      filename =
-        cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), "/");
+      filename = cmStrCat(
+        generatorTarget->LocalGenerator->GetCurrentBinaryDirectory(), "/");
     } else {
       // For GCC we need to have the header file .h[xx]
       // 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"));
 
     const std::string filename_tmp = cmStrCat(filename, ".tmp");
-    {
+    if (!pchReuseFrom) {
       auto pchPrologue = this->Makefile->GetDefinition("CMAKE_PCH_PROLOGUE");
       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;
 }
@@ -3440,8 +3451,18 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
       return std::string();
     }
     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
     if (!this->Makefile->IsOn("CMAKE_LINK_PCH")) {
@@ -3449,12 +3470,40 @@ std::string cmGeneratorTarget::GetPchSource(const std::string& config,
     } else {
       filename += ((language == "C") ? ".c" : ".cxx");
     }
+
     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;
 }
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 4207d65..6c36c4b 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -462,6 +462,8 @@ public:
                            const std::string& language) const;
   std::string GetPchSource(const std::string& config,
                            const std::string& language) const;
+  std::string GetPchFileObject(const std::string& config,
+                               const std::string& language);
 
   bool IsSystemIncludeDirectory(const std::string& dir,
                                 const std::string& config,
@@ -880,6 +882,7 @@ private:
   mutable std::set<std::string> LinkImplicitNullProperties;
   mutable std::map<std::string, std::string> PchHeaders;
   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,
                        std::string const& config,
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 5bbb83c..afcd69f 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -4,7 +4,9 @@
 
 #include "cmAlgorithms.h"
 #include "cmComputeLinkInformation.h"
+#include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
+#include "cmCustomCommandLines.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpressionEvaluationFile.h"
@@ -2255,23 +2257,124 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target,
     return;
   }
 
+  const char* pchReuseFrom =
+    target->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM");
+
   auto pch_sf = this->Makefile->GetOrCreateSource(
     pchSource, false, cmSourceFileLocationKind::Known);
   std::string pchFile = pchHeader;
 
   if (!this->GetGlobalGenerator()->IsXcode()) {
+    if (!pchReuseFrom) {
+      target->AddSource(pchSource, true);
+    }
+
     // Exclude the pch files from linking
     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 {
       pchFile += pchExtension;
       pch_sf->SetProperty("PCH_EXTENSION", pchExtension.c_str());
     }
 
-    target->AddSource(pchSource, true);
-
     for (auto& str : { std::ref(useOptionList), std::ref(createOptionList) }) {
       cmSystemTools::ReplaceString(str, "<PCH_HEADER>", pchHeader);
       cmSystemTools::ReplaceString(str, "<PCH_FILE>", pchFile);
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 6637e32..e65fb25 100644
--- a/Source/cmTarget.cxx
+++ b/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_OPTIONS);
   MAKE_STATIC_PROP(PRECOMPILE_HEADERS);
+  MAKE_STATIC_PROP(PRECOMPILE_HEADERS_REUSE_FROM);
   MAKE_STATIC_PROP(CUDA_PTX_COMPILATION);
   MAKE_STATIC_PROP(EXPORT_NAME);
   MAKE_STATIC_PROP(IMPORTED_GLOBAL);
@@ -1231,6 +1232,41 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
       << impl->Name << "\")\n";
     impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
     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 {
     impl->Properties.SetProperty(prop, value);
   }
@@ -1308,6 +1344,14 @@ void cmTarget::AppendProperty(const std::string& prop, const char* value,
       impl->LinkDirectoriesBacktraces.push_back(lfbt);
     }
   } 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) {
       impl->PrecompileHeadersEntries.emplace_back(value);
       cmListFileBacktrace lfbt = impl->Makefile->GetBacktrace();
diff --git a/Source/cmTargetPrecompileHeadersCommand.cxx b/Source/cmTargetPrecompileHeadersCommand.cxx
index 30cf1be..97f1bea 100644
--- a/Source/cmTargetPrecompileHeadersCommand.cxx
+++ b/Source/cmTargetPrecompileHeadersCommand.cxx
@@ -10,7 +10,7 @@
 bool cmTargetPrecompileHeadersCommand::InitialPass(
   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(
diff --git a/Source/cmTargetPropCommandBase.cxx b/Source/cmTargetPropCommandBase.cxx
index 3aa845c..4bc3125 100644
--- a/Source/cmTargetPropCommandBase.cxx
+++ b/Source/cmTargetPropCommandBase.cxx
@@ -65,6 +65,19 @@ bool cmTargetPropCommandBase::HandleArguments(
     ++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;
 
   while (argIndex < args.size()) {
diff --git a/Source/cmTargetPropCommandBase.h b/Source/cmTargetPropCommandBase.h
index 943285d..b244417 100644
--- a/Source/cmTargetPropCommandBase.h
+++ b/Source/cmTargetPropCommandBase.h
@@ -17,9 +17,10 @@ class cmTargetPropCommandBase : public cmCommand
 public:
   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,
diff --git a/Tests/RunCMake/PrecompileHeaders/PchReuseFrom.cmake b/Tests/RunCMake/PrecompileHeaders/PchReuseFrom.cmake
new file mode 100644
index 0000000..4502456
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir-build-stderr.txt b/Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir-build-stderr.txt
new file mode 100644
index 0000000..8cdcfd9
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir-build-stderr.txt
@@ -0,0 +1,2 @@
+^(|Warning #670: precompiled header file [^
+]* was not generated in this directory)$
diff --git a/Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir.cmake b/Tests/RunCMake/PrecompileHeaders/PchReuseFromSubdir.cmake
new file mode 100644
index 0000000..fff74dc
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
index fffcc47..bd3b1b8 100644
--- a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
+++ b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
@@ -16,3 +16,5 @@ run_cmake(DisabledPch)
 run_test(PchInterface)
 run_cmake(PchPrologueEpilogue)
 run_test(SkipPrecompileHeaders)
+run_test(PchReuseFrom)
+run_test(PchReuseFromSubdir)
diff --git a/Tests/RunCMake/PrecompileHeaders/empty.c b/Tests/RunCMake/PrecompileHeaders/empty.c
new file mode 100644
index 0000000..30ae1c4
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/empty.c
@@ -0,0 +1,3 @@
+void nothing()
+{
+}
diff --git a/Tests/RunCMake/PrecompileHeaders/include/foo.h b/Tests/RunCMake/PrecompileHeaders/include/foo.h
index 4a49474..fc0ae14 100644
--- a/Tests/RunCMake/PrecompileHeaders/include/foo.h
+++ b/Tests/RunCMake/PrecompileHeaders/include/foo.h
@@ -1,6 +1,6 @@
 #ifndef foo_h
 #define foo_h
 
-extern int foo();
+int foo(void);
 
 #endif
diff --git a/Tests/RunCMake/PrecompileHeaders/subdir/CMakeLists.txt b/Tests/RunCMake/PrecompileHeaders/subdir/CMakeLists.txt
new file mode 100644
index 0000000..fa926c4
--- /dev/null
+++ b/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)
-- 
cgit v0.12