Merge topic 'orkun_26080_27_06_2024'

91b2ce4a69 Autogen: Remove <target_name>_autogen_timestamp_deps target

Acked-by: Kitware Robot <kwrobot@kitware.com>
Tested-by: buildbot <buildbot@kitware.com>
Merge-request: !9631
diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim
index 5d412c2..f9efd9e 100644
--- a/Auxiliary/vim/syntax/cmake.vim
+++ b/Auxiliary/vim/syntax/cmake.vim
@@ -416,6 +416,7 @@
             \ VS_DOTNET_STARTUP_OBJECT
             \ VS_DOTNET_TARGET_FRAMEWORK_VERSION
             \ VS_DPI_AWARE
+            \ VS_FRAMEWORK_REFERENCES
             \ VS_GLOBAL_KEYWORD
             \ VS_GLOBAL_PROJECT_TYPES
             \ VS_GLOBAL_ROOTNAMESPACE
diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index 77357c0..6169038 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -26,6 +26,7 @@
                      [JOB_POOL job_pool]
                      [JOB_SERVER_AWARE <bool>]
                      [VERBATIM] [APPEND] [USES_TERMINAL]
+                     [CODEGEN]
                      [COMMAND_EXPAND_LISTS]
                      [DEPENDS_EXPLICIT_ONLY])
 
@@ -203,6 +204,18 @@
   ``${CC} "-I$<JOIN:$<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>,;-I>" foo.cc``
   to be properly expanded.
 
+``CODEGEN``
+  .. versionadded:: 3.31
+
+  Adds the custom command to a global ``codegen`` target that can be
+  used to execute the custom command while avoiding the majority of the
+  build graph.
+
+  This option is supported only by :ref:`Ninja Generators` and
+  :ref:`Makefile Generators`, and is ignored by other generators.
+  Furthermore, this option is allowed only if policy :policy:`CMP0171`
+  is set to ``NEW``.
+
 ``IMPLICIT_DEPENDS``
   Request scanning of implicit dependencies of an input file.
   The language given specifies the programming language whose
@@ -454,6 +467,25 @@
   where ``<config>`` is the build configuration, and then compile the generated
   source as part of a library.
 
+.. versionadded:: 3.31
+  Use the ``CODEGEN`` option to add a custom command's outputs to the builtin
+  ``codegen`` target.  This is useful to make generated code available for
+   static analysis without building the entire project.  For example:
+
+  .. code-block:: cmake
+
+    add_executable(someTool someTool.c)
+
+    add_custom_command(
+      OUTPUT out.c
+      COMMAND someTool -o out.c
+      CODEGEN)
+
+    add_library(myLib out.c)
+
+  A user may build the ``codegen`` target to generate ``out.c``.
+  ``someTool`` is built as dependency, but ``myLib`` is not built at all.
+
 Example: Generating Files for Multiple Targets
 """"""""""""""""""""""""""""""""""""""""""""""
 
diff --git a/Help/cpack_gen/archive.rst b/Help/cpack_gen/archive.rst
index 7f7921d..5836f91 100644
--- a/Help/cpack_gen/archive.rst
+++ b/Help/cpack_gen/archive.rst
@@ -91,14 +91,10 @@
 
   .. versionadded:: 3.18
 
-  :Default: ``1``
+  :Default: value of :variable:`CPACK_THREADS`
 
   If set to ``0``, the number of available cores on the machine will be used instead.
-  The default is ``1`` which limits compression to a single thread. Note that
-  not all compression modes support threading in all environments. Currently,
-  only the XZ compression may support it.
-
-  See also the :variable:`CPACK_THREADS` variable.
+  Note that not all compression modes support threading in all environments.
 
   .. versionadded:: 3.21
 
diff --git a/Help/cpack_gen/rpm.rst b/Help/cpack_gen/rpm.rst
index 4a2ce5f..ebd3766 100644
--- a/Help/cpack_gen/rpm.rst
+++ b/Help/cpack_gen/rpm.rst
@@ -246,9 +246,8 @@
  :Default: (system default)
 
  May be used to override RPM compression type to be used to build the
- RPM. For example some Linux distribution now default to ``lzma`` or ``xz``
- compression whereas older cannot use such RPM. Using this one can enforce
- compression type to be used.
+ RPM. For example some Linux distributions default to ``xz`` or ``zstd``.
+ Using this, one can specify a specific compression type to be used.
 
  Possible values are:
 
@@ -264,6 +263,11 @@
   ``gzip``
     GNU Gzip compression
 
+  ``zstd``
+    .. versionadded:: 3.31
+
+    Zstandard compression
+
 .. variable:: CPACK_RPM_PACKAGE_AUTOREQ
               CPACK_RPM_<component>_PACKAGE_AUTOREQ
 
diff --git a/Help/manual/cmake-cxxmodules.7.rst b/Help/manual/cmake-cxxmodules.7.rst
index 840d65f..201a415 100644
--- a/Help/manual/cmake-cxxmodules.7.rst
+++ b/Help/manual/cmake-cxxmodules.7.rst
@@ -44,7 +44,7 @@
 
 .. _`P1689R5`: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html
 
-.. note:
+.. note::
 
    CMake is focusing on correct builds before looking at performance
    improvements. There are known tactics within the chosen strategy which may
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 7155404..90c71b7 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -51,6 +51,14 @@
 to determine whether to report an error on use of deprecated macros or
 functions.
 
+Policies Introduced by CMake 3.31
+=================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0171: 'codegen' is a reserved target name. </policy/CMP0171>
+
 Policies Introduced by CMake 3.30
 =================================
 
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 673bc7c..fea661a 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -430,6 +430,7 @@
    /prop_tgt/VS_DOTNET_STARTUP_OBJECT
    /prop_tgt/VS_DOTNET_TARGET_FRAMEWORK_VERSION
    /prop_tgt/VS_DPI_AWARE
+   /prop_tgt/VS_FRAMEWORK_REFERENCES
    /prop_tgt/VS_GLOBAL_KEYWORD
    /prop_tgt/VS_GLOBAL_PROJECT_TYPES
    /prop_tgt/VS_GLOBAL_ROOTNAMESPACE
diff --git a/Help/policy/CMP0171.rst b/Help/policy/CMP0171.rst
new file mode 100644
index 0000000..c364bf4
--- /dev/null
+++ b/Help/policy/CMP0171.rst
@@ -0,0 +1,26 @@
+CMP0171
+-------
+
+.. versionadded:: 3.31
+
+``codegen`` is a reserved target name.
+
+CMake 3.30 and earlier did not reserve ``codegen`` as a builtin target name,
+leaving projects free to create their own target with that name.
+CMake 3.31 and later prefer to reserve ``codegen`` as a builtin target name
+to drive custom commands created with the ``CODEGEN`` option to
+:command:`add_custom_command`.  In order to support building the ``codegen``
+target in scripted environments, e.g., ``cmake --build . --target codegen``,
+the ``codegen`` target needs to be generated even if no custom commands
+use the ``CODEGEN`` option.  This policy provides compatibility for projects
+that have not been updated to avoid creating a target named ``codegen``.
+
+The ``OLD`` behavior of this policy allows projects to create a target
+with the name ``codegen``.  The ``NEW`` behavior halts with a fatal error
+if a target with the name ``codegen`` is created.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 3.31
+.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
+.. include:: STANDARD_ADVICE.txt
+
+.. include:: DEPRECATED.txt
diff --git a/Help/prop_tgt/VS_FRAMEWORK_REFERENCES.rst b/Help/prop_tgt/VS_FRAMEWORK_REFERENCES.rst
new file mode 100644
index 0000000..925ac68
--- /dev/null
+++ b/Help/prop_tgt/VS_FRAMEWORK_REFERENCES.rst
@@ -0,0 +1,12 @@
+VS_FRAMEWORK_REFERENCES
+-----------------------
+
+.. versionadded:: 3.31
+
+Visual Studio framework references.
+Specify a :ref:`semicolon-separated list <CMake Language Lists>` of framework references
+to be added to a generated Visual Studio project. For example:
+
+* "Microsoft.WindowsDesktop.App.WPF" for WPF applications
+* "Microsoft.WindowsDesktop.App.WindowsForms" for WinForms applications
+* "Microsoft.WindowsDesktop.App" for applications using both frameworks
diff --git a/Help/release/dev/codegen.rst b/Help/release/dev/codegen.rst
new file mode 100644
index 0000000..e8b61c9
--- /dev/null
+++ b/Help/release/dev/codegen.rst
@@ -0,0 +1,10 @@
+codegen
+-------
+
+* The :ref:`Ninja Generators` and :ref:`Makefile Generators` now produce
+  a ``codegen`` build target.  See policy :policy:`CMP0171`.  It drives a
+  subset of the build graph sufficient to run custom commands created with
+  :command:`add_custom_command`'s new ``CODEGEN`` option.
+
+* The :command:`add_custom_command` command gained a ``CODEGEN`` option
+  to mark a custom commands outputs as dependencies of a ``codegen`` target.
diff --git a/Help/release/dev/cpack-rpm-zstd.rst b/Help/release/dev/cpack-rpm-zstd.rst
new file mode 100644
index 0000000..828d5f6
--- /dev/null
+++ b/Help/release/dev/cpack-rpm-zstd.rst
@@ -0,0 +1,5 @@
+cpack-rpm-zstd
+--------------
+
+* The :cpack_gen:`CPack RPM Generator` gained support for ``zstd`` as a
+  :variable:`CPACK_RPM_COMPRESSION_TYPE` value.
diff --git a/Help/release/dev/vs-framework-references.rst b/Help/release/dev/vs-framework-references.rst
new file mode 100644
index 0000000..1f50b50
--- /dev/null
+++ b/Help/release/dev/vs-framework-references.rst
@@ -0,0 +1,5 @@
+vs-framework-references
+-----------------------
+
+* The :prop_tgt:`VS_FRAMEWORK_REFERENCES` target property was added
+  to tell :ref:`Visual Studio Generators` to add framework references.
diff --git a/Modules/FindImageMagick.cmake b/Modules/FindImageMagick.cmake
index 6baf471..50f6e46 100644
--- a/Modules/FindImageMagick.cmake
+++ b/Modules/FindImageMagick.cmake
@@ -93,6 +93,8 @@
   Compile options of <component>.
 
 ``ImageMagick_<component>_LIBRARIES``
+  .. versionadded:: 3.31
+
   Full path to <component> libraries.
 
 
@@ -168,6 +170,12 @@
     set(ImageMagick_${component}_INCLUDE_DIRS
       ${ImageMagick_${component}_INCLUDE_DIRS} PARENT_SCOPE)
 
+    set(ImageMagick_${component}_LIBRARIES
+      ${ImageMagick_${component}_LIBRARY}
+      )
+    set(ImageMagick_${component}_LIBRARIES
+      ${ImageMagick_${component}_LIBRARIES} PARENT_SCOPE)
+
     set(ImageMagick_${component}_COMPILE_OPTIONS ${PC_${component}_CFLAGS_OTHER})
 
     # Add the per-component include directories to the full include dirs.
@@ -185,11 +193,13 @@
       )
     set(ImageMagick_COMPILE_OPTIONS ${ImageMagick_COMPILE_OPTIONS} PARENT_SCOPE)
 
-    add_library(ImageMagick::${component} UNKNOWN IMPORTED)
-    set_target_properties(ImageMagick::${component} PROPERTIES
-      INTERFACE_INCLUDE_DIRECTORIES "${ImageMagick_${component}_INCLUDE_DIRS}"
-      INTERFACE_COMPILE_OPTIONS "${ImageMagick_${component}_COMPILE_OPTIONS}"
-      IMPORTED_LOCATION "${ImageMagick_${component}_LIBRARY}")
+    if(NOT TARGET ImageMagick::${component})
+      add_library(ImageMagick::${component} UNKNOWN IMPORTED)
+      set_target_properties(ImageMagick::${component} PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "${ImageMagick_${component}_INCLUDE_DIRS}"
+        INTERFACE_COMPILE_OPTIONS "${ImageMagick_${component}_COMPILE_OPTIONS}"
+        IMPORTED_LOCATION "${ImageMagick_${component}_LIBRARY}")
+    endif()
   endif()
 endfunction()
 
diff --git a/Modules/Internal/CPack/CPackRPM.cmake b/Modules/Internal/CPack/CPackRPM.cmake
index 4a741ee..e3f8f5e 100644
--- a/Modules/Internal/CPack/CPackRPM.cmake
+++ b/Modules/Internal/CPack/CPackRPM.cmake
@@ -1034,27 +1034,32 @@
   # CPACK_RPM_COMPRESSION_TYPE
   #
   if (CPACK_RPM_COMPRESSION_TYPE)
-     if(CPACK_RPM_PACKAGE_DEBUG)
-       message("CPackRPM:Debug: User Specified RPM compression type: ${CPACK_RPM_COMPRESSION_TYPE}")
-     endif()
-     if(CPACK_RPM_COMPRESSION_TYPE STREQUAL "lzma")
-       set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.lzdio")
-     endif()
-     if(CPACK_RPM_COMPRESSION_TYPE STREQUAL "xz")
-       if(CPACK_THREADS GREATER "0")
-         set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w7T${CPACK_THREADS}.xzdio")
-       else()
-         set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w7T.xzdio")
-       endif()
-     endif()
-     if(CPACK_RPM_COMPRESSION_TYPE STREQUAL "bzip2")
-       set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.bzdio")
-     endif()
-     if(CPACK_RPM_COMPRESSION_TYPE STREQUAL "gzip")
-       set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.gzdio")
-     endif()
+    if(CPACK_RPM_PACKAGE_DEBUG)
+      message("CPackRPM:Debug: User Specified RPM compression type: ${CPACK_RPM_COMPRESSION_TYPE}")
+    endif()
+    if(CPACK_RPM_COMPRESSION_TYPE STREQUAL "lzma")
+      set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.lzdio")
+    elseif(CPACK_RPM_COMPRESSION_TYPE STREQUAL "xz")
+      if(CPACK_THREADS GREATER "0")
+        set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w7T${CPACK_THREADS}.xzdio")
+      else()
+        set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w7T.xzdio")
+      endif()
+    elseif(CPACK_RPM_COMPRESSION_TYPE STREQUAL "bzip2")
+      set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.bzdio")
+    elseif(CPACK_RPM_COMPRESSION_TYPE STREQUAL "gzip")
+      set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.gzdio")
+    elseif(CPACK_RPM_COMPRESSION_TYPE STREQUAL "zstd")
+      if(CPACK_THREADS GREATER "0")
+        set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w19T${CPACK_THREADS}.zstdio")
+      else()
+        set(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w19T0.zstdio")
+      endif()
+    else()
+      message(FATAL_ERROR "Specified CPACK_RPM_COMPRESSION_TYPE value is not supported: ${CPACK_RPM_COMPRESSION_TYPE}")
+    endif()
   else()
-     set(CPACK_RPM_COMPRESSION_TYPE_TMP "")
+    set(CPACK_RPM_COMPRESSION_TYPE_TMP "")
   endif()
 
   if(NOT CPACK_RPM_PACKAGE_SOURCES)
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 01d1661..1b9b456 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 30)
-set(CMake_VERSION_PATCH 20240627)
+set(CMake_VERSION_PATCH 20240702)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx
index d943011..d5adba7 100644
--- a/Source/cmAddCustomCommandCommand.cxx
+++ b/Source/cmAddCustomCommandCommand.cxx
@@ -53,6 +53,7 @@
   bool command_expand_lists = false;
   bool depends_explicit_only =
     mf.IsOn("CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY");
+  bool codegen = false;
   std::string implicit_depends_lang;
   cmImplicitDependsList implicit_depends;
 
@@ -111,6 +112,7 @@
   MAKE_STATIC_KEYWORD(VERBATIM);
   MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);
   MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY);
+  MAKE_STATIC_KEYWORD(CODEGEN);
 #undef MAKE_STATIC_KEYWORD
   static std::unordered_set<std::string> const keywords{
     keyAPPEND,
@@ -135,7 +137,8 @@
     keyUSES_TERMINAL,
     keyVERBATIM,
     keyWORKING_DIRECTORY,
-    keyDEPENDS_EXPLICIT_ONLY
+    keyDEPENDS_EXPLICIT_ONLY,
+    keyCODEGEN
   };
 
   for (std::string const& copy : args) {
@@ -166,6 +169,8 @@
         command_expand_lists = true;
       } else if (copy == keyDEPENDS_EXPLICIT_ONLY) {
         depends_explicit_only = true;
+      } else if (copy == keyCODEGEN) {
+        codegen = true;
       } else if (copy == keyTARGET) {
         doing = doing_target;
       } else if (copy == keyARGS) {
@@ -322,6 +327,28 @@
     return false;
   }
 
+  if (codegen) {
+    if (output.empty()) {
+      status.SetError("CODEGEN requires at least 1 OUTPUT.");
+      return false;
+    }
+
+    if (append) {
+      status.SetError("CODEGEN may not be used with APPEND.");
+      return false;
+    }
+
+    if (!implicit_depends.empty()) {
+      status.SetError("CODEGEN is not compatible with IMPLICIT_DEPENDS.");
+      return false;
+    }
+
+    if (mf.GetPolicyStatus(cmPolicies::CMP0171) != cmPolicies::NEW) {
+      status.SetError("CODEGEN option requires policy CMP0171 be set to NEW!");
+      return false;
+    }
+  }
+
   // Check for an append request.
   if (append) {
     mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends,
@@ -355,6 +382,7 @@
     cc->SetOutputs(output);
     cc->SetMainDependency(main_dependency);
     cc->SetDepends(depends);
+    cc->SetCodegen(codegen);
     cc->SetImplicitDepends(implicit_depends);
     mf.AddCustomCommandToOutput(std::move(cc));
   } else {
diff --git a/Source/cmAddDependenciesCommand.cxx b/Source/cmAddDependenciesCommand.cxx
index 2d55a5a..aa04018 100644
--- a/Source/cmAddDependenciesCommand.cxx
+++ b/Source/cmAddDependenciesCommand.cxx
@@ -30,6 +30,7 @@
     // skip over target_name
     for (std::string const& arg : cmMakeRange(args).advance(1)) {
       target->AddUtility(arg, false, &mf);
+      target->AddCodegenDependency(arg);
     }
   } else {
     mf.IssueMessage(
diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h
index 167e601..6f63d0a 100644
--- a/Source/cmCustomCommand.h
+++ b/Source/cmCustomCommand.h
@@ -132,6 +132,10 @@
   const std::string& GetTarget() const;
   void SetTarget(const std::string& target);
 
+  /** Record if the custom command can be used for code generation. */
+  bool GetCodegen() const { return Codegen; }
+  void SetCodegen(bool b) { Codegen = b; }
+
 private:
   std::vector<std::string> Outputs;
   std::vector<std::string> Byproducts;
@@ -153,6 +157,7 @@
   bool StdPipesUTF8 = false;
   bool HasMainDependency_ = false;
   bool DependsExplicitOnly = false;
+  bool Codegen = false;
 
 // Policies are NEW for synthesized custom commands, and set by cmMakefile for
 // user-created custom commands.
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 019271b..bf2f0fc 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1398,6 +1398,8 @@
     }
   }
 
+  this->ReserveGlobalTargetCodegen();
+
   // update the cache entry for the number of local generators, this is used
   // for progress
   this->GetCMakeInstance()->AddCacheEntry(
@@ -2914,6 +2916,53 @@
   targets.push_back(std::move(gti));
 }
 
+void cmGlobalGenerator::ReserveGlobalTargetCodegen()
+{
+  // Read the policy value at the end of the top-level CMakeLists.txt file
+  // since it's a global policy that affects the whole project.
+  auto& mf = this->Makefiles[0];
+  const auto policyStatus = mf->GetPolicyStatus(cmPolicies::CMP0171);
+
+  this->AllowGlobalTargetCodegen = (policyStatus == cmPolicies::NEW);
+
+  cmTarget* tgt = this->FindTarget("codegen");
+  if (!tgt) {
+    return;
+  }
+
+  MessageType messageType = MessageType::AUTHOR_WARNING;
+  std::ostringstream e;
+  bool issueMessage = false;
+  switch (policyStatus) {
+    case cmPolicies::WARN:
+      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0171) << "\n";
+      issueMessage = true;
+      CM_FALLTHROUGH;
+    case cmPolicies::OLD:
+      break;
+    case cmPolicies::NEW:
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+      issueMessage = true;
+      messageType = MessageType::FATAL_ERROR;
+      break;
+  }
+  if (issueMessage) {
+    e << "The target name \"codegen\" is reserved.";
+    this->GetCMakeInstance()->IssueMessage(messageType, e.str(),
+                                           tgt->GetBacktrace());
+    if (messageType == MessageType::FATAL_ERROR) {
+      cmSystemTools::SetFatalErrorOccurred();
+      return;
+    }
+  }
+}
+
+bool cmGlobalGenerator::CheckCMP0171() const
+{
+  return this->AllowGlobalTargetCodegen;
+}
+
 void cmGlobalGenerator::AddGlobalTarget_EditCache(
   std::vector<GlobalTargetInfo>& targets) const
 {
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 1ca02d9..33c9889 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -653,6 +653,8 @@
 
   virtual std::string& EncodeLiteral(std::string& lit) { return lit; }
 
+  bool CheckCMP0171() const;
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
@@ -719,6 +721,8 @@
   void AddGlobalTarget_Install(std::vector<GlobalTargetInfo>& targets);
   void CreateGlobalTarget(GlobalTargetInfo const& gti, cmMakefile* mf);
 
+  void ReserveGlobalTargetCodegen();
+
   std::string FindMakeProgramFile;
   std::string ConfiguredFilesPath;
   cmake* CMakeInstance;
@@ -891,4 +895,5 @@
   bool ToolSupportsColor;
   bool InstallTargetEnabled;
   bool ConfigureDoneCMP0026AndCMP0024;
+  bool AllowGlobalTargetCodegen;
 };
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index 96c8f25..7d62fa8 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -25,6 +25,7 @@
 
 #include "cmsys/FStream.hxx"
 
+#include "cmCustomCommand.h"
 #include "cmCxxModuleMapper.h"
 #include "cmDyndepCollation.h"
 #include "cmFortranParser.h"
@@ -43,6 +44,7 @@
 #include "cmOutputConverter.h"
 #include "cmRange.h"
 #include "cmScanDepFormat.h"
+#include "cmSourceFile.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
@@ -1627,6 +1629,90 @@
   std::map<std::string, DirectoryTarget> dirTargets =
     this->ComputeDirectoryTargets();
 
+  // Codegen target
+  if (this->CheckCMP0171()) {
+    for (auto const& it : dirTargets) {
+      cmNinjaBuild build("phony");
+      cmGlobalNinjaGenerator::WriteDivider(os);
+      std::string const& currentBinaryDir = it.first;
+      DirectoryTarget const& dt = it.second;
+      std::vector<std::string> configs =
+        dt.LG->GetMakefile()->GetGeneratorConfigs(
+          cmMakefile::IncludeEmptyConfig);
+
+      // Setup target
+      cmNinjaDeps configDeps;
+      build.Comment = cmStrCat("Folder: ", currentBinaryDir);
+      build.Outputs.emplace_back();
+      std::string const buildDirAllTarget =
+        this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/codegen"));
+
+      cmNinjaDeps& explicitDeps = build.ExplicitDeps;
+
+      for (auto const& config : configs) {
+        explicitDeps.clear();
+
+        for (DirectoryTarget::Target const& t : dt.Targets) {
+          if (this->IsExcludedFromAllInConfig(t, config)) {
+            continue;
+          }
+
+          std::vector<cmSourceFile const*> customCommandSources;
+          t.GT->GetCustomCommands(customCommandSources, config);
+          for (cmSourceFile const* sf : customCommandSources) {
+            cmCustomCommand const* cc = sf->GetCustomCommand();
+            if (cc->GetCodegen()) {
+              auto const& outputs = cc->GetOutputs();
+
+              std::transform(outputs.begin(), outputs.end(),
+                             std::back_inserter(explicitDeps),
+                             this->MapToNinjaPath());
+            }
+          }
+        }
+
+        build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
+        // Write target
+        this->WriteBuild(this->EnableCrossConfigBuild() &&
+                             this->CrossConfigs.count(config)
+                           ? os
+                           : *this->GetImplFileStream(config),
+                         build);
+      }
+
+      // Add shortcut target
+      if (this->IsMultiConfig()) {
+        for (auto const& config : configs) {
+          build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
+          build.Outputs.front() = buildDirAllTarget;
+          this->WriteBuild(*this->GetConfigFileStream(config), build);
+        }
+
+        if (!this->DefaultFileConfig.empty()) {
+          build.ExplicitDeps.clear();
+          for (auto const& config : this->DefaultConfigs) {
+            build.ExplicitDeps.push_back(
+              this->BuildAlias(buildDirAllTarget, config));
+          }
+          build.Outputs.front() = buildDirAllTarget;
+          this->WriteBuild(*this->GetDefaultFileStream(), build);
+        }
+      }
+
+      // Add target for all configs
+      if (this->EnableCrossConfigBuild()) {
+        build.ExplicitDeps.clear();
+        for (auto const& config : this->CrossConfigs) {
+          build.ExplicitDeps.push_back(
+            this->BuildAlias(buildDirAllTarget, config));
+        }
+        build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "codegen");
+        this->WriteBuild(os, build);
+      }
+    }
+  }
+
+  // All target
   for (auto const& it : dirTargets) {
     cmNinjaBuild build("phony");
     cmGlobalNinjaGenerator::WriteDivider(os);
diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx
index 56748a5..38cc4f6 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.cxx
+++ b/Source/cmGlobalUnixMakefileGenerator3.cxx
@@ -23,6 +23,7 @@
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTarget.h"
 #include "cmTargetDepend.h"
 #include "cmValue.h"
 #include "cmake.h"
@@ -438,6 +439,10 @@
   // Write directory-level rules for "all".
   this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "all", true, false);
 
+  // Write directory-level rules for "codegen".
+  this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "codegen", true,
+                            false);
+
   // Write directory-level rules for "preinstall".
   this->WriteDirectoryRule2(ruleFileStream, rootLG, dt, "preinstall", true,
                             true);
@@ -765,6 +770,17 @@
                              depends, commands, true);
       }
 
+      // add the codegen rule
+      localName = lg.GetRelativeTargetDirectory(gtarget.get());
+      depends.clear();
+      commands.clear();
+      makeTargetName = cmStrCat(localName, "/codegen");
+      commands.push_back(
+        lg.GetRecursiveMakeCall(makefileName, makeTargetName));
+      this->AppendCodegenTargetDepends(depends, gtarget.get());
+      rootLG.WriteMakeRule(ruleFileStream, "codegen rule for target.",
+                           makeTargetName, depends, commands, true);
+
       // add the clean rule
       localName = lg.GetRelativeTargetDirectory(gtarget.get());
       makeTargetName = cmStrCat(localName, "/clean");
@@ -893,6 +909,29 @@
   }
 }
 
+void cmGlobalUnixMakefileGenerator3::AppendCodegenTargetDepends(
+  std::vector<std::string>& depends, cmGeneratorTarget* target)
+{
+  const std::set<std::string>& codegen_depends =
+    target->Target->GetCodegenDeps();
+
+  for (cmTargetDepend const& i : this->GetTargetDirectDepends(target)) {
+    // Create the target-level dependency.
+    cmGeneratorTarget const* dep = i;
+    if (!dep->IsInBuildSystem()) {
+      continue;
+    }
+    if (codegen_depends.find(dep->GetName()) != codegen_depends.end()) {
+      cmLocalUnixMakefileGenerator3* lg3 =
+        static_cast<cmLocalUnixMakefileGenerator3*>(dep->GetLocalGenerator());
+      std::string tgtName = cmStrCat(
+        lg3->GetRelativeTargetDirectory(const_cast<cmGeneratorTarget*>(dep)),
+        "/all");
+      depends.push_back(tgtName);
+    }
+  }
+}
+
 void cmGlobalUnixMakefileGenerator3::WriteHelpRule(
   std::ostream& ruleFileStream, cmLocalUnixMakefileGenerator3* lg)
 {
diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h
index ee78351..d4fcf88 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.h
+++ b/Source/cmGlobalUnixMakefileGenerator3.h
@@ -223,6 +223,9 @@
   void AppendGlobalTargetDepends(std::vector<std::string>& depends,
                                  cmGeneratorTarget* target);
 
+  void AppendCodegenTargetDepends(std::vector<std::string>& depends,
+                                  cmGeneratorTarget* target);
+
   // Target name hooks for superclass.
   const char* GetAllTargetName() const override { return "all"; }
   const char* GetInstallTargetName() const override { return "install"; }
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index aa3f28e..e6d56d3 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -4626,6 +4626,12 @@
     if (cmCustomCommand* cc = sf->GetCustomCommand()) {
       cc->AppendCommands(commandLines);
       cc->AppendDepends(depends);
+      if (cc->GetCodegen() && !implicit_depends.empty()) {
+        lg.GetCMakeInstance()->IssueMessage(
+          MessageType::FATAL_ERROR,
+          "Cannot append IMPLICIT_DEPENDS to existing CODEGEN custom "
+          "command.");
+      }
       cc->AppendImplicitDepends(implicit_depends);
       return;
     }
diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx
index 7def1fe..4a776b4 100644
--- a/Source/cmLocalUnixMakefileGenerator3.cxx
+++ b/Source/cmLocalUnixMakefileGenerator3.cxx
@@ -1763,6 +1763,21 @@
   this->WriteMakeRule(ruleFileStream, "The main all target", "all", depends,
                       commands, true);
 
+  // Write the codegen rule.
+  if (this->GetGlobalGenerator()->CheckCMP0171()) {
+    recursiveTarget = cmStrCat(this->GetCurrentBinaryDirectory(), "/codegen");
+    depends.clear();
+    commands.clear();
+    if (regenerate) {
+      depends.emplace_back("cmake_check_build_system");
+    }
+    commands.push_back(this->GetRecursiveMakeCall(mf2Dir, recursiveTarget));
+    AppendEcho(commands, "Finished generating code",
+               cmLocalUnixMakefileGenerator3::EchoColor::EchoGenerate);
+    this->WriteMakeRule(ruleFileStream, "The main codegen target", "codegen",
+                        depends, commands, true);
+  }
+
   // Write the clean rule.
   recursiveTarget = cmStrCat(this->GetCurrentBinaryDirectory(), "/clean");
   commands.clear();
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index d5c50bc..9ff0a4a 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -251,6 +251,8 @@
   std::vector<cmSourceFile const*> customCommands;
   this->GeneratorTarget->GetCustomCommands(customCommands,
                                            this->GetConfigName());
+  std::vector<std::string> codegen_depends;
+  codegen_depends.reserve(customCommands.size());
   for (cmSourceFile const* sf : customCommands) {
     if (this->CMP0113New &&
         !this->LocalGenerator->GetCommandsVisited(this->GeneratorTarget)
@@ -273,6 +275,33 @@
           this->LocalGenerator->MaybeRelativeToCurBinDir(byproduct));
       }
     }
+
+    if (ccg.GetCC().GetCodegen()) {
+      std::string const& output = ccg.GetOutputs().front();
+
+      // We always attach the actual commands to the first output.
+      codegen_depends.emplace_back(output);
+    }
+  }
+
+  // Some make tools need a special dependency for an empty rule.
+  if (codegen_depends.empty()) {
+    std::string hack = this->GlobalGenerator->GetEmptyRuleHackDepends();
+    if (!hack.empty()) {
+      codegen_depends.emplace_back(std::move(hack));
+    }
+  }
+
+  // Construct the codegen target.
+  {
+    std::string const codegenTarget = cmStrCat(
+      this->LocalGenerator->GetRelativeTargetDirectory(this->GeneratorTarget),
+      "/codegen");
+
+    // Write the rule.
+    this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, nullptr,
+                                        codegenTarget, codegen_depends, {},
+                                        true);
   }
 
   // Add byproducts from build events to the clean rules
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index d893c44..85f3293 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -525,7 +525,9 @@
          3, 30, 0, cmPolicies::WARN)                                          \
   SELECT(POLICY, CMP0170,                                                     \
          "FETCHCONTENT_FULLY_DISCONNECTED requirements are enforced.", 3, 30, \
-         0, cmPolicies::WARN)
+         0, cmPolicies::WARN)                                                 \
+  SELECT(POLICY, CMP0171, "'codegen' is a reserved target name.", 3, 31, 0,   \
+         cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx
index 8e880fd..d646cce 100644
--- a/Source/cmQtAutoGenInitializer.cxx
+++ b/Source/cmQtAutoGenInitializer.cxx
@@ -1647,13 +1647,10 @@
       sf->SetProperty("SKIP_UNITY_BUILD_INCLUSION", "On");
     }
 
-    std::vector<std::string> ccOutput;
-    ccOutput.push_back(qrc.OutputFile);
+    std::vector<std::string> ccOutput{ qrc.OutputFile };
 
-    std::vector<std::string> ccDepends;
     // Add the .qrc and info file to the custom command dependencies
-    ccDepends.push_back(qrc.QrcFile);
-    ccDepends.push_back(qrc.InfoFile);
+    std::vector<std::string> ccDepends{ qrc.QrcFile, qrc.InfoFile };
 
     cmCustomCommandLines commandLines;
     AddCMakeProcessToCommandLines(qrc.InfoFile, "cmake_autorcc", commandLines);
@@ -1676,13 +1673,43 @@
         if (!qrc.Unique) {
           ccName += cmStrCat('_', qrc.QrcPathChecksum);
         }
+        cmTarget* autoRccTarget = nullptr;
+        // When CMAKE_GLOBAL_AUTORCC_TARGET is ON and qrc is not generated,
+        // Add generate a timestamp file and a custom command to touch it.
+        // This will ensure that the global autorcc target is run only when the
+        // qrc file changes.
+        if (!qrc.Generated && this->Rcc.GlobalTarget) {
+          cm::string_view const timestampFileName = "global_rcc_timestamp";
+          auto const outputFile =
+            cmStrCat(this->Dir.Build, "/", timestampFileName);
+          commandLines.push_back(cmMakeCommandLine(
+            { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile }));
+          cc->SetByproducts(ccOutput);
+          cc->SetDepends(ccDepends);
+          cc->SetEscapeOldStyle(false);
+          cc->SetOutputs(outputFile);
+          cc->SetCommandLines(commandLines);
+          this->LocalGen->AddCustomCommandToOutput(std::move(cc));
+          this->AddGeneratedSource(outputFile, this->Rcc);
+          ccDepends.clear();
+          ccDepends.push_back(outputFile);
 
-        cc->SetByproducts(ccOutput);
-        cc->SetDepends(ccDepends);
-        cc->SetEscapeOldStyle(false);
-        cmTarget* autoRccTarget =
-          this->LocalGen->AddUtilityCommand(ccName, true, std::move(cc));
+          auto ccRccTarget = cm::make_unique<cmCustomCommand>();
+          ccRccTarget->SetWorkingDirectory(this->Dir.Work.c_str());
+          ccRccTarget->SetComment(ccComment.c_str());
+          ccRccTarget->SetStdPipesUTF8(true);
+          ccRccTarget->SetDepends(ccDepends);
+          ccRccTarget->SetEscapeOldStyle(false);
 
+          autoRccTarget = this->LocalGen->AddUtilityCommand(
+            ccName, true, std::move(ccRccTarget));
+        } else {
+          cc->SetByproducts(ccOutput);
+          cc->SetDepends(ccDepends);
+          cc->SetEscapeOldStyle(false);
+          autoRccTarget =
+            this->LocalGen->AddUtilityCommand(ccName, true, std::move(cc));
+        }
         // Create autogen generator target
         this->LocalGen->AddGeneratorTarget(
           cm::make_unique<cmGeneratorTarget>(autoRccTarget, this->LocalGen));
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 1284130..a6e6984 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -657,6 +657,7 @@
   bool PerConfig;
   cmTarget::Visibility TargetVisibility;
   std::set<BT<std::pair<std::string, bool>>> Utilities;
+  std::set<std::string> CodegenDependencies;
   std::vector<cmCustomCommand> PreBuildCommands;
   std::vector<cmCustomCommand> PreLinkCommands;
   std::vector<cmCustomCommand> PostBuildCommands;
@@ -1238,6 +1239,16 @@
   this->impl->Utilities.emplace(std::move(util));
 }
 
+void cmTarget::AddCodegenDependency(std::string const& name)
+{
+  this->impl->CodegenDependencies.emplace(name);
+}
+
+std::set<std::string> const& cmTarget::GetCodegenDeps() const
+{
+  return this->impl->CodegenDependencies;
+}
+
 std::set<BT<std::pair<std::string, bool>>> const& cmTarget::GetUtilities()
   const
 {
diff --git a/Source/cmTarget.h b/Source/cmTarget.h
index 385dfe7..220ac13 100644
--- a/Source/cmTarget.h
+++ b/Source/cmTarget.h
@@ -173,6 +173,11 @@
   void AddUtility(std::string const& name, bool cross,
                   cmMakefile const* mf = nullptr);
   void AddUtility(BT<std::pair<std::string, bool>> util);
+
+  void AddCodegenDependency(std::string const& name);
+
+  std::set<std::string> const& GetCodegenDeps() const;
+
   //! Get the utilities used by this target
   std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const;
 
diff --git a/Source/cmTargetTraceDependencies.cxx b/Source/cmTargetTraceDependencies.cxx
index cc91a42..f14cfbf 100644
--- a/Source/cmTargetTraceDependencies.cxx
+++ b/Source/cmTargetTraceDependencies.cxx
@@ -7,10 +7,12 @@
 
 #include <cmext/algorithm>
 
+#include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmList.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmSourceFile.h"
@@ -132,6 +134,8 @@
     // The name is a byproduct of a utility target or a PRE_BUILD, PRE_LINK, or
     // POST_BUILD command.
     this->GeneratorTarget->Target->AddUtility(t->GetName(), false);
+
+    this->GeneratorTarget->Target->AddCodegenDependency(t->GetName());
   }
   if (cmSourceFile* sf = i->second.Source) {
     // For now only follow the dependency if the source file is not a
@@ -213,6 +217,11 @@
       // Collect target-level dependencies referenced in command lines.
       for (auto const& util : ccg.GetUtilities()) {
         this->GeneratorTarget->Target->AddUtility(util);
+
+        if (ccg.GetCC().GetCodegen()) {
+          this->GeneratorTarget->Target->AddCodegenDependency(
+            util.Value.first);
+        }
       }
 
       // Collect file-level dependencies referenced in DEPENDS.
@@ -226,6 +235,8 @@
       // The dependency does not name a target and may be a file we
       // know how to generate.  Queue it.
       this->FollowName(dep);
+    } else {
+      this->GeneratorTarget->Target->AddCodegenDependency(dep);
     }
   }
 }
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 0fb8bae..eb14e24 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -813,6 +813,7 @@
     this->WriteCustomCommands(e0);
     this->WriteAllSources(e0);
     this->WriteDotNetReferences(e0);
+    this->WriteFrameworkReferences(e0);
     this->WritePackageReferences(e0);
     this->WriteImports(e0);
     this->WriteEmbeddedResourceGroup(e0);
@@ -1187,6 +1188,21 @@
   this->WriteDotNetReferenceCustomTags(e2, ref);
 }
 
+void cmVisualStudio10TargetGenerator::WriteFrameworkReferences(Elem& e0)
+{
+  cmList references;
+  if (cmValue vsFrameworkReferences =
+        this->GeneratorTarget->GetProperty("VS_FRAMEWORK_REFERENCES")) {
+    references.assign(*vsFrameworkReferences);
+  }
+
+  Elem e1(e0, "ItemGroup");
+  for (auto const& ref : references) {
+    Elem e2(e1, "FrameworkReference");
+    e2.Attribute("Include", ref);
+  }
+}
+
 void cmVisualStudio10TargetGenerator::WriteImports(Elem& e0)
 {
   cmValue imports =
diff --git a/Source/cmVisualStudio10TargetGenerator.h b/Source/cmVisualStudio10TargetGenerator.h
index 056f426..53eb1af 100644
--- a/Source/cmVisualStudio10TargetGenerator.h
+++ b/Source/cmVisualStudio10TargetGenerator.h
@@ -91,6 +91,7 @@
   void WriteDotNetReference(Elem& e1, std::string const& ref,
                             std::string const& hint,
                             std::string const& config);
+  void WriteFrameworkReferences(Elem& e0);
   void WriteDotNetDocumentationFile(Elem& e0);
   void WriteImports(Elem& e0);
   void WriteDotNetReferenceCustomTags(Elem& e2, std::string const& ref);
diff --git a/Tests/RunCMake/Autogen_5/RunCMakeTest.cmake b/Tests/RunCMake/Autogen_5/RunCMakeTest.cmake
index 8060ec4..1f6f7d4 100644
--- a/Tests/RunCMake/Autogen_5/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Autogen_5/RunCMakeTest.cmake
@@ -27,5 +27,21 @@
         endblock()
       endforeach()
     endif()
+    if (RunCMake_GENERATOR MATCHES "Ninja")
+      block()
+        set(RunCMake_TEST_BINARY_DIR
+          ${RunCMake_BINARY_DIR}/RccGlobalAutoRcc-build)
+        run_cmake_with_options(RccExample ${RunCMake_TEST_OPTIONS}
+          -DCMAKE_GLOBAL_AUTORCC_TARGET=ON)
+        set(RunCMake_TEST_NO_CLEAN 1)
+        set(RunCMake_TEST_VARIANT_DESCRIPTION "-First-build")
+        run_cmake_command(RccGlobalAutoRcc-build ${CMAKE_COMMAND}
+          --build . --config Debug)
+        set(RunCMake_TEST_VARIANT_DESCRIPTION "-Second-build-nothing-to-do")
+        set(RunCMake_TEST_NOT_EXPECT_stdout "Automatic RCC for data.qrc")
+        run_cmake_command(RccGlobalAutoRcc-build ${CMAKE_COMMAND}
+          --build . --config Debug)
+      endblock()
+    endif()
   endif()
 endif ()
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt b/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt
new file mode 100644
index 0000000..155ddd2
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at CMP0171-NEW\.cmake:[0-9]+ \(add_custom_target\):
+  The target name "codegen" is reserved\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake b/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake
new file mode 100644
index 0000000..547f6c0
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-NEW.cmake
@@ -0,0 +1,6 @@
+# codegen is now a reserved name and this will cause an error since the policy is new.
+add_custom_target(codegen
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt b/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt
new file mode 100644
index 0000000..1ae3318
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at CMP0171-OLD\.cmake:[0-9]+ \(add_custom_command\):
+  add_custom_command CODEGEN option requires policy CMP0171 be set to NEW\!
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake b/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake
new file mode 100644
index 0000000..c34b3fb
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-OLD.cmake
@@ -0,0 +1,9 @@
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  # This will cause an error since the CODEGEN option
+  # requires that CMP0171 is set to NEW
+  CODEGEN
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt b/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt
new file mode 100644
index 0000000..ee79553
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-WARN-stderr.txt
@@ -0,0 +1,9 @@
+CMake Warning \(dev\) at CMP0171-WARN\.cmake:[0-9]+ \(add_custom_target\):
+  Policy CMP0171 is not set: 'codegen' is a reserved target name\.  Run "cmake
+  --help-policy CMP0171" for policy details.  Use the cmake_policy command to
+  set the policy and suppress this warning\.
+
+  The target name "codegen" is reserved\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
diff --git a/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake b/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake
new file mode 100644
index 0000000..6d61723
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-WARN.cmake
@@ -0,0 +1,6 @@
+# CMake should warn the user if they have a target named codegen.
+add_custom_target(codegen
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt b/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-codegen-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake b/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMP0171-codegen.cmake
diff --git a/Tests/RunCMake/CMP0171/CMakeLists.txt b/Tests/RunCMake/CMP0171/CMakeLists.txt
new file mode 100644
index 0000000..1a5755c
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.29)
+project(${RunCMake_TEST} LANGUAGES C)
+
+include(${RunCMake_TEST}.cmake)
+
+enable_testing()
diff --git a/Tests/RunCMake/CMP0171/RunCMakeTest.cmake b/Tests/RunCMake/CMP0171/RunCMakeTest.cmake
new file mode 100644
index 0000000..75941d7
--- /dev/null
+++ b/Tests/RunCMake/CMP0171/RunCMakeTest.cmake
@@ -0,0 +1,18 @@
+include(RunCMake)
+
+run_cmake("CMP0171-WARN")
+
+run_cmake_with_options(CMP0171-OLD "-DCMAKE_POLICY_DEFAULT_CMP0171=OLD")
+
+run_cmake_with_options(CMP0171-NEW "-DCMAKE_POLICY_DEFAULT_CMP0171=NEW")
+
+# The entire point of this test is to ensure the codegen target is not created
+# unintentionally. It can only be created if CMP0171 is NEW.
+block()
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0171-codegen-build)
+  run_cmake(CMP0171-codegen)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OUTPUT_MERGE 1)
+  # This command will fail with either 1 or 2 depending.
+  run_cmake_command(CMP0171-codegen-build ${CMAKE_COMMAND} --build . --config Debug --target codegen)
+endblock()
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index bfa59d6..db5ef96 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -180,6 +180,7 @@
 add_RunCMake_test(CMP0165)
 add_RunCMake_test(CMP0169)
 add_RunCMake_test(CMP0170)
+add_RunCMake_test(CMP0171)
 
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
@@ -1228,7 +1229,8 @@
 add_RunCMake_test(VerifyHeaderSets)
 add_RunCMake_test(set_tests_properties)
 
-if(${CMAKE_GENERATOR} MATCHES "Make|Ninja")
+if(CMAKE_GENERATOR MATCHES "Make|Ninja")
+  add_RunCMake_test(Codegen)
   add_RunCMake_test(TransformDepfile)
 endif()
 
diff --git a/Tests/RunCMake/Codegen/CMakeLists.txt b/Tests/RunCMake/Codegen/CMakeLists.txt
new file mode 100644
index 0000000..f65150d
--- /dev/null
+++ b/Tests/RunCMake/Codegen/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.29)
+project(${RunCMake_TEST} LANGUAGES C)
+
+# This value is read from the top level CMakeLists.txt
+cmake_policy(SET CMP0171 NEW)
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Codegen/RunCMakeTest.cmake b/Tests/RunCMake/Codegen/RunCMakeTest.cmake
new file mode 100644
index 0000000..bbd70b0
--- /dev/null
+++ b/Tests/RunCMake/Codegen/RunCMakeTest.cmake
@@ -0,0 +1,33 @@
+include(RunCMake)
+
+function(run_codegen case)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
+
+  run_cmake(${case})
+
+  set(RunCMake_TEST_NO_CLEAN 1)
+
+  run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --target codegen --config Debug)
+endfunction()
+
+# Builds codegen target when there are no custom commands marked codegen
+run_codegen("no-codegen")
+
+# We don't want codegen to drive parts of the project that are EXCLUDE_FROM_ALL
+run_codegen("exclude-from-all")
+
+# Ensures codegen builds minimal build graphs
+run_codegen("min-graph-1")
+run_codegen("min-graph-2")
+run_codegen("min-graph-3")
+
+# Handle specific cases that can affect codegen
+run_codegen("add-dependencies")
+run_codegen("add-custom-command-depends")
+run_codegen("byproducts")
+
+# Error handling
+run_cmake("implicit-depends")
+run_cmake("implicit-depends-append-codegen")
+run_cmake("append-implicit-depends")
+run_cmake("no-output")
diff --git a/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake b/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-custom-command-depends-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/add-custom-command-depends.cmake b/Tests/RunCMake/Codegen/add-custom-command-depends.cmake
new file mode 100644
index 0000000..793ab5f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-custom-command-depends.cmake
@@ -0,0 +1,16 @@
+add_custom_target(foobar
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(
+  OUTPUT generated.hpp
+  # This test will fail if DEPENDS isn't accounted for in the codegen build graph
+  DEPENDS foobar
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+                                   ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
diff --git a/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake b/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-dependencies-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/add-dependencies.cmake b/Tests/RunCMake/Codegen/add-dependencies.cmake
new file mode 100644
index 0000000..fbb7e99
--- /dev/null
+++ b/Tests/RunCMake/Codegen/add-dependencies.cmake
@@ -0,0 +1,18 @@
+add_custom_target(foobar
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(
+  OUTPUT generated.hpp
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+                                   ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
+
+# This test will fail if add_dependencies isn't account for in the
+# codegen build graph
+add_dependencies(hpp_creator foobar)
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends-result.txt b/Tests/RunCMake/Codegen/append-implicit-depends-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt b/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt
new file mode 100644
index 0000000..c8ef03e
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error:
+  Cannot append IMPLICIT_DEPENDS to existing CODEGEN custom command\.
diff --git a/Tests/RunCMake/Codegen/append-implicit-depends.cmake b/Tests/RunCMake/Codegen/append-implicit-depends.cmake
new file mode 100644
index 0000000..d212fe5
--- /dev/null
+++ b/Tests/RunCMake/Codegen/append-implicit-depends.cmake
@@ -0,0 +1,19 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+        ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  CODEGEN
+)
+
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+
+  # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+  IMPLICIT_DEPENDS C main.c
+
+  APPEND
+)
diff --git a/Tests/RunCMake/Codegen/byproducts-build-check.cmake b/Tests/RunCMake/Codegen/byproducts-build-check.cmake
new file mode 100644
index 0000000..d371d73
--- /dev/null
+++ b/Tests/RunCMake/Codegen/byproducts-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/byproducts.cmake b/Tests/RunCMake/Codegen/byproducts.cmake
new file mode 100644
index 0000000..ea0b6c7
--- /dev/null
+++ b/Tests/RunCMake/Codegen/byproducts.cmake
@@ -0,0 +1,19 @@
+add_custom_target(foobar
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  BYPRODUCTS
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+# This codegen step relies on the BYPRODUCTS of the previous command.
+# If foobar isn't properly accounted for as a dependency it will fail.
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+                                   ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  CODEGEN
+)
+
+add_custom_target(hpp_creator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp)
diff --git a/Tests/RunCMake/Codegen/error.c b/Tests/RunCMake/Codegen/error.c
new file mode 100644
index 0000000..34cb350
--- /dev/null
+++ b/Tests/RunCMake/Codegen/error.c
@@ -0,0 +1 @@
+#error "This file should not be compiled"
diff --git a/Tests/RunCMake/Codegen/exclude-from-all.cmake b/Tests/RunCMake/Codegen/exclude-from-all.cmake
new file mode 100644
index 0000000..bcd4ac0
--- /dev/null
+++ b/Tests/RunCMake/Codegen/exclude-from-all.cmake
@@ -0,0 +1,11 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  COMMAND
+    ${CMAKE_COMMAND} -E false
+  CODEGEN
+)
+
+# We don't want codegen to drive parts of the project that are EXCLUDE_FROM_ALL.
+# This tests that foobar is properly excluded from the codegen build.
+add_executable(foobar EXCLUDE_FROM_ALL error.c ${CMAKE_CURRENT_BINARY_DIR}/generated.h)
diff --git a/Tests/RunCMake/Codegen/generated.h.in b/Tests/RunCMake/Codegen/generated.h.in
new file mode 100644
index 0000000..82ccf67
--- /dev/null
+++ b/Tests/RunCMake/Codegen/generated.h.in
@@ -0,0 +1 @@
+// hello
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt
new file mode 100644
index 0000000..570cf62
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at implicit-depends-append-codegen\.cmake:[0-9]+ \(add_custom_command\):
+  add_custom_command CODEGEN may not be used with APPEND\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake b/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake
new file mode 100644
index 0000000..76151cc
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-append-codegen.cmake
@@ -0,0 +1,18 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+
+  # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+  IMPLICIT_DEPENDS C main.c
+)
+
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+        ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  CODEGEN
+  APPEND
+)
diff --git a/Tests/RunCMake/Codegen/implicit-depends-result.txt b/Tests/RunCMake/Codegen/implicit-depends-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/implicit-depends-stderr.txt b/Tests/RunCMake/Codegen/implicit-depends-stderr.txt
new file mode 100644
index 0000000..b9ea8f4
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at implicit-depends\.cmake:[0-9]+ \(add_custom_command\):
+  add_custom_command CODEGEN is not compatible with IMPLICIT_DEPENDS\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/Codegen/implicit-depends.cmake b/Tests/RunCMake/Codegen/implicit-depends.cmake
new file mode 100644
index 0000000..011d4b3
--- /dev/null
+++ b/Tests/RunCMake/Codegen/implicit-depends.cmake
@@ -0,0 +1,11 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+        ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
+  CODEGEN
+  # ERROR out if IMPLICIT_DEPENDS is used with CODEGEN
+  IMPLICIT_DEPENDS C main.c
+)
diff --git a/Tests/RunCMake/Codegen/main.c b/Tests/RunCMake/Codegen/main.c
new file mode 100644
index 0000000..8488f4e
--- /dev/null
+++ b/Tests/RunCMake/Codegen/main.c
@@ -0,0 +1,4 @@
+int main(void)
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake
new file mode 100644
index 0000000..32e1557
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-1-build-check.cmake
@@ -0,0 +1,13 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.h")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
+
+# foobar should be built since it was needed
+# by the code generation
+set(filename "${RunCMake_TEST_BINARY_DIR}/foobar.txt")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-1.cmake b/Tests/RunCMake/Codegen/min-graph-1.cmake
new file mode 100644
index 0000000..ea47b8f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-1.cmake
@@ -0,0 +1,26 @@
+add_executable(foobar main.c)
+add_custom_command(
+  TARGET foobar POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E
+    copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/foobar.txt
+)
+
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+        ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  COMMAND
+    # Generate a header file that requires foobar
+    foobar
+  CODEGEN
+)
+
+add_library(errorlib
+  # If this library is built error.c will cause the build to fail
+  error.c
+  ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
diff --git a/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake
new file mode 100644
index 0000000..fab168b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-2-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/generated.h")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-2.cmake b/Tests/RunCMake/Codegen/min-graph-2.cmake
new file mode 100644
index 0000000..277a901
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-2.cmake
@@ -0,0 +1,18 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+        ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+  CODEGEN
+)
+
+# This target should not be built. It has no reason
+# to be part of the codegen build graph
+add_custom_target(error_custom_target ALL
+  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+
+  # Cause the build to fail
+  COMMAND ${CMAKE_COMMAND} -E false
+)
diff --git a/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake b/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake
new file mode 100644
index 0000000..734777b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-3-build-check.cmake
@@ -0,0 +1,5 @@
+set(filename "${RunCMake_TEST_BINARY_DIR}/error_lib.c")
+if (NOT EXISTS "${filename}")
+  set(RunCMake_TEST_FAILED "expected file NOT created:\n ${filename}")
+  return()
+endif()
diff --git a/Tests/RunCMake/Codegen/min-graph-3.cmake b/Tests/RunCMake/Codegen/min-graph-3.cmake
new file mode 100644
index 0000000..c7d61dc
--- /dev/null
+++ b/Tests/RunCMake/Codegen/min-graph-3.cmake
@@ -0,0 +1,12 @@
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c
+  COMMAND
+    ${CMAKE_COMMAND} -E
+        copy ${CMAKE_CURRENT_SOURCE_DIR}/error.c
+        ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c
+  CODEGEN
+)
+
+# This test will fail if error_lib.c is actually compiled
+add_executable(foobar ${CMAKE_CURRENT_BINARY_DIR}/error_lib.c)
diff --git a/Tests/RunCMake/Codegen/no-codegen-check.cmake b/Tests/RunCMake/Codegen/no-codegen-check.cmake
new file mode 100644
index 0000000..97fc46b
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-codegen-check.cmake
@@ -0,0 +1,5 @@
+# Verify generated.hpp was NOT created
+set(unexpected "${RunCMake_TEST_BINARY_DIR}/generated.hpp")
+if(EXISTS "${unexpected}")
+  set(RunCMake_TEST_FAILED "unexpected file created:\n  ${unexpected}")
+endif()
diff --git a/Tests/RunCMake/Codegen/no-codegen.cmake b/Tests/RunCMake/Codegen/no-codegen.cmake
new file mode 100644
index 0000000..00ddd03
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-codegen.cmake
@@ -0,0 +1,6 @@
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.hpp
+)
diff --git a/Tests/RunCMake/Codegen/no-output-result.txt b/Tests/RunCMake/Codegen/no-output-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/Codegen/no-output-stderr.txt b/Tests/RunCMake/Codegen/no-output-stderr.txt
new file mode 100644
index 0000000..7aad679
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at no-output\.cmake:[0-9]+ \(add_custom_command\):
+  add_custom_command CODEGEN requires at least 1 OUTPUT\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/Codegen/no-output.cmake b/Tests/RunCMake/Codegen/no-output.cmake
new file mode 100644
index 0000000..61eb83c
--- /dev/null
+++ b/Tests/RunCMake/Codegen/no-output.cmake
@@ -0,0 +1,11 @@
+add_custom_target(foobar
+  COMMAND ${CMAKE_COMMAND} -E copy
+    ${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in
+    ${CMAKE_CURRENT_BINARY_DIR}/generated.h
+)
+
+add_custom_command(TARGET foobar POST_BUILD
+  COMMAND
+    ${CMAKE_COMMAND} -E true
+  CODEGEN
+)
diff --git a/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake b/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
index 88fd1e8..f1e8b30 100644
--- a/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
+++ b/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
@@ -17,7 +17,7 @@
   list(SORT expected)
 
   file(GLOB_RECURSE actual "${dir}/*")
-  list(FILTER actual EXCLUDE REGEX "/CMakeFiles/|\\.ninja$|/CMakeCache\\.txt$|/target_files[^/]*\\.cmake$|/\\.ninja_[^/]*$|/cmake_install\\.cmake$|\\.ilk$|\\.manifest$|\\.odx$|\\.pdb$|\\.exp$|/install_manifest\\.txt$|/\\.qt/QtDeploySupport[^/]*\\.cmake$")
+  list(FILTER actual EXCLUDE REGEX "/CMakeFiles/|\\.ninja$|/CMakeCache\\.txt$|/target_files[^/]*\\.cmake$|/\\.ninja_[^/]*$|/cmake_install\\.cmake$|\\.ilk$|\\.manifest$|\\.odx$|\\.pdb$|\\.exp$|/install_manifest\\.txt$|/\\.qt/(QtDeploySupport|QtDeployTargets)[^/]*\\.cmake$")
   foreach(f IN LISTS _check_files_INCLUDE _check_files_EXCLUDE)
     if(EXISTS ${f})
       list(APPEND actual ${f})
diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
index e0d74cf..85fefbf 100644
--- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
@@ -98,6 +98,7 @@
 run_cmake(VsCLREmpty)
 run_cmake(VsCLRPure)
 run_cmake(VsCLRSafe)
+run_cmake(VsFrameworkReference)
 
 if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 19.20)
   run_cmake(VsCLRNetcore)
diff --git a/Tests/RunCMake/VS10Project/VsFrameworkReference-check.cmake b/Tests/RunCMake/VS10Project/VsFrameworkReference-check.cmake
new file mode 100644
index 0000000..7abe8a0
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/VsFrameworkReference-check.cmake
@@ -0,0 +1,24 @@
+set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/foo.vcxproj")
+if(NOT EXISTS "${vcProjectFile}")
+  set(RunCMake_TEST_FAILED "Project file ${vcProjectFile} does not exist.")
+  return()
+endif()
+
+set(frameworkReferenceSet FALSE)
+
+file(STRINGS "${vcProjectFile}" lines)
+foreach(line IN LISTS lines)
+  if(line MATCHES "^ *<FrameworkReference Include=\"([^\"]*)\" />$")
+    if("${CMAKE_MATCH_1}" STREQUAL "Microsoft.WindowsDesktop.App.WPF")
+        message(STATUS "foo.vcxproj has FrameworkReference set")
+        set(frameworkReferenceSet TRUE)
+    else()
+        message(STATUS "foo.vcxproj has FrameworkReference incorrectly set to ${CMAKE_MATCH_1}")
+    endif()
+  endif()
+endforeach()
+
+if(NOT frameworkReferenceSet)
+  set(RunCMake_TEST_FAILED "FrameworkReference not found or not set correctly.")
+  return()
+endif()
diff --git a/Tests/RunCMake/VS10Project/VsFrameworkReference.cmake b/Tests/RunCMake/VS10Project/VsFrameworkReference.cmake
new file mode 100644
index 0000000..0e4d6fb
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/VsFrameworkReference.cmake
@@ -0,0 +1,8 @@
+enable_language(CXX)
+
+add_executable(foo foo.cpp)
+
+set_target_properties(foo PROPERTIES
+    COMMON_LANGUAGE_RUNTIME "netcore"
+    DOTNET_TARGET_FRAMEWORK "net8.0-windows"
+    VS_FRAMEWORK_REFERENCES "Microsoft.WindowsDesktop.App.WPF")