Răsfoiți Sursa

Makefiles Generators: use compiler for dependencies generation

Each source compilation generates a dependencies file. These dependencies
files are consolidated in one file per target. This consolidation is done
as part of command 'cmake -E cmake_depends` launched before evaluation of
makefile dependency graph.

The consolidation uses the same approach as `CMake` dependencies management.

Fixes: #21321
Marc Chevrier 5 ani în urmă
părinte
comite
2c71d051fa
52 a modificat fișierele cu 1312 adăugiri și 270 ștergeri
  1. 1 0
      Help/manual/cmake-variables.7.rst
  2. 5 0
      Help/release/dev/makefiles-dependencies-use-compiler.rst
  3. 9 0
      Help/variable/CMAKE_DEPENDS_USE_COMPILER.rst
  4. 4 1
      Modules/CMakeDetermineCompilerId.cmake
  5. 12 0
      Modules/Compiler/AppleClang-C.cmake
  6. 8 0
      Modules/Compiler/AppleClang-CXX.cmake
  7. 9 0
      Modules/Compiler/AppleClang-OBJC.cmake
  8. 10 0
      Modules/Compiler/AppleClang-OBJCXX.cmake
  9. 13 0
      Modules/Compiler/Clang-C.cmake
  10. 6 0
      Modules/Compiler/Clang-CUDA.cmake
  11. 13 0
      Modules/Compiler/Clang-CXX.cmake
  12. 9 0
      Modules/Compiler/Clang-OBJC.cmake
  13. 8 0
      Modules/Compiler/Clang-OBJCXX.cmake
  14. 10 0
      Modules/Compiler/GNU-C.cmake
  15. 10 0
      Modules/Compiler/GNU-CXX.cmake
  16. 9 0
      Modules/Compiler/GNU-OBJC.cmake
  17. 9 0
      Modules/Compiler/GNU-OBJCXX.cmake
  18. 6 0
      Modules/Compiler/Intel-C.cmake
  19. 6 0
      Modules/Compiler/Intel-CXX.cmake
  20. 6 0
      Modules/Compiler/Intel-ISPC.cmake
  21. 8 0
      Modules/Compiler/NVIDIA-CUDA.cmake
  22. 16 0
      Modules/Platform/Windows-Clang-C.cmake
  23. 16 0
      Modules/Platform/Windows-Clang-CXX.cmake
  24. 7 0
      Modules/Platform/Windows-Intel-C.cmake
  25. 7 0
      Modules/Platform/Windows-Intel-CXX.cmake
  26. 7 0
      Modules/Platform/Windows-MSVC-C.cmake
  27. 7 0
      Modules/Platform/Windows-MSVC-CXX.cmake
  28. 7 0
      Modules/Platform/Windows-MSVC.cmake
  29. 2 0
      Source/CMakeLists.txt
  30. 267 0
      Source/cmDependsCompiler.cxx
  31. 60 0
      Source/cmDependsCompiler.h
  32. 10 0
      Source/cmGlobalUnixMakefileGenerator3.h
  33. 330 199
      Source/cmLocalUnixMakefileGenerator3.cxx
  34. 140 15
      Source/cmMakefileTargetGenerator.cxx
  35. 155 28
      Source/cmcmd.cxx
  36. 6 0
      Tests/BuildDepends/Project/CMakeLists.txt
  37. 5 1
      Tests/IncludeDirectories/CMakeLists.txt
  38. 46 0
      Tests/RunCMake/BuildDepends/CompilerDependencies.cmake
  39. 9 0
      Tests/RunCMake/BuildDepends/CompilerDependencies.step1.cmake
  40. 3 0
      Tests/RunCMake/BuildDepends/CompilerDependencies.step2.cmake
  41. 10 0
      Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
  42. 3 0
      Tests/RunCMake/CMakeLists.txt
  43. 3 0
      Tests/RunCMake/Make/TargetMessages-OFF-build-check.cmake
  44. 1 5
      Tests/RunCMake/Make/TargetMessages-OFF-build-stdout.txt
  45. 3 0
      Tests/RunCMake/Make/TargetMessages-ON-build-check.cmake
  46. 1 8
      Tests/RunCMake/Make/TargetMessages-ON-build-stdout.txt
  47. 3 0
      Tests/RunCMake/Make/TargetMessages-VAR-OFF-build-check.cmake
  48. 1 5
      Tests/RunCMake/Make/TargetMessages-VAR-OFF-build-stdout.txt
  49. 3 0
      Tests/RunCMake/Make/TargetMessages-VAR-ON-build-check.cmake
  50. 1 8
      Tests/RunCMake/Make/TargetMessages-VAR-ON-build-stdout.txt
  51. 10 0
      Tests/RunCMake/Make/TargetMessages-validation.cmake
  52. 2 0
      bootstrap

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

@@ -387,6 +387,7 @@ Variables that Control the Build
    /variable/CMAKE_DEFAULT_BUILD_TYPE
    /variable/CMAKE_DEFAULT_CONFIGS
    /variable/CMAKE_DISABLE_PRECOMPILE_HEADERS
+   /variable/CMAKE_DEPENDS_USE_COMPILER
    /variable/CMAKE_ENABLE_EXPORTS
    /variable/CMAKE_EXE_LINKER_FLAGS
    /variable/CMAKE_EXE_LINKER_FLAGS_CONFIG

+ 5 - 0
Help/release/dev/makefiles-dependencies-use-compiler.rst

@@ -0,0 +1,5 @@
+makefiles-dependencies-use-compiler
+-----------------------------------
+
+* The :ref:`Makefile Generators` gained the capability, for a selection of
+  compilers, to use the compiler itself to generate implicit dependencies.

+ 9 - 0
Help/variable/CMAKE_DEPENDS_USE_COMPILER.rst

@@ -0,0 +1,9 @@
+CMAKE_DEPENDS_USE_COMPILER
+--------------------------
+
+.. versionadded:: 3.20
+
+For the :ref:`Makefile Generators`, source dependencies are now, for a
+selection of compilers, generated by the compiler itself. By defining this
+variable with value ``FALSE``, you can restore the legacy behavior (i.e. using
+``CMake`` for dependencies discovery).

+ 4 - 1
Modules/CMakeDetermineCompilerId.cmake

@@ -152,7 +152,10 @@ function(CMAKE_DETERMINE_COMPILER_ID lang flagvar src)
     set(CMAKE_EXECUTABLE_FORMAT "Unknown" CACHE INTERNAL "Executable file format")
   endif()
 
-  if(CMAKE_GENERATOR MATCHES "^Ninja" AND MSVC_${lang}_ARCHITECTURE_ID)
+  if((CMAKE_GENERATOR MATCHES "^Ninja"
+        OR ((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+          AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"))
+      AND MSVC_${lang}_ARCHITECTURE_ID)
     foreach(userflags "${CMAKE_${lang}_COMPILER_ID_FLAGS_LIST}" "")
       CMAKE_DETERMINE_MSVC_SHOWINCLUDES_PREFIX(${lang} "${userflags}")
     endforeach()

+ 12 - 0
Modules/Compiler/AppleClang-C.cmake

@@ -1,6 +1,18 @@
 include(Compiler/Clang)
 __compiler_clang(C)
 
+
+if(NOT "x${CMAKE_C_SIMULATE_ID}" STREQUAL "xMSVC")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles"
+      AND CMAKE_DEPFILE_FLAGS_C)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_C_DEPFILE_FORMAT gcc)
+    set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+  endif()
+endif()
+
+
 if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.0)
   set(CMAKE_C90_STANDARD_COMPILE_OPTION "-std=c90")
   set(CMAKE_C90_EXTENSION_COMPILE_OPTION "-std=gnu90")

+ 8 - 0
Modules/Compiler/AppleClang-CXX.cmake

@@ -2,6 +2,14 @@ include(Compiler/Clang)
 __compiler_clang(CXX)
 
 if(NOT "x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles"
+      AND CMAKE_DEPFILE_FLAGS_CXX)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+    set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+  endif()
+
   set(CMAKE_CXX_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN "-fvisibility-inlines-hidden")
 endif()
 

+ 9 - 0
Modules/Compiler/AppleClang-OBJC.cmake

@@ -1,5 +1,14 @@
 include(Compiler/Clang-OBJC)
 
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles"
+    AND CMAKE_DEPFILE_FLAGS_OBJC)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJC_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJC_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 if(NOT CMAKE_OBJC_COMPILER_VERSION VERSION_LESS 4.0)
   set(CMAKE_OBJC90_STANDARD_COMPILE_OPTION "-std=c90")
   set(CMAKE_OBJC90_EXTENSION_COMPILE_OPTION "-std=gnu90")

+ 10 - 0
Modules/Compiler/AppleClang-OBJCXX.cmake

@@ -1,5 +1,15 @@
 include(Compiler/Clang-OBJCXX)
 
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles"
+    AND CMAKE_DEPFILE_FLAGS_OBJCXX)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJCXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJCXX_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 set(CMAKE_OBJCXX_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN "-fvisibility-inlines-hidden")
 
 if(NOT CMAKE_OBJCXX_COMPILER_VERSION VERSION_LESS 4.0)

+ 13 - 0
Modules/Compiler/Clang-C.cmake

@@ -8,6 +8,19 @@ endif()
 
 if("x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
   set(CMAKE_C_CLANG_TIDY_DRIVER_MODE "cl")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_C)
+    set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+  endif()
+elseif("x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_C)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_C_DEPFILE_FORMAT gcc)
+    set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+  endif()
 endif()
 
 if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.4)

+ 6 - 0
Modules/Compiler/Clang-CUDA.cmake

@@ -3,6 +3,12 @@ __compiler_clang(CUDA)
 
 # Set explicitly, because __compiler_clang() doesn't set this if we're simulating MSVC.
 set(CMAKE_DEPFILE_FLAGS_CUDA "-MD -MT <DEP_TARGET> -MF <DEP_FILE>")
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_CUDA_DEPFILE_FORMAT gcc)
+  set(CMAKE_CUDA_DEPENDS_USE_COMPILER TRUE)
+endif()
 
 # C++03 isn't supported for CXX, but is for CUDA, so we need to set these manually.
 # Do this before __compiler_clang_cxx_standards() since that adds the feature.

+ 13 - 0
Modules/Compiler/Clang-CXX.cmake

@@ -3,6 +3,14 @@ __compiler_clang(CXX)
 __compiler_clang_cxx_standards(CXX)
 
 if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_CXX)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+    set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+  endif()
+
   set(CMAKE_CXX_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN "-fvisibility-inlines-hidden")
 endif()
 
@@ -13,4 +21,9 @@ endif()
 
 if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
   set(CMAKE_CXX_CLANG_TIDY_DRIVER_MODE "cl")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles"
+      AND CMAKE_DEPFILE_FLAGS_CXX)
+    set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+  endif()
 endif()

+ 9 - 0
Modules/Compiler/Clang-OBJC.cmake

@@ -1,6 +1,15 @@
 include(Compiler/Clang)
 __compiler_clang(OBJC)
 
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_OBJC)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJC_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJC_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 if(NOT CMAKE_OBJC_COMPILER_VERSION VERSION_LESS 3.4)
   set(CMAKE_OBJC90_STANDARD_COMPILE_OPTION "-std=c90")
   set(CMAKE_OBJC90_EXTENSION_COMPILE_OPTION "-std=gnu90")

+ 8 - 0
Modules/Compiler/Clang-OBJCXX.cmake

@@ -1,3 +1,11 @@
 include(Compiler/Clang)
 __compiler_clang(OBJCXX)
 __compiler_clang_cxx_standards(OBJCXX)
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_OBJCXX)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJCXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJCXX_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 10 - 0
Modules/Compiler/GNU-C.cmake

@@ -1,6 +1,16 @@
 include(Compiler/GNU)
 __compiler_gnu(C)
 
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_C)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_C_DEPFILE_FORMAT gcc)
+  set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 if (NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5)
   set(CMAKE_C90_STANDARD_COMPILE_OPTION "-std=c90")
   set(CMAKE_C90_EXTENSION_COMPILE_OPTION "-std=gnu90")

+ 10 - 0
Modules/Compiler/GNU-CXX.cmake

@@ -1,6 +1,16 @@
 include(Compiler/GNU)
 __compiler_gnu(CXX)
 
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_CXX)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 if (WIN32)
   if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
     set(CMAKE_CXX_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN "-fno-keep-inline-dllexport")

+ 9 - 0
Modules/Compiler/GNU-OBJC.cmake

@@ -1,2 +1,11 @@
 include(Compiler/GNU)
 __compiler_gnu(OBJC)
+
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_OBJC)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJC_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJC_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 9 - 0
Modules/Compiler/GNU-OBJCXX.cmake

@@ -1,6 +1,15 @@
 include(Compiler/GNU)
 __compiler_gnu(OBJCXX)
 
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_OBJCXX)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_OBJCXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_OBJCXX_DEPENDS_USE_COMPILER TRUE)
+endif()
+
+
 if(NOT CMAKE_OBJCXX_COMPILER_VERSION VERSION_LESS 4.2)
   set(CMAKE_OBJCXX_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN "-fvisibility-inlines-hidden")
 endif()

+ 6 - 0
Modules/Compiler/Intel-C.cmake

@@ -6,6 +6,12 @@ string(APPEND CMAKE_C_FLAGS_RELEASE_INIT " -DNDEBUG")
 string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO_INIT " -DNDEBUG")
 
 set(CMAKE_DEPFILE_FLAGS_C "-MD -MT <DEP_TARGET> -MF <DEP_FILE>")
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_C_DEPFILE_FORMAT gcc)
+  set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+endif()
 
 if("x${CMAKE_C_SIMULATE_ID}" STREQUAL "xMSVC")
 

+ 6 - 0
Modules/Compiler/Intel-CXX.cmake

@@ -6,6 +6,12 @@ string(APPEND CMAKE_CXX_FLAGS_RELEASE_INIT " -DNDEBUG")
 string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT " -DNDEBUG")
 
 set(CMAKE_DEPFILE_FLAGS_CXX "-MD -MT <DEP_TARGET> -MF <DEP_FILE>")
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+endif()
 
 if("x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC")
 

+ 6 - 0
Modules/Compiler/Intel-ISPC.cmake

@@ -4,6 +4,12 @@ include(Compiler/CMakeCommonCompilerMacros)
 #set(CMAKE_ISPC_VERBOSE_FLAG )
 
 set(CMAKE_DEPFILE_FLAGS_ISPC "-M -MT <DEP_TARGET> -MF <DEP_FILE>")
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_ISPC_DEPFILE_FORMAT gcc)
+  set(CMAKE_ISPC_DEPENDS_USE_COMPILER TRUE)
+endif()
 
 string(APPEND CMAKE_ISPC_FLAGS_INIT " ")
 string(APPEND CMAKE_ISPC_FLAGS_DEBUG_INIT "-O0 -g")

+ 8 - 0
Modules/Compiler/NVIDIA-CUDA.cmake

@@ -30,6 +30,14 @@ if (CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 10.2.89)
   # to get header dependency information
   set(CMAKE_DEPFILE_FLAGS_CUDA "-MD -MT <DEP_TARGET> -MF <DEP_FILE>")
 endif()
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  if (NOT CMAKE_DEPFILE_FLAGS_CUDA)
+    set(CMAKE_CUDA_DEPENDS_EXTRA_COMMANDS "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> ${_CMAKE_COMPILE_AS_CUDA_FLAG} -M <SOURCE> -MT <OBJECT> -o <DEP_FILE>")
+  endif()
+  set(CMAKE_CUDA_DEPFILE_FORMAT gcc)
+  set(CMAKE_CUDA_DEPENDS_USE_COMPILER TRUE)
+endif()
 
 if(NOT "x${CMAKE_CUDA_SIMULATE_ID}" STREQUAL "xMSVC")
   set(CMAKE_CUDA_COMPILE_OPTIONS_PIE -Xcompiler=-fPIE)

+ 16 - 0
Modules/Platform/Windows-Clang-C.cmake

@@ -1,2 +1,18 @@
 include(Platform/Windows-Clang)
 __windows_compiler_clang(C)
+
+if("x${MAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_C)
+    set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+  endif()
+elseif("x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_C)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_C_DEPFILE_FORMAT gcc)
+    set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+  endif()
+endif()

+ 16 - 0
Modules/Platform/Windows-Clang-CXX.cmake

@@ -1,3 +1,19 @@
 include(Platform/Windows-Clang)
 set(_COMPILE_CXX_MSVC " -TP")
 __windows_compiler_clang(CXX)
+
+if("x${MAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_CXX)
+    set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+  endif()
+elseif("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
+  if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+      AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+      AND CMAKE_DEPFILE_FLAGS_CXX)
+    # dependencies are computed by the compiler itself
+    set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+    set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+  endif()
+endif()

+ 7 - 0
Modules/Platform/Windows-Intel-C.cmake

@@ -2,3 +2,10 @@ include(Platform/Windows-Intel)
 __windows_compiler_intel(C)
 set(CMAKE_NINJA_DEPTYPE_C intel) # special value handled by CMake
 set(CMAKE_DEPFILE_FLAGS_C "-QMMD -QMT <DEP_TARGET> -QMF <DEP_FILE>")
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_C_DEPFILE_FORMAT gcc)
+  set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 7 - 0
Modules/Platform/Windows-Intel-CXX.cmake

@@ -3,3 +3,10 @@ set(_COMPILE_CXX " /TP")
 __windows_compiler_intel(CXX)
 set(CMAKE_NINJA_DEPTYPE_CXX intel) # special value handled by CMake
 set(CMAKE_DEPFILE_FLAGS_CXX "-QMMD -QMT <DEP_TARGET> -QMF <DEP_FILE>")
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake")
+  # dependencies are computed by the compiler itself
+  set(CMAKE_CXX_DEPFILE_FORMAT gcc)
+  set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 7 - 0
Modules/Platform/Windows-MSVC-C.cmake

@@ -3,3 +3,10 @@ if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 18.0)
   set(_FS_C " /FS")
 endif()
 __windows_compiler_msvc(C)
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_C)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_C_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 7 - 0
Modules/Platform/Windows-MSVC-CXX.cmake

@@ -4,3 +4,10 @@ if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18.0)
   set(_FS_CXX " /FS")
 endif()
 __windows_compiler_msvc(CXX)
+
+if((NOT DEFINED CMAKE_DEPENDS_USE_COMPILER OR CMAKE_DEPENDS_USE_COMPILER)
+    AND CMAKE_GENERATOR MATCHES "Makefiles|WMake"
+    AND CMAKE_DEPFILE_FLAGS_CXX)
+  # dependencies are computed by the compiler itself
+  set(CMAKE_CXX_DEPENDS_USE_COMPILER TRUE)
+endif()

+ 7 - 0
Modules/Platform/Windows-MSVC.cmake

@@ -437,6 +437,13 @@ macro(__windows_compiler_msvc lang)
   set(CMAKE_${lang}_LINKER_SUPPORTS_PDB ON)
   set(CMAKE_NINJA_DEPTYPE_${lang} msvc)
   __windows_compiler_msvc_enable_rc("${_PLATFORM_DEFINES} ${_PLATFORM_DEFINES_${lang}}")
+
+  # define generic information about compiler dependencies
+  # activation is done on per language platform configuration basis
+  if (MSVC_VERSION GREATER 1300)
+    set(CMAKE_DEPFILE_FLAGS_${lang} "/showIncludes")
+    set(CMAKE_${lang}_DEPFILE_FORMAT msvc)
+  endif()
 endmacro()
 
 macro(__windows_compiler_msvc_enable_rc flags)

+ 2 - 0
Source/CMakeLists.txt

@@ -224,6 +224,8 @@ set(SRCS
   cmDependsJava.h
   cmDependsJavaParserHelper.cxx
   cmDependsJavaParserHelper.h
+  cmDependsCompiler.cxx
+  cmDependsCompiler.h
   cmDocumentation.cxx
   cmDocumentationFormatter.cxx
   cmDocumentationSection.cxx

+ 267 - 0
Source/cmDependsCompiler.cxx

@@ -0,0 +1,267 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDependsCompiler.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include <cm/string_view>
+#include <cm/vector>
+#include <cmext/string_view>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmFileTime.h"
+#include "cmGlobalUnixMakefileGenerator3.h"
+#include "cmLocalUnixMakefileGenerator3.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+namespace {
+std::string& ReplaceAll(std::string& data, const std::string& toSearch,
+                        const std::string& replaceStr)
+{
+  // Get the first occurrence
+  auto pos = data.find(toSearch);
+  // Repeat until the end is reached
+  while (pos != std::string::npos) {
+    // Replace this occurrence of Sub String
+    data.replace(pos, toSearch.size(), replaceStr);
+    // Get the next occurrence from the current position
+    pos = data.find(toSearch, pos + replaceStr.size());
+  }
+
+  return data;
+}
+
+std::string& NormalizePath(std::string& item)
+{
+  ReplaceAll(item, "$$", "$");
+  ReplaceAll(item, "\\ ", " ");
+  ReplaceAll(item, "\\#", "#");
+  ReplaceAll(item, "\\", "/");
+
+  return item;
+}
+
+void ParseLine(const std::string& line, std::vector<std::string>& depends)
+{
+  auto start = line.find_first_not_of(' ');
+  if (start == std::string::npos || line[start] == '#') {
+    return;
+  }
+
+  auto index = start;
+  while ((index = line.find(' ', index)) != std::string::npos) {
+    if (line[index - 1] == '\\') {
+      index += 1;
+      continue;
+    }
+
+    auto item = line.substr(start, index - start);
+    if (item.back() != ':') {
+      // check that ':' is not present after some spaces
+      auto index2 = line.find_first_not_of(' ', index + 1);
+      if (index2 == std::string::npos || line[index2] != ':') {
+        // this is a dependency, add it
+        depends.emplace_back(std::move(NormalizePath(item)));
+      } else {
+        index = index2;
+      }
+    }
+
+    start = line.find_first_not_of(' ', index + 1);
+    index = start;
+  }
+  if (start != std::string::npos) {
+    auto item = line.substr(start);
+    if (line.back() != ':') {
+      // this is a dependency, add it
+      depends.emplace_back(std::move(NormalizePath(item)));
+    }
+  }
+}
+}
+
+bool cmDependsCompiler::CheckDependencies(
+  const std::string& internalDepFile, const std::vector<std::string>& depFiles,
+  cmDepends::DependencyMap& dependencies,
+  const std::function<bool(const std::string&)>& isValidPath)
+{
+  bool status = true;
+  bool forceReadDeps = true;
+
+  cmFileTime internalDepFileTime;
+  // read cached dependencies stored in internal file
+  if (cmSystemTools::FileExists(internalDepFile)) {
+    internalDepFileTime.Load(internalDepFile);
+    forceReadDeps = false;
+
+    // read current dependencies
+    cmsys::ifstream fin(internalDepFile.c_str());
+    if (fin) {
+      std::string line;
+      std::string depender;
+      std::vector<std::string>* currentDependencies = nullptr;
+      while (std::getline(fin, line)) {
+        if (line.empty() || line.front() == '#') {
+          continue;
+        }
+        // Drop carriage return character at the end
+        if (line.back() == '\r') {
+          line.pop_back();
+          if (line.empty()) {
+            continue;
+          }
+        }
+        // Check if this a depender line
+        if (line.front() != ' ') {
+          depender = std::move(line);
+          currentDependencies = &dependencies[depender];
+          continue;
+        }
+        // This is a dependee line
+        if (currentDependencies != nullptr) {
+          currentDependencies->emplace_back(line.substr(1));
+        }
+      }
+      fin.close();
+    }
+  }
+
+  // Now, update dependencies map with all new compiler generated
+  // dependencies files
+  cmFileTime depFileTime;
+  for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) {
+    const auto& source = *dep++;
+    const auto& target = *dep++;
+    const auto& format = *dep++;
+    const auto& depFile = *dep;
+
+    if (!cmSystemTools::FileExists(depFile)) {
+      continue;
+    }
+
+    if (!forceReadDeps) {
+      depFileTime.Load(depFile);
+    }
+    if (forceReadDeps || depFileTime.Newer(internalDepFileTime)) {
+      status = false;
+      if (this->Verbose) {
+        cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile,
+                                       "\" is newer than depends file \"",
+                                       internalDepFile, "\".\n"));
+      }
+      cmsys::ifstream fin(depFile.c_str());
+      if (!fin) {
+        continue;
+      }
+
+      std::vector<std::string> depends;
+      std::string line;
+      if (format == "msvc"_s) {
+        if (!isValidPath) {
+          // insert source as first dependency
+          depends.push_back(source);
+        }
+        while (cmSystemTools::GetLineFromStream(fin, line)) {
+          depends.emplace_back(std::move(line));
+        }
+      } else {
+        while (cmSystemTools::GetLineFromStream(fin, line)) {
+          if (line.empty()) {
+            continue;
+          }
+          if (line.back() == '\\') {
+            line.pop_back();
+          }
+          ParseLine(line, depends);
+        }
+
+        // depending of the effective format of the dependencies file generated
+        // by the compiler, the target can be wrongly identified as a
+        // dependency so remove it from the list
+        if (depends.front() == target) {
+          depends.erase(depends.begin());
+        }
+
+        if (isValidPath) {
+          // remove first dependency because it must not be filtered out
+          depends.erase(depends.begin());
+        }
+      }
+
+      if (isValidPath) {
+        cm::erase_if(depends, isValidPath);
+        // insert source as first dependency
+        depends.insert(depends.begin(), source);
+      }
+
+      dependencies[target] = std::move(depends);
+    }
+  }
+
+  return status;
+}
+
+void cmDependsCompiler::WriteDependencies(
+  const cmDepends::DependencyMap& dependencies, std::ostream& makeDepends,
+  std::ostream& internalDepends)
+{
+  // dependencies file consumed by make tool
+  const auto& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>(
+                               this->LocalGenerator->GetGlobalGenerator())
+                               ->LineContinueDirective;
+  const auto& binDir = this->LocalGenerator->GetBinaryDirectory();
+  cmDepends::DependencyMap makeDependencies(dependencies);
+  std::unordered_set<cm::string_view> phonyTargets;
+
+  // external dependencies file
+  for (auto& node : makeDependencies) {
+    auto& deps = node.second;
+    std::transform(
+      deps.cbegin(), deps.cend(), deps.begin(),
+      [this, &binDir](const std::string& dep) {
+        return LocalGenerator->ConvertToMakefilePath(
+          this->LocalGenerator->MaybeConvertToRelativePath(binDir, dep));
+      });
+
+    makeDepends << this->LocalGenerator->ConvertToMakefilePath(node.first)
+                << ": " << deps.front();
+    // first dependency is the source, remove it because should not be declared
+    // as phony target
+    deps.erase(deps.begin());
+    for (const auto& dep : deps) {
+      makeDepends << ' ' << lineContinue << "  " << dep;
+      phonyTargets.emplace(dep.data(), dep.length());
+    }
+    makeDepends << std::endl << std::endl;
+  }
+
+  // add phony targets
+  for (const auto& target : phonyTargets) {
+    makeDepends << std::endl << target << ':' << std::endl;
+  }
+
+  // internal dependencies file
+  for (const auto& node : dependencies) {
+    internalDepends << node.first << std::endl;
+    for (const auto& dep : node.second) {
+      internalDepends << ' ' << dep << std::endl;
+    }
+    internalDepends << std::endl;
+  }
+}
+
+void cmDependsCompiler::ClearDependencies(
+  const std::vector<std::string>& depFiles)
+{
+  for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) {
+    dep += 3;
+    cmSystemTools::RemoveFile(*dep);
+  }
+}

+ 60 - 0
Source/cmDependsCompiler.h

@@ -0,0 +1,60 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <functional>
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmDepends.h"
+
+class cmLocalUnixMakefileGenerator3;
+
+/** \class cmDepends
+ * \brief Dependencies files manager.
+ *
+ * This class is responsible for maintaining a compiler_depends.make file in
+ * the build tree corresponding to an object file.
+ */
+class cmDependsCompiler
+{
+public:
+  cmDependsCompiler() = default;
+  ~cmDependsCompiler() = default;
+
+  /** should this be verbose in its output */
+  void SetVerbose(bool verb) { this->Verbose = verb; }
+
+  /** Set the local generator for the directory in which we are
+      scanning dependencies.  This is not a full local generator; it
+      has been setup to do relative path conversions for the current
+      directory.  */
+  void SetLocalGenerator(cmLocalUnixMakefileGenerator3* lg)
+  {
+    this->LocalGenerator = lg;
+  }
+
+  /** Read dependencies for the target file. Return true if
+      dependencies didn't changed and false if not.
+      Up-to-date Dependencies will be stored in deps. */
+  bool CheckDependencies(
+    const std::string& internalDepFile,
+    const std::vector<std::string>& depFiles,
+    cmDepends::DependencyMap& dependencies,
+    const std::function<bool(const std::string&)>& isValidPath);
+
+  /** Write dependencies for the target file.  */
+  void WriteDependencies(const cmDepends::DependencyMap& dependencies,
+                         std::ostream& makeDepends,
+                         std::ostream& internalDepends);
+
+  /** Clear dependencies for the target so they will be regenerated.  */
+  void ClearDependencies(const std::vector<std::string>& depFiles);
+
+private:
+  bool Verbose = false;
+  cmLocalUnixMakefileGenerator3* LocalGenerator = nullptr;
+};

+ 10 - 0
Source/cmGlobalUnixMakefileGenerator3.h

@@ -127,6 +127,12 @@ public:
   void WriteConvenienceRules(std::ostream& ruleFileStream,
                              std::set<std::string>& emitted);
 
+  // Make tool supports dependency files generated by compiler
+  bool SupportsCompilerDependencies()
+  {
+    return this->ToolSupportsCompilerDependencies;
+  }
+
   /** Get the command to use for a target that has no rule.  This is
       used for multiple output dependencies and for cmake_force.  */
   std::string GetEmptyRuleHackCommand() { return this->EmptyRuleHackCommand; }
@@ -219,6 +225,10 @@ protected:
 
   bool CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const override { return true; }
 
+  // Specify if the make tool is able to consume dependency files
+  // generated by the compiler
+  bool ToolSupportsCompilerDependencies = true;
+
   // Some make programs (Borland) do not keep a rule if there are no
   // dependencies or commands.  This is a problem for creating rules
   // that might not do anything but might have other dependencies

+ 330 - 199
Source/cmLocalUnixMakefileGenerator3.cxx

@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <cassert>
 #include <cstdio>
+#include <functional>
 #include <sstream>
 #include <utility>
 
@@ -19,6 +20,7 @@
 #include "cmCMakePath.h"
 #include "cmCustomCommand.h" // IWYU pragma: keep
 #include "cmCustomCommandGenerator.h"
+#include "cmDependsCompiler.h"
 #include "cmFileTimeCache.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
@@ -52,8 +54,9 @@
 #  include "cmDependsJava.h"
 #endif
 
+namespace {
 // Helper function used below.
-static std::string cmSplitExtension(std::string const& in, std::string& base)
+std::string cmSplitExtension(std::string const& in, std::string& base)
 {
   std::string ext;
   std::string::size_type dot_pos = in.rfind('.');
@@ -67,6 +70,43 @@ static std::string cmSplitExtension(std::string const& in, std::string& base)
   return ext;
 }
 
+// Helper predicate for removing absolute paths that don't point to the
+// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY
+// is set ON, to only consider in-project dependencies during the build.
+class NotInProjectDir
+{
+public:
+  // Constructor with the source and binary directory's path
+  NotInProjectDir(cm::string_view sourceDir, cm::string_view binaryDir)
+    : SourceDir(sourceDir)
+    , BinaryDir(binaryDir)
+  {
+  }
+
+  // Operator evaluating the predicate
+  bool operator()(const std::string& p) const
+  {
+    auto path = cmCMakePath(p).Normal();
+
+    // Keep all relative paths:
+    if (path.IsRelative()) {
+      return false;
+    }
+
+    // If it's an absolute path, check if it starts with the source
+    // directory:
+    return !(cmCMakePath(SourceDir).IsPrefix(path) ||
+             cmCMakePath(BinaryDir).IsPrefix(path));
+  }
+
+private:
+  // The path to the source directory
+  cm::string_view SourceDir;
+  // The path to the binary directory
+  cm::string_view BinaryDir;
+};
+}
+
 cmLocalUnixMakefileGenerator3::cmLocalUnixMakefileGenerator3(
   cmGlobalGenerator* gg, cmMakefile* mf)
   : cmLocalCommonGenerator(gg, mf, mf->GetCurrentBinaryDirectory())
@@ -554,8 +594,10 @@ void cmLocalUnixMakefileGenerator3::WriteMakeRule(
     }
   }
 
-  // Write the list of commands.
-  os << cmWrap("\t", commands, "", "\n") << "\n";
+  if (!commands.empty()) {
+    // Write the list of commands.
+    os << cmWrap("\t", commands, "", "\n") << "\n";
+  }
   if (symbolic && !this->IsWatcomWMake()) {
     os << ".PHONY : " << tgt << "\n";
   }
@@ -1300,91 +1342,153 @@ bool cmLocalUnixMakefileGenerator3::UpdateDependencies(
     cmSystemTools::Error("Target DependInfo.cmake file not found");
   }
 
+  bool status = true;
+
   // Check if any multiple output pairs have a missing file.
   this->CheckMultipleOutputs(verbose);
 
   std::string const targetDir = cmSystemTools::GetFilenamePath(tgtInfo);
-  std::string const internalDependFile = targetDir + "/depend.internal";
-  std::string const dependFile = targetDir + "/depend.make";
-
-  // If the target DependInfo.cmake file has changed since the last
-  // time dependencies were scanned then force rescanning.  This may
-  // happen when a new source file is added and CMake regenerates the
-  // project but no other sources were touched.
-  bool needRescanDependInfo = false;
-  cmFileTimeCache* ftc =
-    this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache();
-  {
-    int result;
-    if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) {
-      if (verbose) {
-        cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo,
-                                       "\" is newer than depender \"",
-                                       internalDependFile, "\".\n"));
+  if (!this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) {
+    // dependencies are managed by CMake itself
+
+    std::string const internalDependFile = targetDir + "/depend.internal";
+    std::string const dependFile = targetDir + "/depend.make";
+
+    // If the target DependInfo.cmake file has changed since the last
+    // time dependencies were scanned then force rescanning.  This may
+    // happen when a new source file is added and CMake regenerates the
+    // project but no other sources were touched.
+    bool needRescanDependInfo = false;
+    cmFileTimeCache* ftc =
+      this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache();
+    {
+      int result;
+      if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) {
+        if (verbose) {
+          cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo,
+                                         "\" is newer than depender \"",
+                                         internalDependFile, "\".\n"));
+        }
+        needRescanDependInfo = true;
       }
-      needRescanDependInfo = true;
     }
-  }
 
-  // If the directory information is newer than depend.internal, include dirs
-  // may have changed. In this case discard all old dependencies.
-  bool needRescanDirInfo = false;
-  {
-    std::string dirInfoFile =
-      cmStrCat(this->GetCurrentBinaryDirectory(),
-               "/CMakeFiles/CMakeDirectoryInformation.cmake");
-    int result;
-    if (!ftc->Compare(internalDependFile, dirInfoFile, &result) ||
-        result < 0) {
-      if (verbose) {
-        cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile,
-                                       "\" is newer than depender \"",
-                                       internalDependFile, "\".\n"));
+    // If the directory information is newer than depend.internal, include
+    // dirs may have changed. In this case discard all old dependencies.
+    bool needRescanDirInfo = false;
+    {
+      std::string dirInfoFile =
+        cmStrCat(this->GetCurrentBinaryDirectory(),
+                 "/CMakeFiles/CMakeDirectoryInformation.cmake");
+      int result;
+      if (!ftc->Compare(internalDependFile, dirInfoFile, &result) ||
+          result < 0) {
+        if (verbose) {
+          cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile,
+                                         "\" is newer than depender \"",
+                                         internalDependFile, "\".\n"));
+        }
+        needRescanDirInfo = true;
       }
-      needRescanDirInfo = true;
+    }
+
+    // Check the implicit dependencies to see if they are up to date.
+    // The build.make file may have explicit dependencies for the object
+    // files but these will not affect the scanning process so they need
+    // not be considered.
+    cmDepends::DependencyMap validDependencies;
+    bool needRescanDependencies = false;
+    if (!needRescanDirInfo) {
+      cmDependsC checker;
+      checker.SetVerbose(verbose);
+      checker.SetFileTimeCache(ftc);
+      // cmDependsC::Check() fills the vector validDependencies() with the
+      // dependencies for those files where they are still valid, i.e.
+      // neither the files themselves nor any files they depend on have
+      // changed. We don't do that if the CMakeDirectoryInformation.cmake
+      // file has changed, because then potentially all dependencies have
+      // changed. This information is given later on to cmDependsC, which
+      // then only rescans the files where it did not get valid dependencies
+      // via this dependency vector. This means that in the normal case, when
+      // only few or one file have been edited, then also only this one file
+      // is actually scanned again, instead of all files for this target.
+      needRescanDependencies =
+        !checker.Check(dependFile, internalDependFile, validDependencies);
+    }
+
+    if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) {
+      // The dependencies must be regenerated.
+      std::string targetName = cmSystemTools::GetFilenameName(targetDir);
+      targetName = targetName.substr(0, targetName.length() - 4);
+      std::string message =
+        cmStrCat("Scanning dependencies of target ", targetName);
+      cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta |
+                                         cmsysTerminal_Color_ForegroundBold,
+                                       message.c_str(), true, color);
+
+      status = this->ScanDependencies(targetDir, dependFile,
+                                      internalDependFile, validDependencies);
     }
   }
 
-  // Check the implicit dependencies to see if they are up to date.
-  // The build.make file may have explicit dependencies for the object
-  // files but these will not affect the scanning process so they need
-  // not be considered.
-  cmDepends::DependencyMap validDependencies;
-  bool needRescanDependencies = false;
-  if (!needRescanDirInfo) {
-    cmDependsC checker;
-    checker.SetVerbose(verbose);
-    checker.SetFileTimeCache(ftc);
-    // cmDependsC::Check() fills the vector validDependencies() with the
-    // dependencies for those files where they are still valid, i.e. neither
-    // the files themselves nor any files they depend on have changed.
-    // We don't do that if the CMakeDirectoryInformation.cmake file has
-    // changed, because then potentially all dependencies have changed.
-    // This information is given later on to cmDependsC, which then only
-    // rescans the files where it did not get valid dependencies via this
-    // dependency vector. This means that in the normal case, when only
-    // few or one file have been edited, then also only this one file is
-    // actually scanned again, instead of all files for this target.
-    needRescanDependencies =
-      !checker.Check(dependFile, internalDependFile, validDependencies);
-  }
-
-  if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) {
-    // The dependencies must be regenerated.
-    std::string targetName = cmSystemTools::GetFilenameName(targetDir);
-    targetName = targetName.substr(0, targetName.length() - 4);
-    std::string message =
-      cmStrCat("Scanning dependencies of target ", targetName);
-    cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta |
-                                       cmsysTerminal_Color_ForegroundBold,
-                                     message.c_str(), true, color);
-
-    return this->ScanDependencies(targetDir, dependFile, internalDependFile,
-                                  validDependencies);
+  auto depends =
+    this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES");
+  if (!depends.empty()) {
+    // dependencies are managed by compiler
+    auto depFiles = cmExpandedList(depends);
+    std::string const internalDepFile =
+      targetDir + "/compiler_depend.internal";
+    std::string const depFile = targetDir + "/compiler_depend.make";
+    cmDepends::DependencyMap dependencies;
+    cmDependsCompiler depsManager;
+    bool projectOnly = cmIsOn(
+      this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_IN_PROJECT_ONLY"));
+
+    depsManager.SetVerbose(verbose);
+    depsManager.SetLocalGenerator(this);
+
+    if (!depsManager.CheckDependencies(
+          internalDepFile, depFiles, dependencies,
+          projectOnly ? NotInProjectDir(this->GetSourceDirectory(),
+                                        this->GetBinaryDirectory())
+                      : std::function<bool(const std::string&)>())) {
+      // regenerate dependencies files
+      std::string targetName =
+        cmCMakePath(targetDir).GetFileName().RemoveExtension().GenericString();
+      auto message = cmStrCat(
+        "Consolidate compiler generated dependencies of target ", targetName);
+      cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta |
+                                         cmsysTerminal_Color_ForegroundBold,
+                                       message.c_str(), true, color);
+
+      // Open the make depends file.  This should be copy-if-different
+      // because the make tool may try to reload it needlessly otherwise.
+      cmGeneratedFileStream ruleFileStream(
+        depFile, false, this->GlobalGenerator->GetMakefileEncoding());
+      ruleFileStream.SetCopyIfDifferent(true);
+      if (!ruleFileStream) {
+        return false;
+      }
+
+      // Open the cmake dependency tracking file.  This should not be
+      // copy-if-different because dependencies are re-scanned when it is
+      // older than the DependInfo.cmake.
+      cmGeneratedFileStream internalRuleFileStream(
+        internalDepFile, false, this->GlobalGenerator->GetMakefileEncoding());
+      if (!internalRuleFileStream) {
+        return false;
+      }
+
+      this->WriteDisclaimer(ruleFileStream);
+      this->WriteDisclaimer(internalRuleFileStream);
+
+      depsManager.WriteDependencies(dependencies, ruleFileStream,
+                                    internalRuleFileStream);
+    }
   }
 
   // The dependencies are already up-to-date.
-  return true;
+  return status;
 }
 
 bool cmLocalUnixMakefileGenerator3::ScanDependencies(
@@ -1723,166 +1827,193 @@ void cmLocalUnixMakefileGenerator3::ClearDependencies(cmMakefile* mf,
   cmDepends clearer;
   clearer.SetVerbose(verbose);
   for (std::string const& file : files) {
-    std::string dir = cmSystemTools::GetFilenamePath(file);
-
-    // Clear the implicit dependency makefile.
-    std::string dependFile = dir + "/depend.make";
-    clearer.Clear(dependFile);
+    auto snapshot = mf->GetState()->CreateBaseSnapshot();
+    cmMakefile lmf(mf->GetGlobalGenerator(), snapshot);
+    lmf.ReadListFile(file);
 
-    // Remove the internal dependency check file to force
-    // regeneration.
-    std::string internalDependFile = dir + "/depend.internal";
-    cmSystemTools::RemoveFile(internalDependFile);
-  }
-}
+    if (!lmf.GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) {
+      std::string dir = cmSystemTools::GetFilenamePath(file);
 
-namespace {
-// Helper predicate for removing absolute paths that don't point to the
-// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY
-// is set ON, to only consider in-project dependencies during the build.
-class NotInProjectDir
-{
-public:
-  // Constructor with the source and binary directory's path
-  NotInProjectDir(cm::string_view sourceDir, cm::string_view binaryDir)
-    : SourceDir(sourceDir)
-    , BinaryDir(binaryDir)
-  {
-  }
+      // Clear the implicit dependency makefile.
+      std::string dependFile = dir + "/depend.make";
+      clearer.Clear(dependFile);
 
-  // Operator evaluating the predicate
-  bool operator()(const std::string& p) const
-  {
-    auto path = cmCMakePath(p).Normal();
+      // Remove the internal dependency check file to force
+      // regeneration.
+      std::string internalDependFile = dir + "/depend.internal";
+      cmSystemTools::RemoveFile(internalDependFile);
+    }
 
-    // Keep all relative paths:
-    if (path.IsRelative()) {
-      return false;
+    auto depsFiles = lmf.GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES");
+    if (!depsFiles.empty()) {
+      auto dir = cmCMakePath(file).GetParentPath();
+      // Clear the implicit dependency makefile.
+      auto depFile = cmCMakePath(dir).Append("compiler_depend.make");
+      clearer.Clear(depFile.GenericString());
+
+      // Remove the internal dependency check file
+      auto internalDepFile =
+        cmCMakePath(dir).Append("compiler_depend.internal");
+      cmSystemTools::RemoveFile(internalDepFile.GenericString());
+
+      // Touch timestamp file to force dependencies regeneration
+      auto DepTimestamp = cmCMakePath(dir).Append("compiler_depend.ts");
+      cmSystemTools::Touch(DepTimestamp.GenericString(), true);
+
+      // clear the dependencies files generated by the compiler
+      std::vector<std::string> dependencies = cmExpandedList(depsFiles);
+      cmDependsCompiler depsManager;
+      depsManager.SetVerbose(verbose);
+      depsManager.ClearDependencies(dependencies);
     }
-    // If it's an absolute path, check if it starts with the source
-    // directory:
-    return !(cmCMakePath(SourceDir).IsPrefix(path) ||
-             cmCMakePath(BinaryDir).IsPrefix(path));
   }
-
-private:
-  // The path to the source directory
-  cm::string_view SourceDir;
-  // The path to the binary directory
-  cm::string_view BinaryDir;
-};
 }
 
 void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo(
   std::ostream& cmakefileStream, cmGeneratorTarget* target)
 {
-  ImplicitDependLanguageMap const& implicitLangs =
-    this->GetImplicitDepends(target);
+  // To enable dependencies filtering
+  cmakefileStream << "\n"
+                  << "# Consider dependencies only in project.\n"
+                  << "set(CMAKE_DEPENDS_IN_PROJECT_ONLY "
+                  << (cmIsOn(this->Makefile->GetSafeDefinition(
+                        "CMAKE_DEPENDS_IN_PROJECT_ONLY"))
+                        ? "ON"
+                        : "OFF")
+                  << ")\n\n";
+
+  auto const& implicitLangs =
+    this->GetImplicitDepends(target, cmDependencyScannerKind::CMake);
 
   // list the languages
-  cmakefileStream
-    << "# The set of languages for which implicit dependencies are needed:\n";
+  cmakefileStream << "# The set of languages for which implicit "
+                     "dependencies are needed:\n";
   cmakefileStream << "set(CMAKE_DEPENDS_LANGUAGES\n";
   for (auto const& implicitLang : implicitLangs) {
     cmakefileStream << "  \"" << implicitLang.first << "\"\n";
   }
   cmakefileStream << "  )\n";
 
-  // now list the files for each language
-  cmakefileStream
-    << "# The set of files for implicit dependencies of each language:\n";
-  for (auto const& implicitLang : implicitLangs) {
-    cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << implicitLang.first
-                    << "\n";
-    ImplicitDependFileMap const& implicitPairs = implicitLang.second;
-
-    // for each file pair
-    for (auto const& implicitPair : implicitPairs) {
-      for (auto const& di : implicitPair.second) {
-        cmakefileStream << "  \"" << di << "\" ";
-        cmakefileStream << "\"" << implicitPair.first << "\"\n";
+  if (!implicitLangs.empty()) {
+    // now list the files for each language
+    cmakefileStream
+      << "# The set of files for implicit dependencies of each language:\n";
+    for (auto const& implicitLang : implicitLangs) {
+      const auto& lang = implicitLang.first;
+
+      cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << lang << "\n";
+      auto const& implicitPairs = implicitLang.second;
+
+      // for each file pair
+      for (auto const& implicitPair : implicitPairs) {
+        for (auto const& di : implicitPair.second) {
+          cmakefileStream << "  \"" << di << "\" ";
+          cmakefileStream << "\"" << implicitPair.first << "\"\n";
+        }
       }
-    }
-    cmakefileStream << "  )\n";
-
-    // Tell the dependency scanner what compiler is used.
-    std::string cidVar =
-      cmStrCat("CMAKE_", implicitLang.first, "_COMPILER_ID");
-    cmProp cid = this->Makefile->GetDefinition(cidVar);
-    if (cmNonempty(cid)) {
-      cmakefileStream << "set(CMAKE_" << implicitLang.first
-                      << "_COMPILER_ID \"" << *cid << "\")\n";
-    }
+      cmakefileStream << "  )\n";
 
-    if (implicitLang.first == "Fortran") {
-      std::string smodSep =
-        this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP");
-      std::string smodExt =
-        this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT");
-      cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep
-                      << "\")\n";
-      cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt
-                      << "\")\n";
-    }
+      // Tell the dependency scanner what compiler is used.
+      std::string cidVar = cmStrCat("CMAKE_", lang, "_COMPILER_ID");
+      cmProp cid = this->Makefile->GetDefinition(cidVar);
+      if (cmNonempty(cid)) {
+        cmakefileStream << "set(CMAKE_" << lang << "_COMPILER_ID \"" << *cid
+                        << "\")\n";
+      }
 
-    // Build a list of preprocessor definitions for the target.
-    std::set<std::string> defines;
-    this->GetTargetDefines(target, this->GetConfigName(), implicitLang.first,
-                           defines);
-    if (!defines.empty()) {
-      /* clang-format off */
+      if (lang == "Fortran") {
+        std::string smodSep =
+          this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP");
+        std::string smodExt =
+          this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT");
+        cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep
+                        << "\")\n";
+        cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt
+                        << "\")\n";
+      }
+
+      // Build a list of preprocessor definitions for the target.
+      std::set<std::string> defines;
+      this->GetTargetDefines(target, this->GetConfigName(), lang, defines);
+      if (!defines.empty()) {
+        /* clang-format off */
       cmakefileStream
         << "\n"
         << "# Preprocessor definitions for this target.\n"
-        << "set(CMAKE_TARGET_DEFINITIONS_" << implicitLang.first << "\n";
-      /* clang-format on */
-      for (std::string const& define : defines) {
-        cmakefileStream << "  " << cmOutputConverter::EscapeForCMake(define)
-                        << "\n";
+        << "set(CMAKE_TARGET_DEFINITIONS_" << lang << "\n";
+        /* clang-format on */
+        for (std::string const& define : defines) {
+          cmakefileStream << "  " << cmOutputConverter::EscapeForCMake(define)
+                          << "\n";
+        }
+        cmakefileStream << "  )\n";
+      }
+
+      // Target-specific include directories:
+      cmakefileStream << "\n"
+                      << "# The include file search paths:\n";
+      cmakefileStream << "set(CMAKE_" << lang << "_TARGET_INCLUDE_PATH\n";
+      std::vector<std::string> includes;
+
+      this->GetIncludeDirectories(includes, target, lang,
+                                  this->GetConfigName());
+      std::string const& binaryDir = this->GetState()->GetBinaryDirectory();
+      if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) {
+        std::string const& sourceDir = this->GetState()->GetSourceDirectory();
+        cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir));
+      }
+      for (std::string const& include : includes) {
+        cmakefileStream << "  \""
+                        << this->MaybeConvertToRelativePath(binaryDir, include)
+                        << "\"\n";
       }
       cmakefileStream << "  )\n";
     }
 
-    // Target-specific include directories:
-    cmakefileStream << "\n"
-                    << "# The include file search paths:\n";
-    cmakefileStream << "set(CMAKE_" << implicitLang.first
-                    << "_TARGET_INCLUDE_PATH\n";
-    std::vector<std::string> includes;
-
-    this->GetIncludeDirectories(includes, target, implicitLang.first,
-                                this->GetConfigName());
-    std::string const& binaryDir = this->GetState()->GetBinaryDirectory();
-    if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) {
-      std::string const& sourceDir = this->GetState()->GetSourceDirectory();
-      cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir));
+    // Store include transform rule properties.  Write the directory
+    // rules first because they may be overridden by later target rules.
+    std::vector<std::string> transformRules;
+    if (cmProp xform =
+          this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
+      cmExpandList(*xform, transformRules);
     }
-    for (std::string const& include : includes) {
-      cmakefileStream << "  \""
-                      << this->MaybeConvertToRelativePath(binaryDir, include)
-                      << "\"\n";
+    if (cmProp xform =
+          target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
+      cmExpandList(*xform, transformRules);
+    }
+    if (!transformRules.empty()) {
+      cmakefileStream << "\nset(CMAKE_INCLUDE_TRANSFORMS\n";
+      for (std::string const& tr : transformRules) {
+        cmakefileStream << "  " << cmOutputConverter::EscapeForCMake(tr)
+                        << "\n";
+      }
+      cmakefileStream << "  )\n";
     }
-    cmakefileStream << "  )\n";
   }
 
-  // Store include transform rule properties.  Write the directory
-  // rules first because they may be overridden by later target rules.
-  std::vector<std::string> transformRules;
-  if (cmProp xform =
-        this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
-    cmExpandList(*xform, transformRules);
-  }
-  if (cmProp xform =
-        target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
-    cmExpandList(*xform, transformRules);
-  }
-  if (!transformRules.empty()) {
-    cmakefileStream << "set(CMAKE_INCLUDE_TRANSFORMS\n";
-    for (std::string const& tr : transformRules) {
-      cmakefileStream << "  " << cmOutputConverter::EscapeForCMake(tr) << "\n";
+  auto const& compilerLangs =
+    this->GetImplicitDepends(target, cmDependencyScannerKind::Compiler);
+
+  // list the dependency files managed by the compiler
+  cmakefileStream << "\n# The set of dependency files which are needed:\n";
+  cmakefileStream << "set(CMAKE_DEPENDS_DEPENDENCY_FILES\n";
+  for (auto const& compilerLang : compilerLangs) {
+    auto depFormat = this->Makefile->GetSafeDefinition(
+      cmStrCat("CMAKE_", compilerLang.first, "_DEPFILE_FORMAT"));
+    auto const& compilerPairs = compilerLang.second;
+    for (auto const& compilerPair : compilerPairs) {
+      for (auto const& src : compilerPair.second) {
+        cmakefileStream << "  \"" << src << "\" \""
+                        << this->MaybeConvertToRelativePath(
+                             this->GetBinaryDirectory(), compilerPair.first)
+                        << "\" \"" << depFormat << "\" \""
+                        << this->MaybeConvertToRelativePath(
+                             this->GetBinaryDirectory(), compilerPair.first)
+                        << ".d\"\n";
+      }
     }
-    cmakefileStream << "  )\n";
   }
+  cmakefileStream << "  )\n";
 }
 
 void cmLocalUnixMakefileGenerator3::WriteDisclaimer(std::ostream& os)

+ 140 - 15
Source/cmMakefileTargetGenerator.cxx

@@ -12,7 +12,9 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/string_view>
 #include <cmext/algorithm>
+#include <cmext/string_view>
 
 #include "cmComputeLinkInformation.h"
 #include "cmCustomCommand.h"
@@ -326,7 +328,45 @@ void cmMakefileTargetGenerator::WriteCommonCodeRules()
     << cmSystemTools::ConvertToOutputPath(
          this->LocalGenerator->MaybeConvertToRelativePath(
            this->LocalGenerator->GetBinaryDirectory(), dependFileNameFull))
-    << "\n\n";
+    << "\n";
+
+  std::string depsUseCompiler = "CMAKE_DEPENDS_USE_COMPILER";
+  if (!this->Makefile->IsDefinitionSet(depsUseCompiler) ||
+      this->Makefile->IsOn(depsUseCompiler)) {
+    std::string compilerDependFile =
+      cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make");
+    *this->BuildFileStream
+      << "# Include any dependencies generated by the "
+         "compiler for this target.\n"
+      << this->GlobalGenerator->IncludeDirective << " " << root
+      << cmSystemTools::ConvertToOutputPath(
+           this->LocalGenerator->MaybeConvertToRelativePath(
+             this->LocalGenerator->GetBinaryDirectory(), compilerDependFile))
+      << "\n\n";
+
+    if (!cmSystemTools::FileExists(compilerDependFile)) {
+      // Write an empty dependency file.
+      cmGeneratedFileStream depFileStream(
+        compilerDependFile, false,
+        this->GlobalGenerator->GetMakefileEncoding());
+      depFileStream << "# Empty compiler generated dependencies file for "
+                    << this->GeneratorTarget->GetName() << ".\n"
+                    << "# This may be replaced when dependencies are built.\n";
+    }
+
+    std::string compilerDependTimestamp =
+      cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts");
+    if (!cmSystemTools::FileExists(compilerDependTimestamp)) {
+      // Write a dependency timestamp file.
+      cmGeneratedFileStream depFileStream(
+        compilerDependTimestamp, false,
+        this->GlobalGenerator->GetMakefileEncoding());
+      depFileStream << "# CMAKE generated file: DO NOT EDIT!\n"
+                    << "# Timestamp file for compiler generated dependencies "
+                       "management for "
+                    << this->GeneratorTarget->GetName() << ".\n";
+    }
+  }
 
   if (!this->NoRuleMessages) {
     // Include the progress variables for the target.
@@ -473,6 +513,14 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     return;
   }
 
+  // Use compiler to generate dependencies, if supported.
+  bool compilerGenerateDeps =
+    this->GlobalGenerator->SupportsCompilerDependencies() &&
+    cmIsOn(this->Makefile->GetDefinition(
+      cmStrCat("CMAKE_", lang, "_DEPENDS_USE_COMPILER")));
+  auto scanner = compilerGenerateDeps ? cmDependencyScannerKind::Compiler
+                                      : cmDependencyScannerKind::CMake;
+
   // Get the full path name of the object file.
   std::string const& objectName =
     this->GeneratorTarget->GetObjectName(&source);
@@ -512,7 +560,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
   std::string srcFullPath =
     cmSystemTools::CollapseFullPath(source.GetFullPath());
   this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang,
-                                           objFullPath, srcFullPath);
+                                           objFullPath, srcFullPath, scanner);
 
   this->LocalGenerator->AppendRuleDepend(depends,
                                          this->FlagFileNameFull.c_str());
@@ -554,8 +602,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
         depends.push_back(
           this->GeneratorTarget->GetPchFile(config, lang, arch));
       }
-      this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang,
-                                               objFullPath, pchHeader);
+      this->LocalGenerator->AddImplicitDepends(
+        this->GeneratorTarget, lang, objFullPath, pchHeader, scanner);
     }
   }
 
@@ -689,7 +737,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     source.GetFullPath(), cmOutputConverter::SHELL);
 
   // Construct the build message.
-  std::vector<std::string> no_commands;
+  std::vector<std::string> no_depends;
   std::vector<std::string> commands;
 
   // add in a progress call if needed
@@ -783,6 +831,26 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
                                     "$(" + lang + "_INCLUDES)");
   vars.Includes = includesString.c_str();
 
+  std::string dependencyTarget;
+  std::string shellDependencyFile;
+  std::string dependencyTimestamp;
+  if (compilerGenerateDeps) {
+    dependencyTarget = this->LocalGenerator->EscapeForShell(
+      this->LocalGenerator->ConvertToMakefilePath(
+        this->LocalGenerator->MaybeConvertToRelativePath(
+          this->LocalGenerator->GetBinaryDirectory(), relativeObj)));
+    vars.DependencyTarget = dependencyTarget.c_str();
+
+    auto depFile = cmStrCat(obj, ".d");
+    shellDependencyFile = this->LocalGenerator->ConvertToOutputFormat(
+      depFile, cmOutputConverter::SHELL);
+    vars.DependencyFile = shellDependencyFile.c_str();
+
+    dependencyTimestamp = this->LocalGenerator->MaybeConvertToRelativePath(
+      this->LocalGenerator->GetBinaryDirectory(),
+      cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"));
+  }
+
   // At the moment, it is assumed that C, C++, Fortran, and CUDA have both
   // assembly and preprocessor capabilities. The same is true for the
   // ability to export compile commands
@@ -954,6 +1022,53 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
       }
     }
 
+    std::string flagsWithDeps(flags);
+
+    if (compilerGenerateDeps) {
+      // Injects dependency computation
+      auto depFlags = this->Makefile->GetSafeDefinition(
+        cmStrCat("CMAKE_DEPFILE_FLAGS_", lang));
+
+      if (!depFlags.empty()) {
+        // Add dependency flags
+        rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
+                                                     depFlags, vars);
+        flagsWithDeps.append(1, ' ');
+        flagsWithDeps.append(depFlags);
+      }
+      vars.Flags = flagsWithDeps.c_str();
+
+      const auto& extraCommands = this->Makefile->GetSafeDefinition(
+        cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS"));
+      if (!extraCommands.empty()) {
+        auto commandList = cmExpandedList(extraCommands);
+        compileCommands.insert(compileCommands.end(), commandList.cbegin(),
+                               commandList.cend());
+      }
+
+      const auto& depFormat = this->Makefile->GetRequiredDefinition(
+        cmStrCat("CMAKE_", lang, "_DEPFILE_FORMAT"));
+
+      if (depFormat == "msvc"_s) {
+        // compiler must be launched through a wrapper to pick-up dependencies
+        std::string depFilter =
+          "$(CMAKE_COMMAND) -E cmake_cl_compile_depends ";
+        depFilter += cmStrCat("--dep-file=", shellDependencyFile);
+        depFilter +=
+          cmStrCat(" --working-dir=",
+                   this->LocalGenerator->ConvertToOutputFormat(
+                     this->LocalGenerator->GetCurrentBinaryDirectory(),
+                     cmOutputConverter::SHELL));
+        const auto& prefix = this->Makefile->GetSafeDefinition(
+          cmStrCat("CMAKE_", lang, "_CL_SHOWINCLUDES_PREFIX"));
+        depFilter += cmStrCat(" --filter-prefix=",
+                              this->LocalGenerator->ConvertToOutputFormat(
+                                prefix, cmOutputConverter::SHELL));
+        depFilter += " -- ";
+        compileCommands.front().insert(0, depFilter);
+      }
+    }
+
     // Expand placeholders in the commands.
     for (std::string& compileCommand : compileCommands) {
       compileCommand = cmStrCat(launcher, compileCommand);
@@ -979,8 +1094,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
       cmExpandList(evaluated_outputs, outputs);
     }
   }
-  if (!ispcHeaderRelative
-         .empty()) { // can't move ispcHeader as vars is using it
+  if (!ispcHeaderRelative.empty()) {
+    // can't move ispcHeader as vars is using it
     outputs.emplace_back(ispcHeaderRelative);
   }
 
@@ -988,10 +1103,19 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     this->CleanFiles.insert(outputs.begin() + 1, outputs.end());
   }
 
+  if (compilerGenerateDeps) {
+    depends.push_back(dependencyTimestamp);
+  }
+
   // Write the rule.
   this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
                       commands);
 
+  if (compilerGenerateDeps) {
+    // set back flags without dependency generation
+    vars.Flags = flags.c_str();
+  }
+
   bool do_preprocess_rules = lang_has_preprocessor &&
     this->LocalGenerator->GetCreatePreprocessedSourceRules();
   bool do_assembly_rules =
@@ -1388,10 +1512,10 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule(
 
     std::string registerFileCmd;
 
-    // The generated register file contains macros that when expanded register
-    // the device routines. Because the routines are the same for all
-    // architectures the register file will be the same too. Thus generate it
-    // only on the first invocation to reduce overhead.
+    // The generated register file contains macros that when expanded
+    // register the device routines. Because the routines are the same for
+    // all architectures the register file will be the same too. Thus
+    // generate it only on the first invocation to reduce overhead.
     if (fatbinaryDepends.size() == 1) {
       std::string registerFileRel =
         this->LocalGenerator->MaybeConvertToRelativePath(
@@ -1426,7 +1550,8 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule(
                                       fatbinaryOutputRel, fatbinaryDepends,
                                       { fatbinaryCommand }, false);
 
-  // Compile the stub that registers the kernels and contains the fatbinaries.
+  // Compile the stub that registers the kernels and contains the
+  // fatbinaries.
   cmRulePlaceholderExpander::RuleVariables vars;
   vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
   vars.CMTargetType =
@@ -1839,9 +1964,9 @@ bool cmMakefileTargetGenerator::CheckUseResponseFileForObjects(
   if (size_t const limit = cmSystemTools::CalculateCommandLineLengthLimit()) {
     // Compute the total length of our list of object files with room
     // for argument separation and quoting.  This does not convert paths
-    // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so the
-    // actual list will likely be much shorter than this.  However, in the
-    // worst case all objects will remain as absolute paths.
+    // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so
+    // the actual list will likely be much shorter than this.  However, in
+    // the worst case all objects will remain as absolute paths.
     size_t length = 0;
     for (std::string const& obj : this->Objects) {
       length += obj.size() + 3;

+ 155 - 28
Source/cmcmd.cxx

@@ -33,6 +33,13 @@
 #  include "bindexplib.h"
 #endif
 
+#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES)
+#  include <algorithm>
+
+#  include "cmCMakePath.h"
+#  include "cmProcessTools.h"
+#endif
+
 #if !defined(CMAKE_BOOTSTRAP) && defined(_WIN32) && !defined(__CYGWIN__)
 #  include "cmVisualStudioWCEPlatformParser.h"
 #endif
@@ -66,6 +73,7 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
                              std::vector<std::string>::const_iterator argEnd);
 
+namespace {
 void CMakeCommandUsage(const char* program)
 {
   std::ostringstream errorStream;
@@ -144,8 +152,7 @@ void CMakeCommandUsage(const char* program)
   cmSystemTools::Error(errorStream.str());
 }
 
-static bool cmTarFilesFrom(std::string const& file,
-                           std::vector<std::string>& files)
+bool cmTarFilesFrom(std::string const& file, std::vector<std::string>& files)
 {
   if (cmSystemTools::FileIsDirectory(file)) {
     std::ostringstream e;
@@ -180,7 +187,7 @@ static bool cmTarFilesFrom(std::string const& file,
   return true;
 }
 
-static void cmCatFile(const std::string& fileToAppend)
+void cmCatFile(const std::string& fileToAppend)
 {
 #ifdef _WIN32
   _setmode(fileno(stdout), _O_BINARY);
@@ -190,7 +197,7 @@ static void cmCatFile(const std::string& fileToAppend)
   std::cout << source.rdbuf();
 }
 
-static bool cmRemoveDirectory(const std::string& dir, bool recursive = true)
+bool cmRemoveDirectory(const std::string& dir, bool recursive = true)
 {
   if (cmSystemTools::FileIsSymlink(dir)) {
     if (!cmSystemTools::RemoveFile(dir)) {
@@ -208,9 +215,123 @@ static bool cmRemoveDirectory(const std::string& dir, bool recursive = true)
   return true;
 }
 
-static int HandleIWYU(const std::string& runCmd,
-                      const std::string& /* sourceFile */,
-                      const std::vector<std::string>& orig_cmd)
+#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES)
+class CLIncludeParser : public cmProcessTools::LineParser
+{
+public:
+  CLIncludeParser(cm::string_view includePrefix, cmsys::ofstream& depFile,
+                  std::ostream& output)
+    : IncludePrefix(includePrefix)
+    , DepFile(depFile)
+    , Output(output)
+  {
+  }
+
+private:
+  bool ProcessLine() override
+  {
+    if (cmHasPrefix(this->Line, this->IncludePrefix)) {
+      this->DepFile << cmCMakePath(
+                         cmTrimWhitespace(this->Line.c_str() +
+                                          this->IncludePrefix.size()))
+                         .GenericString()
+                    << std::endl;
+    } else {
+      this->Output << this->Line << std::endl << std::flush;
+    }
+
+    return true;
+  }
+
+  cm::string_view IncludePrefix;
+  cmsys::ofstream& DepFile;
+  std::ostream& Output;
+};
+
+class CLOutputLogger : public cmProcessTools::OutputLogger
+{
+public:
+  CLOutputLogger(std::ostream& log)
+    : cmProcessTools::OutputLogger(log)
+  {
+  }
+
+  bool ProcessLine() override
+  {
+    *this->Log << std::flush;
+    return true;
+  }
+};
+
+int CLCompileAndDependencies(const std::vector<std::string>& args)
+{
+  std::string depFile;
+  std::string currentBinaryDir;
+  std::string filterPrefix;
+  std::vector<std::string> command;
+  for (auto it = args.cbegin() + 2; it != args.cend(); it++) {
+    if (cmHasLiteralPrefix(*it, "--dep-file=")) {
+      depFile = it->substr(11);
+    } else if (cmHasLiteralPrefix(*it, "--working-dir=")) {
+      currentBinaryDir = it->substr(14);
+    } else if (cmHasLiteralPrefix(*it, "--filter-prefix=")) {
+      filterPrefix = it->substr(16);
+    } else if (*it == "--") {
+      command.insert(command.begin(), ++it, args.cend());
+      break;
+    } else {
+      return 1;
+    }
+  }
+
+  std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp(
+    cmsysProcess_New(), cmsysProcess_Delete);
+  std::vector<const char*> argv(command.size() + 1);
+  std::transform(command.begin(), command.end(), argv.begin(),
+                 [](std::string const& s) { return s.c_str(); });
+  argv.back() = nullptr;
+  cmsysProcess_SetCommand(cp.get(), argv.data());
+  cmsysProcess_SetWorkingDirectory(cp.get(), currentBinaryDir.c_str());
+
+  cmsys::ofstream fout(depFile.c_str());
+  if (!fout) {
+    return 3;
+  }
+
+  CLIncludeParser includeParser(filterPrefix, fout, std::cout);
+  CLOutputLogger errLogger(std::cerr);
+
+  // Start the process.
+  cmProcessTools::RunProcess(cp.get(), &includeParser, &errLogger);
+
+  int status = 0;
+  // handle status of process
+  switch (cmsysProcess_GetState(cp.get())) {
+    case cmsysProcess_State_Exited:
+      status = cmsysProcess_GetExitValue(cp.get());
+      break;
+    case cmsysProcess_State_Exception:
+      status = 1;
+      break;
+    case cmsysProcess_State_Error:
+      status = 2;
+      break;
+    default:
+      break;
+  }
+
+  if (status != 0) {
+    // remove the dependencies file because potentially invalid
+    fout.close();
+    cmSystemTools::RemoveFile(depFile);
+  }
+
+  return status;
+}
+#endif
+
+int HandleIWYU(const std::string& runCmd, const std::string& /* sourceFile */,
+               const std::vector<std::string>& orig_cmd)
 {
   // Construct the iwyu command line by taking what was given
   // and adding all the arguments we give to the compiler.
@@ -235,8 +356,8 @@ static int HandleIWYU(const std::string& runCmd,
   return 0;
 }
 
-static int HandleTidy(const std::string& runCmd, const std::string& sourceFile,
-                      const std::vector<std::string>& orig_cmd)
+int HandleTidy(const std::string& runCmd, const std::string& sourceFile,
+               const std::vector<std::string>& orig_cmd)
 {
   // Construct the clang-tidy command line by taking what was given
   // and adding our compiler command line.  The clang-tidy tool will
@@ -265,9 +386,8 @@ static int HandleTidy(const std::string& runCmd, const std::string& sourceFile,
   return ret;
 }
 
-static int HandleLWYU(const std::string& runCmd,
-                      const std::string& /* sourceFile */,
-                      const std::vector<std::string>&)
+int HandleLWYU(const std::string& runCmd, const std::string& /* sourceFile */,
+               const std::vector<std::string>&)
 {
   // Construct the ldd -r -u (link what you use lwyu) command line
   // ldd -u -r lwuy target
@@ -298,9 +418,8 @@ static int HandleLWYU(const std::string& runCmd,
   return 0;
 }
 
-static int HandleCppLint(const std::string& runCmd,
-                         const std::string& sourceFile,
-                         const std::vector<std::string>&)
+int HandleCppLint(const std::string& runCmd, const std::string& sourceFile,
+                  const std::vector<std::string>&)
 {
   // Construct the cpplint command line.
   std::vector<std::string> cpplint_cmd = cmExpandedList(runCmd, true);
@@ -326,9 +445,8 @@ static int HandleCppLint(const std::string& runCmd,
   return 0;
 }
 
-static int HandleCppCheck(const std::string& runCmd,
-                          const std::string& sourceFile,
-                          const std::vector<std::string>& orig_cmd)
+int HandleCppCheck(const std::string& runCmd, const std::string& sourceFile,
+                   const std::vector<std::string>& orig_cmd)
 {
   // Construct the cpplint command line.
   std::vector<std::string> cppcheck_cmd = cmExpandedList(runCmd, true);
@@ -391,7 +509,7 @@ struct CoCompiler
   bool NoOriginalCommand;
 };
 
-static const std::array<CoCompiler, 5> CoCompilers = {
+const std::array<CoCompiler, 5> CoCompilers = {
   { // Table of options and handlers.
     { "--cppcheck=", HandleCppCheck, false },
     { "--cpplint=", HandleCppLint, false },
@@ -405,6 +523,7 @@ struct CoCompileJob
   std::string Command;
   CoCompileHandler Handler;
 };
+}
 
 // called when args[0] == "__run_co_compile"
 int cmcmd::HandleCoCompileCommands(std::vector<std::string> const& args)
@@ -586,7 +705,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
       } else if (args[2] == "--ignore-eol") {
         filesDiffer = cmsys::SystemTools::TextFilesDiffer(args[3], args[4]);
       } else {
-        ::CMakeCommandUsage(args[0].c_str());
+        CMakeCommandUsage(args[0].c_str());
         return 2;
       }
 
@@ -621,8 +740,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
           }
         }
         if (outValid) {
-          // The def file already exists and all input files are older than the
-          // existing def file.
+          // The def file already exists and all input files are older than
+          // the existing def file.
           return 0;
         }
       }
@@ -1162,6 +1281,13 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
       return 1;
     }
 
+#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES)
+    // Internal CMake compiler dependencies filtering
+    if (args[1] == "cmake_cl_compile_depends") {
+      return CLCompileAndDependencies(args);
+    }
+#endif
+
     // Internal CMake link script support.
     if (args[1] == "cmake_link_script" && args.size() >= 3) {
       return cmcmd::ExecuteLinkScript(args);
@@ -1412,7 +1538,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
     }
   }
 
-  ::CMakeCommandUsage(args[0].c_str());
+  CMakeCommandUsage(args[0].c_str());
   return 1;
 }
 
@@ -1779,8 +1905,8 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args)
       skipNextArg = false;
       continue;
     }
-    // We use ++ as seperator between the preprocessing step definition and the
-    // rc compilation step becase we need to prepend a -- to seperate the
+    // We use ++ as seperator between the preprocessing step definition and
+    // the rc compilation step becase we need to prepend a -- to seperate the
     // source file properly from other options when using clang-cl for
     // preprocessing.
     if (arg == "++") {
@@ -1830,7 +1956,8 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args)
     return 1;
   }
   // Since we might have skipped the last argument to llvm-rc
-  // we need to make sure the llvm-rc source file is present in the commandline
+  // we need to make sure the llvm-rc source file is present in the
+  // commandline
   if (resource_compile.back() != intermediate_file) {
     resource_compile.push_back(intermediate_file);
   }
@@ -2123,8 +2250,8 @@ int cmVSLink::LinkIncremental()
   // http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx
 
   //    1.  Compiler compiles the application and generates the *.obj files.
-  //    2.  An empty manifest file is generated if this is a clean build and if
-  //    not the previous one is reused.
+  //    2.  An empty manifest file is generated if this is a clean build and
+  //    if not the previous one is reused.
   //    3.  The resource compiler (rc.exe) compiles the *.manifest file to a
   //    *.res file.
   //    4.  Linker generates the binary (EXE or DLL) with the /incremental

+ 6 - 0
Tests/BuildDepends/Project/CMakeLists.txt

@@ -102,6 +102,12 @@ target_link_libraries(zot zot_pch)
 if(NOT CMAKE_OSX_ARCHITECTURES MATCHES "[;$]")
   target_precompile_headers(zot_pch PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/zot_pch.hxx)
 endif()
+if (CMAKE_CXX_DEPENDS_USE_COMPILER AND
+    CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.4")
+  # Mixing pre-compile headers and flags to generate dependencies (-M options family)
+  # causes the compiler to crash
+  set_property(TARGET zot_pch PROPERTY DISABLE_PRECOMPILE_HEADERS ON)
+endif()
 
 # Test the #include line macro transformation rule support.
 set_property(

+ 5 - 1
Tests/IncludeDirectories/CMakeLists.txt

@@ -67,7 +67,11 @@ else()
 endif()
 
 # Test escaping of special characters in include directory paths.
-set(special_chars "~@%&{}()!'")
+set(special_chars "~@&{}()!'")
+if(NOT CMAKE_GENERATOR MATCHES "(Unix|MinGW|MSYS) Makefiles")
+  # when compiler is used for dependencies, special characters for make are not escaped
+  string(APPEND special_chars "%")
+endif()
 if(NOT CMAKE_GENERATOR STREQUAL "Watcom WMake")
   # Watcom seems to have no way to encode these characters.
   string(APPEND special_chars "#=[]")

+ 46 - 0
Tests/RunCMake/BuildDepends/CompilerDependencies.cmake

@@ -0,0 +1,46 @@
+enable_language(C)
+
+add_executable(main ${CMAKE_CURRENT_BINARY_DIR}/main.c)
+
+file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake CONTENT "
+set(check_pairs
+  \"$<TARGET_FILE:main>|${CMAKE_CURRENT_BINARY_DIR}/main.c\"
+  \"$<TARGET_FILE:main>|${CMAKE_CURRENT_BINARY_DIR}/main.h\"
+  )
+set(check_exes
+  \"$<TARGET_FILE:main>\"
+  )
+
+if (check_step EQUAL 2)
+  include(\"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/Makefile.cmake\")
+  if (NOT CMAKE_DEPEND_INFO_FILES)
+    set(RunCMake_TEST_FAILED \"Variable CMAKE_DEPEND_INFO_FILES not found.\")
+  else()
+    include(\"${CMAKE_CURRENT_BINARY_DIR}/\${CMAKE_DEPEND_INFO_FILES}\")
+    if (NOT CMAKE_DEPENDS_DEPENDENCY_FILES)
+      set(RunCMake_TEST_FAILED \"Variable CMAKE_DEPENDS_DEPENDENCY_FILES not found.\")
+    else()
+      list(GET CMAKE_DEPENDS_DEPENDENCY_FILES 1 OBJECT_FILE)
+      list(GET CMAKE_DEPENDS_DEPENDENCY_FILES 3 DEP_FILE)
+      if (NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/\${DEP_FILE}\")
+        set(RunCMake_TEST_FAILED \"File \${DEP_FILE} not found.\")
+      else()
+        set (TARGET_DEP_FILE \"${CMAKE_CURRENT_BINARY_DIR}/\${DEP_FILE}\")
+        cmake_path(REPLACE_FILENAME TARGET_DEP_FILE \"compiler_depend.make\")
+        file(READ \"\${TARGET_DEP_FILE}\" DEPENDS_CONTENT)
+        if (WIN32)
+          string (REPLACE \"\\\\\" \"/\" DEPENDS_CONTENT \"\${DEPENDS_CONTENT}\")
+          string (TOLOWER \"\${DEPENDS_CONTENT}\" DEPENDS_CONTENT)
+          string (TOLOWER \"\${OBJECT_FILE}\" OBJECT_FILE)
+        else()
+          string(REPLACE \"\\\\ \" \" \" DEPENDS_CONTENT \"\${DEPENDS_CONTENT}\")
+        endif()
+        if(NOT DEPENDS_CONTENT MATCHES \"\${OBJECT_FILE} *:.+main.c\"
+            OR NOT DEPENDS_CONTENT MATCHES \"main.h\")
+          set(RunCMake_TEST_FAILED \"Dependency file '\${TARGET_DEP_FILE}' badly generated.\")
+        endif()
+      endif()
+    endif()
+  endif()
+endif()
+")

+ 9 - 0
Tests/RunCMake/BuildDepends/CompilerDependencies.step1.cmake

@@ -0,0 +1,9 @@
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/main.h" [[
+#define COUNT 1
+]])
+
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/main.c" [[
+#include "main.h"
+
+int main(void) { return COUNT; }
+]])

+ 3 - 0
Tests/RunCMake/BuildDepends/CompilerDependencies.step2.cmake

@@ -0,0 +1,3 @@
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/main.h" [[
+#define COUNT 2
+]])

+ 10 - 0
Tests/RunCMake/BuildDepends/RunCMakeTest.cmake

@@ -113,3 +113,13 @@ if(CMake_TEST_BuildDepends_GNU_AS)
   set(ENV{ASM} "${CMake_TEST_BuildDepends_GNU_AS}")
   run_BuildDepends(GNU-AS)
 endif()
+
+if ((RunCMake_GENERATOR STREQUAL "Unix Makefiles"
+      AND (CMAKE_C_COMPILER_ID STREQUAL "GNU"
+        OR CMAKE_C_COMPILER_ID STREQUAL "Clang"
+        OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang"))
+    OR (RunCMake_GENERATOR STREQUAL "NMake Makefiles"
+      AND MSVC_VERSION GREATER 1300
+      AND CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
+  run_BuildDepends(CompilerDependencies)
+endif()

+ 3 - 0
Tests/RunCMake/CMakeLists.txt

@@ -203,7 +203,10 @@ if(NOT DEFINED CMake_TEST_BuildDepends_GNU_AS
     set(CMake_TEST_BuildDepends_GNU_AS "${_gnu_as}")
   endif()
 endif()
+
 add_RunCMake_test(BuildDepends
+  -DMSVC_VERSION=${MSVC_VERSION}
+  -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
   -DCMake_TEST_BuildDepends_GNU_AS=${CMake_TEST_BuildDepends_GNU_AS}
   )
 if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")

+ 3 - 0
Tests/RunCMake/Make/TargetMessages-OFF-build-check.cmake

@@ -0,0 +1,3 @@
+
+set (CHECK_TARGET_MESSAGES OFF)
+include ("${CMAKE_CURRENT_LIST_DIR}/TargetMessages-validation.cmake")

+ 1 - 5
Tests/RunCMake/Make/TargetMessages-OFF-build-stdout.txt

@@ -1,5 +1 @@
-^(([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*
-)*Scanning dependencies of target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*$
+.*

+ 3 - 0
Tests/RunCMake/Make/TargetMessages-ON-build-check.cmake

@@ -0,0 +1,3 @@
+
+set (CHECK_TARGET_MESSAGES ON)
+include ("${CMAKE_CURRENT_LIST_DIR}/TargetMessages-validation.cmake")

+ 1 - 8
Tests/RunCMake/Make/TargetMessages-ON-build-stdout.txt

@@ -1,8 +1 @@
-^(([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*
-)*Scanning dependencies of target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*
-Built target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*$
+.*

+ 3 - 0
Tests/RunCMake/Make/TargetMessages-VAR-OFF-build-check.cmake

@@ -0,0 +1,3 @@
+
+set (CHECK_TARGET_MESSAGES OFF)
+include ("${CMAKE_CURRENT_LIST_DIR}/TargetMessages-validation.cmake")

+ 1 - 5
Tests/RunCMake/Make/TargetMessages-VAR-OFF-build-stdout.txt

@@ -1,5 +1 @@
-^(([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*
-)*Scanning dependencies of target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*$
+.*

+ 3 - 0
Tests/RunCMake/Make/TargetMessages-VAR-ON-build-check.cmake

@@ -0,0 +1,3 @@
+
+set (CHECK_TARGET_MESSAGES ON)
+include ("${CMAKE_CURRENT_LIST_DIR}/TargetMessages-validation.cmake")

+ 1 - 8
Tests/RunCMake/Make/TargetMessages-VAR-ON-build-stdout.txt

@@ -1,8 +1 @@
-^(([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*
-)*Scanning dependencies of target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*
-Built target CustomTarget(
-([^B]|B[^u]|Bu[^i]|Bui[^l]|Buil[^t]|Built[^ ])[^
-]*)*$
+.*

+ 10 - 0
Tests/RunCMake/Make/TargetMessages-validation.cmake

@@ -0,0 +1,10 @@
+
+if (CHECK_TARGET_MESSAGES)
+  if (NOT actual_stdout MATCHES "Built target CustomTarget")
+    set (RunCMake_TEST_FAILED "Not found expected 'Built target' message.")
+  endif()
+else()
+  if (actual_stdout MATCHES "Built target CustomTarget")
+    set (RunCMake_TEST_FAILED "Found unexpected 'Built target' message.")
+  endif()
+endif()

+ 2 - 0
bootstrap

@@ -1054,12 +1054,14 @@ else
   CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES} \
     cmDepends \
     cmDependsC \
+    cmDependsCompiler \
     cmGlobalUnixMakefileGenerator3 \
     cmLocalUnixMakefileGenerator3 \
     cmMakefileExecutableTargetGenerator \
     cmMakefileLibraryTargetGenerator \
     cmMakefileTargetGenerator \
     cmMakefileUtilityTargetGenerator \
+    cmProcessTools \
     "
 
   JSONCPP_CXX_SOURCES=