Browse Source

FASTBuild: Add generator

Fixes: #15294
Eduard Voronkin 2 months ago
parent
commit
01147454e7
84 changed files with 6336 additions and 65 deletions
  1. 8 0
      Help/envvar/CMAKE_FASTBUILD_VERBOSE_GENERATOR.rst
  2. 69 0
      Help/generator/FASTBuild.rst
  3. 1 0
      Help/manual/cmake-env-variables.7.rst
  4. 8 0
      Help/manual/cmake-generators.7.rst
  5. 7 0
      Help/manual/cmake-variables.7.rst
  6. 4 0
      Help/release/dev/fastbuild.rst
  7. 9 0
      Help/variable/CMAKE_FASTBUILD_CACHE_PATH.rst
  8. 13 0
      Help/variable/CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV.rst
  9. 8 0
      Help/variable/CMAKE_FASTBUILD_COMPILER_EXTRA_FILES.rst
  10. 30 0
      Help/variable/CMAKE_FASTBUILD_ENV_OVERRIDES.rst
  11. 15 0
      Help/variable/CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT.rst
  12. 8 0
      Help/variable/CMAKE_FASTBUILD_USE_LIGHTCACHE.rst
  13. 9 0
      Help/variable/CMAKE_FASTBUILD_VERBOSE_GENERATOR.rst
  14. 4 3
      Modules/CMakeDetermineCUDACompiler.cmake
  15. 1 1
      Modules/CMakeDetermineCompilerId.cmake
  16. 7 0
      Modules/CMakeFastbuildFindMake.cmake
  17. 4 1
      Modules/CMakeParseImplicitLinkInfo.cmake
  18. 1 1
      Modules/CTestTargets.cmake
  19. 2 2
      Modules/CTestUseLaunchers.cmake
  20. 8 2
      Modules/Compiler/Clang.cmake
  21. 3 3
      Modules/ExternalProject.cmake
  22. 1 1
      Modules/Platform/Windows-MSVC.cmake
  23. 12 0
      Source/CMakeLists.txt
  24. 2 1
      Source/CTest/cmCTestBuildAndTest.cxx
  25. 2 1
      Source/cmCommonTargetGenerator.cxx
  26. 22 0
      Source/cmFastbuildLinkLineComputer.cxx
  27. 31 0
      Source/cmFastbuildLinkLineComputer.h
  28. 1948 0
      Source/cmFastbuildNormalTargetGenerator.cxx
  29. 141 0
      Source/cmFastbuildNormalTargetGenerator.h
  30. 896 0
      Source/cmFastbuildTargetGenerator.cxx
  31. 158 0
      Source/cmFastbuildTargetGenerator.h
  32. 126 0
      Source/cmFastbuildUtilityTargetGenerator.cxx
  33. 18 0
      Source/cmFastbuildUtilityTargetGenerator.h
  34. 4 0
      Source/cmGeneratorExpressionNode.cxx
  35. 21 4
      Source/cmGeneratorTarget.cxx
  36. 1848 0
      Source/cmGlobalFastbuildGenerator.cxx
  37. 602 0
      Source/cmGlobalFastbuildGenerator.h
  38. 1 0
      Source/cmGlobalGenerator.cxx
  39. 2 0
      Source/cmGlobalGenerator.h
  40. 1 1
      Source/cmLinkLineComputer.h
  41. 96 0
      Source/cmLocalFastbuildGenerator.cxx
  42. 35 0
      Source/cmLocalFastbuildGenerator.h
  43. 12 2
      Source/cmLocalGenerator.cxx
  44. 12 0
      Source/cmOutputConverter.cxx
  45. 2 1
      Source/cmOutputConverter.h
  46. 10 0
      Source/cmState.cxx
  47. 3 0
      Source/cmState.h
  48. 21 0
      Source/cmake.cxx
  49. 12 0
      Source/cmcmd.cxx
  50. 1 1
      Tests/BuildDepends/CMakeLists.txt
  51. 1 1
      Tests/BuildDepends/Project/CMakeLists.txt
  52. 5 6
      Tests/CMakeLists.txt
  53. 1 1
      Tests/CheckSwift.cmake
  54. 6 0
      Tests/CustomCommand/CMakeLists.txt
  55. 1 1
      Tests/ExportImport/Export/CMakeLists.txt
  56. 2 2
      Tests/ExportImport/Import/A/CMakeLists.txt
  57. 4 0
      Tests/FortranModules/CMakeLists.txt
  58. 1 1
      Tests/GeneratorExpression/CMakeLists.txt
  59. 1 0
      Tests/IncludeDirectories/CMakeLists.txt
  60. 1 2
      Tests/PDBDirectoryAndName/CMakeLists.txt
  61. 1 2
      Tests/PrecompiledHeader/CMakeLists.txt
  62. 6 1
      Tests/Preprocess/CMakeLists.txt
  63. 1 1
      Tests/RunCMake/AutoExportDll/RunCMakeTest.cmake
  64. 2 2
      Tests/RunCMake/Autogen_1/RunCMakeTest.cmake
  65. 3 0
      Tests/RunCMake/Autogen_1/check-fancy-generated.cmake
  66. 8 2
      Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
  67. 1 1
      Tests/RunCMake/BuiltinTargets/RunCMakeTest.cmake
  68. 1 1
      Tests/RunCMake/CMP0037/RunCMakeTest.cmake
  69. 3 3
      Tests/RunCMake/CMakeLists.txt
  70. 1 1
      Tests/RunCMake/CTest/RunCMakeTest.cmake
  71. 3 0
      Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
  72. 1 1
      Tests/RunCMake/ExternalProject/EnvVars-build-stdout.txt
  73. 2 2
      Tests/RunCMake/ExternalProject/LogOutputOnFailure-build-stdout.txt
  74. 2 2
      Tests/RunCMake/ExternalProject/LogOutputOnFailureMerged-build-stdout.txt
  75. 3 0
      Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake
  76. 1 1
      Tests/RunCMake/MultiLint/C-Build-stdout.txt
  77. 1 1
      Tests/RunCMake/MultiLint/C-launch-Build-stdout.txt
  78. 1 1
      Tests/RunCMake/MultiLint/CXX-Build-stdout.txt
  79. 1 1
      Tests/RunCMake/MultiLint/CXX-launch-Build-stdout.txt
  80. 1 1
      Tests/RunCMake/XcFramework/RunCMakeTest.cmake
  81. 2 0
      Tests/RunCMake/add_custom_target/RunCMakeTest.cmake
  82. 7 0
      Tests/RunCMake/property_init/CompileSources.cmake
  83. 1 1
      Tests/RunCMake/try_compile/ConfigureLog-config.txt
  84. 1 1
      Tests/RunCMake/try_run/ConfigureLog-config.txt

+ 8 - 0
Help/envvar/CMAKE_FASTBUILD_VERBOSE_GENERATOR.rst

@@ -0,0 +1,8 @@
+CMAKE_FASTBUILD_VERBOSE_GENERATOR
+---------------------------------
+
+.. include:: include/ENV_VAR.rst
+
+The ``CMAKE_FASTBUILD_VERBOSE_GENERATOR`` environment variable specifies a custom default
+value for the :variable:`CMAKE_FASTBUILD_VERBOSE_GENERATOR` variable in place of the
+default values specified by CMake itself.

+ 69 - 0
Help/generator/FASTBuild.rst

@@ -0,0 +1,69 @@
+FASTBuild
+=========
+
+Generates a ``fbuild.bff`` file, which can be used to build the project with
+`FASTBuild <https://www.fastbuild.org/docs/home.html>`_.
+
+Usage
+-----
+
+Specify the generator when invoking :manual:`cmake(1)`:
+
+.. code-block:: shell
+
+  cmake [<options>] -G FASTBuild -B <path-to-build> [-S <path-to-source>]
+
+This writes a FASTBuild configuration file named ``fbuild.bff`` into
+``<path-to-build>``.
+
+.. note::
+
+   This generator also produces IDE project files for Visual Studio and Xcode,
+   which are placed under:
+
+   * ``<path-to-build>/VisualStudio`` – Visual Studio solution and projects
+   * ``<path-to-build>/XCode`` – Xcode workspace and projects
+
+   These IDE files can be generated by building ``xcode`` or ``solution`` targets
+   and will build using FASTBuild as the backend.
+
+Configuration Variables
+-----------------------
+
+The following variables can be used to configure this generator:
+
+* :variable:`CMAKE_FASTBUILD_CACHE_PATH`
+* :variable:`CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV`
+* :variable:`CMAKE_FASTBUILD_COMPILER_EXTRA_FILES`
+* :variable:`CMAKE_FASTBUILD_ENV_OVERRIDES`
+* :variable:`CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT`
+* :variable:`CMAKE_FASTBUILD_USE_LIGHTCACHE`
+* :variable:`CMAKE_FASTBUILD_VERBOSE_GENERATOR`
+
+Notes
+-----
+
+* This generator does not support directories as outputs of custom commands.
+  If you do specify a directory as an output, it should be marked with the
+  ``SYMBOLIC`` property to avoid incorrect behavior.
+* It is highly advised to use custom commands with only one output.
+  If multiple outputs are specified, the generator will emit an additional
+  rule to check the outputs at build time. This adds overhead and is necessary
+  because FASTBuild natively supports only a single output per custom step.
+
+Example
+-------
+
+.. code-block:: shell
+
+  cmake [<options>] -G FASTBuild -B <path-to-build> -DCMAKE_BUILD_TYPE=Release
+  cmake --build <path-to-build> --target my_app
+
+This generates ``fbuild.bff`` in ``<path-to-build>`` and uses FASTBuild
+to build the ``my_app`` target.
+
+See Also
+--------
+
+* :manual:`cmake-generators(7)`
+* `FASTBuild Documentation <https://www.fastbuild.org/docs/documentation.html>`_

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

@@ -54,6 +54,7 @@ Environment Variables that Control the Build
    /envvar/CMAKE_CROSSCOMPILING_EMULATOR
    /envvar/CMAKE_EXPORT_BUILD_DATABASE
    /envvar/CMAKE_EXPORT_COMPILE_COMMANDS
+   /envvar/CMAKE_FASTBUILD_VERBOSE_GENERATOR
    /envvar/CMAKE_GENERATOR
    /envvar/CMAKE_GENERATOR_INSTANCE
    /envvar/CMAKE_GENERATOR_PLATFORM

+ 8 - 0
Help/manual/cmake-generators.7.rst

@@ -63,6 +63,14 @@ Ninja Generators
    /generator/Ninja
    /generator/Ninja Multi-Config
 
+FASTBuild Generator
+^^^^^^^^^^^^^^^^^^^
+
+.. toctree::
+   :maxdepth: 1
+
+   /generator/FASTBuild
+
 .. _`IDE Build Tool Generators`:
 
 IDE Build Tool Generators

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

@@ -55,6 +55,13 @@ Variables that Provide Information
    /variable/CMAKE_EXECUTABLE_SUFFIX
    /variable/CMAKE_EXECUTABLE_SUFFIX_LANG
    /variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES
+   /variable/CMAKE_FASTBUILD_CACHE_PATH
+   /variable/CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV
+   /variable/CMAKE_FASTBUILD_COMPILER_EXTRA_FILES
+   /variable/CMAKE_FASTBUILD_ENV_OVERRIDES
+   /variable/CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT
+   /variable/CMAKE_FASTBUILD_USE_LIGHTCACHE
+   /variable/CMAKE_FASTBUILD_VERBOSE_GENERATOR
    /variable/CMAKE_FIND_DEBUG_MODE
    /variable/CMAKE_FIND_DEBUG_MODE_NO_IMPLICIT_CONFIGURE_LOG
    /variable/CMAKE_FIND_PACKAGE_NAME

+ 4 - 0
Help/release/dev/fastbuild.rst

@@ -0,0 +1,4 @@
+FASTBuild
+---------
+
+* The :generator:`FASTBuild` generator was added.

+ 9 - 0
Help/variable/CMAKE_FASTBUILD_CACHE_PATH.rst

@@ -0,0 +1,9 @@
+CMAKE_FASTBUILD_CACHE_PATH
+---------------------------
+
+Specifies the directory for the FASTBuild artifact cache.
+
+Defaults to :variable:`CMAKE_BINARY_DIR` ``/fbuild.cache`` if not set.
+
+See the `FASTBuild caching documentation <https://www.fastbuild.org/docs/features/caching.html>`_
+for more information.

+ 13 - 0
Help/variable/CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV.rst

@@ -0,0 +1,13 @@
+CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV
+----------------------------------
+
+Controls capturing of the system environment into ``fbuild.bff``.
+Setting it to ``OFF`` makes the invocation of all tools (compilers and other external processes) hermetic.
+
+.. note::
+
+   Setting this variable to ``OFF`` can break MSVC toolchains that rely on
+   environment variables such as ``INCLUDE`` or ``LIB`` unless these are
+   manually configured elsewhere.
+
+Defaults to ``ON``.

+ 8 - 0
Help/variable/CMAKE_FASTBUILD_COMPILER_EXTRA_FILES.rst

@@ -0,0 +1,8 @@
+CMAKE_FASTBUILD_COMPILER_EXTRA_FILES
+-------------------------------------
+
+Specifies a semicolon-separated list of additional files (usually DLLs) to
+include in the FASTBuild ``Compiler()`` node.
+
+See the `FASTBuild Compiler() documentation <https://www.fastbuild.org/docs/functions/compiler.html>`_
+for more information.

+ 30 - 0
Help/variable/CMAKE_FASTBUILD_ENV_OVERRIDES.rst

@@ -0,0 +1,30 @@
+CMAKE_FASTBUILD_ENV_OVERRIDES
+-----------------------------
+
+Allows overriding environment variables in the captured environment written to
+``fbuild.bff``.
+
+Specify a CMake-style list of key=value pairs. These values will override the
+corresponding variables in the environment block that FASTBuild uses during
+execution of tools (e.g., compilers, linkers, resource compilers, etc.).
+
+This is especially useful for ensuring consistent behavior when tools depend
+on environment variables (e.g., overriding ``PATH`` to control tool resolution
+for ``rc.exe`` or ``mt.exe``).
+
+Example:
+
+.. code-block:: cmake
+
+   set(CMAKE_FASTBUILD_ENV_OVERRIDES
+       "PATH=C:/MyTools/bin"
+       "TMP=C:/temp"
+       "MY_CUSTOM_VAR=some_value"
+   )
+
+.. note::
+
+   This only affects the environment seen by FASTBuild-generated rules.
+   It does **not** modify the environment in which CMake itself runs.
+
+Defaults to empty (no overrides).

+ 15 - 0
Help/variable/CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT.rst

@@ -0,0 +1,15 @@
+CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT
+------------------------------------------
+
+By default, custom commands declaring only ``BYPRODUCTS`` will always
+run unconditionally.
+You can use this variable to make FASTBuild rerun the command only when its
+inputs have changed or the byproduct file is missing.
+
+.. note::
+
+   When this variable is ``OFF`` (the default), ``BYPRODUCTS`` are treated
+   similarly to how Ninja handles them — as opaque side effects — and the
+   generator emits ``ExecAlways`` nodes to ensure they always run.
+
+Defaults to ``OFF``.

+ 8 - 0
Help/variable/CMAKE_FASTBUILD_USE_LIGHTCACHE.rst

@@ -0,0 +1,8 @@
+CMAKE_FASTBUILD_USE_LIGHTCACHE
+-------------------------------
+
+Enables FASTBuild’s *light caching* mode, which accelerates cache lookups by
+parsing source files directly (instead of invoking the compiler preprocessor).
+
+See the `FASTBuild Compiler() documentation <https://www.fastbuild.org/docs/functions/compiler.html>`_
+for more information.

+ 9 - 0
Help/variable/CMAKE_FASTBUILD_VERBOSE_GENERATOR.rst

@@ -0,0 +1,9 @@
+CMAKE_FASTBUILD_VERBOSE_GENERATOR
+----------------------------------
+
+Enables verbose logging during FASTBuild file generation.
+
+Initialized by the :envvar:`CMAKE_FASTBUILD_VERBOSE_GENERATOR`
+environment variable.
+
+Defaults to ``OFF``.

+ 4 - 3
Modules/CMakeDetermineCUDACompiler.cmake

@@ -4,9 +4,10 @@
 include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake)
 include(${CMAKE_ROOT}/Modules/CMakeParseImplicitLinkInfo.cmake)
 
-if(NOT ((CMAKE_GENERATOR MATCHES "Make") OR
-        (CMAKE_GENERATOR MATCHES "Ninja") OR
-        (CMAKE_GENERATOR MATCHES "Visual Studio (1|[9][0-9])")))
+if( NOT ( ("${CMAKE_GENERATOR}" MATCHES "Make") OR
+          ("${CMAKE_GENERATOR}" MATCHES "Ninja") OR
+          ("${CMAKE_GENERATOR}" MATCHES "FASTBuild") OR
+          ("${CMAKE_GENERATOR}" MATCHES "Visual Studio (1|[9][0-9])") ) )
   message(FATAL_ERROR "CUDA language not currently supported by \"${CMAKE_GENERATOR}\" generator")
 endif()
 

+ 1 - 1
Modules/CMakeDetermineCompilerId.cmake

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

+ 7 - 0
Modules/CMakeFastbuildFindMake.cmake

@@ -0,0 +1,7 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+find_program(CMAKE_MAKE_PROGRAM
+  NAMES fbuild
+  DOC "Program used to build from FASTBuild .bff files.")
+mark_as_advanced(CMAKE_MAKE_PROGRAM)

+ 4 - 1
Modules/CMakeParseImplicitLinkInfo.cmake

@@ -86,7 +86,10 @@ function(cmake_parse_implicit_link_info2 text log_var obj_regex)
   # whole line and just the command (argv[0]).
   set(linker_regex "^( *|.*[/\\])(${linker}|${startfile}|([^/\\]+-)?ld|collect2)[^/\\]*( |$)")
   set(linker_exclude_regex "collect2 version |^[A-Za-z0-9_]+=|/ldfe ")
-  set(linker_tool_regex "^[ \t]*(->|\")?[ \t]*(([^\"]*[/\\])?(${linker}))(\"|,| |$)")
+
+  # Skip FASTBuild's output mangling, like:
+  # "2> -Build: 0 ms    .../link.exe"/"13>-Build: 0 ms    .../link.exe"
+  set(linker_tool_regex "^[ \t]*(->|\"|[0-9]+>[ \t-]*Build:[ \t0-9]+ ms[ \t]*)?[ \t]*(([^\"]*[/\\])?(${linker}))(\"|,| |$)")
   set(linker_tool_exclude_regex "cuda-fake-ld|-fuse-ld=|^ExecuteExternalTool ")
   if(is_lfortran_less_0_40)
     # lfortran < 0.40 has no way to pass -v to clang/gcc driver.

+ 1 - 1
Modules/CTestTargets.cmake

@@ -86,7 +86,7 @@ if(NOT _CTEST_TARGETS_ADDED)
   endforeach()
 
   # For Makefile generators add more granular targets.
-  if("${CMAKE_GENERATOR}" MATCHES "(Ninja|Make)")
+  if("${CMAKE_GENERATOR}" MATCHES "(Ninja|Make|FASTBuild)")
     # Make targets for Experimental builds
     foreach(mode Nightly Experimental Continuous)
       foreach(testtype

+ 2 - 2
Modules/CTestUseLaunchers.cmake

@@ -60,7 +60,7 @@ if(NOT DEFINED CTEST_USE_LAUNCHERS AND DEFINED ENV{CTEST_USE_LAUNCHERS_DEFAULT})
     CACHE INTERNAL "CTEST_USE_LAUNCHERS initial value from ENV")
 endif()
 
-if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
+if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja|FASTBuild")
   set(CTEST_USE_LAUNCHERS 0)
 endif()
 
@@ -77,7 +77,7 @@ if(CTEST_USE_LAUNCHERS)
   set(__launch_custom_options
     "${__launch_common_options} --output <OUTPUT>")
 
-  if("${CMAKE_GENERATOR}" MATCHES "Ninja")
+  if("${CMAKE_GENERATOR}" MATCHES "Ninja|FASTBuild")
     string(APPEND __launch_compile_options " --filter-prefix <CMAKE_CL_SHOWINCLUDES_PREFIX>")
   endif()
 

+ 8 - 2
Modules/Compiler/Clang.cmake

@@ -113,8 +113,14 @@ else()
     if(CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0.0 AND NOT __is_apple_clang)
       set(CMAKE_${lang}_COMPILE_OPTIONS_INSTANTIATE_TEMPLATES_PCH -fpch-instantiate-templates)
     endif()
-    set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH -Xclang -include-pch -Xclang <PCH_FILE> -Xclang -include -Xclang <PCH_HEADER>)
-    set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -emit-pch -Xclang -include -Xclang <PCH_HEADER> -x ${__pch_header_${lang}})
+    if (CMAKE_GENERATOR MATCHES "FASTBuild")
+      # We can't use "-Xclang -emit-pch" since Fastbuild gets spammed with binary content of the .pch file while trying to scan dependencies.
+      set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH -include-pch <PCH_FILE> -Xclang -include -Xclang <PCH_HEADER>)
+      set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -include -Xclang <PCH_HEADER> -x ${__pch_header_${lang}})
+    else()
+      set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH -Xclang -include-pch -Xclang <PCH_FILE> -Xclang -include -Xclang <PCH_HEADER>)
+      set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -emit-pch -Xclang -include -Xclang <PCH_HEADER> -x ${__pch_header_${lang}})
+    endif()
 
     # '-fcolor-diagnostics' introduced since Clang 2.6
     if(CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 2.6)

+ 3 - 3
Modules/ExternalProject.cmake

@@ -3231,11 +3231,11 @@ function(ExternalProject_Add name)
   # rebuilds.  It is important that 'done' is not the output of any
   # custom command so that CMake does not propagate build rules to
   # other external project targets, which may cause problems during
-  # parallel builds.  However, the Ninja generator needs to see the entire
+  # parallel builds.  However, the Ninja and Fastbuild generators need to see the entire
   # dependency graph, and can cope with custom commands belonging to
-  # multiple targets, so we add the 'done' mark as an output for Ninja only.
+  # multiple targets, so we add the 'done' mark as an output for Ninja and Fastbuild only.
   set(complete_outputs ${complete_stamp_file})
-  if(${CMAKE_GENERATOR} MATCHES "Ninja")
+  if(${CMAKE_GENERATOR} MATCHES "Ninja|FASTBuild")
     set(complete_outputs ${complete_outputs} ${done_stamp_file})
   endif()
 

+ 1 - 1
Modules/Platform/Windows-MSVC.cmake

@@ -388,7 +388,7 @@ endif()
 unset(__WINDOWS_MSVC_CMP0184)
 
 macro(__windows_compiler_msvc lang)
-  if(NOT MSVC_VERSION LESS 1400)
+  if(NOT MSVC_VERSION LESS 1400 AND NOT CMAKE_GENERATOR MATCHES "FASTBuild")
     # for 2005 make sure the manifest is put in the dll with mt
     set(_CMAKE_VS_LINK_DLL "<CMAKE_COMMAND> -E vs_link_dll --msvc-ver=${MSVC_VERSION} --intdir=<OBJECT_DIR> --rc=<CMAKE_RC_COMPILER> --mt=<CMAKE_MT> --manifests <MANIFESTS> -- ")
     set(_CMAKE_VS_LINK_EXE "<CMAKE_COMMAND> -E vs_link_exe --msvc-ver=${MSVC_VERSION} --intdir=<OBJECT_DIR> --rc=<CMAKE_RC_COMPILER> --mt=<CMAKE_MT> --manifests <MANIFESTS> -- ")

+ 12 - 0
Source/CMakeLists.txt

@@ -782,6 +782,18 @@ add_library(
   cmNinjaLinkLineComputer.h
   cmNinjaLinkLineDeviceComputer.cxx
   cmNinjaLinkLineDeviceComputer.h
+  # FASTBuild support
+  cmGlobalFastbuildGenerator.cxx
+  cmGlobalFastbuildGenerator.h
+  cmLocalFastbuildGenerator.cxx
+  cmLocalFastbuildGenerator.h
+  cmFastbuildTargetGenerator.cxx
+  cmFastbuildTargetGenerator.h
+  cmFastbuildLinkLineComputer.cxx
+  cmFastbuildNormalTargetGenerator.cxx
+  cmFastbuildNormalTargetGenerator.h
+  cmFastbuildUtilityTargetGenerator.cxx
+  cmFastbuildUtilityTargetGenerator.h
 
   cm_get_date.h
   cm_get_date.c

+ 2 - 1
Source/CTest/cmCTestBuildAndTest.cxx

@@ -57,7 +57,8 @@ bool cmCTestBuildAndTest::RunCMake(cmake* cm)
   }
   if (!this->BuildMakeProgram.empty() &&
       (this->BuildGenerator.find("Make") != std::string::npos ||
-       this->BuildGenerator.find("Ninja") != std::string::npos)) {
+       this->BuildGenerator.find("Ninja") != std::string::npos ||
+       this->BuildGenerator.find("FASTBuild") != std::string::npos)) {
     args.push_back("-DCMAKE_MAKE_PROGRAM:FILEPATH=" + this->BuildMakeProgram);
   }
 

+ 2 - 1
Source/cmCommonTargetGenerator.cxx

@@ -458,7 +458,8 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules(
           this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
             cmStrCat(tidy, ";--extra-arg-before=--driver-mode=", driverMode,
                      exportFixes));
-      } else if (generatorName.find("Ninja") != std::string::npos) {
+      } else if (generatorName.find("Ninja") != std::string::npos ||
+                 generatorName.find("FASTBuild") != std::string::npos) {
         if (!clangTidyExportFixedDir.empty()) {
           this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
           cmSystemTools::MakeDirectory(

+ 22 - 0
Source/cmFastbuildLinkLineComputer.cxx

@@ -0,0 +1,22 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmFastbuildLinkLineComputer.h"
+
+#include "cmGlobalFastbuildGenerator.h"
+
+class cmOutputConverter;
+
+cmFastbuildLinkLineComputer::cmFastbuildLinkLineComputer(
+  cmOutputConverter* outputConverter, cmStateDirectory const& stateDir,
+  cmGlobalFastbuildGenerator const* gg)
+  : cmLinkLineComputer(outputConverter, stateDir)
+  , GG(gg)
+{
+}
+
+std::string cmFastbuildLinkLineComputer::ConvertToLinkReference(
+  std::string const& lib) const
+{
+  return this->GG->ConvertToFastbuildPath(lib);
+}

+ 31 - 0
Source/cmFastbuildLinkLineComputer.h

@@ -0,0 +1,31 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+
+#include "cmLinkLineComputer.h"
+
+class cmGlobalFastbuildGenerator;
+class cmOutputConverter;
+class cmStateDirectory;
+
+class cmFastbuildLinkLineComputer : public cmLinkLineComputer
+{
+public:
+  cmFastbuildLinkLineComputer(cmOutputConverter* outputConverter,
+                              cmStateDirectory const& stateDir,
+                              cmGlobalFastbuildGenerator const* gg);
+
+  cmFastbuildLinkLineComputer(cmFastbuildLinkLineComputer const&) = delete;
+  cmFastbuildLinkLineComputer& operator=(cmFastbuildLinkLineComputer const&) =
+    delete;
+
+  std::string ConvertToLinkReference(std::string const& input) const override;
+
+private:
+  cmGlobalFastbuildGenerator const* GG;
+};

+ 1948 - 0
Source/cmFastbuildNormalTargetGenerator.cxx

@@ -0,0 +1,1948 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmFastbuildNormalTargetGenerator.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include <cm/memory>
+#include <cm/optional>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmAlgorithms.h"
+#include "cmCommonTargetGenerator.h"
+#include "cmCryptoHash.h"
+#include "cmFastbuildTargetGenerator.h"
+#include "cmGeneratedFileStream.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalCommonGenerator.h"
+#include "cmGlobalFastbuildGenerator.h"
+#include "cmLinkLineComputer.h"
+#include "cmList.h"
+#include "cmListFileCache.h"
+#include "cmLocalCommonGenerator.h"
+#include "cmLocalFastbuildGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmOSXBundleGenerator.h"
+#include "cmObjectLocation.h"
+#include "cmOutputConverter.h"
+#include "cmSourceFile.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTargetDepend.h"
+#include "cmValue.h"
+#include "cmake.h"
+
+namespace {
+
+std::string const COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
+std::string const COMPILE_OPTIONS("COMPILE_OPTIONS");
+std::string const COMPILE_FLAGS("COMPILE_FLAGS");
+std::string const CMAKE_LANGUAGE("CMAKE");
+std::string const INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
+
+} // anonymous namespace
+
+cmFastbuildNormalTargetGenerator::cmFastbuildNormalTargetGenerator(
+  cmGeneratorTarget* gt, std::string configParam)
+  : cmFastbuildTargetGenerator(gt, std::move(configParam))
+  , RulePlaceholderExpander(
+      this->LocalCommonGenerator->CreateRulePlaceholderExpander())
+  , ObjectOutDir(this->GetGlobalGenerator()->ConvertToFastbuildPath(
+      this->GeneratorTarget->GetObjectDirectory(Config)))
+  , Languages(GetLanguages())
+  , CompileObjectCmakeRules(GetCompileObjectCommand())
+{
+
+  LogMessage(cmStrCat("objectOutDir: ", ObjectOutDir));
+  this->OSXBundleGenerator = cm::make_unique<cmOSXBundleGenerator>(gt);
+  this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
+
+  // Quotes to account for potential spaces.
+  RulePlaceholderExpander->SetTargetImpLib(
+    "\"" FASTBUILD_DOLLAR_TAG "TargetOutputImplib" FASTBUILD_DOLLAR_TAG "\"");
+  for (auto const& lang : Languages) {
+    TargetIncludesByLanguage[lang] = this->GetIncludes(lang, Config);
+    LogMessage("targetIncludes for lang " + lang + " = " +
+               TargetIncludesByLanguage[lang]);
+
+    for (auto const& arch : this->GetArches()) {
+      auto& flags = CompileFlagsByLangAndArch[std::make_pair(lang, arch)];
+      this->LocalCommonGenerator->GetTargetCompileFlags(
+        this->GeneratorTarget, Config, lang, flags, arch);
+      LogMessage(
+        cmStrCat("Lang: ", lang, ", arch: ", arch, ", flags: ", flags));
+    }
+  }
+}
+
+std::string cmFastbuildNormalTargetGenerator::DetectCompilerFlags(
+  cmSourceFile const& srcFile, std::string const& arch)
+{
+  std::string const language = srcFile.GetLanguage();
+  cmGeneratorExpressionInterpreter genexInterpreter(
+    this->GetLocalGenerator(), Config, this->GeneratorTarget, language);
+
+  std::vector<std::string> sourceIncludesVec;
+  if (cmValue cincludes = srcFile.GetProperty(INCLUDE_DIRECTORIES)) {
+    this->LocalGenerator->AppendIncludeDirectories(
+      sourceIncludesVec,
+      genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES), srcFile);
+  }
+  std::string sourceIncludesStr = this->LocalGenerator->GetIncludeFlags(
+    sourceIncludesVec, this->GeneratorTarget, language, Config, false);
+  LogMessage(cmStrCat("sourceIncludes = ", sourceIncludesStr));
+
+  std::string compileFlags =
+    CompileFlagsByLangAndArch[std::make_pair(language, arch)];
+  this->GeneratorTarget->AddExplicitLanguageFlags(compileFlags, srcFile);
+
+  if (cmValue const cflags = srcFile.GetProperty(COMPILE_FLAGS)) {
+    this->LocalGenerator->AppendFlags(
+      compileFlags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS));
+  }
+
+  if (cmValue const coptions = srcFile.GetProperty(COMPILE_OPTIONS)) {
+    this->LocalGenerator->AppendCompileOptions(
+      compileFlags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS));
+  }
+  // Source includes take precedence over target includes.
+  this->LocalGenerator->AppendFlags(compileFlags, sourceIncludesStr);
+  this->LocalGenerator->AppendFlags(compileFlags,
+                                    TargetIncludesByLanguage[language]);
+
+  if (language == "Fortran") {
+    this->AppendFortranFormatFlags(compileFlags, srcFile);
+    this->AppendFortranPreprocessFlags(compileFlags, srcFile);
+  }
+
+  LogMessage(cmStrCat("compileFlags = ", compileFlags));
+  return compileFlags;
+}
+
+void cmFastbuildNormalTargetGenerator::GetLinkerExecutableAndArgs(
+  std::string const& command, std::string& outLinkerExecutable,
+  std::string& outLinkerArgs)
+{
+  if (command.empty()) {
+    return;
+  }
+
+  LogMessage("Link Command: " + command);
+
+  auto const& compilers = this->GetGlobalGenerator()->Compilers;
+  auto const linkerLauncherVarName = FASTBUILD_LINKER_LAUNCHER_PREFIX +
+    this->GeneratorTarget->GetLinkerLanguage(Config);
+  auto const iter = compilers.find(linkerLauncherVarName);
+  // Tested in "RunCMake.LinkerLauncher" test.
+  if (iter != compilers.end()) {
+    LogMessage("Linker launcher: " + iter->first);
+    outLinkerExecutable = iter->second.Executable;
+    outLinkerArgs = cmStrCat(iter->second.Args, " ", command);
+  } else {
+#ifdef _WIN32
+    std::vector<std::string> args;
+    std::string tmp;
+    cmSystemTools::SplitProgramFromArgs(command, tmp, outLinkerArgs);
+    // cmLocalGenerator::GetStaticLibraryFlags seems to add empty quotes when
+    // appending "STATIC_LIBRARY_FLAGS_DEBUG"...
+    cmSystemTools::ReplaceString(outLinkerArgs, "\"\"", "");
+    cmSystemTools::ParseWindowsCommandLine(command.c_str(), args);
+    outLinkerExecutable = std::move(args[0]);
+#else
+    cmSystemTools::SplitProgramFromArgs(command, outLinkerExecutable,
+                                        outLinkerArgs);
+#endif
+  }
+  LogMessage("Linker Exe: " + outLinkerExecutable);
+  LogMessage("Linker args: " + outLinkerArgs);
+}
+
+bool cmFastbuildNormalTargetGenerator::DetectBaseLinkerCommand(
+  std::string& command, std::string const& arch,
+  cmGeneratorTarget::Names const& targetNames)
+{
+  std::string const linkLanguage =
+    this->GeneratorTarget->GetLinkerLanguage(Config);
+  if (linkLanguage.empty()) {
+    cmSystemTools::Error("CMake can not determine linker language for "
+                         "target: " +
+                         this->GeneratorTarget->GetName());
+    return false;
+  }
+  LogMessage("linkLanguage: " + linkLanguage);
+
+  std::string linkLibs;
+  std::string targetFlags;
+  std::string linkFlags;
+  std::string frameworkPath;
+  // Tested in "RunCMake.StandardLinkDirectories" test.
+  std::string linkPath;
+
+  std::unique_ptr<cmLinkLineComputer> const linkLineComputer =
+    this->GetGlobalGenerator()->CreateLinkLineComputer(
+      this->LocalGenerator,
+      this->GetLocalGenerator()->GetStateSnapshot().GetDirectory());
+
+  this->LocalCommonGenerator->GetTargetFlags(
+    linkLineComputer.get(), Config, linkLibs, targetFlags, linkFlags,
+    frameworkPath, linkPath, this->GeneratorTarget);
+
+  // cmLocalGenerator::GetStaticLibraryFlags seems to add empty quotes when
+  // appending "STATIC_LIBRARY_FLAGS_DEBUG"...
+  cmSystemTools::ReplaceString(linkFlags, "\"\"", "");
+  LogMessage("linkLibs: " + linkLibs);
+  LogMessage("targetFlags: " + targetFlags);
+  LogMessage("linkFlags: " + linkFlags);
+  LogMessage("frameworkPath: " + frameworkPath);
+  LogMessage("linkPath: " + linkPath);
+
+  LogMessage("MANIFESTS: " + this->GetManifests(Config));
+
+  cmComputeLinkInformation* linkInfo =
+    this->GeneratorTarget->GetLinkInformation(Config);
+  if (!linkInfo) {
+    return false;
+  }
+
+  // Tested in "RunCMake.RuntimePath" test.
+  std::string const rpath = linkLineComputer->ComputeRPath(*linkInfo);
+  LogMessage("RPath: " + rpath);
+
+  if (!linkFlags.empty()) {
+    linkFlags += " ";
+  }
+  linkFlags += cmJoin({ rpath, frameworkPath, linkPath }, " ");
+
+  cmStateEnums::TargetType const targetType = this->GeneratorTarget->GetType();
+  // Add OS X version flags, if any.
+  if (targetType == cmStateEnums::SHARED_LIBRARY ||
+      targetType == cmStateEnums::MODULE_LIBRARY) {
+    this->AppendOSXVerFlag(linkFlags, linkLanguage, "COMPATIBILITY", true);
+    this->AppendOSXVerFlag(linkFlags, linkLanguage, "CURRENT", false);
+  }
+  // Add Arch flags to link flags for binaries
+  if (targetType == cmStateEnums::SHARED_LIBRARY ||
+      targetType == cmStateEnums::MODULE_LIBRARY ||
+      targetType == cmStateEnums::EXECUTABLE) {
+    this->LocalCommonGenerator->AddArchitectureFlags(
+      linkFlags, this->GeneratorTarget, linkLanguage, Config, arch);
+    this->UseLWYU = this->GetLocalGenerator()->AppendLWYUFlags(
+      linkFlags, this->GetGeneratorTarget(), linkLanguage);
+  }
+
+  cmRulePlaceholderExpander::RuleVariables vars;
+  vars.CMTargetName = this->GeneratorTarget->GetName().c_str();
+  vars.CMTargetType = cmState::GetTargetTypeName(targetType).c_str();
+  vars.Language = linkLanguage.c_str();
+  std::string const manifests =
+    cmJoin(this->GetManifestsAsFastbuildPath(), " ");
+  vars.Manifests = manifests.c_str();
+
+  std::string const stdLibString = this->Makefile->GetSafeDefinition(
+    cmStrCat("CMAKE_", linkLanguage, "_STANDARD_LIBRARIES"));
+
+  LogMessage(cmStrCat("Target type: ", this->GeneratorTarget->GetType()));
+  if (this->GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE ||
+      this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY ||
+      this->GeneratorTarget->GetType() == cmStateEnums::MODULE_LIBRARY) {
+    vars.Objects = FASTBUILD_1_0_INPUT_PLACEHOLDER;
+    vars.LinkLibraries = stdLibString.c_str();
+  } else {
+    vars.Objects = FASTBUILD_1_INPUT_PLACEHOLDER;
+  }
+
+  vars.ObjectDir = FASTBUILD_DOLLAR_TAG "TargetOutDir" FASTBUILD_DOLLAR_TAG;
+  vars.Target = FASTBUILD_2_INPUT_PLACEHOLDER;
+
+  std::string install_dir;
+  std::string target_so_name;
+  if (this->GeneratorTarget->HasSOName(Config)) {
+    vars.SONameFlag = this->Makefile->GetSONameFlag(
+      this->GeneratorTarget->GetLinkerLanguage(Config));
+    target_so_name =
+      cmGlobalFastbuildGenerator::QuoteIfHasSpaces(targetNames.SharedObject);
+    vars.TargetSOName = target_so_name.c_str();
+    // Tested in "RunCMake.RuntimePath / RunCMake.INSTALL_NAME_DIR"
+    // tests.
+    install_dir = this->LocalGenerator->ConvertToOutputFormat(
+      this->GeneratorTarget->GetInstallNameDirForBuildTree(Config),
+      cmOutputConverter::SHELL);
+    vars.TargetInstallNameDir = install_dir.c_str();
+  } else {
+    vars.TargetSOName = "";
+  }
+  vars.TargetPDB = FASTBUILD_DOLLAR_TAG "LinkerPDB" FASTBUILD_DOLLAR_TAG;
+
+  // Setup the target version.
+  std::string targetVersionMajor;
+  std::string targetVersionMinor;
+  {
+    std::ostringstream majorStream;
+    std::ostringstream minorStream;
+    int major;
+    int minor;
+    this->GeneratorTarget->GetTargetVersion(major, minor);
+    majorStream << major;
+    minorStream << minor;
+    targetVersionMajor = majorStream.str();
+    targetVersionMinor = minorStream.str();
+  }
+  vars.TargetVersionMajor = targetVersionMajor.c_str();
+  vars.TargetVersionMinor = targetVersionMinor.c_str();
+
+  vars.Defines =
+    FASTBUILD_DOLLAR_TAG "CompileDefineFlags" FASTBUILD_DOLLAR_TAG;
+  vars.Flags = targetFlags.c_str();
+  vars.LinkFlags = linkFlags.c_str();
+  vars.LanguageCompileFlags = "";
+  std::string const linker = this->GeneratorTarget->GetLinkerTool(Config);
+  vars.Linker = linker.c_str();
+  std::string const targetSupportPath = this->ConvertToFastbuildPath(
+    this->GetGeneratorTarget()->GetCMFSupportDirectory());
+  vars.TargetSupportDir = targetSupportPath.c_str();
+
+  LogMessage("linkFlags: " + linkFlags);
+  LogMessage("linker: " + linker);
+
+  std::string linkRule = GetLinkCommand();
+  ApplyLinkRuleLauncher(linkRule);
+  RulePlaceholderExpander->ExpandRuleVariables(
+    dynamic_cast<cmLocalFastbuildGenerator*>(this->LocalCommonGenerator),
+    linkRule, vars);
+
+  command = std::move(linkRule);
+  LogMessage(cmStrCat("Expanded link command: ", command));
+  return true;
+}
+
+void cmFastbuildNormalTargetGenerator::ApplyCompileRuleLauncher(
+  std::string& command)
+{
+  std::string const val = this->GetLocalGenerator()->GetRuleLauncher(
+    this->GetGeneratorTarget(), "RULE_LAUNCH_LINK", Config);
+  if (cmNonempty(val)) {
+    LogMessage("RULE_LAUNCH_LINK: " + val);
+    command = cmStrCat(val, " ", command);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::ApplyLinkRuleLauncher(
+  std::string& command)
+{
+  std::string const val = this->GetLocalGenerator()->GetRuleLauncher(
+    this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE", Config);
+  if (cmNonempty(val)) {
+    LogMessage("RULE_LAUNCH_COMPILE: " + val);
+    command = cmStrCat(val, " ", command);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::ApplyLWYUToLinkerCommand(
+  FastbuildLinkerNode& linkerNode)
+{
+  cmValue const lwyuCheck =
+    this->Makefile->GetDefinition("CMAKE_LINK_WHAT_YOU_USE_CHECK");
+  if (this->UseLWYU && lwyuCheck) {
+    LogMessage("UseLWYU=true");
+    std::string args = " -E __run_co_compile --lwyu=";
+    args += this->GetLocalGenerator()->EscapeForShell(*lwyuCheck);
+
+    args += cmStrCat(
+      " --source=",
+      this->ConvertToFastbuildPath(this->GetGeneratorTarget()->GetFullPath(
+        Config, cmStateEnums::RuntimeBinaryArtifact,
+        /*realname=*/true)));
+
+    LogMessage("LWUY args: " + args);
+    linkerNode.LinkerStampExe = cmSystemTools::GetCMakeCommand();
+    linkerNode.LinkerStampExeArgs = std::move(args);
+  }
+}
+
+std::string cmFastbuildNormalTargetGenerator::ComputeDefines(
+  cmSourceFile const& srcFile)
+{
+  std::string const language = srcFile.GetLanguage();
+  std::set<std::string> defines;
+  cmGeneratorExpressionInterpreter genexInterpreter(
+    this->GetLocalGenerator(), Config, this->GeneratorTarget, language);
+
+  if (auto compile_defs = srcFile.GetProperty(COMPILE_DEFINITIONS)) {
+    this->GetLocalGenerator()->AppendDefines(
+      defines, genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS));
+  }
+
+  std::string defPropName = "COMPILE_DEFINITIONS_";
+  defPropName += cmSystemTools::UpperCase(Config);
+  if (auto config_compile_defs = srcFile.GetProperty(defPropName)) {
+    this->GetLocalGenerator()->AppendDefines(
+      defines,
+      genexInterpreter.Evaluate(*config_compile_defs, COMPILE_DEFINITIONS));
+  }
+
+  std::string definesString = this->GetDefines(language, Config);
+  LogMessage(cmStrCat("TARGET DEFINES = ", definesString));
+  this->GetLocalGenerator()->JoinDefines(defines, definesString, language);
+
+  LogMessage(cmStrCat("DEFINES = ", definesString));
+  return definesString;
+}
+
+void cmFastbuildNormalTargetGenerator::ComputePCH(
+  cmSourceFile const& srcFile, FastbuildObjectListNode& node,
+  std::set<std::string>& createdPCH)
+{
+  if (srcFile.GetProperty("SKIP_PRECOMPILE_HEADERS")) {
+    return;
+  }
+  // We have already computed PCH for this node.
+  if (!node.PCHOptions.empty() || !node.PCHInputFile.empty() ||
+      !node.PCHOutputFile.empty()) {
+    return;
+  }
+  std::string const language = srcFile.GetLanguage();
+  cmGeneratorExpressionInterpreter genexInterpreter(
+    this->GetLocalGenerator(), Config, this->GeneratorTarget, language);
+
+  //.cxx
+  std::string const pchSource =
+    this->GeneratorTarget->GetPchSource(Config, language);
+  //.hxx
+  std::string const pchHeader =
+    this->GeneratorTarget->GetPchHeader(Config, language);
+  //.pch
+  std::string const pchFile =
+    this->GeneratorTarget->GetPchFile(Config, language);
+
+  if (pchHeader.empty() || pchFile.empty()) {
+    return;
+  }
+  // In "RunCMake.GenEx-TARGET_PROPERTY" test we call set
+  // CMAKE_PCH_EXTENSION="", so pchHeader becomes same as pchFile...
+  if (pchHeader == pchFile) {
+    LogMessage("pchHeader == pchFile > skipping");
+    LogMessage("pchHeader: " + pchHeader);
+    LogMessage("pchFile: " + pchFile);
+    return;
+  }
+
+  node.PCHOutputFile =
+    this->GetGlobalGenerator()->ConvertToFastbuildPath(pchFile);
+  // Tell the ObjectList how to use PCH.
+  std::string const pchUseOption =
+    this->GeneratorTarget->GetPchUseCompileOptions(Config, language);
+  LogMessage(cmStrCat("pchUseOption: ", pchUseOption));
+
+  std::string origCompileOptions = node.CompilerOptions;
+  for (auto const& opt :
+       cmList{ genexInterpreter.Evaluate(pchUseOption, COMPILE_OPTIONS) }) {
+    node.CompilerOptions += " ";
+    node.CompilerOptions += opt;
+  }
+
+  if (!createdPCH.emplace(node.PCHOutputFile).second) {
+    LogMessage(node.PCHOutputFile + " is already created by this target");
+    return;
+  }
+
+  // Short circuit if the PCH has already been created by another target.
+  if (!this->GeneratorTarget->GetSafeProperty("PRECOMPILE_HEADERS_REUSE_FROM")
+         .empty()) {
+    LogMessage(cmStrCat("PCH: ", node.PCHOutputFile,
+                        " already created by another target"));
+    return;
+  }
+
+  node.PCHInputFile =
+    this->GetGlobalGenerator()->ConvertToFastbuildPath(pchSource);
+
+  std::string const pchCreateOptions =
+    this->GeneratorTarget->GetPchCreateCompileOptions(Config, language);
+  LogMessage(cmStrCat("pchCreateOptions: ", pchCreateOptions));
+  char const* sep = "";
+  for (auto const& opt : cmList{
+         genexInterpreter.Evaluate(pchCreateOptions, COMPILE_OPTIONS) }) {
+    node.PCHOptions += sep;
+    node.PCHOptions += opt;
+    sep = " ";
+  }
+
+  // Reuse compiler options for PCH options.
+  node.PCHOptions += origCompileOptions;
+  if (this->Makefile->GetSafeDefinition("CMAKE_" + language +
+                                        "_COMPILER_ID") == "MSVC") {
+    cmSystemTools::ReplaceString(node.PCHOptions,
+                                 FASTBUILD_2_INPUT_PLACEHOLDER,
+                                 FASTBUILD_3_INPUT_PLACEHOLDER);
+  }
+
+  LogMessage("PCH Source: " + pchSource);
+  LogMessage("node.PCHInputFile: " + node.PCHInputFile);
+  LogMessage("node.PCHOutputFile: " + node.PCHOutputFile);
+  LogMessage("node.PCHOptions: " + node.PCHOptions);
+  LogMessage("node.CompilerOptions: " + node.CompilerOptions);
+}
+
+void cmFastbuildNormalTargetGenerator::EnsureDirectoryExists(
+  std::string const& path) const
+{
+  if (cmSystemTools::FileIsFullPath(path.c_str())) {
+    cmSystemTools::MakeDirectory(path.c_str());
+  } else {
+    auto* gg = this->GetGlobalGenerator();
+    std::string fullPath = gg->GetCMakeInstance()->GetHomeOutputDirectory();
+    // Also ensures there is a trailing slash.
+    fullPath += path;
+    cmSystemTools::MakeDirectory(fullPath);
+  }
+}
+
+std::vector<std::string>
+cmFastbuildNormalTargetGenerator::GetManifestsAsFastbuildPath() const
+{
+  std::vector<cmSourceFile const*> manifest_srcs;
+  this->GeneratorTarget->GetManifests(manifest_srcs, Config);
+  std::vector<std::string> manifests;
+  manifests.reserve(manifest_srcs.size());
+  for (auto& manifest_src : manifest_srcs) {
+    std::string str = this->ConvertToFastbuildPath(
+      cmSystemTools::ConvertToOutputPath(manifest_src->GetFullPath()));
+    LogMessage("Manifest: " + str);
+    manifests.emplace_back(std::move(str));
+  }
+
+  return manifests;
+}
+
+void cmFastbuildNormalTargetGenerator::GenerateModuleDefinitionInfo(
+  FastbuildTarget& target) const
+{
+  cmGeneratorTarget::ModuleDefinitionInfo const* mdi =
+    GeneratorTarget->GetModuleDefinitionInfo(Config);
+  if (mdi && mdi->DefFileGenerated) {
+    FastbuildExecNode execNode;
+    execNode.Name = target.Name + "-def-files";
+    execNode.ExecExecutable = cmSystemTools::GetCMakeCommand();
+    execNode.ExecArguments =
+      cmStrCat("-E __create_def ", FASTBUILD_2_INPUT_PLACEHOLDER, " ",
+               FASTBUILD_1_INPUT_PLACEHOLDER);
+    std::string const obj_list_file = mdi->DefFile + ".objs";
+
+    auto const nm_executable = GetMakefile()->GetDefinition("CMAKE_NM");
+    if (!nm_executable.IsEmpty()) {
+      execNode.ExecArguments += " --nm=";
+      execNode.ExecArguments += ConvertToFastbuildPath(*nm_executable);
+    }
+    execNode.ExecOutput = ConvertToFastbuildPath(mdi->DefFile);
+    execNode.ExecInput.push_back(ConvertToFastbuildPath(obj_list_file));
+
+    // RunCMake.AutoExportDll
+    for (auto const& objList : target.ObjectListNodes) {
+      execNode.PreBuildDependencies.emplace(objList.Name);
+    }
+    // Tested in "RunCMake.AutoExportDll" / "ModuleDefinition" tests.
+    for (auto& linkerNode : target.LinkerNode) {
+      linkerNode.Libraries2.emplace_back(execNode.Name);
+    }
+
+    target.PreLinkExecNodes.Nodes.emplace_back(std::move(execNode));
+
+    // create a list of obj files for the -E __create_def to read
+    cmGeneratedFileStream fout(obj_list_file);
+    // Since we generate this file once during configuration, we should not
+    // remove it when "clean" is built.
+    // Tested in "RunCMake.AutoExportDll" / "ModuleDefinition" tests.
+    this->GetGlobalGenerator()->AllFilesToKeep.insert(obj_list_file);
+
+    if (mdi->WindowsExportAllSymbols) {
+      std::vector<cmSourceFile const*> objectSources;
+      GeneratorTarget->GetObjectSources(objectSources, Config);
+      std::map<cmSourceFile const*, cmObjectLocations> mapping;
+      for (cmSourceFile const* it : objectSources) {
+        mapping[it];
+      }
+      GeneratorTarget->LocalGenerator->ComputeObjectFilenames(mapping,
+                                                              GeneratorTarget);
+
+      std::vector<std::string> objs;
+      for (cmSourceFile const* it : objectSources) {
+        auto const& v = mapping[it];
+        LogMessage("Obj source : " + v.LongLoc.GetPath());
+        std::string objFile = this->ConvertToFastbuildPath(
+          GeneratorTarget->GetObjectDirectory(Config) + v.LongLoc.GetPath());
+        objFile = cmSystemTools::ConvertToOutputPath(objFile);
+        LogMessage("objFile path: " + objFile);
+        objs.push_back(objFile);
+      }
+
+      std::vector<cmSourceFile const*> externalObjectSources;
+      GeneratorTarget->GetExternalObjects(externalObjectSources, Config);
+      for (cmSourceFile const* it : externalObjectSources) {
+        objs.push_back(cmSystemTools::ConvertToOutputPath(
+          this->ConvertToFastbuildPath(it->GetFullPath())));
+      }
+
+      for (std::string const& objFile : objs) {
+        if (cmHasLiteralSuffix(objFile, ".obj")) {
+          fout << objFile << "\n";
+        }
+      }
+    }
+    for (cmSourceFile const* src : mdi->Sources) {
+      fout << src->GetFullPath() << "\n";
+    }
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AddPrebuildDeps(
+  FastbuildTarget& target) const
+{
+  // All ObjectLists should wait for PRE_BUILD.
+  for (FastbuildObjectListNode& node : target.ObjectListNodes) {
+    if (!target.PreBuildExecNodes.Name.empty()) {
+      node.PreBuildDependencies.emplace(target.PreBuildExecNodes.Name);
+    }
+    if (!target.ExecNodes.Name.empty()) {
+      node.PreBuildDependencies.emplace(target.ExecNodes.Name);
+    }
+  }
+  for (auto& linkerNode : target.LinkerNode) {
+    // Wait for 'PRE_BUILD' custom commands.
+    if (!target.PreBuildExecNodes.Name.empty()) {
+      linkerNode.PreBuildDependencies.emplace(target.PreBuildExecNodes.Name);
+    }
+
+    // Wait for regular custom commands.
+    if (!target.ExecNodes.Name.empty()) {
+      linkerNode.PreBuildDependencies.emplace(target.ExecNodes.Name);
+    }
+    // All targets that we depend on must be prebuilt.
+    if (!target.DependenciesAlias.PreBuildDependencies.empty()) {
+      linkerNode.PreBuildDependencies.emplace(target.DependenciesAlias.Name);
+    }
+  }
+}
+
+std::set<std::string> cmFastbuildNormalTargetGenerator::GetLanguages()
+{
+  std::set<std::string> result;
+  this->GetGeneratorTarget()->GetLanguages(result, Config);
+  for (std::string const& lang : result) {
+    this->GetGlobalGenerator()->AddCompiler(lang, this->GetMakefile());
+  }
+  LogMessage("Languages: " + cmJoin(result, ", "));
+  return result;
+}
+
+std::unordered_map<std::string, std::string>
+cmFastbuildNormalTargetGenerator::GetCompileObjectCommand() const
+{
+  std::unordered_map<std::string, std::string> result;
+  result.reserve(Languages.size());
+  for (std::string const& lang : Languages) {
+    std::vector<std::string> commands;
+    std::string cmakeVar;
+    if (lang == "CUDA") {
+      if (this->GeneratorTarget->GetPropertyAsBool(
+            "CUDA_SEPARABLE_COMPILATION")) {
+        cmakeVar = "CMAKE_CUDA_COMPILE_SEPARABLE_COMPILATION";
+      } else if (this->GeneratorTarget->GetPropertyAsBool(
+                   "CUDA_PTX_COMPILATION")) {
+        cmakeVar = "CMAKE_CUDA_COMPILE_PTX_COMPILATION";
+      } else {
+        cmakeVar = "CMAKE_CUDA_COMPILE_WHOLE_COMPILATION";
+      }
+    } else {
+      cmakeVar = "CMAKE_";
+      cmakeVar += lang;
+      cmakeVar += "_COMPILE_OBJECT";
+    }
+    std::string cmakeValue =
+      LocalCommonGenerator->GetMakefile()->GetSafeDefinition(cmakeVar);
+
+    LogMessage(cmakeVar.append(" = ").append(cmakeValue));
+
+    result[lang] = std::move(cmakeValue);
+  }
+  return result;
+}
+
+std::string cmFastbuildNormalTargetGenerator::GetLinkCommand() const
+{
+  std::string const& linkLanguage = GeneratorTarget->GetLinkerLanguage(Config);
+  std::string linkCmdVar =
+    GeneratorTarget->GetCreateRuleVariable(linkLanguage, Config);
+  std::string res = this->Makefile->GetSafeDefinition(linkCmdVar);
+  if (res.empty() &&
+      this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY) {
+    linkCmdVar = linkCmdVar =
+      cmStrCat("CMAKE_", linkLanguage, "_ARCHIVE_CREATE");
+    res = this->Makefile->GetSafeDefinition(linkCmdVar);
+  }
+  LogMessage("Link rule: " + cmStrCat(linkCmdVar, " = ", res));
+  return res;
+}
+
+void cmFastbuildNormalTargetGenerator::AddCompilerLaunchersForLanguages()
+{
+  // General rule for all languages.
+  std::string const launchCompile = this->GetLocalGenerator()->GetRuleLauncher(
+    this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE", Config);
+  // See if we need to use a compiler launcher like ccache or distcc
+  for (std::string const& language : Languages) {
+    std::string const compilerLauncher =
+      cmCommonTargetGenerator::GetCompilerLauncher(language, Config);
+    LogMessage("compilerLauncher: " + compilerLauncher);
+    std::vector<std::string> expanded;
+    cmExpandList(compilerLauncher, expanded);
+
+    if (!expanded.empty()) {
+      std::string const exe = expanded[0];
+      expanded.erase(expanded.begin());
+      this->GetGlobalGenerator()->AddLauncher(FASTBUILD_LAUNCHER_PREFIX, exe,
+                                              language, cmJoin(expanded, " "));
+    } else if (!launchCompile.empty()) {
+      std::string exe;
+      std::string args;
+      cmSystemTools::SplitProgramFromArgs(launchCompile, exe, args);
+      this->GetGlobalGenerator()->AddLauncher(FASTBUILD_LAUNCHER_PREFIX, exe,
+                                              language, args);
+    }
+  }
+}
+void cmFastbuildNormalTargetGenerator::AddLinkerLauncher()
+{
+  std::string const linkerLauncher =
+    cmCommonTargetGenerator::GetLinkerLauncher(Config);
+  std::vector<std::string> args;
+#ifdef _WIN32
+  cmSystemTools::ParseWindowsCommandLine(linkerLauncher.c_str(), args);
+#else
+  cmSystemTools::ParseUnixCommandLine(linkerLauncher.c_str(), args);
+#endif
+  if (!args.empty()) {
+    std::string const exe = std::move(args[0]);
+    args.erase(args.begin());
+    this->GetGlobalGenerator()->AddLauncher(
+      FASTBUILD_LINKER_LAUNCHER_PREFIX, exe,
+      this->GeneratorTarget->GetLinkerLanguage(Config), cmJoin(args, " "));
+  }
+}
+void cmFastbuildNormalTargetGenerator::AddCMakeLauncher()
+{
+  // Add CMake launcher (might be used for static analysis).
+  this->GetGlobalGenerator()->AddLauncher(FASTBUILD_LAUNCHER_PREFIX,
+                                          cmSystemTools::GetCMakeCommand(),
+                                          CMAKE_LANGUAGE, "");
+}
+
+void cmFastbuildNormalTargetGenerator::ComputePaths(
+  FastbuildTarget& target) const
+{
+  std::string const objPath = GetGeneratorTarget()->GetSupportDirectory();
+  EnsureDirectoryExists(objPath);
+  target.Variables["TargetOutDir"] =
+    cmSystemTools::ConvertToOutputPath(this->ConvertToFastbuildPath(objPath));
+
+  if (GeneratorTarget->GetType() <= cmStateEnums::MODULE_LIBRARY) {
+    std::string const pdbDir = GeneratorTarget->GetPDBDirectory(Config);
+    LogMessage("GetPDBDirectory: " + pdbDir);
+    EnsureDirectoryExists(pdbDir);
+    std::string const linkerPDB =
+      cmStrCat(pdbDir, '/', this->GeneratorTarget->GetPDBName(Config));
+
+    if (!linkerPDB.empty()) {
+      target.Variables["LinkerPDB"] = cmSystemTools::ConvertToOutputPath(
+        this->ConvertToFastbuildPath(linkerPDB));
+    }
+  }
+  if (GeneratorTarget->GetType() <= cmStateEnums::OBJECT_LIBRARY) {
+    std::string const pdbDir = GeneratorTarget->GetCompilePDBDirectory(Config);
+    LogMessage("GetCompilePDBDirectory: " + pdbDir);
+    EnsureDirectoryExists(pdbDir);
+    std::string pdbName = this->GeneratorTarget->GetCompilePDBName(Config);
+    LogMessage("GetCompilePDBName: " + pdbDir);
+    // If we don't have Compiler's PDB, we must add a trailing slash to satisfy
+    // MSVC.
+    bool needTrailingSlash = false;
+    if (pdbName.empty()) {
+      needTrailingSlash = true;
+    }
+    std::string const compilerPDB = cmStrCat(pdbDir, '\\', pdbName);
+    if (!compilerPDB.empty()) {
+      target.Variables["CompilerPDB"] = cmSystemTools::ConvertToOutputPath(
+        this->ConvertToFastbuildPath(compilerPDB) +
+        (needTrailingSlash ? "\\ " : ""));
+    }
+  }
+  std::string impLibFile = ConvertToFastbuildPath(
+    GeneratorTarget->GetFullPath(Config, cmStateEnums::ImportLibraryArtifact));
+  cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(impLibFile));
+  if (!impLibFile.empty()) {
+    cmSystemTools::ConvertToOutputSlashes(impLibFile);
+    target.Variables["TargetOutputImplib"] = std::move(impLibFile);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::Generate()
+{
+  this->GeneratorTarget->CheckCxxModuleStatus(Config);
+
+  FastbuildTarget fastbuildTarget;
+  fastbuildTarget.Name = GetTargetName();
+  fastbuildTarget.BaseName = this->GeneratorTarget->GetName();
+
+  LogMessage("<-------------->");
+  LogMessage("Generate target: " + fastbuildTarget.Name);
+  LogMessage("Config: " + Config);
+
+  LogMessage("Deps: ");
+  for (cmTargetDepend const& dep : TargetDirectDependencies) {
+    auto const tname = dep->GetName();
+    LogMessage(tname);
+    FastbuildTargetDep targetDep{ tname };
+    if (dep->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+      targetDep.Type = FastbuildTargetDepType::ORDER_ONLY;
+    } else {
+      targetDep.Type = FastbuildTargetDepType::ALL;
+    }
+    fastbuildTarget.PreBuildDependencies.emplace(std::move(targetDep));
+  }
+
+  ComputePaths(fastbuildTarget);
+  AddCompilerLaunchersForLanguages();
+  AddLinkerLauncher();
+  AddCMakeLauncher();
+
+  for (auto& cc : GenerateCommands(FastbuildBuildStep::PRE_BUILD).Nodes) {
+    fastbuildTarget.PreBuildExecNodes.PreBuildDependencies.emplace(cc.Name);
+    fastbuildTarget.PreBuildDependencies.emplace(cc.Name);
+    this->GetGlobalGenerator()->AddTarget(std::move(cc));
+  }
+  for (auto& cc : GenerateCommands(FastbuildBuildStep::PRE_LINK).Nodes) {
+    cc.PreBuildDependencies.emplace(fastbuildTarget.Name +
+                                    FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX);
+    fastbuildTarget.PreLinkExecNodes.Nodes.emplace_back(std::move(cc));
+  }
+  for (auto& cc : GenerateCommands(FastbuildBuildStep::REST).Nodes) {
+    fastbuildTarget.ExecNodes.PreBuildDependencies.emplace(cc.Name);
+    this->GetGlobalGenerator()->AddTarget(std::move(cc));
+  }
+  for (auto& cc : GenerateCommands(FastbuildBuildStep::POST_BUILD).Nodes) {
+    fastbuildTarget.PostBuildExecNodes.Alias.PreBuildDependencies.emplace(
+      cc.Name);
+    fastbuildTarget.PostBuildExecNodes.Nodes.emplace_back(std::move(cc));
+  }
+
+  fastbuildTarget.ObjectListNodes = GenerateObjects();
+  std::vector<std::string> objectDepends;
+  AddObjectDependencies(fastbuildTarget, objectDepends);
+
+  GenerateLink(fastbuildTarget, objectDepends);
+
+  if (fastbuildTarget.LinkerNode.size() > 1) {
+    if (!this->GeneratorTarget->IsApple()) {
+      cmSystemTools::Error(
+        "Can't handle more than 1 arch on non-Apple target");
+      return;
+    }
+    AddLipoCommand(fastbuildTarget);
+  }
+  fastbuildTarget.CopyNodes = std::move(this->CopyNodes);
+
+  // Generate symlink commands if real output name differs from "expected".
+  for (auto& symlink : GetSymlinkExecs()) {
+    fastbuildTarget.PostBuildExecNodes.Alias.PreBuildDependencies.emplace(
+      symlink.Name);
+    fastbuildTarget.PostBuildExecNodes.Nodes.emplace_back(std::move(symlink));
+  }
+  {
+    auto appleTextStubCommand = GetAppleTextStubCommand();
+    if (!appleTextStubCommand.Name.empty()) {
+      fastbuildTarget.PostBuildExecNodes.Alias.PreBuildDependencies.emplace(
+        appleTextStubCommand.Name);
+      fastbuildTarget.PostBuildExecNodes.Nodes.emplace_back(
+        std::move(appleTextStubCommand));
+    }
+  }
+
+  AddPrebuildDeps(fastbuildTarget);
+
+  fastbuildTarget.IsGlobal =
+    GeneratorTarget->GetType() == cmStateEnums::GLOBAL_TARGET;
+  fastbuildTarget.ExcludeFromAll =
+    this->GetGlobalGenerator()->IsExcluded(GeneratorTarget);
+  if (GeneratorTarget->GetPropertyAsBool("DONT_DISTRIBUTE")) {
+    fastbuildTarget.AllowDistribution = false;
+  }
+
+  GenerateModuleDefinitionInfo(fastbuildTarget);
+  // Needs to be called after we've added all PRE-LINK steps (like creation of
+  // .def files on Windows).
+  AddLinkerNodeDependnecies(fastbuildTarget);
+
+  // Must be called after "GenerateObjects", since it also adds Prebuild deps
+  // to it.
+  // Also after "GenerateModuleDefinitionInfo", since uses PreLinkExecNodes.
+
+  fastbuildTarget.GenerateAliases();
+  if (!fastbuildTarget.ExecNodes.PreBuildDependencies.empty()) {
+    fastbuildTarget.DependenciesAlias.PreBuildDependencies.emplace(
+      fastbuildTarget.ExecNodes.Name);
+  }
+
+  fastbuildTarget.Hidden = false;
+
+  fastbuildTarget.BasePath = this->GetMakefile()->GetCurrentSourceDirectory();
+
+  this->GetGlobalGenerator()->AddIDEProject(fastbuildTarget, Config);
+
+  AddStampExeIfApplicable(fastbuildTarget);
+
+  AdditionalCleanFiles();
+
+  this->GetGlobalGenerator()->AddTarget(std::move(fastbuildTarget));
+}
+
+void cmFastbuildNormalTargetGenerator::ProcessManifests(
+  FastbuildLinkerNode& linkerNode) const
+{
+  if (this->GetGlobalGenerator()->GetCMakeInstance()->GetIsInTryCompile()) {
+    return;
+  }
+  auto manifests = this->GetManifestsAsFastbuildPath();
+  if (manifests.empty()) {
+    return;
+  }
+  // Manifests should always be in .Libraries2, so we re-link when needed.
+  // Tested in RunCMake.BuildDepends
+  for (auto const& manifest : manifests) {
+    linkerNode.Libraries2.emplace_back(manifest);
+  }
+
+  if (this->Makefile->GetSafeDefinition("CMAKE_C_COMPILER_ID") != "MSVC") {
+    return;
+  }
+
+  for (auto const& manifest : manifests) {
+    linkerNode.LinkerOptions =
+      cmStrCat("/MANIFESTINPUT:", manifest, ' ', linkerNode.LinkerOptions);
+  }
+  // /MANIFESTINPUT only works with /MANIFEST:EMBED
+  linkerNode.LinkerOptions =
+    cmStrCat("/MANIFEST:EMBED ", linkerNode.LinkerOptions);
+}
+
+void cmFastbuildNormalTargetGenerator::AddStampExeIfApplicable(
+  FastbuildTarget& fastbuildTarget) const
+{
+  LogMessage("AddStampExeIfApplicable(...)");
+  if (fastbuildTarget.LinkerNode.empty() ||
+      (fastbuildTarget.LinkerNode[0].Type != FastbuildLinkerNode::EXECUTABLE &&
+       fastbuildTarget.LinkerNode[0].Type !=
+         FastbuildLinkerNode::SHARED_LIBRARY)) {
+    return;
+  }
+  // File which executes all POST_BUILD steps.
+  // We use it in .LinkerStampExeArgs in order to run POST_BUILD steps after
+  // the compilation.
+  if (!fastbuildTarget.PostBuildExecNodes.Nodes.empty()) {
+    std::string const AllPostBuildExecsScriptFile =
+      cmStrCat(this->Makefile->GetHomeOutputDirectory(), "/CMakeFiles/",
+               fastbuildTarget.Name,
+               "-all-postbuild-commands" FASTBUILD_SCRIPT_FILE_EXTENSION);
+
+    CollapseAllExecsIntoOneScriptfile(
+      AllPostBuildExecsScriptFile, fastbuildTarget.PostBuildExecNodes.Nodes);
+    auto& linkerNode = fastbuildTarget.LinkerNode.back();
+    // On macOS, a target may have multiple linker nodes (e.g., for different
+    // architectures). In that case, add the POST_BUILD step to only one node
+    // to avoid running lipo multiple times.
+    linkerNode.LinkerStampExe =
+      cmGlobalFastbuildGenerator::GetExternalShellExecutable();
+    linkerNode.LinkerStampExeArgs = FASTBUILD_SCRIPT_FILE_ARG;
+    linkerNode.LinkerStampExeArgs +=
+      cmGlobalFastbuildGenerator::QuoteIfHasSpaces(
+        AllPostBuildExecsScriptFile);
+
+  } else {
+    LogMessage("No POST_BUILD steps for target: " + fastbuildTarget.Name);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::CollapseAllExecsIntoOneScriptfile(
+  std::string const& scriptFileName,
+  std::vector<FastbuildExecNode> const& execs) const
+{
+  cmsys::ofstream scriptFile(scriptFileName.c_str());
+  if (!scriptFile.is_open()) {
+    cmSystemTools::Error("Failed to open: " + scriptFileName);
+    return;
+  }
+  LogMessage("Writing collapsed Execs to " + scriptFileName);
+  auto const shell = cmGlobalFastbuildGenerator::GetExternalShellExecutable();
+  for (auto const& exec : execs) {
+    if (exec.ScriptFile.empty()) {
+      scriptFile << cmSystemTools::ConvertToOutputPath(exec.ExecExecutable)
+                 << " " << exec.ExecArguments << '\n';
+    } else {
+#if defined(_WIN32)
+      scriptFile << "call "
+                 << cmSystemTools::ConvertToWindowsOutputPath(exec.ScriptFile)
+                 << '\n';
+#else
+      scriptFile << cmSystemTools::ConvertToOutputPath(shell) << " "
+                 << cmSystemTools::ConvertToOutputPath(exec.ScriptFile)
+                 << '\n';
+#endif
+    }
+  }
+}
+
+std::string cmFastbuildNormalTargetGenerator::ComputeCodeCheckOptions(
+  cmSourceFile const& srcFile)
+{
+  cmValue const skipCodeCheck = srcFile.GetProperty("SKIP_LINTING");
+  std::string staticCheckRule;
+  if (!skipCodeCheck.IsOn()) {
+    std::string compilerLauncher;
+    staticCheckRule = this->GenerateCodeCheckRules(srcFile, compilerLauncher,
+                                                   "", Config, nullptr);
+    LogMessage(cmStrCat("CodeCheck: ", staticCheckRule));
+  }
+  return staticCheckRule;
+}
+
+void cmFastbuildNormalTargetGenerator::ComputeCompilerAndOptions(
+  std::string const& compilerOptions, std::string const& staticCheckOptions,
+  std::string const& language, FastbuildObjectListNode& outObjectList)
+{
+  auto& compilers = this->GetGlobalGenerator()->Compilers;
+  auto const compilerIter =
+    compilers.find(FASTBUILD_COMPILER_PREFIX + language);
+  auto const launcherIter =
+    compilers.find(FASTBUILD_LAUNCHER_PREFIX + language);
+  if (!staticCheckOptions.empty()) {
+    // If we want to run static checks - use CMake as a launcher.
+    // Tested in "RunCMake.ClangTidy", "RunCMake.IncludeWhatYouUse",
+    // "RunCMake.Cpplint", "RunCMake.Cppcheck", "RunCMake.MultiLint" tests.
+    outObjectList.Compiler = "." FASTBUILD_LAUNCHER_PREFIX + CMAKE_LANGUAGE;
+    outObjectList.CompilerOptions = staticCheckOptions;
+    // Add compile command which will be passed to the static analyzer via
+    // dash-dash.
+    if (compilerIter != compilers.end()) {
+      // Wrap in quotes to account for potential spaces in the path.
+      outObjectList.CompilerOptions +=
+        cmGlobalFastbuildGenerator::QuoteIfHasSpaces(
+          compilerIter->second.Executable);
+      outObjectList.CompilerOptions += compilerOptions;
+    }
+  } else if (launcherIter != compilers.end()) {
+    // Tested in "RunCMake.CompilerLauncher" test.
+    outObjectList.Compiler = "." + launcherIter->first;
+    outObjectList.CompilerOptions = launcherIter->second.Args;
+
+    auto vars = cmFastbuildNormalTargetGenerator::ComputeRuleVariables();
+    vars.Language = language.c_str();
+    std::string const targetSupportPath = this->ConvertToFastbuildPath(
+      this->GetGeneratorTarget()->GetCMFSupportDirectory());
+    vars.TargetSupportDir = targetSupportPath.c_str();
+    RulePlaceholderExpander->ExpandRuleVariables(
+      LocalCommonGenerator, outObjectList.CompilerOptions, vars);
+
+    // Add compiler executable explicitly to the compile options.
+    if (compilerIter != compilers.end()) {
+      outObjectList.CompilerOptions += " ";
+      // Wrap in quotes to account for potential spaces in the path.
+      outObjectList.CompilerOptions +=
+        cmGlobalFastbuildGenerator::QuoteIfHasSpaces(
+          compilerIter->second.Executable);
+      outObjectList.CompilerOptions += compilerOptions;
+    }
+  } else if (compilerIter != compilers.end()) {
+    outObjectList.Compiler = "." + compilerIter->first;
+    outObjectList.CompilerOptions = compilerOptions;
+  }
+  LogMessage(cmStrCat(".Compiler = ", outObjectList.Compiler));
+  LogMessage(cmStrCat(".CompilerOptions = ", outObjectList.CompilerOptions));
+}
+
+cmRulePlaceholderExpander::RuleVariables
+cmFastbuildNormalTargetGenerator::ComputeRuleVariables() const
+{
+  cmRulePlaceholderExpander::RuleVariables compileObjectVars;
+  compileObjectVars.CMTargetName = GeneratorTarget->GetName().c_str();
+  compileObjectVars.CMTargetType =
+    cmState::GetTargetTypeName(GeneratorTarget->GetType()).c_str();
+  compileObjectVars.CMTargetLabels =
+    this->GetGeneratorTarget()->GetTargetLabelsString().c_str();
+  compileObjectVars.Source = FASTBUILD_1_INPUT_PLACEHOLDER;
+  compileObjectVars.Object = FASTBUILD_2_INPUT_PLACEHOLDER;
+  compileObjectVars.ObjectDir =
+    FASTBUILD_DOLLAR_TAG "TargetOutputDir" FASTBUILD_DOLLAR_TAG;
+  compileObjectVars.ObjectFileDir = "";
+  compileObjectVars.Flags = "";
+  compileObjectVars.Includes = "";
+  compileObjectVars.Defines = "";
+  compileObjectVars.Includes = "";
+  compileObjectVars.TargetCompilePDB =
+    FASTBUILD_DOLLAR_TAG "CompilerPDB" FASTBUILD_DOLLAR_TAG;
+  compileObjectVars.Config = Config.c_str();
+  return compileObjectVars;
+}
+
+std::vector<std::string> cmFastbuildNormalTargetGenerator::GetSourceProperty(
+  cmSourceFile const& srcFile, std::string const& prop) const
+{
+  std::vector<std::string> res;
+  if (cmValue val = srcFile.GetProperty(prop)) {
+    cmExpandList(*val, res);
+    return GetGlobalGenerator()->ConvertToFastbuildPath(res);
+  }
+  return res;
+}
+
+void cmFastbuildNormalTargetGenerator::AppendExtraResources(
+  std::set<std::string>& deps) const
+{
+  // Generate Fastbuild's "Copy" commands to copy resources.
+  auto const generateCopyCommands =
+    [this](std::vector<cmSourceFile const*>& frameworkDeps) {
+      this->OSXBundleGenerator->GenerateMacOSXContentStatements(
+        frameworkDeps, this->MacOSXContentGenerator.get(), Config);
+    };
+
+  std::vector<cmSourceFile const*> headerSources;
+  this->GeneratorTarget->GetHeaderSources(headerSources, Config);
+  generateCopyCommands(headerSources);
+
+  std::vector<cmSourceFile const*> extraSources;
+  this->GeneratorTarget->GetExtraSources(extraSources, Config);
+  generateCopyCommands(extraSources);
+
+  std::vector<cmSourceFile const*> externalObjects;
+  this->GeneratorTarget->GetExternalObjects(externalObjects, Config);
+  generateCopyCommands(externalObjects);
+
+  for (FastbuildCopyNode const& node : this->CopyNodes) {
+    LogMessage("Adding resource: " + node.Name);
+    deps.emplace(node.Name);
+  }
+}
+
+std::string cmFastbuildNormalTargetGenerator::GetCompileOptions(
+  cmSourceFile const& srcFile, std::string const& arch)
+{
+  std::string const language = srcFile.GetLanguage();
+  cmRulePlaceholderExpander::RuleVariables compileObjectVars =
+    ComputeRuleVariables();
+  std::string const compilerFlags = DetectCompilerFlags(srcFile, arch);
+  std::string const compilerDefines = ComputeDefines(srcFile);
+  compileObjectVars.Flags = compilerFlags.c_str();
+  compileObjectVars.Defines = compilerDefines.c_str();
+  compileObjectVars.Language = language.c_str();
+
+  std::string rule = CompileObjectCmakeRules.at(language);
+  RulePlaceholderExpander->ExpandRuleVariables(LocalCommonGenerator, rule,
+                                               compileObjectVars);
+
+  std::string compilerExecutable;
+  // Remove the compiler from .CompilerOptions, since it would be set as
+  // .Compiler in Fastbuild.
+  // See https://www.fastbuild.org/docs/functions/objectlist.html for a
+  // reference.
+  std::string options;
+  if (!cmSystemTools::SplitProgramFromArgs(rule, compilerExecutable,
+                                           options)) {
+    cmSystemTools::Error(cmStrCat("Failed to split compiler options: ", rule));
+  }
+  LogMessage("Expanded compile options = " + options);
+  LogMessage("Compiler executable = " + compilerExecutable);
+  return options;
+}
+
+std::vector<std::string> cmFastbuildNormalTargetGenerator::GetArches() const
+{
+  auto arches = this->GetGeneratorTarget()->GetAppleArchs(Config, {});
+  // Don't add any arch-specific logic if arch is only one.
+  if (arches.empty() || arches.size() == 1) {
+    arches.clear();
+    arches.emplace_back();
+  }
+  return arches;
+}
+
+std::vector<FastbuildObjectListNode>
+cmFastbuildNormalTargetGenerator::GenerateObjects()
+{
+  this->GetGlobalGenerator()->AllFoldersToClean.insert(ObjectOutDir);
+
+  std::map<std::string, FastbuildObjectListNode> nodesPermutations;
+
+  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
+
+  std::vector<cmSourceFile const*> objectSources;
+  GeneratorTarget->GetObjectSources(objectSources, Config);
+
+  std::set<std::string> createdPCH;
+
+  for (cmSourceFile const* source : objectSources) {
+
+    cmSourceFile const& srcFile = *source;
+
+    // Do not generate separate node for PCH source file.
+    if (this->GeneratorTarget->GetPchSource(Config, srcFile.GetLanguage()) ==
+        srcFile.GetFullPath()) {
+      continue;
+    }
+
+    std::string const language = srcFile.GetLanguage();
+    LogMessage(cmStrCat("Source file: ",
+                        this->ConvertToFastbuildPath(srcFile.GetFullPath())));
+    LogMessage("Language: " + language);
+
+    std::string const staticCheckOptions = ComputeCodeCheckOptions(srcFile);
+
+    for (auto const& arch : this->GetArches()) {
+      std::string const compileOptions = GetCompileOptions(srcFile, arch);
+
+      std::string objOutDirWithPossibleSubdir = ObjectOutDir;
+
+      // If object should be placed in some subdir in the output
+      // path. Tested in "SourceGroups" test.
+      auto const subdir = cmSystemTools::GetFilenamePath(
+        this->GeneratorTarget->GetObjectName(source));
+      if (!subdir.empty()) {
+        objOutDirWithPossibleSubdir += "/";
+        objOutDirWithPossibleSubdir += subdir;
+      }
+
+      std::string const objectListHash = hash.HashString(cmStrCat(
+        compileOptions, staticCheckOptions, objOutDirWithPossibleSubdir,
+        // If file does not need PCH - it must be in another ObjectList.
+        srcFile.GetProperty("SKIP_PRECOMPILE_HEADERS"),
+        srcFile.GetLanguage()));
+
+      LogMessage("ObjectList Hash: " + objectListHash);
+
+      FastbuildObjectListNode& objectListNode =
+        nodesPermutations[objectListHash];
+
+      // Absolute path needed in "RunCMake.SymlinkTrees" test.
+      objectListNode.CompilerInputFiles.push_back(srcFile.GetFullPath());
+
+      std::vector<std::string> const outputs =
+        GetSourceProperty(srcFile, "OBJECT_OUTPUTS");
+      objectListNode.ObjectOutputs.insert(outputs.begin(), outputs.end());
+
+      std::vector<std::string> const depends =
+        GetSourceProperty(srcFile, "OBJECT_DEPENDS");
+      objectListNode.ObjectDepends.insert(depends.begin(), depends.end());
+
+      // We have already computed properties that are computed below.
+      // (.CompilerOptions, .PCH*, etc.). Short circuit this iteration.
+      if (!objectListNode.CompilerOptions.empty()) {
+        continue;
+      }
+
+      objectListNode.CompilerOutputPath = objOutDirWithPossibleSubdir;
+      LogMessage(cmStrCat("Output path: ", objectListNode.CompilerOutputPath));
+
+      ComputeCompilerAndOptions(compileOptions, staticCheckOptions, language,
+                                objectListNode);
+      ComputePCH(*source, objectListNode, createdPCH);
+
+      objectListNode.Name =
+        cmStrCat(language, "_ObjectGroup_", GetTargetName());
+      // TODO: Ask cmake the output objects and group by extension instead
+      // of doing this
+      if (language == "RC") {
+        objectListNode.CompilerOutputExtension = ".res";
+      } else {
+        if (!arch.empty()) {
+          objectListNode.CompilerOutputExtension = cmStrCat('.', arch);
+          objectListNode.arch = arch;
+        }
+        objectListNode.CompilerOutputExtension +=
+          this->GetMakefile()->GetSafeDefinition(
+            cmStrCat("CMAKE_", language, "_OUTPUT_EXTENSION"));
+      }
+    }
+  }
+
+  int groupNameCount = 1;
+
+  for (auto& val : nodesPermutations) {
+    auto& objectListNode = val.second;
+    objectListNode.Name =
+      cmStrCat(objectListNode.Name, "-", objectListNode.CompilerOutputPath,
+               "-", std::to_string(groupNameCount++));
+    LogMessage(cmStrCat("ObjectList name: ", objectListNode.Name));
+  }
+
+  std::vector<FastbuildObjectListNode> objects;
+  objects.reserve(nodesPermutations.size());
+  for (auto& val : nodesPermutations) {
+    auto& node = val.second;
+    objects.emplace_back(std::move(node));
+    if (!objects.back().PCHInputFile.empty()) {
+      // Node that produces PCH should be the first one, since other nodes
+      // might reuse this PCH.
+      std::swap(*objects.begin(), objects.back());
+    }
+  }
+  return objects;
+}
+
+std::string cmFastbuildNormalTargetGenerator::ResolveIfAlias(
+  std::string const& targetName) const
+{
+  LogMessage("targetName: " + targetName);
+  std::map<std::string, std::string> const aliases =
+    this->Makefile->GetAliasTargets();
+  auto const iter = aliases.find(targetName);
+  if (iter != aliases.end()) {
+    LogMessage("Non alias name: " + iter->second);
+    return iter->second;
+  }
+  return targetName;
+}
+
+void cmFastbuildNormalTargetGenerator::AppendExternalObject(
+  FastbuildLinkerNode& linkerNode, std::set<std::string>& linkedDeps) const
+{
+  // Different aspects of this logic exercised in "ObjectLibrary" and
+  // "ExportImport" test. When making changes here - verify that both of those
+  // tests are still passing.
+  LogMessage("AppendExternalObject(...)");
+  std::vector<cmSourceFile const*> extObjects;
+  this->GeneratorTarget->GetExternalObjects(extObjects, Config);
+  for (cmSourceFile const* src : extObjects) {
+
+    std::string const pathToObj =
+      this->ConvertToFastbuildPath(src->GetFullPath());
+    LogMessage("EXT OBJ: " + pathToObj);
+    std::string const objLibName = ResolveIfAlias(src->GetObjectLibrary());
+    LogMessage("GetObjectLibrary: " + objLibName);
+    // Tested in "ExternalOBJ" test.
+    cmTarget const* target =
+      this->GlobalCommonGenerator->FindTarget(objLibName);
+    if (objLibName.empty()) {
+      linkerNode.LibrarianAdditionalInputs.emplace_back(pathToObj);
+    }
+    // We know how to generate this target and haven't added this dependency
+    // yet.
+    else if (target) {
+      if (!linkedDeps.emplace(objLibName + FASTBUILD_OBJECTS_ALIAS_POSTFIX)
+             .second) {
+        LogMessage("Object Target: " + objLibName +
+                   FASTBUILD_OBJECTS_ALIAS_POSTFIX " already linked");
+        continue;
+      }
+      linkerNode.LibrarianAdditionalInputs.emplace_back(
+        objLibName + FASTBUILD_OBJECTS_ALIAS_POSTFIX);
+    } else if (linkedDeps.emplace(pathToObj).second) {
+      LogMessage("Adding obj dep : " + pathToObj);
+      linkerNode.LibrarianAdditionalInputs.emplace_back(pathToObj);
+    }
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendExeToLink(
+  FastbuildLinkerNode& linkerNode,
+  cmComputeLinkInformation::Item const& item) const
+{
+  std::string const decorated =
+    item.GetFormattedItem(this->ConvertToFastbuildPath(item.Value.Value))
+      .Value;
+  LogMessage("Linking to executable : " + decorated);
+  // Tested in "InterfaceLinkLibrariesDirect" and "Plugin" test.
+  linkerNode.LinkerOptions +=
+    (" " + cmGlobalFastbuildGenerator::QuoteIfHasSpaces(decorated));
+}
+
+std::string cmFastbuildNormalTargetGenerator::GetImportedLoc(
+  cmComputeLinkInformation::Item const& item) const
+{
+  // Link to import library when possible.
+  // Tested in "StagingPrefix" test on Windows/MSVC.
+  cmStateEnums::ArtifactType const artifact =
+    item.Target->HasImportLibrary(Config)
+    ? cmStateEnums::ImportLibraryArtifact
+    : cmStateEnums::RuntimeBinaryArtifact;
+
+  std::string importedLoc = this->ConvertToFastbuildPath(
+    item.Target->GetFullPath(Config, artifact, true));
+  LogMessage("ImportedGetLocation: " + importedLoc);
+  return importedLoc;
+}
+
+void cmFastbuildNormalTargetGenerator::AppendTargetDep(
+  FastbuildLinkerNode& linkerNode, std::set<std::string>& linkedObjects,
+  cmComputeLinkInformation::Item const& item) const
+{
+  LogMessage("AppendTargetDep(...)");
+  cmStateEnums::TargetType const depType = item.Target->GetType();
+  LogMessage("Link dep type: " + std::to_string(depType));
+  LogMessage("Target name: " + item.Target->GetName());
+  auto const resolvedTargetName = ResolveIfAlias(item.Target->GetName());
+  LogMessage("Resolved: " + resolvedTargetName);
+  if (depType == cmStateEnums::INTERFACE_LIBRARY) {
+    return;
+  }
+  std::string const feature = item.GetFeatureName();
+
+  if (item.Target->IsImported()) {
+
+    if (feature == "FRAMEWORK") {
+      // Use just framework's name. The exact path where to look for the
+      // framework will be provided from "frameworkPath" in
+      // "cmFastbuildNormalTargetGenerator::DetectBaseLinkerCommand(...)".
+      // Tested in "RunCMake.Framework - ImportedFrameworkConsumption".
+      std::string const decorated =
+        item.GetFormattedItem(item.Value.Value).Value;
+      LogMessage("Adding framework dep <" + decorated + "> to command line");
+      linkerNode.LinkerOptions += (" " + decorated);
+      return;
+    }
+    if (depType == cmStateEnums::UNKNOWN_LIBRARY) {
+      LogMessage("Unknown library -- adding to LibrarianAdditionalInputs or "
+                 "Libraries2");
+      if (UsingCommandLine) {
+        AppendCommandLineDep(linkerNode, item);
+      } else {
+        AppendLinkDep(linkerNode, GetImportedLoc(item));
+      }
+      return;
+    }
+    // Tested in "ExportImport" test.
+    if (depType == cmStateEnums::EXECUTABLE) {
+      AppendExeToLink(linkerNode, item);
+      return;
+    }
+    // Skip exported objects.
+    // Tested in "ExportImport" test.
+    if (depType == cmStateEnums::OBJECT_LIBRARY) {
+      LogMessage("target : " + item.Target->GetName() +
+                 " already linked... Skipping");
+      return;
+    }
+    // Tested in "ExportImport" test.
+    cmList const list{ GetImportedLoc(item) };
+    for (std::string const& linkDep : list) {
+      AppendLinkDep(linkerNode, linkDep);
+    }
+  } else {
+    if (depType == cmStateEnums::SHARED_LIBRARY &&
+        this->GeneratorTarget->GetPropertyAsBool("LINK_DEPENDS_NO_SHARED")) {
+      // It moves the dep outside of FASTBuild control, so the binary won't
+      // be re-built if the shared lib has changed.
+      // Tested in "BuildDepends" test.
+      LogMessage("LINK_DEPENDS_NO_SHARED is set on the target, adding dep" +
+                 item.Value.Value + " as is");
+      linkerNode.LinkerOptions +=
+        (" " + cmGlobalFastbuildGenerator::QuoteIfHasSpaces(item.Value.Value));
+      return;
+    }
+    // Just add path to binary artifact to command line (except for OBJECT
+    // libraries which we will link directly).
+    if (UsingCommandLine && depType != cmStateEnums::OBJECT_LIBRARY) {
+      AppendCommandLineDep(linkerNode, item);
+      return;
+    }
+    // This dep has a special way of linking to it (e.g.
+    // "CMAKE_LINK_LIBRARY_USING_<FEATURE>").
+    bool const isFeature = !feature.empty() && feature != "DEFAULT";
+    if (isFeature) {
+      std::string const decorated =
+        item.GetFormattedItem(this->ConvertToFastbuildPath(item.Value.Value))
+          .Value;
+      LogMessage("Prepending with feature: " + decorated);
+      linkerNode.LinkerOptions += (" " + decorated);
+    }
+
+    std::string dep = resolvedTargetName +
+      (depType == cmStateEnums::OBJECT_LIBRARY
+         ? FASTBUILD_OBJECTS_ALIAS_POSTFIX
+         : FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX);
+    if (!linkerNode.Arch.empty()) {
+      dep += cmStrCat('-', linkerNode.Arch);
+    }
+    // If we have a special way of linking the dep, we can't have it in
+    // ".Libraries" (since there might be multiple such deps, but
+    // FASTBuild expands ".Libraries" as a continuous array, so we can't
+    // inject any properties in between). Tested in
+    // "RunCMake.target_link_libraries-LINK_LIBRARY" test.
+    if (isFeature) {
+      LogMessage("AppendTargetDep: " + dep + " as prebuild");
+      linkerNode.PreBuildDependencies.emplace(dep);
+      return;
+    }
+
+    if (depType != cmStateEnums::OBJECT_LIBRARY ||
+        linkedObjects.emplace(dep).second) {
+      AppendLinkDep(linkerNode, dep);
+    }
+    AppendTransitivelyLinkedObjects(*item.Target, linkedObjects);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendPrebuildDeps(
+  FastbuildLinkerNode& linkerNode,
+  cmComputeLinkInformation::Item const& item) const
+{
+  if (!item.Target->IsImported()) {
+    return;
+  }
+  // In "RunCMake.FileAPI" imported object library "imported_object_lib" is
+  // added w/o import location...
+  if (item.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+    return;
+  }
+  cmList const list{ GetImportedLoc(item) };
+  for (std::string const& linkDep : list) {
+    // In case we know how to generate this file (needed for proper
+    // sorting by deps). Tested in "RunCMake.target_link_libraries-ALIAS"
+    // test.
+    auto fastbuildTarget =
+      this->GetGlobalGenerator()->GetTargetByOutputName(linkDep);
+    std::string fastbuildTargetName;
+    if (fastbuildTarget) {
+      fastbuildTargetName = std::move(fastbuildTarget->Name);
+    }
+    if (!fastbuildTargetName.empty()) {
+      LogMessage("Adding dep to " + fastbuildTargetName);
+      linkerNode.PreBuildDependencies.insert(std::move(fastbuildTargetName));
+    } else {
+      LogMessage("Adding dep " + linkDep + " for sorting");
+      linkerNode.PreBuildDependencies.insert(linkDep);
+    }
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendTransitivelyLinkedObjects(
+  cmGeneratorTarget const& target, std::set<std::string>& linkedObjects) const
+{
+  std::vector<std::string> objs;
+  // Consider that all those object are now linked as well.
+  // Tested in "ExportImport" test.
+  target.GetTargetObjectNames(Config, objs);
+  for (std::string const& obj : objs) {
+    std::string const pathToObj = this->ConvertToFastbuildPath(
+      cmStrCat(target.GetObjectDirectory(Config), '/', obj));
+    linkedObjects.insert(pathToObj);
+  }
+  // Object libs should not be propagated transitively. It's especially
+  // important for LinkObjRHSObject2 test where the absence of the propagation
+  // is tested.
+  for (auto const& linkedTarget :
+       target.Target->GetLinkImplementationEntries()) {
+    auto objAlias = linkedTarget.Value + FASTBUILD_OBJECTS_ALIAS_POSTFIX;
+    LogMessage("Object target is linked transitively " + objAlias);
+    linkedObjects.emplace(std::move(objAlias));
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendCommandLineDep(
+  FastbuildLinkerNode& linkerNode,
+  cmComputeLinkInformation::Item const& item) const
+{
+  LogMessage("AppendCommandLineDep(...)");
+  // Tested in:
+  // "LinkDirectory" (TargetType::EXECUTABLE),
+  // "ObjC.simple-build-test" (TargetType::SHARED_LIBRARY),
+  // "XCTest" (TargetType::MODULE_LIBRARY) tests.
+
+  std::string formatted;
+  if (item.Target && item.Target->IsImported()) {
+    formatted = GetImportedLoc(item);
+  } else {
+    formatted = item.GetFormattedItem(item.Value.Value).Value;
+  }
+  formatted = this->ConvertToFastbuildPath(formatted);
+
+  LogMessage("Unknown link dep: " + formatted + ", adding to command line");
+
+  // Only add real artifacts to .Libraries2, otherwise Fastbuild will always
+  // consider the target out-of-date (since its input doesn't exist).
+  if (item.IsPath == cmComputeLinkInformation::ItemIsPath::Yes &&
+      item.GetFeatureName() == "DEFAULT") {
+    linkerNode.LinkerOptions +=
+      (" " + cmGlobalFastbuildGenerator::QuoteIfHasSpaces(formatted));
+    AppendToLibraries2IfApplicable(linkerNode, std::move(formatted));
+  } else {
+    // It's some link option, not a path.
+    linkerNode.LinkerOptions += (" " + formatted);
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendToLibraries2IfApplicable(
+  FastbuildLinkerNode& linkerNode, std::string dep) const
+{
+  // Strings like "-framework Cocoa" in .Libraries2 node will always make the
+  // target out-of-date (since it never exists).
+  if (this->GeneratorTarget->IsApple() &&
+      cmSystemTools::StringStartsWith(dep, "-framework")) {
+    LogMessage("Not adding framework: " + dep + " to .Libraries2");
+    return;
+  }
+
+  auto const target = this->GetGlobalGenerator()->GetTargetByOutputName(dep);
+  // Fastbuild doesn't support executables in .Libraries2, though we can use
+  // Executables via "-bundle_loader" on Apple.
+  if (this->GeneratorTarget->IsApple() && target &&
+      !target->LinkerNode.empty() &&
+      target->LinkerNode[0].Type == FastbuildLinkerNode::EXECUTABLE) {
+    LogMessage("Not adding DLL/Executable(" + linkerNode.Name +
+               " to .Libraries2");
+    return;
+  }
+
+  // Additing to .Libraries2 for tracking.
+  LogMessage("Adding " + dep + " .Libraries2");
+  linkerNode.Libraries2.emplace_back(std::move(dep));
+}
+
+void cmFastbuildNormalTargetGenerator::AppendLINK_DEPENDS(
+  FastbuildLinkerNode& linkerNode) const
+{
+  // LINK_DEPENDS and such.
+  // Tested in "BuildDepends" test.
+  for (std::string const& lang : Languages) {
+    for (BT<std::string> const& dep :
+         this->GeneratorTarget->GetLinkDepends(Config, lang)) {
+      // We can't add "LINK_DEPENDS" to .PreBuildDependencies, since FASTBuild
+      // only forces such targets to be built and doesn't force re-linking if
+      // they've changed.
+      linkerNode.Libraries2.emplace_back(
+        this->ConvertToFastbuildPath(dep.Value));
+    }
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendLinkDep(
+  FastbuildLinkerNode& linkerNode, std::string dep) const
+{
+  LogMessage("AppendLinkDep: " + dep +
+             " to .LibrarianAdditionalInputs/.Libraries");
+  linkerNode.LibrarianAdditionalInputs.emplace_back(std::move(dep));
+}
+
+void cmFastbuildNormalTargetGenerator::AppendDirectObjectLibs(
+  FastbuildLinkerNode& linkerNode, std::set<std::string>& linkedObjects)
+{
+  auto const srcs = this->GeneratorTarget->GetSourceFiles(Config);
+  for (auto const& entry : srcs) {
+    auto const objLib = entry.Value->GetObjectLibrary();
+    auto const objPath = entry.Value->GetFullPath();
+    LogMessage("Source obj entry: " + objPath);
+    if (!objLib.empty()) {
+      auto* const objTarget =
+        this->LocalGenerator->FindGeneratorTargetToUse(objLib);
+      if (objTarget) {
+        LogMessage("Imported: " + std::to_string(objTarget->IsImported()));
+        std::string fastbuildTarget;
+        // If target is imported - we don't have it in our build file, so can't
+        // refer to it by name. Use file path to the object then.
+        // Tested in "ExportImport" test.
+        if (objTarget->IsImported()) {
+          fastbuildTarget = entry.Value->GetFullPath();
+        } else {
+          // Mark all target objects as linked.
+          linkedObjects.emplace(this->ConvertToFastbuildPath(objPath));
+          fastbuildTarget =
+            objTarget->GetName() + FASTBUILD_OBJECTS_ALIAS_POSTFIX;
+        }
+        if (linkedObjects.emplace(fastbuildTarget).second) {
+          LogMessage("Adding object target: " + fastbuildTarget);
+          linkerNode.LibrarianAdditionalInputs.emplace_back(
+            std::move(fastbuildTarget));
+        }
+      }
+    }
+  }
+}
+
+void cmFastbuildNormalTargetGenerator::AppendLinkDeps(
+  std::set<FastbuildTargetDep>& preBuildDeps, FastbuildLinkerNode& linkerNode)
+{
+  std::set<std::string> linkedObjects;
+  cmComputeLinkInformation const* linkInfo =
+    this->GeneratorTarget->GetLinkInformation(Config);
+  if (!linkInfo) {
+    return;
+  }
+
+  UsingCommandLine = false;
+  AppendLINK_DEPENDS(linkerNode);
+  // Object libs that are linked directly to target (e.g.
+  // add_executable(test_exe archiveObjs)
+  AppendDirectObjectLibs(linkerNode, linkedObjects);
+  // target_link_libraries.
+  cmComputeLinkInformation::ItemVector const items = linkInfo->GetItems();
+
+  LogMessage(cmStrCat("Link items size: ", items.size()));
+  for (cmComputeLinkInformation::Item const& item : items) {
+    std::string const feature = item.GetFeatureName();
+    LogMessage("GetFeatureName: " + feature);
+    if (!feature.empty()) {
+      LogMessage("GetFormattedItem: " +
+                 item.GetFormattedItem(item.Value.Value).Value);
+    }
+    // We're linked to `$<TARGET_OBJECTS>`.
+    // Static libs transitively propagate such deps, see:
+    // https://cmake.org/cmake/help/latest/command/target_link_libraries.html#linking-object-libraries-via-target-objects
+    if (item.ObjectSource &&
+        linkerNode.Type != FastbuildLinkerNode::STATIC_LIBRARY) {
+      // Tested in "ObjectLibrary" test.
+      auto libName = item.ObjectSource->GetObjectLibrary();
+      std::string dep = libName + FASTBUILD_OBJECTS_ALIAS_POSTFIX;
+      if (linkedObjects.emplace(dep).second) {
+        FastbuildTargetDep targetDep{ std::move(libName) };
+        targetDep.Type = FastbuildTargetDepType::ORDER_ONLY;
+        preBuildDeps.emplace(std::move(targetDep));
+        linkerNode.LibrarianAdditionalInputs.emplace_back(std::move(dep));
+      }
+    } else if (linkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY) {
+      LogMessage("Skipping linking to STATIC_LIBRARY (" + linkerNode.Name +
+                 ")");
+      continue;
+    }
+    // We're linked to exact target.
+    else if (item.Target) {
+      AppendTargetDep(linkerNode, linkedObjects, item);
+      AppendPrebuildDeps(linkerNode, item);
+    } else {
+      AppendCommandLineDep(linkerNode, item);
+      UsingCommandLine = true;
+    }
+  }
+  AppendExternalObject(linkerNode, linkedObjects);
+}
+
+void cmFastbuildNormalTargetGenerator::AddLipoCommand(FastbuildTarget& target)
+{
+  static auto const lipo = cmSystemTools::FindProgram("lipo");
+  LogMessage("found lipo at " + lipo);
+  FastbuildExecNode exec;
+  exec.ExecExecutable = lipo;
+  exec.ExecOutput = target.RealOutput;
+  for (auto const& ArchSpecificTarget : target.LinkerNode) {
+    exec.ExecInput.emplace_back(ArchSpecificTarget.LinkerOutput);
+  }
+  exec.ExecArguments +=
+    "-create -output " + target.RealOutput + " " + cmJoin(exec.ExecInput, " ");
+  target.PostBuildExecNodes.Alias.PreBuildDependencies.emplace(
+    exec.ExecOutput);
+  target.PostBuildExecNodes.Nodes.emplace_back(std::move(exec));
+}
+
+void cmFastbuildNormalTargetGenerator::GenerateLink(
+  FastbuildTarget& target, std::vector<std::string> const& objectDepends)
+{
+  std::string const targetName = this->GetTargetName();
+  cmGeneratorTarget::Names const targetNames = DetectOutput();
+  LogMessage("targetNames.Real: " + targetNames.Real);
+  LogMessage("targetNames.ImportOutput: " + targetNames.ImportOutput);
+  LogMessage("targetNames.SharedObject: " + targetNames.SharedObject);
+  LogMessage("targetNames.Base: " + targetNames.Base);
+
+  std::vector<std::string> allNodes;
+  auto const arches = this->GetArches();
+  for (std::size_t i = 0; i < arches.size(); ++i) {
+    auto const& arch = arches[i];
+    FastbuildLinkerNode linkerNode;
+    ProcessManifests(linkerNode);
+    // Objects built by the current target.
+    for (auto const& objectList : target.ObjectListNodes) {
+      if (objectList.arch.empty() || objectList.arch == arch) {
+        linkerNode.LibrarianAdditionalInputs.push_back(objectList.Name);
+      }
+    }
+
+    // Detection of the link command as follows:
+    auto const type = this->GeneratorTarget->GetType();
+    switch (type) {
+      case cmStateEnums::EXECUTABLE: {
+        LogMessage("Generating EXECUTABLE");
+        linkerNode.Type = FastbuildLinkerNode::EXECUTABLE;
+        break;
+      }
+      case cmStateEnums::MODULE_LIBRARY: {
+        LogMessage("Generating MODULE_LIBRARY");
+        linkerNode.Type = FastbuildLinkerNode::SHARED_LIBRARY;
+        break;
+      }
+      case cmStateEnums::SHARED_LIBRARY: {
+        LogMessage("Generating SHARED_LIBRARY");
+        linkerNode.Type = FastbuildLinkerNode::SHARED_LIBRARY;
+        break;
+      }
+      case cmStateEnums::STATIC_LIBRARY: {
+        LogMessage("Generating STATIC_LIBRARY");
+        linkerNode.Type = FastbuildLinkerNode::STATIC_LIBRARY;
+        break;
+      }
+      case cmStateEnums::OBJECT_LIBRARY: {
+        LogMessage("Generating OBJECT_LIBRARY");
+        return;
+      }
+      default: {
+        LogMessage("Skipping GenerateLink");
+        return;
+      }
+    }
+
+    std::string const targetOutput =
+      ConvertToFastbuildPath(GeneratorTarget->GetFullPath(Config));
+    std::string targetOutputReal = ConvertToFastbuildPath(
+      GeneratorTarget->GetFullPath(Config, cmStateEnums::RuntimeBinaryArtifact,
+                                   /*realname=*/true));
+    LogMessage("targetOutput: " + targetOutput);
+    LogMessage("targetOutputReal: " + targetOutputReal);
+
+    std::string const output =
+      cmSystemTools::GetFilenameName(targetNames.Output);
+    std::string const outputReal =
+      cmSystemTools::GetFilenameName(targetNames.Real);
+    // Generate "Copy" nodes for copying Framework / Bundle resources.
+    AppendExtraResources(linkerNode.PreBuildDependencies);
+
+    if (type == cmStateEnums::EXECUTABLE ||
+        type == cmStateEnums::SHARED_LIBRARY) {
+      // Tested in "RunCMake.BuildDepends" test (we need to rebuild when
+      // manifest  changes).
+      std::copy(objectDepends.begin(), objectDepends.end(),
+                std::back_inserter(linkerNode.Libraries2));
+    }
+
+    if (GeneratorTarget->IsAppBundleOnApple()) {
+      // Create the app bundle
+      std::string outpath = GeneratorTarget->GetDirectory(Config);
+      this->OSXBundleGenerator->CreateAppBundle(targetNames.Output, outpath,
+                                                Config);
+      targetOutputReal = outpath;
+      targetOutputReal += "/";
+      targetOutputReal += outputReal;
+      targetOutputReal = this->ConvertToFastbuildPath(targetOutputReal);
+    } else if (GeneratorTarget->IsFrameworkOnApple()) {
+      // Create the library framework.
+      this->OSXBundleGenerator->CreateFramework(
+        targetNames.Output, GeneratorTarget->GetDirectory(Config), Config);
+    } else if (GeneratorTarget->IsCFBundleOnApple()) {
+      // Create the core foundation bundle.
+      this->OSXBundleGenerator->CreateCFBundle(
+        targetNames.Output, GeneratorTarget->GetDirectory(Config), Config);
+    }
+
+    std::string linkCmd;
+    if (!DetectBaseLinkerCommand(linkCmd, arch, targetNames)) {
+      LogMessage("No linker command detected");
+      return;
+    }
+
+    std::string executable;
+    std::string linkerOptions;
+    std::string linkerType = "auto";
+
+    GetLinkerExecutableAndArgs(linkCmd, executable, linkerOptions);
+
+    linkerNode.Compiler = ".Compiler_dummy";
+    linkerNode.CompilerOptions = " ";
+
+    linkerNode.Name = targetName;
+    linkerNode.LinkerOutput = targetOutputReal;
+    this->GetGlobalGenerator()->AddFileToClean(linkerNode.LinkerOutput);
+    target.RealOutput = targetOutputReal;
+    if (!arch.empty()) {
+      linkerNode.Name += cmStrCat('-', arch);
+      linkerNode.LinkerOutput += cmStrCat('.', arch);
+      linkerNode.Arch = arch;
+    }
+    linkerNode.Linker = executable;
+    linkerNode.LinkerType = linkerType;
+    linkerNode.LinkerOptions += linkerOptions;
+
+    AppendLinkDeps(target.PreBuildDependencies, linkerNode);
+    ApplyLWYUToLinkerCommand(linkerNode);
+
+    // On macOS, only the last LinkerNode performs lipo in POST_BUILD.
+    // Make it depend on all previous nodes to ensure correct execution order.
+    if (i == arches.size() - 1) {
+      for (auto& prevNode : allNodes) {
+        linkerNode.PreBuildDependencies.emplace(std::move(prevNode));
+      }
+    } else {
+      allNodes.emplace_back(linkerNode.Name);
+    }
+    if (!target.ObjectListNodes.empty()) {
+      // Just reuse any of compiler options mainly for the correct IDE project
+      // generation.
+      linkerNode.CompilerOptions = target.ObjectListNodes[0].CompilerOptions;
+    }
+    target.LinkerNode.emplace_back(std::move(linkerNode));
+  }
+}
+
+std::vector<FastbuildExecNode>
+cmFastbuildNormalTargetGenerator::GetSymlinkExecs() const
+{
+  std::vector<FastbuildExecNode> res;
+  cmGeneratorTarget::Names const targetNames = DetectOutput();
+  LogMessage("targetNames.Real: " + targetNames.Real);
+  LogMessage("targetNames.ImportOutput: " + targetNames.ImportOutput);
+  LogMessage("targetNames.SharedObject: " + targetNames.SharedObject);
+  LogMessage("targetNames.Base: " + targetNames.Base);
+
+  std::string const targetOutput =
+    ConvertToFastbuildPath(GeneratorTarget->GetFullPath(Config));
+  std::string const targetOutputReal = ConvertToFastbuildPath(
+    GeneratorTarget->GetFullPath(Config, cmStateEnums::RuntimeBinaryArtifact,
+                                 /*realname=*/true));
+  LogMessage("targetOutput: " + targetOutput);
+
+  LogMessage("targetOutputReal: " + targetOutputReal);
+
+  if (targetOutput != targetOutputReal &&
+      !GeneratorTarget->IsFrameworkOnApple()) {
+    auto const generateSymlinkCommand = [&](std::string const& from,
+                                            std::string const& to) {
+      if (from.empty() || to.empty() || from == to) {
+        return;
+      }
+      LogMessage("Symlinking " + from + " -> " + to);
+      FastbuildExecNode postBuildExecNode;
+      postBuildExecNode.Name = "cmake_symlink_" + to;
+      postBuildExecNode.ExecOutput =
+        cmJoin({ GeneratorTarget->GetDirectory(Config), to }, "/");
+      postBuildExecNode.ExecExecutable = cmSystemTools::GetCMakeCommand();
+      postBuildExecNode.ExecArguments =
+        "-E cmake_symlink_executable " +
+        cmGlobalFastbuildGenerator::QuoteIfHasSpaces(from) + " " +
+        cmGlobalFastbuildGenerator::QuoteIfHasSpaces(
+          this->ConvertToFastbuildPath(postBuildExecNode.ExecOutput));
+      res.emplace_back(std::move(postBuildExecNode));
+    };
+    generateSymlinkCommand(targetNames.Real, targetNames.Output);
+    generateSymlinkCommand(targetNames.Real, targetNames.SharedObject);
+    generateSymlinkCommand(targetNames.ImportReal, targetNames.ImportOutput);
+  }
+  return res;
+}

+ 141 - 0
Source/cmFastbuildNormalTargetGenerator.h

@@ -0,0 +1,141 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "cmComputeLinkInformation.h"
+#include "cmFastbuildTargetGenerator.h"
+#include "cmGeneratorTarget.h"
+#include "cmRulePlaceholderExpander.h"
+class cmSourceFile;
+struct FastbuildExecNode;
+struct FastbuildLinkerNode;
+struct FastbuildObjectListNode;
+struct FastbuildTarget;
+struct FastbuildTargetDep;
+
+class cmFastbuildNormalTargetGenerator : public cmFastbuildTargetGenerator
+{
+  std::unique_ptr<cmRulePlaceholderExpander> const RulePlaceholderExpander;
+  std::string const ObjectOutDir;
+  std::set<std::string> const Languages;
+  std::unordered_map<std::string, std::string> const CompileObjectCmakeRules;
+
+  // Now we're adding our link deps to command line and using .Libraries2 for
+  // tracking deps.
+  bool UsingCommandLine = false;
+
+  std::unordered_map<std::string, std::string> TargetIncludesByLanguage;
+  std::map<std::pair<std::string, std::string>, std::string>
+    CompileFlagsByLangAndArch;
+
+public:
+  cmFastbuildNormalTargetGenerator(cmGeneratorTarget* gt, std::string config);
+
+  void Generate() override;
+
+private:
+  void GenerateLink(FastbuildTarget& target,
+                    std::vector<std::string> const& objectDepends);
+  bool DetectBaseLinkerCommand(std::string& command, std::string const& arch,
+                               cmGeneratorTarget::Names const& targetNames);
+
+  // Get languages used by the target.
+  std::set<std::string> GetLanguages();
+  // Returns mapping from language to command how to compile object file for
+  // the it.
+  // Example return value: {"CXX" : "<CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES>
+  // <FLAGS> -o <OBJECT> -c <SOURCE>" }
+  std::unordered_map<std::string, std::string> GetCompileObjectCommand() const;
+  std::string GetLinkCommand() const;
+
+  void AddCompilerLaunchersForLanguages();
+  void AddLinkerLauncher();
+  void AddCMakeLauncher();
+
+  void ComputePaths(FastbuildTarget& fastbuildTarget) const;
+
+  std::string ComputeCodeCheckOptions(cmSourceFile const& srcFile);
+
+  cmRulePlaceholderExpander::RuleVariables ComputeRuleVariables() const;
+
+  std::vector<std::string> GetSourceProperty(cmSourceFile const& srcFile,
+                                             std::string const& prop) const;
+
+  std::string GetCompileOptions(cmSourceFile const& srcFile,
+                                std::string const& arch);
+
+  std::vector<std::string> GetArches() const;
+
+  std::vector<FastbuildObjectListNode> GenerateObjects();
+  // Computes .CompilerOptions for the ObjectList node.
+  void ComputeCompilerAndOptions(std::string const& compilerOptions,
+                                 std::string const& staticCheckOptions,
+                                 std::string const& language,
+                                 FastbuildObjectListNode& outObjectList);
+
+  std::string GetImportedLoc(cmComputeLinkInformation::Item const& item) const;
+  std::string ResolveIfAlias(std::string const& targetName) const;
+
+  void AppendExtraResources(std::set<std::string>& deps) const;
+  void AppendExternalObject(FastbuildLinkerNode& linkerNode,
+                            std::set<std::string>& linkedObjects) const;
+  void AppendExeToLink(FastbuildLinkerNode& linkerNode,
+                       cmComputeLinkInformation::Item const& item) const;
+  void AppendTargetDep(FastbuildLinkerNode& linkerNode,
+                       std::set<std::string>& linkedObjects,
+                       cmComputeLinkInformation::Item const& item) const;
+  void AppendPrebuildDeps(FastbuildLinkerNode& linkerNode,
+                          cmComputeLinkInformation::Item const& item) const;
+  void AppendTransitivelyLinkedObjects(
+    cmGeneratorTarget const& target,
+    std::set<std::string>& linkedObjects) const;
+  void AppendCommandLineDep(FastbuildLinkerNode& linkerNode,
+                            cmComputeLinkInformation::Item const& item) const;
+  void AppendToLibraries2IfApplicable(FastbuildLinkerNode& linkerNode,
+                                      std::string dep) const;
+  void AppendLINK_DEPENDS(FastbuildLinkerNode& linkerNode) const;
+  void AppendLinkDep(FastbuildLinkerNode& linkerNode, std::string dep) const;
+  void AppendDirectObjectLibs(FastbuildLinkerNode& linkerNode,
+                              std::set<std::string>& linkedObjects);
+
+  void AppendLinkDeps(std::set<FastbuildTargetDep>& preBuildDeps,
+                      FastbuildLinkerNode& linkerNode);
+  void AddLipoCommand(FastbuildTarget& target);
+  void GenerateModuleDefinitionInfo(FastbuildTarget& target) const;
+  std::vector<FastbuildExecNode> GetSymlinkExecs() const;
+  void ProcessManifests(FastbuildLinkerNode& linkerNode) const;
+  void AddStampExeIfApplicable(FastbuildTarget& fastbuildTarget) const;
+  void CollapseAllExecsIntoOneScriptfile(
+    std::string const& scriptFileName,
+    std::vector<FastbuildExecNode> const& execs) const;
+
+  void AddPrebuildDeps(FastbuildTarget& target) const;
+
+  std::string DetectCompilerFlags(cmSourceFile const& srcFile,
+                                  std::string const& arch);
+
+  void GetLinkerExecutableAndArgs(std::string const& command,
+                                  std::string& outLinkerExecutable,
+                                  std::string& outLinkerArgs);
+
+  void ApplyCompileRuleLauncher(std::string& command);
+  void ApplyLinkRuleLauncher(std::string& command);
+  void ApplyLWYUToLinkerCommand(FastbuildLinkerNode& linkerNode);
+
+  std::string ComputeDefines(cmSourceFile const& srcFile);
+
+  void ComputePCH(cmSourceFile const& srcFile, FastbuildObjectListNode& node,
+                  std::set<std::string>& createdPCH);
+
+  std::vector<std::string> GetManifestsAsFastbuildPath() const;
+
+  void EnsureDirectoryExists(std::string const& path) const;
+};

+ 896 - 0
Source/cmFastbuildTargetGenerator.cxx

@@ -0,0 +1,896 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmFastbuildTargetGenerator.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <cm/memory>
+#include <cm/optional>
+
+#include "cmCryptoHash.h"
+#include "cmCustomCommand.h"
+#include "cmCustomCommandGenerator.h"
+#include "cmCustomCommandLines.h"
+#include "cmFastbuildNormalTargetGenerator.h"
+#include "cmFastbuildUtilityTargetGenerator.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalCommonGenerator.h"
+#include "cmGlobalFastbuildGenerator.h"
+#include "cmList.h"
+#include "cmListFileCache.h"
+#include "cmLocalCommonGenerator.h"
+#include "cmLocalFastbuildGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmOSXBundleGenerator.h"
+#include "cmOutputConverter.h"
+#include "cmRulePlaceholderExpander.h"
+#include "cmSourceFile.h"
+#include "cmState.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmValue.h"
+
+#define FASTBUILD_DOLLAR_TAG "FASTBUILD_DOLLAR_TAG"
+
+constexpr auto FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT =
+  "CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT";
+constexpr auto FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC =
+  "CMAKE_FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC";
+
+cmFastbuildTargetGenerator* cmFastbuildTargetGenerator::New(
+  cmGeneratorTarget* target, std::string config)
+{
+  switch (target->GetType()) {
+    case cmStateEnums::EXECUTABLE:
+    case cmStateEnums::SHARED_LIBRARY:
+    case cmStateEnums::STATIC_LIBRARY:
+    case cmStateEnums::MODULE_LIBRARY:
+    case cmStateEnums::OBJECT_LIBRARY:
+      return new cmFastbuildNormalTargetGenerator(target, std::move(config));
+
+    case cmStateEnums::UTILITY:
+    case cmStateEnums::GLOBAL_TARGET:
+    case cmStateEnums::INTERFACE_LIBRARY:
+      return new cmFastbuildUtilityTargetGenerator(target, std::move(config));
+
+    default:
+      return nullptr;
+  }
+}
+
+cmFastbuildTargetGenerator::cmFastbuildTargetGenerator(
+  cmGeneratorTarget* target, std::string configParam)
+  : cmCommonTargetGenerator(target)
+  , LocalGenerator(
+      static_cast<cmLocalFastbuildGenerator*>(target->GetLocalGenerator()))
+  , TargetDirectDependencies(
+      this->GlobalCommonGenerator->GetTargetDirectDepends(GeneratorTarget))
+  , Config(std::move(configParam))
+{
+  this->MacOSXContentGenerator =
+    cm::make_unique<MacOSXContentGeneratorType>(this, Config);
+}
+
+void cmFastbuildTargetGenerator::LogMessage(std::string const& m) const
+{
+  this->GetGlobalGenerator()->LogMessage(m);
+}
+
+std::string cmFastbuildTargetGenerator::GetUtilityAliasFromBuildStep(
+  FastbuildBuildStep step) const
+{
+  if (step == FastbuildBuildStep::PRE_BUILD) {
+    return GetTargetName() + FASTBUILD_PRE_BUILD_ALIAS_POSTFIX;
+  }
+  if (step == FastbuildBuildStep::PRE_LINK) {
+    return GetTargetName() + FASTBUILD_PRE_LINK_ALIAS_POSTFIX;
+  }
+  if (step == FastbuildBuildStep::POST_BUILD) {
+    return GetTargetName() + FASTBUILD_POST_BUILD_ALIAS_POSTFIX;
+  }
+  return GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
+}
+
+void cmFastbuildTargetGenerator::MacOSXContentGeneratorType::operator()(
+  cmSourceFile const& source, char const* pkgloc,
+  std::string const& configName)
+{
+  // Skip OS X content when not building a Framework or Bundle.
+  if (!this->Generator->GetGeneratorTarget()->IsBundleOnApple()) {
+    return;
+  }
+
+  // Get the input file location.
+  std::string input = source.GetFullPath();
+  input = this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(input);
+
+  // Get the output file location.
+  std::string output =
+    this->Generator->OSXBundleGenerator->InitMacOSXContentDirectory(
+      pkgloc, configName);
+
+  output += "/";
+  output += cmSystemTools::GetFilenameName(input);
+  output =
+    this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(output);
+
+  FastbuildCopyNode node;
+  node.Name = "Copy_" + output;
+  node.Source = std::move(input);
+  if (cmSystemTools::FileIsDirectory(node.Source)) {
+    node.CopyDir = true;
+  }
+  node.Dest = std::move(output);
+  // Just in case if "from" is generated by some custom command.
+  // Tested in "BundleTest" test.
+  node.PreBuildDependencies =
+    this->Generator->GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
+
+  this->Generator->CopyNodes.emplace_back(std::move(node));
+}
+
+std::string cmFastbuildTargetGenerator::GetCustomCommandTargetName(
+  cmCustomCommand const& cc, FastbuildBuildStep step) const
+{
+  std::string const extra = this->Makefile->GetCurrentBinaryDirectory();
+  std::string targetName = "cc";
+
+  std::string extras = extra;
+
+  // Compute hash based on commands & args & output.
+  for (cmCustomCommandLine const& commandLine : cc.GetCommandLines()) {
+    extras += cmJoin(commandLine, "");
+  }
+  for (std::string const& output : cc.GetOutputs()) {
+    extras += output;
+  }
+
+  extras += std::to_string(static_cast<int>(step));
+
+  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
+  targetName += "-" + hash.HashString(extras).substr(0, 7);
+
+  return targetName;
+}
+
+std::vector<std::string> cmFastbuildTargetGenerator::GetInputFiles(
+  cmCustomCommandGenerator const& ccg, FastbuildBuildStep step) const
+{
+  std::vector<std::string> result;
+  auto const& cc = ccg.GetCC();
+  LogMessage("CC Name: " + GetCustomCommandTargetName(cc, step));
+  for (std::string const& dep : ccg.GetDepends()) {
+    LogMessage("Custom command dep: " + dep);
+    // Tested in EmptyDepends test.
+    std::string realDep;
+    if (this->LocalCommonGenerator->GetRealDependency(dep, Config, realDep)) {
+      auto list = cmList{ cmGeneratorExpression::Evaluate(
+        this->ConvertToFastbuildPath(realDep), this->LocalGenerator, Config) };
+      if (!realDep.empty()) {
+        LogMessage("Custom command real dep: " + realDep);
+        for (auto const& item : list) {
+          result.emplace_back(item);
+        }
+      }
+    }
+  }
+
+  if (cc.HasMainDependency()) {
+    LogMessage("CC main dep: " + cc.GetMainDependency());
+  }
+  LogMessage("cc Target: " + cc.GetTarget());
+  for (auto const& impDep : cc.GetImplicitDepends()) {
+    LogMessage("CC imp dep: " + impDep.first + ", " + impDep.second);
+  }
+  for (std::string const& dep : cc.GetOutputs()) {
+    LogMessage("Custom command output: " + this->ConvertToFastbuildPath(dep));
+  }
+  for (std::string const& dep : cc.GetByproducts()) {
+    LogMessage("Custom command byproducts: " +
+               this->ConvertToFastbuildPath(dep));
+  }
+  return result;
+}
+
+void cmFastbuildTargetGenerator::WriteScriptProlog(cmsys::ofstream& file) const
+{
+#ifdef _WIN32
+  file << "@echo off\n";
+#else
+  file << "set -e\n\n";
+#endif
+}
+void cmFastbuildTargetGenerator::WriteScriptEpilog(cmsys::ofstream& file) const
+{
+  (void)file;
+#ifdef _WIN32
+  file << "goto :EOF\n\n"
+          ":ABORT\n"
+          "set ERROR_CODE=%ERRORLEVEL%\n"
+          "echo Batch file failed at line %FAIL_LINE% "
+          "with errorcode %ERRORLEVEL%\n"
+          "exit /b %ERROR_CODE%";
+#endif
+}
+
+std::string cmFastbuildTargetGenerator::GetScriptWorkingDir(
+  cmCustomCommandGenerator const& ccg) const
+{
+  std::string workingDirectory = ccg.GetWorkingDirectory();
+  if (workingDirectory.empty()) {
+    return this->LocalCommonGenerator->GetCurrentBinaryDirectory();
+  }
+  return workingDirectory;
+}
+
+std::string cmFastbuildTargetGenerator::GetScriptFilename(
+  std::string const& utilityTargetName) const
+{
+  std::string scriptFileName = Makefile->GetCurrentBinaryDirectory();
+  scriptFileName += "/CMakeFiles/";
+  scriptFileName += utilityTargetName;
+  scriptFileName += FASTBUILD_SCRIPT_FILE_EXTENSION;
+  return scriptFileName;
+}
+
+void cmFastbuildTargetGenerator::AddCommentPrinting(
+  std::vector<std::string>& cmdLines,
+  cmCustomCommandGenerator const& ccg) const
+{
+  std::string cmakeCommand = this->GetLocalGenerator()->ConvertToOutputFormat(
+    cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
+  auto const comment = ccg.GetComment();
+  if (comment) {
+    // Comment printing should be first. Tested in
+    // RunCMake.ExternalProject:EnvVars-build test.
+    cmdLines.insert(
+      cmdLines.begin(),
+      cmakeCommand.append(" -E echo ")
+        .append(LocalGenerator->EscapeForShell(cmGeneratorExpression::Evaluate(
+          *comment, this->LocalGenerator, Config))));
+  }
+}
+
+std::string cmFastbuildTargetGenerator::GetCdCommand(
+  cmCustomCommandGenerator const& ccg) const
+{
+  return cmStrCat(FASTBUILD_SCRIPT_CD,
+                  this->LocalGenerator->ConvertToOutputFormat(
+                    GetScriptWorkingDir(ccg), cmOutputConverter::SHELL));
+}
+
+void cmFastbuildTargetGenerator::WriteCmdsToFile(
+  cmsys::ofstream& file, std::vector<std::string> const& cmds) const
+{
+#ifdef _WIN32
+  int line = 1;
+  for (auto cmd : cmds) {
+    // On Windows batch, '%' is a special character that needs to be
+    // doubled to be escaped
+    cmSystemTools::ReplaceString(cmd, "%", "%%");
+    file << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)" << '\n';
+#else
+  for (auto const& cmd : cmds) {
+    file << cmd << '\n';
+#endif
+  }
+}
+
+void cmFastbuildTargetGenerator::AddOutput(cmCustomCommandGenerator const& ccg,
+                                           FastbuildExecNode& exec)
+{
+  std::string dummyOutput = cmSystemTools::JoinPath(
+    { LocalCommonGenerator->GetMakefile()->GetHomeOutputDirectory(),
+      "/_fbuild_dummy" });
+  this->GetGlobalGenerator()->AllFoldersToClean.insert(dummyOutput);
+
+  dummyOutput.append("/").append(exec.Name).append(
+    FASTBUILD_DUMMY_OUTPUT_EXTENSION);
+
+  std::vector<std::string> const& outputs = ccg.GetOutputs();
+  std::vector<std::string> const& byproducts = ccg.GetByproducts();
+
+  exec.OutputsAlias.Name = exec.Name + FASTBUILD_OUTPUTS_ALIAS_POSTFIX;
+  // If CC doesn't have any output - we should always run it.
+  // Tested in "RunCMake.CMakePresetsBuild" test.
+  bool hasAnyNonSymbolicOutput = false;
+
+  bool const trackByproducts =
+    this->Makefile->IsDefinitionSet(FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT);
+
+  auto const isSymbolic = [this](std::string const& file) {
+    cmSourceFile* sf = this->Makefile->GetSource(file);
+    if (sf && sf->GetPropertyAsBool("SYMBOLIC")) {
+      LogMessage("Skipping symbolic file: " + file);
+      return true;
+    }
+    return false;
+  };
+
+  for (std::string const& output : outputs) {
+    // Tested in "RunCMake.BuildDepends".
+    if (isSymbolic(output)) {
+      continue;
+    }
+    hasAnyNonSymbolicOutput = true;
+    std::string const outputPath = this->ConvertToFastbuildPath(output);
+    LogMessage("CC's output: " + outputPath);
+    exec.OutputsAlias.PreBuildDependencies.emplace(outputPath);
+    // Ensure output path exists. For some reason, "CMake -E touch" fails with
+    // "cmake -E touch: failed to update "...
+    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(outputPath));
+    this->GetGlobalGenerator()->AddFileToClean(outputPath);
+  }
+
+  exec.ByproductsAlias.Name = exec.Name + FASTBUILD_BYPRODUCTS_ALIAS_POSTFIX;
+  for (std::string const& byproduct : byproducts) {
+    if (trackByproducts) {
+      hasAnyNonSymbolicOutput = true;
+    }
+    std::string const byproductPath = this->ConvertToFastbuildPath(byproduct);
+    exec.ByproductsAlias.PreBuildDependencies.emplace(byproductPath);
+    this->GetGlobalGenerator()->AddFileToClean(byproductPath);
+  }
+
+  auto const addDummyOutput = [&] {
+    // So that the dummy file is always created.
+    exec.ExecUseStdOutAsOutput = true;
+    exec.ExecOutput = this->ConvertToFastbuildPath(dummyOutput);
+    for (auto const& output : exec.OutputsAlias.PreBuildDependencies) {
+      OutputsToReplace[output.Name] = exec.ExecOutput;
+      LogMessage("Adding replace from " + output.Name + " to " +
+                 exec.ExecOutput);
+    }
+  };
+
+  // We don't have any output that is expected to appear on disk -> run always.
+  // Tested in "RunCMake.ExternalProject":BUILD_ALWAYS
+  if (!hasAnyNonSymbolicOutput) {
+    exec.ExecAlways = true;
+    addDummyOutput();
+    return;
+  }
+
+  if (!exec.OutputsAlias.PreBuildDependencies.empty()) {
+    exec.ExecOutput = this->ConvertToFastbuildPath(
+      exec.OutputsAlias.PreBuildDependencies.begin()->Name);
+  } else {
+    exec.ExecOutput = this->ConvertToFastbuildPath(
+      exec.ByproductsAlias.PreBuildDependencies.begin()->Name);
+  }
+
+  // Optionally add the "deps-check" Exec if we have more than 1 OUTPUT, but
+  // allow user to opt out.
+  if (exec.OutputsAlias.PreBuildDependencies.size() > 1 &&
+      !this->Makefile->IsDefinitionSet(
+        FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC)) {
+    exec.NeedsDepsCheckExec = true;
+  }
+}
+
+void cmFastbuildTargetGenerator::AddExecArguments(
+  FastbuildExecNode& exec, std::string const& scriptFilename) const
+{
+  exec.ExecArguments = FASTBUILD_SCRIPT_FILE_ARG;
+  exec.ExecArguments +=
+    cmGlobalFastbuildGenerator::QuoteIfHasSpaces(scriptFilename);
+
+  exec.ScriptFile = scriptFilename;
+  exec.ExecExecutable =
+    cmGlobalFastbuildGenerator::GetExternalShellExecutable();
+}
+
+std::vector<std::string> cmFastbuildTargetGenerator::GetDepends(
+  cmCustomCommandGenerator const& ccg) const
+{
+  std::vector<std::string> res;
+  for (auto dep : ccg.GetDepends()) {
+    LogMessage("Dep: " + dep);
+    auto orig = dep;
+    if (this->LocalCommonGenerator->GetRealDependency(dep, Config, dep)) {
+      LogMessage("Real dep: " + dep);
+    }
+    dep = this->ConvertToFastbuildPath(dep);
+    LogMessage("Real dep converted: " + dep);
+
+    auto const targetInfo = this->LocalGenerator->GetSourcesWithOutput(dep);
+    if (targetInfo.Target) {
+      LogMessage("dep: " + dep + ", target: " + targetInfo.Target->GetName());
+      auto const& target = targetInfo.Target;
+      auto const processCCs = [this, &res,
+                               dep](std::vector<cmCustomCommand> const& ccs,
+                                    FastbuildBuildStep step) {
+        for (auto const& cc : ccs) {
+          for (auto const& output : cc.GetOutputs()) {
+            LogMessage("dep: " + dep + ", post output: " +
+                       this->ConvertToFastbuildPath(output));
+            if (this->ConvertToFastbuildPath(output) == dep) {
+              auto ccName = this->GetCustomCommandTargetName(cc, step);
+              LogMessage("Additional CC dep from target: " + ccName);
+              res.emplace_back(std::move(ccName));
+            }
+          }
+          for (auto const& byproduct : cc.GetByproducts()) {
+            LogMessage("dep: " + dep + ", post byproduct: " +
+                       this->ConvertToFastbuildPath(byproduct));
+            if (this->ConvertToFastbuildPath(byproduct) == dep) {
+              auto ccName = this->GetCustomCommandTargetName(cc, step);
+              LogMessage("Additional CC dep from target: " + ccName);
+              res.emplace_back(std::move(ccName));
+            }
+          }
+        }
+      };
+      processCCs(target->GetPreBuildCommands(), FastbuildBuildStep::PRE_BUILD);
+      processCCs(target->GetPreLinkCommands(), FastbuildBuildStep::PRE_LINK);
+      processCCs(target->GetPostBuildCommands(),
+                 FastbuildBuildStep::POST_BUILD);
+      continue;
+    }
+    if (!targetInfo.Source) {
+      LogMessage("dep: " + dep + ", no source, byproduct: " +
+                 std::to_string(targetInfo.SourceIsByproduct));
+      // Tested in "OutDir" test.
+      res.emplace_back(std::move(orig));
+      continue;
+    }
+    if (!targetInfo.Source->GetCustomCommand()) {
+      LogMessage("dep: " + dep + ", no GetCustomCommand");
+      continue;
+    }
+    if (targetInfo.Source && targetInfo.Source->GetCustomCommand()) {
+      auto ccName = this->GetCustomCommandTargetName(
+        *targetInfo.Source->GetCustomCommand(), FastbuildBuildStep::REST);
+      LogMessage("Additional CC dep: " + ccName);
+      res.emplace_back(std::move(ccName));
+    }
+  }
+  return res;
+}
+
+void cmFastbuildTargetGenerator::ReplaceProblematicMakeVars(
+  std::string& command) const
+{
+  // TODO: fix problematic global targets.  For now, search and replace the
+  // makefile vars.
+  cmSystemTools::ReplaceString(
+    command, "$(CMAKE_SOURCE_DIR)",
+    this->LocalGenerator->ConvertToOutputFormat(
+      this->LocalGenerator->GetSourceDirectory(), cmOutputConverter::SHELL));
+  cmSystemTools::ReplaceString(
+    command, "$(CMAKE_BINARY_DIR)",
+    this->LocalGenerator->ConvertToOutputFormat(
+      this->LocalGenerator->GetBinaryDirectory(), cmOutputConverter::SHELL));
+  cmSystemTools::ReplaceString(command, "$(ARGS)", "");
+}
+
+FastbuildExecNode cmFastbuildTargetGenerator::GetAppleTextStubCommand() const
+{
+  FastbuildExecNode res;
+  if (!this->GeneratorTarget->IsApple() ||
+      !this->GeneratorTarget->HasImportLibrary(Config)) {
+    return res;
+  }
+
+  auto const names = DetectOutput();
+  std::string const outpathImp =
+    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
+      Config, cmStateEnums::ImportLibraryArtifact));
+
+  std::string const binPath =
+    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
+      Config, cmStateEnums::RuntimeBinaryArtifact));
+
+  cmSystemTools::MakeDirectory(outpathImp);
+
+  std::string rule = this->LocalGenerator->GetMakefile()->GetSafeDefinition(
+    "CMAKE_CREATE_TEXT_STUBS");
+  LogMessage("CMAKE_CREATE_TEXT_STUBS:" + rule);
+
+  auto rulePlaceholderExpander =
+    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+
+  cmRulePlaceholderExpander::RuleVariables vars;
+  res.ExecOutput = cmStrCat(outpathImp, '/', names.ImportReal);
+  res.ExecInput = { cmStrCat(binPath, '/', names.SharedObject) };
+
+  vars.Target = res.ExecInput[0].c_str();
+  rulePlaceholderExpander->SetTargetImpLib(res.ExecOutput);
+  rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), rule,
+                                               vars);
+
+  LogMessage("CMAKE_CREATE_TEXT_STUBS expanded:" + rule);
+  std::string executable;
+  std::string args;
+  if (!cmSystemTools::SplitProgramFromArgs(rule, executable, args)) {
+    cmSystemTools::Error("Failed to split program from args: " + rule);
+    return res;
+  }
+
+  res.Name = "create_" + names.ImportOutput + "_text_stub";
+  res.ExecExecutable = std::move(executable);
+  res.ExecArguments = std::move(args);
+  res.ExecWorkingDir = this->LocalCommonGenerator->GetCurrentBinaryDirectory();
+
+  // Wait for the build.
+  res.PreBuildDependencies.emplace(this->GetTargetName());
+  return res;
+}
+FastbuildExecNode cmFastbuildTargetGenerator::GetDepsCheckExec(
+  FastbuildExecNode const& depender)
+{
+  FastbuildExecNode exec;
+  exec.Name = depender.Name + "-check-depends";
+  exec.ExecAlways = true;
+  exec.ExecUseStdOutAsOutput = true;
+  exec.ExecOutput = depender.ExecOutput + ".deps-checker";
+  exec.ExecExecutable = cmSystemTools::GetCMakeCommand();
+  exec.ExecArguments += "-E cmake_fastbuild_check_depends ";
+  exec.ExecArguments += depender.ExecOutput + " ";
+  char const* sep = "";
+  for (auto const& dep : depender.OutputsAlias.PreBuildDependencies) {
+    exec.ExecArguments += sep;
+    exec.ExecArguments += dep.Name;
+    sep = " ";
+  }
+  for (auto const& dep : depender.ByproductsAlias.PreBuildDependencies) {
+    exec.ExecArguments += sep;
+    exec.ExecArguments += dep.Name;
+    sep = " ";
+  }
+  return exec;
+}
+
+FastbuildExecNodes cmFastbuildTargetGenerator::GenerateCommands(
+  FastbuildBuildStep buildStep)
+{
+  FastbuildExecNodes execs;
+  execs.Alias.Name = GetUtilityAliasFromBuildStep(buildStep);
+
+  std::vector<cmCustomCommand> commands;
+  if (buildStep == FastbuildBuildStep::PRE_BUILD) {
+    commands = GeneratorTarget->GetPreBuildCommands();
+    LogMessage("STEP: PRE_BUILD");
+  } else if (buildStep == FastbuildBuildStep::PRE_LINK) {
+    commands = GeneratorTarget->GetPreLinkCommands();
+    LogMessage("STEP: PRE_LINK");
+  } else if (buildStep == FastbuildBuildStep::POST_BUILD) {
+    commands = GeneratorTarget->GetPostBuildCommands();
+    LogMessage("STEP: POST_BUILD");
+  } else {
+    LogMessage("STEP: ALL CUSTOM COMMANDS");
+    std::vector<cmSourceFile const*> customCommands;
+    GeneratorTarget->GetCustomCommands(customCommands, Config);
+    for (cmSourceFile const* source : customCommands) {
+      cmCustomCommand const* cmd = source->GetCustomCommand();
+      if (!cmd->GetCommandLines().empty()) {
+        commands.emplace_back(*cmd);
+      }
+    }
+  }
+  LogMessage(cmStrCat("Number of custom commands: ", commands.size()));
+  for (cmCustomCommand const& customCommand : commands) {
+    cmCustomCommandGenerator ccg(customCommand, Config, LocalCommonGenerator);
+    std::string launcher = this->MakeCustomLauncher(ccg);
+
+    std::string const execName =
+      GetCustomCommandTargetName(customCommand, buildStep);
+
+    std::vector<std::string> cmdLines;
+    if (ccg.GetNumberOfCommands() > 0) {
+      cmdLines.push_back(GetCdCommand(ccg));
+    }
+
+    // Since we are not using FASTBuild Exec nodes natively, we need to
+    // have shell specific escape.
+    this->LocalGenerator->GetState()->SetFastbuildMake(false);
+    // To avoid replacing $ with $$ in the command line.
+    this->LocalGenerator->SetLinkScriptShell(true);
+    for (unsigned j = 0; j != ccg.GetNumberOfCommands(); ++j) {
+      std::string const command = ccg.GetCommand(j);
+      // Tested in "CustomCommand" ("empty_command") test.
+      if (!command.empty()) {
+
+        cmdLines.emplace_back(launcher +
+                              this->LocalGenerator->ConvertToOutputFormat(
+                                command, cmOutputConverter::SHELL));
+
+        std::string& cmd = cmdLines.back();
+        ccg.AppendArguments(j, cmd);
+        ReplaceProblematicMakeVars(cmd);
+        LogMessage("cmCustomCommandLine: " + cmd);
+      }
+    }
+    if (cmdLines.empty()) {
+      return {};
+    }
+    this->LocalGenerator->GetState()->SetFastbuildMake(true);
+
+    FastbuildExecNode execNode;
+    execNode.Name = execName;
+    std::vector<std::string> const inputFiles = GetInputFiles(ccg, buildStep);
+    for (std::string const& file : inputFiles) {
+      LogMessage("Input file: " + file);
+    }
+    for (auto const& util : ccg.GetUtilities()) {
+      auto const& utilTargetName = util.Value.first;
+      LogMessage("Util: " + utilTargetName +
+                 ", cross: " + std::to_string(util.Value.second));
+      auto* const target = this->Makefile->FindTargetToUse(utilTargetName);
+
+      if (target && target->IsImported()) {
+        std::string importedLoc =
+          this->ConvertToFastbuildPath(target->ImportedGetFullPath(
+            Config, cmStateEnums::ArtifactType::RuntimeBinaryArtifact));
+        if (importedLoc.empty()) {
+          importedLoc =
+            this->ConvertToFastbuildPath(target->ImportedGetFullPath(
+              Config, cmStateEnums::ArtifactType::ImportLibraryArtifact));
+        }
+        LogMessage("adding file level dep on imporated target: " +
+                   importedLoc);
+        execNode.PreBuildDependencies.emplace(std::move(importedLoc));
+        continue;
+      }
+      // This CC uses some executable produced by another target. Add explicit
+      // dep. Tested in "CustomCommand" test.
+      if (util.Value.second) {
+        if (utilTargetName != customCommand.GetTarget()) {
+          LogMessage("Adding util dep: " + utilTargetName);
+          execNode.PreBuildDependencies.emplace(utilTargetName);
+        }
+      }
+    }
+
+    execNode.ExecInput = inputFiles;
+    execs.Alias.PreBuildDependencies.emplace(execNode.Name);
+
+    LogMessage(cmStrCat("cmdLines size ", cmdLines.size()));
+
+    if (!cmdLines.empty()) {
+      std::string const scriptFileName = GetScriptFilename(execName);
+      cmsys::ofstream scriptFile(scriptFileName.c_str());
+
+      AddOutput(ccg, execNode);
+      AddExecArguments(execNode, scriptFileName);
+      AddCommentPrinting(cmdLines, ccg);
+
+      WriteScriptProlog(scriptFile);
+      WriteCmdsToFile(scriptFile, cmdLines);
+      WriteScriptEpilog(scriptFile);
+
+      execNode.ExecWorkingDir = GetScriptWorkingDir(ccg);
+    }
+
+    // Tested in "ObjectLibrary / complexOneConfig" tests.
+    for (std::string& additionalDep : GetDepends(ccg)) {
+      if (additionalDep != execName) {
+        LogMessage("Adding additional dep: " + additionalDep);
+        execNode.PreBuildDependencies.emplace(std::move(additionalDep));
+      }
+    }
+
+    if (buildStep == FastbuildBuildStep::POST_BUILD) {
+      execNode.PreBuildDependencies.emplace(GetTargetName() +
+                                            FASTBUILD_BUILD_ALIAS_POSTFIX);
+      // Execute POST_BUILD in order in which they are declared.
+      // Tested in "complex" test.
+      for (auto& exec : execs.Nodes) {
+        execNode.PreBuildDependencies.emplace(exec.Name);
+      }
+    }
+    for (auto const& out : execNode.OutputsAlias.PreBuildDependencies) {
+      LogMessage("Adding replace from " + out.Name + " to " + execName);
+      OutputToExecName[out.Name] = execName;
+    }
+    execs.Nodes.emplace_back(std::move(execNode));
+  }
+  for (auto& exec : execs.Nodes) {
+    for (auto& inputFile : exec.ExecInput) {
+      auto const iter = OutputsToReplace.find(inputFile);
+      if (iter != OutputsToReplace.end()) {
+        LogMessage("Replacing input: " + inputFile + " with " + iter->second);
+        inputFile = iter->second;
+      }
+      auto const depIter = std::find_if(
+        exec.PreBuildDependencies.begin(), exec.PreBuildDependencies.end(),
+        [this](FastbuildTargetDep const& dep) {
+          return !OutputToExecName[dep.Name].empty();
+        });
+      if (depIter != exec.PreBuildDependencies.end()) {
+        LogMessage("Replacing dep " + depIter->Name + " with " +
+                   OutputToExecName[depIter->Name]);
+        exec.PreBuildDependencies.emplace(OutputToExecName[depIter->Name]);
+        exec.PreBuildDependencies.erase(depIter);
+      }
+    }
+    if (exec.NeedsDepsCheckExec) {
+      auto depsCheckExec = GetDepsCheckExec(exec);
+      LogMessage("Adding deps check Exec: " + depsCheckExec.Name);
+      exec.PreBuildDependencies.emplace(depsCheckExec.Name);
+      this->GetGlobalGenerator()->AddTarget(std::move(depsCheckExec));
+    }
+  }
+  return execs;
+}
+
+std::string cmFastbuildTargetGenerator::MakeCustomLauncher(
+  cmCustomCommandGenerator const& ccg)
+{
+  // Copied from cmLocalNinjaGenerator::MakeCustomLauncher.
+  cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
+
+  if (!cmNonempty(property_value)) {
+    return std::string();
+  }
+
+  // Expand rule variables referenced in the given launcher command.
+  cmRulePlaceholderExpander::RuleVariables vars;
+
+  std::string output;
+  std::vector<std::string> const& outputs = ccg.GetOutputs();
+  for (size_t i = 0; i < outputs.size(); ++i) {
+    output =
+      cmStrCat(output,
+               this->LocalGenerator->ConvertToOutputFormat(
+                 ccg.GetWorkingDirectory().empty()
+                   ? this->LocalGenerator->MaybeRelativeToCurBinDir(outputs[i])
+                   : outputs[i],
+                 cmOutputConverter::SHELL));
+    if (i != outputs.size() - 1) {
+      output = cmStrCat(output, ',');
+    }
+  }
+  vars.Output = output.c_str();
+  vars.Role = ccg.GetCC().GetRole().c_str();
+
+  auto rulePlaceholderExpander =
+    this->LocalGenerator->CreateRulePlaceholderExpander();
+
+  std::string launcher = *property_value;
+  rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator, launcher,
+                                               vars);
+  if (!launcher.empty()) {
+    launcher += " ";
+  }
+
+  LogMessage("CC Launcher: " + launcher);
+  return launcher;
+}
+
+std::string cmFastbuildTargetGenerator::GetTargetName() const
+{
+  if (this->GeneratorTarget->GetType() == cmStateEnums::GLOBAL_TARGET) {
+    return this->GetGlobalGenerator()->GetTargetName(GeneratorTarget);
+  }
+  return this->GeneratorTarget->GetName();
+}
+
+cmGeneratorTarget::Names cmFastbuildTargetGenerator::DetectOutput() const
+{
+  if (GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE) {
+    return GeneratorTarget->GetExecutableNames(Config);
+  }
+  return GeneratorTarget->GetLibraryNames(Config);
+}
+
+void cmFastbuildTargetGenerator::AddObjectDependencies(
+  FastbuildTarget& fastbuildTarget,
+  std::vector<std::string>& allObjectDepends) const
+{
+  auto const FindObjListWhichOutputs = [&fastbuildTarget](
+                                         std::string const& output) {
+    for (FastbuildObjectListNode const& objList :
+         fastbuildTarget.ObjectListNodes) {
+      if (objList.ObjectOutputs.find(output) != objList.ObjectOutputs.end()) {
+        return objList.Name;
+      }
+    }
+    return std::string{};
+  };
+
+  for (FastbuildObjectListNode& objList : fastbuildTarget.ObjectListNodes) {
+    objList.PreBuildDependencies.emplace(
+      fastbuildTarget.Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX);
+    for (auto const& objDep : objList.ObjectDepends) {
+      // Check if there is another object list which outputs (OBJECT_OUTPUTS)
+      // something that this object list needs (OBJECT_DEPENDS).
+      auto anotherObjList = FindObjListWhichOutputs(objDep);
+      if (!anotherObjList.empty()) {
+        LogMessage("Adding explicit <OBJECT_DEPENDS> dep: " + anotherObjList);
+        allObjectDepends.emplace_back(anotherObjList);
+        objList.PreBuildDependencies.emplace(std::move(anotherObjList));
+
+      } else {
+        LogMessage("Adding <OBJECT_DEPENDS> dep: " + objDep);
+        allObjectDepends.emplace_back(objDep);
+        objList.PreBuildDependencies.emplace(objDep);
+      }
+    }
+  }
+  cmGlobalFastbuildGenerator::TopologicalSort(fastbuildTarget.ObjectListNodes);
+}
+
+void cmFastbuildTargetGenerator::AddLinkerNodeDependnecies(
+  FastbuildTarget& fastbuildTarget)
+{
+  for (auto& linkerNode : fastbuildTarget.LinkerNode) {
+    linkerNode.PreBuildDependencies.emplace(
+      fastbuildTarget.Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX);
+
+    if (!fastbuildTarget.PreLinkExecNodes.Nodes.empty()) {
+      linkerNode.PreBuildDependencies.emplace(
+        fastbuildTarget.Name + FASTBUILD_PRE_LINK_ALIAS_POSTFIX);
+    }
+  }
+}
+
+std::string cmFastbuildTargetGenerator::GetClangTidyReplacementsFilePath(
+  std::string const& directory, cmSourceFile const& source,
+  std::string const& /*config*/) const
+{
+
+  std::string objectDir =
+    this->ConvertToFastbuildPath(this->GeneratorTarget->GetSupportDirectory());
+  std::string const& objectName =
+    this->GeneratorTarget->GetObjectName(&source);
+  std::string path =
+    cmStrCat(directory, '/', objectDir, '/', objectName, ".yaml");
+  LogMessage("ClangTidy replacements file: " + path);
+  return path;
+}
+
+void cmFastbuildTargetGenerator::AddIncludeFlags(std::string& languageFlags,
+                                                 std::string const& language,
+                                                 std::string const&)
+{
+  std::vector<std::string> includes;
+  this->LocalGenerator->GetIncludeDirectories(includes, this->GeneratorTarget,
+                                              language, Config);
+  // Add include directory flags.
+  std::string includeFlags = this->LocalGenerator->GetIncludeFlags(
+    includes, this->GeneratorTarget, language, Config, false);
+
+  this->LocalGenerator->AppendFlags(languageFlags, includeFlags);
+}
+
+std::string cmFastbuildTargetGenerator::GetName()
+{
+  return GeneratorTarget->GetName();
+}
+
+std::string cmFastbuildTargetGenerator::ConvertToFastbuildPath(
+  std::string const& path) const
+{
+  return GetGlobalGenerator()->ConvertToFastbuildPath(path);
+}
+
+cmGlobalFastbuildGenerator* cmFastbuildTargetGenerator::GetGlobalGenerator()
+  const
+{
+  return this->LocalGenerator->GetGlobalFastbuildGenerator();
+}
+
+void cmFastbuildTargetGenerator::AdditionalCleanFiles()
+{
+  if (cmValue prop_value =
+        this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    auto* lg = this->LocalGenerator;
+    cmList cleanFiles(cmGeneratorExpression::Evaluate(*prop_value, lg, Config,
+                                                      this->GeneratorTarget));
+    std::string const& binaryDir = lg->GetCurrentBinaryDirectory();
+    auto* gg = lg->GetGlobalFastbuildGenerator();
+    for (auto const& cleanFile : cleanFiles) {
+      // Support relative paths
+      gg->AddFileToClean(gg->ConvertToFastbuildPath(
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir)));
+    }
+  }
+}

+ 158 - 0
Source/cmFastbuildTargetGenerator.h

@@ -0,0 +1,158 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmCommonTargetGenerator.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalFastbuildGenerator.h"
+#include "cmGlobalGenerator.h"
+#include "cmOSXBundleGenerator.h"
+
+class cmCustomCommand;
+class cmCustomCommandGenerator;
+class cmLocalFastbuildGenerator;
+class cmMakefile;
+class cmSourceFile;
+
+enum class FastbuildBuildStep
+{
+  PRE_BUILD,
+  PRE_LINK,
+  POST_BUILD,
+  REST,
+};
+
+class cmFastbuildTargetGenerator : public cmCommonTargetGenerator
+{
+public:
+  /// Create a cmFastbuildTargetGenerator according to the @a target's type and
+  /// config.
+  static cmFastbuildTargetGenerator* New(cmGeneratorTarget* target,
+                                         std::string config);
+
+  cmFastbuildTargetGenerator(cmGeneratorTarget* target, std::string config);
+
+  virtual void Generate() {}
+
+  std::string GetClangTidyReplacementsFilePath(
+    std::string const& directory, cmSourceFile const& source,
+    std::string const& Config) const override;
+
+  void AddIncludeFlags(std::string& languageFlags, std::string const& language,
+                       std::string const&) override;
+
+  cmGeneratorTarget::Names DetectOutput() const;
+
+  void AddObjectDependencies(FastbuildTarget& fastbuildTarget,
+                             std::vector<std::string>& allObjectDepends) const;
+  void AddLinkerNodeDependnecies(FastbuildTarget& fastbuildTarget);
+
+  std::string ConvertToFastbuildPath(std::string const& path) const;
+
+  cmGlobalFastbuildGenerator* GetGlobalGenerator() const;
+
+  std::string GetName();
+
+  cmMakefile* GetMakefile() const { return this->Makefile; }
+
+  cmGeneratorTarget* GetGeneratorTarget() const
+  {
+    return this->GeneratorTarget;
+  }
+
+  void LogMessage(std::string const& m) const;
+
+private:
+  std::string GetUtilityAliasFromBuildStep(FastbuildBuildStep step) const;
+
+protected:
+  cmLocalFastbuildGenerator* GetLocalGenerator() const
+  {
+    return this->LocalGenerator;
+  }
+
+  std::string GetTargetName() const;
+  std::string GetCdCommand(cmCustomCommandGenerator const& ccg) const;
+  std::string GetScriptWorkingDir(cmCustomCommandGenerator const& ccg) const;
+  std::string GetScriptFilename(std::string const& utilityTargetName) const;
+  std::vector<std::string> GetDepends(
+    cmCustomCommandGenerator const& ccg) const;
+
+  void AddCommentPrinting(std::vector<std::string>& cmdLines,
+                          cmCustomCommandGenerator const& ccg) const;
+
+  void WriteCmdsToFile(cmsys::ofstream& file,
+                       std::vector<std::string> const& cmds) const;
+
+  void AddOutput(cmCustomCommandGenerator const& ccg, FastbuildExecNode& exec);
+
+  void AddExecArguments(FastbuildExecNode& exec,
+                        std::string const& scriptFilename) const;
+
+  void ReplaceProblematicMakeVars(std::string& command) const;
+
+  FastbuildExecNodes GenerateCommands(FastbuildBuildStep buildStep);
+  FastbuildExecNode GetAppleTextStubCommand() const;
+  FastbuildExecNode GetDepsCheckExec(FastbuildExecNode const& depender);
+
+  std::string MakeCustomLauncher(cmCustomCommandGenerator const& ccg);
+
+  std::string GetCustomCommandTargetName(cmCustomCommand const& cc,
+                                         FastbuildBuildStep step) const;
+
+  std::vector<std::string> GetInputFiles(cmCustomCommandGenerator const& ccg,
+                                         FastbuildBuildStep step) const;
+
+  void WriteScriptProlog(cmsys::ofstream& file) const;
+  void WriteScriptEpilog(cmsys::ofstream& file) const;
+
+  void AdditionalCleanFiles();
+
+  // write rules for Mac OS X Application Bundle content.
+  struct MacOSXContentGeneratorType
+    : cmOSXBundleGenerator::MacOSXContentGeneratorType
+  {
+    MacOSXContentGeneratorType(cmFastbuildTargetGenerator* g, std::string cfg)
+      : Generator(g)
+      , Config(std::move(cfg))
+    {
+    }
+
+    void operator()(cmSourceFile const& source, char const* pkgloc,
+                    std::string const& config) override;
+
+  private:
+    cmFastbuildTargetGenerator* Generator;
+    std::string const Config;
+  };
+  friend struct MacOSXContentGeneratorType;
+
+  // "MacOSXContentGenerator" has to be per-config once multiconfig generator
+  // is implemented.
+  std::unique_ptr<MacOSXContentGeneratorType> MacOSXContentGenerator;
+  std::unique_ptr<cmOSXBundleGenerator> OSXBundleGenerator;
+  std::set<std::string> MacContentFolders;
+
+  std::vector<FastbuildCopyNode> CopyNodes;
+
+  cmLocalFastbuildGenerator* LocalGenerator;
+  cmGlobalGenerator::TargetDependSet const TargetDirectDependencies;
+  std::string const Config;
+
+  // Sometimes CMake adds equivalent custom commands to different targets.
+  // Not really supported by FBuild (can't have different exec nodes producing
+  // same output). So, in such cases we need to "re-map" the exec to produce a
+  // "dummy" output (and update all deps within the target).
+  // TODO: potentially 1 map should be enough?
+  std::unordered_map<std::string, std::string> OutputsToReplace;
+  std::unordered_map<std::string, std::string> OutputToExecName;
+};

+ 126 - 0
Source/cmFastbuildUtilityTargetGenerator.cxx

@@ -0,0 +1,126 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmFastbuildUtilityTargetGenerator.h"
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmFastbuildTargetGenerator.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalFastbuildGenerator.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmStateTypes.h"
+#include "cmTarget.h"
+#include "cmTargetDepend.h"
+
+cmFastbuildUtilityTargetGenerator::cmFastbuildUtilityTargetGenerator(
+  cmGeneratorTarget* gt, std::string configParam)
+  : cmFastbuildTargetGenerator(gt, std::move(configParam))
+{
+}
+
+void cmFastbuildUtilityTargetGenerator::Generate()
+{
+  std::string targetName = GeneratorTarget->GetName();
+
+  if (this->GeneratorTarget->GetType() == cmStateEnums::GLOBAL_TARGET) {
+    targetName = GetGlobalGenerator()->GetTargetName(GeneratorTarget);
+  }
+
+  FastbuildAliasNode fastbuildTarget;
+  fastbuildTarget.Name = targetName;
+
+  LogMessage("<-------------->");
+  LogMessage("Generate Utility target: " + targetName);
+  LogMessage("Config: " + Config);
+  for (auto const& dep : TargetDirectDependencies) {
+    LogMessage("Dep: " + dep->GetName());
+  }
+
+  std::vector<std::string> nonImportedUtils;
+  for (BT<std::pair<std::string, bool>> const& util :
+       this->GeneratorTarget->GetUtilities()) {
+    if (util.Value.first == targetName) {
+      continue;
+    }
+    auto const& utilTargetName =
+      this->ConvertToFastbuildPath(util.Value.first);
+    LogMessage("Util: " + utilTargetName);
+    auto* const target = this->Makefile->FindTargetToUse(utilTargetName);
+    if (target && target->IsImported()) {
+      LogMessage("Skipping imported util target: " + utilTargetName);
+      continue;
+    }
+    // Since interface target don't appear in the generated build files,
+    // transitively propagate their deps (if any).
+    // Tested in "ExternalProjectSubdir" test.
+    if (target && target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+      for (auto const& dep : target->GetUtilities()) {
+        auto const& depName = this->ConvertToFastbuildPath(dep.Value.first);
+        LogMessage("Transitively propagating iface dep: " + depName +
+                   ", is cross: " + std::to_string(dep.Value.second));
+        nonImportedUtils.emplace_back(depName);
+        fastbuildTarget.PreBuildDependencies.emplace(
+          this->ConvertToFastbuildPath(depName));
+      }
+    } else {
+      nonImportedUtils.emplace_back(utilTargetName);
+      fastbuildTarget.PreBuildDependencies.emplace(utilTargetName);
+    }
+  }
+  if (this->GetGlobalGenerator()->IsExcluded(this->GetGeneratorTarget())) {
+    LogMessage("Excluding " + targetName + " from ALL");
+    fastbuildTarget.ExcludeFromAll = true;
+  }
+  auto preBuild = GenerateCommands(FastbuildBuildStep::PRE_BUILD);
+
+  // Tested in "RunCMake.CPack*" tests.
+  // Utility target "package" has packaging steps as "POST_BUILD".
+  for (auto& exec : GenerateCommands(FastbuildBuildStep::POST_BUILD).Nodes) {
+    fastbuildTarget.PreBuildDependencies.emplace(exec.Name);
+    for (std::string const& util : nonImportedUtils) {
+      LogMessage("Adding: util " + util);
+      exec.PreBuildDependencies.emplace(util);
+    }
+    // So POST_BUILD is executed AFTER PRE_BUILD (tested in "CustomCommand"
+    // test).
+    for (auto const& pre : preBuild.Nodes) {
+      LogMessage("Adding: " + pre.Name);
+      exec.PreBuildDependencies.emplace(pre.Name);
+    }
+    this->GetGlobalGenerator()->AddTarget(std::move(exec));
+  }
+
+  for (auto& exec : preBuild.Nodes) {
+    LogMessage("Adding exec " + exec.Name);
+    fastbuildTarget.PreBuildDependencies.emplace(exec.Name);
+    this->GetGlobalGenerator()->AddTarget(std::move(exec));
+  }
+
+  for (auto& exec : GenerateCommands(FastbuildBuildStep::REST).Nodes) {
+    fastbuildTarget.PreBuildDependencies.emplace(exec.Name);
+    for (auto const& dep : TargetDirectDependencies) {
+      LogMessage("Direct dep " + dep->GetName() +
+                 "-all propagating to CC: " + exec.Name);
+      // All custom commands from within the target must be executed AFTER all
+      // the target's deps.
+      FastbuildTargetDep execDep{ dep->GetName() };
+      execDep.Type = FastbuildTargetDepType::ALL;
+      exec.PreBuildDependencies.emplace(std::move(execDep));
+    }
+    this->GetGlobalGenerator()->AddTarget(std::move(exec));
+  }
+  if (fastbuildTarget.PreBuildDependencies.empty()) {
+    if (fastbuildTarget.ExcludeFromAll) {
+      return;
+    }
+    fastbuildTarget.PreBuildDependencies.emplace(FASTBUILD_NOOP_FILE_NAME);
+  }
+  fastbuildTarget.Hidden = false;
+  this->AdditionalCleanFiles();
+  this->GetGlobalGenerator()->AddTarget(std::move(fastbuildTarget));
+}

+ 18 - 0
Source/cmFastbuildUtilityTargetGenerator.h

@@ -0,0 +1,18 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <string>
+
+#include <cmFastbuildTargetGenerator.h>
+
+class cmGeneratorTarget;
+
+class cmFastbuildUtilityTargetGenerator : public cmFastbuildTargetGenerator
+{
+public:
+  cmFastbuildUtilityTargetGenerator(cmGeneratorTarget* gt,
+                                    std::string configParam);
+
+  void Generate() override;
+};

+ 4 - 0
Source/cmGeneratorExpressionNode.cxx

@@ -2413,6 +2413,7 @@ static const struct CompileLanguageNode : public cmGeneratorExpressionNode
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
         genName.find("Watcom WMake") == std::string::npos &&
+        genName.find("FASTBuild") == std::string::npos &&
         genName.find("Green Hills MULTI") == std::string::npos) {
       reportError(context, content->GetOriginalExpression(),
                   "$<COMPILE_LANGUAGE:...> not supported for this generator.");
@@ -2460,6 +2461,7 @@ static const struct CompileLanguageAndIdNode : public cmGeneratorExpressionNode
     std::string genName = gg->GetName();
     if (genName.find("Makefiles") == std::string::npos &&
         genName.find("Ninja") == std::string::npos &&
+        genName.find("FASTBuild") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
         genName.find("Watcom WMake") == std::string::npos &&
@@ -2514,6 +2516,7 @@ static const struct LinkLanguageNode : public cmGeneratorExpressionNode
     std::string genName = gg->GetName();
     if (genName.find("Makefiles") == std::string::npos &&
         genName.find("Ninja") == std::string::npos &&
+        genName.find("FASTBuild") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
         genName.find("Watcom WMake") == std::string::npos &&
@@ -2605,6 +2608,7 @@ static const struct LinkLanguageAndIdNode : public cmGeneratorExpressionNode
     std::string genName = gg->GetName();
     if (genName.find("Makefiles") == std::string::npos &&
         genName.find("Ninja") == std::string::npos &&
+        genName.find("FASTBuild") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
         genName.find("Watcom WMake") == std::string::npos &&

+ 21 - 4
Source/cmGeneratorTarget.cxx

@@ -3233,8 +3233,17 @@ std::string cmGeneratorTarget::GetPchCreateCompileOptions(
     std::string const pchHeader = this->GetPchHeader(config, language, arch);
     std::string const pchFile = this->GetPchFile(config, language, arch);
 
-    cmSystemTools::ReplaceString(createOptionList, "<PCH_HEADER>", pchHeader);
-    cmSystemTools::ReplaceString(createOptionList, "<PCH_FILE>", pchFile);
+    if (GlobalGenerator->IsFastbuild()) {
+      // Account for potential spaces in a shell-friendly way.
+      cmSystemTools::ReplaceString(createOptionList, "<PCH_HEADER>",
+                                   '"' + pchHeader + '"');
+      cmSystemTools::ReplaceString(createOptionList, "<PCH_FILE>",
+                                   '"' + pchFile + '"');
+    } else {
+      cmSystemTools::ReplaceString(createOptionList, "<PCH_HEADER>",
+                                   pchHeader);
+      cmSystemTools::ReplaceString(createOptionList, "<PCH_FILE>", pchFile);
+    }
   }
   return inserted.first->second;
 }
@@ -3268,8 +3277,16 @@ std::string cmGeneratorTarget::GetPchUseCompileOptions(
     std::string const pchHeader = this->GetPchHeader(config, language, arch);
     std::string const pchFile = this->GetPchFile(config, language, arch);
 
-    cmSystemTools::ReplaceString(useOptionList, "<PCH_HEADER>", pchHeader);
-    cmSystemTools::ReplaceString(useOptionList, "<PCH_FILE>", pchFile);
+    if (GlobalGenerator->IsFastbuild()) {
+      // Account for potential spaces in a shell-friendly way.
+      cmSystemTools::ReplaceString(useOptionList, "<PCH_HEADER>",
+                                   '"' + pchHeader + '"');
+      cmSystemTools::ReplaceString(useOptionList, "<PCH_FILE>",
+                                   '"' + pchFile + '"');
+    } else {
+      cmSystemTools::ReplaceString(useOptionList, "<PCH_HEADER>", pchHeader);
+      cmSystemTools::ReplaceString(useOptionList, "<PCH_FILE>", pchFile);
+    }
   }
   return inserted.first->second;
 }

+ 1848 - 0
Source/cmGlobalFastbuildGenerator.cxx

@@ -0,0 +1,1848 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmGlobalFastbuildGenerator.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <initializer_list>
+#include <iterator>
+#include <queue>
+#include <sstream>
+
+#include <cm/memory>
+
+#include "cmsys/FStream.hxx"
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmFastbuildLinkLineComputer.h"
+#include "cmFastbuildTargetGenerator.h" // IWYU pragma: keep
+#include "cmGeneratedFileStream.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobCacheEntry.h"
+#include "cmGlobalGenerator.h"
+#include "cmGlobalGeneratorFactory.h"
+#include "cmList.h"
+#include "cmLocalFastbuildGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmValue.h"
+#include "cmVersion.h"
+#include "cmake.h"
+
+#if defined(_WIN32)
+#  include <future>
+
+#  include <objbase.h>
+#  include <shellapi.h>
+
+#endif
+
+class cmLinkLineComputer;
+
+#define FASTBUILD_REBUILD_BFF_TARGET_NAME "rebuild-bff"
+#define FASTBUILD_GLOB_CHECK_TARGET "glob-check"
+#define FASTBUILD_ENV_VAR_NAME "LocalEnv"
+
+// IDE support
+#define FASTBUILD_XCODE_BASE_PATH "XCode/Projects"
+#define FASTBUILD_VS_BASE_PATH "VisualStudio/Projects"
+
+#define FASTBUILD_IDE_VS_COMMAND_PREFIX "cd ^$(SolutionDir).. && "
+#define FASTBUILD_IDE_BUILD_ARGS " -ide -cache -summary -dist "
+
+constexpr auto FASTBUILD_CAPTURE_SYSTEM_ENV =
+  "CMAKE_FASTBUILD_CAPTURE_SYSTEM_ENV";
+constexpr auto FASTBUILD_ENV_OVERRIDES = "CMAKE_FASTBUILD_ENV_OVERRIDES";
+
+// Inherits from "CMAKE_FASTBUILD_VERBOSE_GENERATOR" env variable.
+constexpr auto FASTBUILD_VERBOSE_GENERATOR =
+  "CMAKE_FASTBUILD_VERBOSE_GENERATOR";
+constexpr auto FASTBUILD_CACHE_PATH = "CMAKE_FASTBUILD_CACHE_PATH";
+// Compiler settings.
+constexpr auto FASTBUILD_COMPILER_EXTRA_FILES =
+  "CMAKE_FASTBUILD_COMPILER_EXTRA_FILES";
+constexpr auto FASTBUILD_USE_LIGHTCACHE = "CMAKE_FASTBUILD_USE_LIGHTCACHE";
+
+template <class T>
+FastbuildAliasNode generateAlias(std::string const& name, char const* postfix,
+                                 T const& nodes)
+{
+  FastbuildAliasNode alias;
+  alias.Name = name + postfix;
+  for (auto const& node : nodes) {
+    alias.PreBuildDependencies.emplace(node.Name);
+  }
+  return alias;
+}
+
+void FastbuildTarget::GenerateAliases()
+{
+  // -deps
+  this->DependenciesAlias.Name =
+    this->Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX;
+  for (auto const& dep : this->PreBuildDependencies) {
+    if (dep.Type != FastbuildTargetDepType::ORDER_ONLY) {
+      this->DependenciesAlias.PreBuildDependencies.emplace(dep);
+    }
+  }
+  // Build.
+  if (!this->LinkerNode.empty() &&
+      this->LinkerNode[0].Type != FastbuildLinkerNode::NONE) {
+    this->BuildAlias = generateAlias(this->Name, FASTBUILD_BUILD_ALIAS_POSTFIX,
+                                     this->LinkerNode);
+  }
+
+  // PRE/POST/REST
+  if (!this->PreBuildExecNodes.PreBuildDependencies.empty()) {
+    this->PreBuildExecNodes.Name =
+      this->Name + FASTBUILD_PRE_BUILD_ALIAS_POSTFIX;
+  }
+  if (!this->PreLinkExecNodes.Nodes.empty()) {
+    this->PreLinkExecNodes.Alias =
+      generateAlias(this->Name, FASTBUILD_PRE_LINK_ALIAS_POSTFIX,
+                    this->PreLinkExecNodes.Nodes);
+  }
+  if (!this->PostBuildExecNodes.Alias.PreBuildDependencies.empty()) {
+    this->PostBuildExecNodes.Alias.Name =
+      this->Name + FASTBUILD_POST_BUILD_ALIAS_POSTFIX;
+  }
+  if (!this->ExecNodes.PreBuildDependencies.empty()) {
+    this->ExecNodes.Name = this->Name + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
+  }
+
+  // -all.
+  FastbuildAliasNode allAlias;
+  allAlias.Name = this->Name + FASTBUILD_ALL_ALIAS_POSTFIX;
+
+  auto const addToAll = [&allAlias](FastbuildAliasNode const& node) {
+    if (!node.PreBuildDependencies.empty()) {
+      allAlias.PreBuildDependencies.emplace(node.Name);
+    }
+  };
+  // Add "-pre" nodes first, so we wait for all generations when building
+  // "-all".
+  for (FastbuildAliasNode const& execs : {
+         PreBuildExecNodes,
+         PreLinkExecNodes.Alias,
+       }) {
+    addToAll(execs);
+  }
+
+  // -build.
+  addToAll(this->BuildAlias);
+  for (FastbuildAliasNode const& node : AliasNodes) {
+    addToAll(node);
+  }
+
+  for (FastbuildAliasNode const& execs : { ExecNodes }) {
+    addToAll(execs);
+  }
+
+  bool const hasStampExeProperty = !this->LinkerNode.empty() &&
+    (this->LinkerNode[0].Type == FastbuildLinkerNode::EXECUTABLE ||
+     this->LinkerNode[0].Type == FastbuildLinkerNode::SHARED_LIBRARY);
+  // If we have .LinkerStampExe - we will use it to execute POST_BUILD steps,
+  // so don't add them to "-all".
+  if (!hasStampExeProperty) {
+    addToAll(this->PostBuildExecNodes.Alias);
+  }
+
+  // Tested in "RunCMake.VerifyHeaderSets" test.
+  if (allAlias.PreBuildDependencies.empty()) {
+    addToAll(DependenciesAlias);
+  }
+
+  for (auto const& objectList : this->ObjectListNodes) {
+    allAlias.PreBuildDependencies.emplace(objectList.Name);
+  }
+
+  for (auto const& linkerNode : this->LinkerNode) {
+    allAlias.PreBuildDependencies.emplace(linkerNode.Name);
+  }
+
+  // Absolutely empty target. But we still should be able to "build" it.
+  if (allAlias.PreBuildDependencies.empty()) {
+    allAlias.PreBuildDependencies.emplace(FASTBUILD_NOOP_FILE_NAME);
+  }
+
+  AliasNodes.emplace_back(std::move(allAlias));
+  // In case we want to build the target by name, but we don't have any output.
+  // OR we have two arches and real output does not match target name (it can
+  // match if lipo produces such file).
+  // RunCMake.CMakePackage / RunCMake.XcFramework
+  if ((this->LinkerNode.size() > 1 && this->RealOutput != this->Name) ||
+      this->RealOutput.empty()) {
+    FastbuildAliasNode alias;
+    alias.Name = this->Name;
+    FastbuildTargetDep dep{ this->Name };
+    dep.Type = FastbuildTargetDepType::ALL;
+    alias.PreBuildDependencies = { std::move(dep) };
+    AliasNodes.emplace_back(std::move(alias));
+  }
+
+  // Link artifacts (should not be added to all
+  // since on Windows it might contain Import Lib and FASTBuild doesn't know
+  // how to create it, so "-all" will fail).
+  AliasNodes.emplace_back(generateAlias(
+    this->Name, FASTBUILD_OBJECTS_ALIAS_POSTFIX, this->ObjectListNodes));
+
+  for (auto const& linkerNode : this->LinkerNode) {
+    if (linkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY ||
+        linkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY ||
+        linkerNode.Type == FastbuildLinkerNode::EXECUTABLE) {
+      std::string name = FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX;
+      if (!linkerNode.Arch.empty()) {
+        name += cmStrCat('-', linkerNode.Arch);
+      }
+#ifdef _WIN32
+      // On Windows DLL and Executables must be linked via Import Lib file
+      // (.lib).
+      if (linkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY ||
+          linkerNode.Type == FastbuildLinkerNode::EXECUTABLE) {
+        FastbuildAliasNode linkAlias;
+        linkAlias.Name = this->Name + FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX;
+        linkAlias.PreBuildDependencies.emplace(
+          FASTBUILD_DOLLAR_TAG "TargetOutputImplib" FASTBUILD_DOLLAR_TAG);
+        AliasNodes.emplace_back(std::move(linkAlias));
+        continue;
+      }
+#endif
+      AliasNodes.emplace_back(
+        generateAlias(this->Name, name.c_str(),
+                      std::vector<FastbuildLinkerNode>{ linkerNode }));
+    }
+  }
+}
+
+cmGlobalFastbuildGenerator::cmGlobalFastbuildGenerator(cmake* cm)
+  : cmGlobalCommonGenerator(cm)
+  , BuildFileStream(nullptr)
+{
+#ifdef _WIN32
+  cm->GetState()->SetWindowsShell(true);
+#endif
+  this->FindMakeProgramFile = "CMakeFastbuildFindMake.cmake";
+  cm->GetState()->SetFastbuildMake(true);
+  cm->GetState()->SetIsGeneratorMultiConfig(false);
+}
+
+void cmGlobalFastbuildGenerator::ProcessEnvironment()
+{
+  bool const CaptureSystemEnv =
+    !this->GetGlobalSetting(FASTBUILD_CAPTURE_SYSTEM_ENV).IsSet() ||
+    this->GetGlobalSetting(FASTBUILD_CAPTURE_SYSTEM_ENV).IsOn();
+  // On Windows environment is needed for MSVC, but preserve ability to discard
+  // it from the generated file if requested.
+  if (CaptureSystemEnv) {
+    LocalEnvironment = cmSystemTools::GetEnvironmentVariables();
+  }
+  // FASTBuild strips off "-isysroot" command line option (see :
+  // https://github.com/fastbuild/fastbuild/issues/1066).
+  // If 'SDK_ROOT' is not set via env and '-isysroot' is absent, AppleClang
+  // seems to use MacOS SDK by default (even though FBuild flattens includes
+  // before compiling). It breaks cross-compilation for iOS. Tested in
+  // "RunCMake.Framework" test.
+  std::string const osxRoot = this->GetSafeGlobalSetting("CMAKE_OSX_SYSROOT");
+  if (!osxRoot.empty()) {
+    LocalEnvironment.emplace_back("SDKROOT=" + osxRoot);
+  }
+
+  auto const EnvOverrides =
+    this->GetSafeGlobalSetting(FASTBUILD_ENV_OVERRIDES);
+
+  if (!EnvOverrides.empty()) {
+    auto const overrideEnvVar = [this](std::string const& prefix,
+                                       std::string val) {
+      auto const iter =
+        std::find_if(LocalEnvironment.begin(), LocalEnvironment.end(),
+                     [&prefix](std::string const& value) {
+                       return cmSystemTools::StringStartsWith(value.c_str(),
+                                                              prefix.c_str());
+                     });
+      if (iter != LocalEnvironment.end()) {
+        *iter = std::move(val);
+      } else {
+        LocalEnvironment.emplace_back(std::move(val));
+      }
+    };
+    for (auto& val : cmList{ EnvOverrides }) {
+      auto const pos = val.find('=');
+      if (pos != std::string::npos && ((pos + 1) < val.size())) {
+        overrideEnvVar(val.substr(0, pos + 1), std::move(val));
+      }
+    }
+  }
+
+  // Empty strings are not allowed.
+  LocalEnvironment.erase(
+    std::remove_if(LocalEnvironment.begin(), LocalEnvironment.end(),
+                   [](std::string const& s) { return s.empty(); }),
+    LocalEnvironment.end());
+}
+
+std::unique_ptr<cmGlobalGeneratorFactory>
+cmGlobalFastbuildGenerator::NewFactory()
+{
+  return std::unique_ptr<cmGlobalGeneratorFactory>(
+    new cmGlobalGeneratorSimpleFactory<cmGlobalFastbuildGenerator>());
+}
+
+void cmGlobalFastbuildGenerator::EnableLanguage(
+  std::vector<std::string> const& lang, cmMakefile* mf, bool optional)
+{
+  this->cmGlobalGenerator::EnableLanguage(lang, mf, optional);
+  for (std::string const& l : lang) {
+    if (l == "NONE") {
+      continue;
+    }
+    this->ResolveLanguageCompiler(l, mf, optional);
+  }
+}
+
+bool cmGlobalFastbuildGenerator::FindMakeProgram(cmMakefile* mf)
+{
+  if (!cmGlobalGenerator::FindMakeProgram(mf)) {
+    return false;
+  }
+  if (auto fastbuildCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
+    this->FastbuildCommand = *fastbuildCommand;
+    std::vector<std::string> command;
+    command.push_back(this->FastbuildCommand);
+    command.emplace_back("-version");
+    std::string version;
+    std::string error;
+    if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
+                                         nullptr,
+                                         cmSystemTools::OUTPUT_NONE)) {
+      mf->IssueMessage(MessageType::FATAL_ERROR,
+                       "Running\n '" + cmJoin(command, "' '") +
+                         "'\n"
+                         "failed with:\n " +
+                         error);
+      cmSystemTools::SetFatalErrorOccurred();
+      return false;
+    }
+    cmsys::RegularExpression versionRegex(R"(^FASTBuild v([0-9]+\.[0-9]+))");
+    versionRegex.find(version);
+    this->FastbuildVersion = versionRegex.match(1);
+  }
+  return true;
+}
+
+std::unique_ptr<cmLocalGenerator>
+cmGlobalFastbuildGenerator::CreateLocalGenerator(cmMakefile* makefile)
+{
+  return std::unique_ptr<cmLocalGenerator>(
+    cm::make_unique<cmLocalFastbuildGenerator>(this, makefile));
+}
+
+std::vector<cmGlobalGenerator::GeneratedMakeCommand>
+cmGlobalFastbuildGenerator::GenerateBuildCommand(
+  std::string const& makeProgram, std::string const& /*projectName*/,
+  std::string const& projectDir, std::vector<std::string> const& targetNames,
+  std::string const& /*config*/, int /*jobs*/, bool verbose,
+  cmBuildOptions /*buildOptions*/, std::vector<std::string> const& makeOptions)
+{
+  GeneratedMakeCommand makeCommand;
+  this->FastbuildCommand = this->SelectMakeProgram(makeProgram);
+  makeCommand.Add(this->FastbuildCommand);
+  // A build command for fastbuild looks like this:
+  // fbuild.exe [make-options] [-config projectName.bff] <target>
+
+  std::string configFile = cmStrCat(projectDir, '/', FASTBUILD_BUILD_FILE);
+
+  // Push in the make options
+  makeCommand.Add(makeOptions.begin(), makeOptions.end());
+
+  if (!configFile.empty()) {
+    makeCommand.Add("-config", configFile);
+  }
+  // Tested in "RunCMake.SymlinkTrees" test.
+  makeCommand.Add("-continueafterdbmove");
+
+  // Tested in RunCMake.LinkWhatYouUse on Linux. (We need to see output of
+  // LinkerStampExe process).
+  // In general, it might be useful to see output of external processes
+  // regardless of their outcome.
+  makeCommand.Add("-showcmdoutput");
+
+  // Add the target-config to the command
+  for (auto const& tname : targetNames) {
+    if (!tname.empty()) {
+      makeCommand.Add(tname);
+    }
+  }
+  if (verbose) {
+    makeCommand.Add("-verbose");
+  }
+
+  // Make "rebuild-bff" target up-to-date before running the build.
+
+  std::string output;
+  ExecuteFastbuildTarget(projectDir, FASTBUILD_REBUILD_BFF_TARGET_NAME, output,
+                         { "-why" });
+
+  // If fbuild.bff was re-generated we need to "restat" it.
+  if (output.find("Need to build") != std::string::npos) {
+    // Let the user know that re-generation happened (and why it
+    // happened).
+    cmSystemTools::Stdout(output);
+    // FASTBuild will consider the target out-of-date in case some of the
+    // inputs have changes after re-generation which might happen if, for
+    // example, configuration depends on some files generated during
+    // the configuration itself.
+    AskCMakeToMakeRebuildBFFUpToDate(projectDir);
+  }
+
+  return { std::move(makeCommand) };
+}
+
+void cmGlobalFastbuildGenerator::ComputeTargetObjectDirectory(
+  cmGeneratorTarget* gt) const
+{
+  // Compute full path to object file directory for this target.
+  std::string dir =
+    cmStrCat(gt->GetSupportDirectory(), '/', this->GetCMakeCFGIntDir(), '/');
+  gt->ObjectDirectory = std::move(dir);
+}
+
+void cmGlobalFastbuildGenerator::AppendDirectoryForConfig(
+  std::string const& prefix, std::string const& config,
+  std::string const& suffix, std::string& dir)
+{
+  if (!config.empty() && this->IsMultiConfig()) {
+    dir += cmStrCat(prefix, config, suffix);
+  }
+}
+
+cmDocumentationEntry cmGlobalFastbuildGenerator::GetDocumentation()
+{
+  return { cmGlobalFastbuildGenerator::GetActualName(),
+           "Generates build.bff files." };
+}
+
+void cmGlobalFastbuildGenerator::Generate()
+{
+  // Check minimum Fastbuild version.
+  if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS,
+                                    this->FastbuildVersion,
+                                    RequiredFastbuildVersion())) {
+    std::ostringstream msg;
+    msg << "The detected version of Fastbuild (" << this->FastbuildVersion;
+    msg << ") is less than the version of Fastbuild required by CMake (";
+    msg << this->RequiredFastbuildVersion() << ").";
+    this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
+                                           msg.str());
+    return;
+  }
+  this->ProcessEnvironment();
+
+  this->OpenBuildFileStream();
+
+  this->WriteSettings();
+  this->WriteEnvironment();
+
+  // Execute the standard generate process
+  cmGlobalGenerator::Generate();
+
+  // Write compilers
+  this->WriteCompilers();
+
+  this->WriteTargets();
+
+  this->CloseBuildFileStream();
+
+  if (cmSystemTools::GetErrorOccurredFlag()) {
+    return;
+  }
+
+  this->RemoveUnknownClangTidyExportFixesFiles();
+
+  if (this->GetCMakeInstance()->GetRegenerateDuringBuild()) {
+    return;
+  }
+  // TODO: figure out how to skip this in TryCompile
+  //  Make "rebuild-bff" target up-to-date after the generation.
+  //  This is actually a noop, it just asks CMake to touch the generated file
+  //  so FASTBuild would consider the target as up-to-date.
+  AskCMakeToMakeRebuildBFFUpToDate(
+    this->GetCMakeInstance()->GetHomeOutputDirectory());
+}
+
+void cmGlobalFastbuildGenerator::AskCMakeToMakeRebuildBFFUpToDate(
+  std::string const& workingDir) const
+{
+  // "restat" the generated build file.
+  // The idea here is to mimic what Ninja's "restat" command does.
+  // We need to make the "rebuild.bff" target up-to-date, so the regeneration
+  // will only be triggered when CMake files have actually changed.
+  // Tested in "RunCMake.Configure" test.
+  cmsys::ofstream{
+    cmStrCat(workingDir, '/', FASTBUILD_RESTAT_FILE).c_str(),
+    std::ios::out | std::ios::binary
+  } << cmStrCat(workingDir, '/', FASTBUILD_BUILD_FILE);
+  std::string output;
+  ExecuteFastbuildTarget(workingDir, FASTBUILD_REBUILD_BFF_TARGET_NAME,
+                         output);
+}
+
+void cmGlobalFastbuildGenerator::ExecuteFastbuildTarget(
+  std::string const& dir, std::string const& target, std::string& output,
+  std::vector<std::string> const& fbuildOptions) const
+{
+  std::vector<std::string> command;
+
+  command.emplace_back(this->FastbuildCommand);
+  command.emplace_back("-config");
+  std::string const file = cmStrCat(dir, '/', FASTBUILD_BUILD_FILE);
+  command.emplace_back(file);
+  command.emplace_back(target);
+  if (!fbuildOptions.empty()) {
+    command.emplace_back(cmJoin(fbuildOptions, " "));
+  }
+
+  int retVal = 0;
+  if (!cmSystemTools::RunSingleCommand(command, &output, nullptr, &retVal,
+                                       dir.c_str(),
+                                       cmSystemTools::OUTPUT_NONE) ||
+      retVal != 0) {
+    cmSystemTools::Error(cmStrCat("Failed to run FASTBuild command:\n  '",
+                                  cmJoin(command, "' '"), "'\nOutput:\n",
+                                  output));
+    cmSystemTools::Stdout(output);
+    std::exit(retVal);
+  }
+}
+
+void cmGlobalFastbuildGenerator::WriteSettings()
+{
+  // Define some placeholder
+  WriteDivider();
+  *this->BuildFileStream << "// Helper variables\n\n";
+
+  WriteVariable("FB_INPUT_1_PLACEHOLDER", Quote("\"%1\""));
+  WriteVariable("FB_INPUT_1_0_PLACEHOLDER", Quote("\"%1[0]\""));
+  WriteVariable("FB_INPUT_1_1_PLACEHOLDER", Quote("\"%1[1]\""));
+  WriteVariable("FB_INPUT_2_PLACEHOLDER", Quote("\"%2\""));
+  WriteVariable("FB_INPUT_3_PLACEHOLDER", Quote("\"%3\""));
+
+  std::string cacheDir;
+  // If explicitly set from CMake.
+  auto val = this->GetSafeGlobalSetting(FASTBUILD_CACHE_PATH);
+  if (!val.empty()) {
+    cacheDir = std::move(val);
+  } else {
+    // Default value.
+    cacheDir =
+      this->GetCMakeInstance()->GetHomeOutputDirectory() + "/fbuild.cache";
+  }
+  cmSystemTools::ConvertToOutputSlashes(cacheDir);
+
+  WriteDivider();
+  *this->BuildFileStream << "// Settings\n\n";
+
+  WriteCommand("Settings");
+  *this->BuildFileStream << "{\n";
+  if (!cacheDir.empty()) {
+    WriteVariable("CachePath", Quote(cacheDir), 1);
+  }
+  // Concurrency groups.
+  WriteStruct(
+    FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME,
+    { { "ConcurrencyGroupName", Quote(FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME) },
+      { "ConcurrencyLimit", "1" } },
+    1);
+
+  WriteArray("ConcurrencyGroups",
+             { "." FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME }, 1);
+
+  *this->BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteEnvironment()
+{
+  if (!LocalEnvironment.empty()) {
+    WriteArray(FASTBUILD_ENV_VAR_NAME, Wrap(LocalEnvironment), 0);
+  }
+}
+
+void cmGlobalFastbuildGenerator::WriteDivider()
+{
+  *this->BuildFileStream << "// ======================================"
+                            "=======================================\n";
+}
+
+void cmGlobalFastbuildGenerator::Indent(int count)
+{
+  for (int i = 0; i < count; ++i) {
+    *this->BuildFileStream << "  ";
+  }
+}
+
+void cmGlobalFastbuildGenerator::WriteComment(std::string const& comment,
+                                              int indent)
+{
+  if (comment.empty()) {
+    return;
+  }
+
+  std::string::size_type lpos = 0;
+  std::string::size_type rpos;
+  *this->BuildFileStream << "\n";
+  Indent(indent);
+  *this->BuildFileStream << "/////////////////////////////////////////////\n";
+  while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
+    Indent(indent);
+    *this->BuildFileStream << "// " << comment.substr(lpos, rpos - lpos)
+                           << "\n";
+    lpos = rpos + 1;
+  }
+  Indent(indent);
+  *this->BuildFileStream << "// " << comment.substr(lpos) << "\n\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteVariable(std::string const& key,
+                                               std::string const& value,
+                                               int indent)
+{
+  WriteVariable(key, value, "=", indent);
+}
+
+void cmGlobalFastbuildGenerator::WriteVariable(std::string const& key,
+                                               std::string const& value,
+                                               std::string const& op,
+                                               int indent)
+{
+  Indent(indent);
+  *this->BuildFileStream << "." << key << " " + op + " " << value << "\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteCommand(std::string const& command,
+                                              std::string const& value,
+                                              int indent)
+{
+  Indent(indent);
+  *this->BuildFileStream << command;
+  if (!value.empty()) {
+    *this->BuildFileStream << "(" << value << ")";
+  }
+  *this->BuildFileStream << "\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteArray(
+  std::string const& key, std::vector<std::string> const& values, int indent)
+{
+  WriteArray(key, values, "=", indent);
+}
+
+void cmGlobalFastbuildGenerator::WriteArray(
+  std::string const& key, std::vector<std::string> const& values,
+  std::string const& op, int indent)
+{
+  WriteVariable(key, "", op, indent);
+  Indent(indent);
+  *this->BuildFileStream << "{\n";
+  char const* sep = "";
+  for (std::string const& value : values) {
+    *this->BuildFileStream << sep;
+    sep = ",\n";
+    Indent(indent + 1);
+    *this->BuildFileStream << value;
+  }
+  *this->BuildFileStream << "\n";
+  Indent(indent);
+  *this->BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteStruct(
+  std::string const& name,
+  std::vector<std::pair<std::string, std::string>> const& variables,
+  int indent)
+{
+  WriteVariable(name, "", "=", indent);
+  Indent(indent);
+  *this->BuildFileStream << "[\n";
+  for (auto const& val : variables) {
+    auto const& key = val.first;
+    auto const& value = val.second;
+    WriteVariable(key, value, "=", indent + 1);
+  }
+  Indent(indent);
+  *this->BuildFileStream << "]\n";
+}
+
+std::string cmGlobalFastbuildGenerator::Quote(std::string const& str,
+                                              std::string const& quotation)
+{
+  std::string result = str;
+  cmSystemTools::ReplaceString(result, quotation, "^" + quotation);
+  cmSystemTools::ReplaceString(result, FASTBUILD_DOLLAR_TAG, "$");
+  return quotation + result + quotation;
+}
+std::string cmGlobalFastbuildGenerator::QuoteIfHasSpaces(std::string str)
+{
+  if (str.find(' ') != std::string::npos) {
+    return '"' + str + '"';
+  }
+  return str;
+}
+
+struct WrapHelper
+{
+  std::string Prefix;
+  std::string Suffix;
+  bool EscapeDollar;
+
+  std::string operator()(std::string in)
+  {
+    // If we have ^ in env variable - need to escape it.
+    cmSystemTools::ReplaceString(in, "^", "^^");
+    // Those all are considered as line ends by FASTBuild.
+    cmSystemTools::ReplaceString(in, "\n", "\\n");
+    cmSystemTools::ReplaceString(in, "\r", "\\r");
+    // Escaping of single quotes tested in "RunCMake.CompilerArgs" test.
+    cmSystemTools::ReplaceString(in, "'", "^'");
+    std::string result = Prefix + in + Suffix;
+    if (EscapeDollar) {
+      cmSystemTools::ReplaceString(result, "$", "^$");
+      cmSystemTools::ReplaceString(result, FASTBUILD_DOLLAR_TAG, "$");
+    }
+    return result;
+  }
+  std::string operator()(FastbuildTargetDep const& in)
+  {
+    std::string res = in.Name;
+    if (in.Type == FastbuildTargetDepType::ALL) {
+      res += FASTBUILD_ALL_ALIAS_POSTFIX;
+    } else if (in.Type == FastbuildTargetDepType::BUILD) {
+      res += FASTBUILD_BUILD_ALIAS_POSTFIX;
+    }
+
+    return (*this)(std::move(res));
+  }
+};
+template <class T>
+std::vector<std::string> cmGlobalFastbuildGenerator::Wrap(
+  T const& in, std::string const& prefix, std::string const& suffix,
+  bool const escape_dollar)
+{
+
+  std::vector<std::string> result;
+
+  WrapHelper helper = { prefix, suffix, escape_dollar };
+
+  std::transform(in.begin(), in.end(), std::back_inserter(result), helper);
+
+  return result;
+}
+
+void cmGlobalFastbuildGenerator::TopologicalSort(
+  std::vector<FastbuildTargetPtrT>& nodes)
+{
+  std::unordered_map<std::string, int> inDegree;
+  std::unordered_map<std::string, std::set<std::string>> reverseDeps;
+  std::unordered_map<std::string, std::size_t> originalIndex;
+
+  // Track original positions
+  for (std::size_t i = 0; i < nodes.size(); ++i) {
+    auto const& node = nodes[i];
+    inDegree[node->Name] = 0;
+    originalIndex[node->Name] = i;
+  }
+
+  // Build reverse dependency graph and in-degree map
+  for (auto const& node : nodes) {
+    for (auto const& dep : node->PreBuildDependencies) {
+      if (inDegree.count(dep.Name)) {
+        reverseDeps[dep.Name].insert(node->Name);
+        ++inDegree[node->Name];
+      }
+    }
+  }
+
+  // Min-heap based on original position
+  auto const cmp = [&](std::string const& a, std::string const& b) {
+    return originalIndex[a] > originalIndex[b];
+  };
+  std::priority_queue<std::string, std::vector<std::string>, decltype(cmp)>
+    zeroInDegree(cmp);
+
+  for (auto const& val : inDegree) {
+    auto const& degree = val.second;
+    auto const& name = val.first;
+    if (degree == 0) {
+      zeroInDegree.push(name);
+    }
+  }
+
+  std::vector<std::string> sorted;
+  while (!zeroInDegree.empty()) {
+    std::string node = zeroInDegree.top();
+    zeroInDegree.pop();
+    sorted.push_back(node);
+    for (auto const& dep : reverseDeps[node]) {
+      if (--inDegree[dep] == 0) {
+        zeroInDegree.push(dep);
+      }
+    }
+  }
+
+  if (sorted.size() != nodes.size()) {
+    cmSystemTools::Error("Failed to sort (Cyclic dependency)");
+    cmSystemTools::Error(cmStrCat("Sorted size: ", sorted.size()));
+    cmSystemTools::Error(cmStrCat("nodes size: ", nodes.size()));
+    for (auto const& node : nodes) {
+      cmSystemTools::Error("Node: " + node->Name);
+      for (auto const& dep : reverseDeps[node->Name]) {
+        cmSystemTools::Error("\tReverse dep: " + dep);
+      }
+      for (auto const& child : node->PreBuildDependencies) {
+        cmSystemTools::Error("\tChild: " + child.Name);
+      }
+    }
+    for (auto const& node : sorted) {
+      cmSystemTools::Error("Sorted: " + node);
+    }
+    for (auto const& node : nodes) {
+      cmSystemTools::Error("In node: " + node->Name);
+    }
+  }
+
+  // Reconstruct sorted nodes
+  std::vector<FastbuildTargetPtrT> result;
+  for (auto const& name : sorted) {
+    auto it = std::find_if(
+      nodes.begin(), nodes.end(), [&name](FastbuildTargetPtrT const& node) {
+        return node /* the node might be in moved-from state*/ &&
+          node->Name == name;
+      });
+    if (it != nodes.end()) {
+      result.emplace_back(std::move(*it));
+    }
+  }
+
+  std::swap(result, nodes);
+}
+
+void cmGlobalFastbuildGenerator::WriteDisclaimer()
+{
+  *this->BuildFileStream << "// CMAKE generated file: DO NOT EDIT!\n"
+                         << "// Generated by \"" << this->GetName() << "\""
+                         << " Generator, CMake Version "
+                         << cmVersion::GetMajorVersion() << "."
+                         << cmVersion::GetMinorVersion() << "\n\n";
+}
+
+void cmGlobalFastbuildGenerator::OpenBuildFileStream()
+{
+  // Compute Fastbuild's build file path.
+  std::string buildFilePath =
+    this->GetCMakeInstance()->GetHomeOutputDirectory();
+  buildFilePath += "/";
+  buildFilePath += FASTBUILD_BUILD_FILE;
+
+  // Get a stream where to generate things.
+  if (!this->BuildFileStream) {
+    this->BuildFileStream = cm::make_unique<cmGeneratedFileStream>(
+      buildFilePath, false, this->GetMakefileEncoding());
+    if (!this->BuildFileStream) {
+      // An error message is generated by the constructor if it cannot
+      // open the file.
+      return;
+    }
+  }
+
+  // Write the do not edit header.
+  this->WriteDisclaimer();
+
+  // Write a comment about this file.
+  *this->BuildFileStream
+    << "// This file contains all the build statements\n\n";
+}
+
+void cmGlobalFastbuildGenerator::CloseBuildFileStream()
+{
+  if (this->BuildFileStream) {
+    this->BuildFileStream.reset();
+  } else {
+    cmSystemTools::Error("Build file stream was not open.");
+  }
+}
+
+void cmGlobalFastbuildGenerator::WriteCompilers()
+{
+  WriteDivider();
+  *this->BuildFileStream << "// Compilers\n\n";
+  for (auto const& val : Compilers) {
+    auto const& compilerDef = val.second;
+
+    std::string fastbuildFamily = "custom";
+
+    if (compilerDef.Language == "C" || compilerDef.Language == "CXX" ||
+        compilerDef.Language == "CUDA") {
+      std::map<std::string, std::string> compilerIdToFastbuildFamily = {
+        { "MSVC", "msvc" },        { "Clang", "clang" },
+        { "AppleClang", "clang" }, { "GNU", "gcc" },
+        { "NVIDIA", "cuda-nvcc" },
+      };
+      auto ft = compilerIdToFastbuildFamily.find(compilerDef.CmakeCompilerID);
+      if (ft != compilerIdToFastbuildFamily.end()) {
+        fastbuildFamily = ft->second;
+      }
+    }
+
+    std::string compilerPath = compilerDef.Executable;
+
+    // Write out the compiler that has been configured
+    WriteCommand("Compiler", Quote(compilerDef.Name));
+    *this->BuildFileStream << "{\n";
+    for (auto const& extra : compilerDef.ExtraVariables) {
+      auto const& extraKey = extra.first;
+      auto const& extraVal = extra.second;
+      WriteVariable(extraKey, Quote(extraVal), 1);
+    }
+    WriteVariable("Executable", Quote(compilerPath), 1);
+    WriteVariable("CompilerFamily", Quote(fastbuildFamily), 1);
+    if (compilerDef.UseLightCache) {
+      WriteVariable("UseLightCache_Experimental", "true", 1);
+    }
+    if (fastbuildFamily == "clang") {
+      WriteVariable("ClangRewriteIncludes", "false", 1);
+    }
+
+    if (compilerDef.DontUseEnv) {
+      LogMessage("Not using system environment");
+    } else {
+      WriteVariable("Environment", "." FASTBUILD_ENV_VAR_NAME, 1);
+    }
+    if (!compilerDef.ExtraFiles.empty()) {
+      // Do not escape '$' sign, CMAKE_${LANG}_FASTBUILD_EXTRA_FILES might
+      // contain FB variables to be expanded (we do use some internally).
+      // Besides a path cannot contain a '$'
+      WriteArray("ExtraFiles", Wrap(compilerDef.ExtraFiles, "'", "'", false),
+                 1);
+    }
+    *this->BuildFileStream << "}\n";
+
+    auto const compilerId = compilerDef.Name;
+    WriteVariable(compilerId, Quote(compilerDef.Name));
+    *this->BuildFileStream << "\n";
+  }
+  // We need this because the Library command needs a compiler
+  // even if don't compile anything
+  if (!this->Compilers.empty()) {
+    WriteVariable("Compiler_dummy",
+                  Quote(this->Compilers.begin()->second.Name));
+  }
+}
+
+void cmGlobalFastbuildGenerator::AddCompiler(std::string const& language,
+                                             cmMakefile* mf)
+{
+  if (this->Compilers.find(FASTBUILD_COMPILER_PREFIX + language) !=
+      this->Compilers.end()) {
+    return;
+  }
+
+  // Calculate the root location of the compiler
+  std::string const variableString = "CMAKE_" + language + "_COMPILER";
+  std::string const compilerLocation = mf->GetSafeDefinition(variableString);
+  if (compilerLocation.empty()) {
+    return;
+  }
+
+  // Calculate the i18n number.
+  std::string i18nNum = "1033";
+
+  // Add the language to the compiler's name
+  FastbuildCompiler compilerDef;
+  compilerDef.ExtraVariables["Root"] =
+    cmSystemTools::GetFilenamePath(compilerLocation);
+  compilerDef.Name = FASTBUILD_COMPILER_PREFIX + language;
+  compilerDef.Executable = compilerLocation;
+  compilerDef.CmakeCompilerID =
+    mf->GetSafeDefinition("CMAKE_" + language + "_COMPILER_ID");
+
+  compilerDef.CmakeCompilerVersion =
+    mf->GetSafeDefinition("CMAKE_" + language + "_COMPILER_VERSION");
+  compilerDef.Language = language;
+
+  if (compilerDef.CmakeCompilerID == "MSVC" &&
+      cmIsOn(mf->GetSafeDefinition(FASTBUILD_USE_LIGHTCACHE)) &&
+      (language == "C" || language == "CXX")) {
+    compilerDef.UseLightCache = true;
+  }
+  cmExpandList(mf->GetSafeDefinition(FASTBUILD_COMPILER_EXTRA_FILES),
+               compilerDef.ExtraFiles);
+
+  // If FASTBUILD_COMPILER_EXTRA_FILES is not set - automatically add extra
+  // files based on compiler (see
+  // https://fastbuild.org/docs/functions/compiler.html)
+  if (compilerDef.ExtraFiles.empty() &&
+      (language == "C" || language == "CXX") &&
+      compilerDef.CmakeCompilerID == "MSVC") {
+    // https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html
+
+    // Visual Studio 17 (19.30 to 19.39)
+    // TODO
+
+    // Visual Studio 16 (19.20 to 19.29)
+    if (cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
+                                      compilerDef.CmakeCompilerVersion,
+                                      "19.20")) {
+      compilerDef.ExtraFiles.push_back("$Root$/c1.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/c1xx.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/c2.dll");
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/atlprov.dll"); // Only needed if using ATL
+      compilerDef.ExtraFiles.push_back("$Root$/msobj140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdb140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdbcore.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdbsrv.exe");
+      compilerDef.ExtraFiles.push_back("$Root$/mspft140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/msvcp140.dll");
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/msvcp140_atomic_wait.dll"); // Required circa 16.8.3
+                                            // (14.28.29333)
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/tbbmalloc.dll"); // Required as of 16.2 (14.22.27905)
+      compilerDef.ExtraFiles.push_back("$Root$/vcruntime140.dll");
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/vcruntime140_1.dll"); // Required as of 16.5.1 (14.25.28610)
+      compilerDef.ExtraFiles.push_back("$Root$/" + i18nNum + "/clui.dll");
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/" + i18nNum + "/mspft140ui.dll"); // Localized messages for
+                                                  // static analysis
+    }
+    // Visual Studio 15 (19.10 to 19.19)
+    else if (cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
+                                           compilerDef.CmakeCompilerVersion,
+                                           "19.10")) {
+      compilerDef.ExtraFiles.push_back("$Root$/c1.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/c1xx.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/c2.dll");
+      compilerDef.ExtraFiles.push_back(
+        "$Root$/atlprov.dll"); // Only needed if using ATL
+      compilerDef.ExtraFiles.push_back("$Root$/msobj140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdb140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdbcore.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/mspdbsrv.exe");
+      compilerDef.ExtraFiles.push_back("$Root$/mspft140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/msvcp140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/vcruntime140.dll");
+      compilerDef.ExtraFiles.push_back("$Root$/" + i18nNum + "/clui.dll");
+    }
+  }
+  // TODO: Handle Intel compiler
+
+  this->Compilers[compilerDef.Name] = std::move(compilerDef);
+}
+
+void cmGlobalFastbuildGenerator::AddLauncher(std::string const& prefix,
+                                             std::string const& launcher,
+                                             std::string const& language,
+                                             std::string const& args)
+{
+  if (this->Compilers.find(prefix + language) != this->Compilers.end()) {
+    return;
+  }
+  LogMessage("Launcher: " + launcher);
+  LogMessage("Launcher args: " + args);
+  FastbuildCompiler compilerDef;
+  compilerDef.Name = prefix + language;
+  compilerDef.Args = args;
+  if (cmSystemTools::FileIsFullPath(launcher)) {
+    compilerDef.Executable = launcher;
+  } else {
+    // FASTBuild needs an absolute path to the executable.
+    compilerDef.Executable = cmSystemTools::FindProgram(launcher);
+    if (compilerDef.Executable.empty()) {
+      cmSystemTools::Error("Failed to find path to " + launcher);
+      return;
+    }
+  }
+  // When CTest is used as a launcher, there is an interesting env variable
+  // "CTEST_LAUNCH_LOGS" which is set by parent CTest process and is expected
+  // to be read from global (sic!) env by the launched CTest process. So we
+  // will need to make this global env available for CTest executable used as a
+  // "launcher". Tested in RunCMake.ctest_labels_for_subprojects test..
+  compilerDef.DontUseEnv = true;
+  this->Compilers[compilerDef.Name] = std::move(compilerDef);
+}
+
+std::string cmGlobalFastbuildGenerator::ConvertToFastbuildPath(
+  std::string const& path) const
+{
+  cmLocalGenerator const* root = LocalGenerators[0].get();
+  return root->MaybeRelativeToWorkDir(cmSystemTools::FileIsFullPath(path)
+                                        ? cmSystemTools::CollapseFullPath(path)
+                                        : path);
+}
+
+std::unique_ptr<cmLinkLineComputer>
+cmGlobalFastbuildGenerator::CreateLinkLineComputer(
+  cmOutputConverter* outputConverter,
+  cmStateDirectory const& /* stateDir */) const
+{
+  return cm::make_unique<cmFastbuildLinkLineComputer>(
+    outputConverter,
+    this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this);
+}
+
+void cmGlobalFastbuildGenerator::WriteExec(FastbuildExecNode const& Exec,
+                                           int indent)
+{
+  auto const identPlus1 = indent + 1;
+  WriteCommand("Exec", Exec.Name.empty() ? std::string{} : Quote(Exec.Name),
+               indent);
+  Indent(indent);
+  *BuildFileStream << "{\n";
+  {
+    if (!Exec.PreBuildDependencies.empty()) {
+      WriteArray("PreBuildDependencies", Wrap(Exec.PreBuildDependencies),
+                 identPlus1);
+    }
+    WriteVariable("ExecExecutable", Quote(Exec.ExecExecutable), identPlus1);
+    if (!Exec.ExecArguments.empty()) {
+      WriteVariable("ExecArguments", Quote(Exec.ExecArguments), identPlus1);
+    }
+    if (!Exec.ExecWorkingDir.empty()) {
+      WriteVariable("ExecWorkingDir", Quote(Exec.ExecWorkingDir), identPlus1);
+    }
+    if (!Exec.ExecInput.empty()) {
+      WriteArray("ExecInput", Wrap(Exec.ExecInput), identPlus1);
+    }
+    if (Exec.ExecUseStdOutAsOutput) {
+      WriteVariable("ExecUseStdOutAsOutput", "true", identPlus1);
+    }
+    if (!Exec.ExecInputPath.empty()) {
+      WriteArray("ExecInputPath", Wrap(Exec.ExecInputPath), identPlus1);
+    }
+    if (!Exec.ExecInputPattern.empty()) {
+      WriteArray("ExecInputPattern", Wrap(Exec.ExecInputPattern), identPlus1);
+    }
+    WriteVariable("ExecAlwaysShowOutput", "true", identPlus1);
+    WriteVariable("ExecOutput", Quote(Exec.ExecOutput), identPlus1);
+    WriteVariable("ExecAlways", Exec.ExecAlways ? "true" : "false",
+                  identPlus1);
+    if (!Exec.ConcurrencyGroupName.empty()) {
+      WriteVariable("ConcurrencyGroupName", Quote(Exec.ConcurrencyGroupName),
+                    identPlus1);
+    }
+  }
+  Indent(indent);
+  *BuildFileStream << "}\n";
+  WriteAlias(Exec.OutputsAlias);
+  WriteAlias(Exec.ByproductsAlias);
+}
+
+void cmGlobalFastbuildGenerator::WriteObjectList(
+  FastbuildObjectListNode const& ObjectList, bool allowDistribution)
+{
+  WriteCommand("ObjectList", Quote(ObjectList.Name), 1);
+  Indent(1);
+  *BuildFileStream << "{\n";
+  {
+    if (!allowDistribution) {
+      WriteVariable("AllowDistribution", "false", 2);
+    }
+    if (!ObjectList.PreBuildDependencies.empty()) {
+      WriteArray("PreBuildDependencies", Wrap(ObjectList.PreBuildDependencies),
+                 2);
+    }
+    WriteVariable("Compiler", ObjectList.Compiler, 2);
+    // If only PCH output is present - this node reuses existing PCH.
+    if (!ObjectList.PCHOutputFile.empty()) {
+      WriteVariable("PCHOutputFile", Quote(ObjectList.PCHOutputFile), 2);
+    }
+    // If PCHInputFile and PCHOptions are present  - this node creates PCH.
+    if (!ObjectList.PCHInputFile.empty() && !ObjectList.PCHOptions.empty()) {
+      WriteVariable("PCHInputFile", Quote(ObjectList.PCHInputFile), 2);
+      WriteVariable("PCHOptions", Quote(ObjectList.PCHOptions), 2);
+    }
+    WriteVariable("CompilerOptions", Quote(ObjectList.CompilerOptions), 2);
+    WriteVariable("CompilerOutputPath", Quote(ObjectList.CompilerOutputPath),
+                  2);
+    WriteVariable("CompilerOutputExtension",
+                  Quote(ObjectList.CompilerOutputExtension), 2);
+    WriteVariable("CompilerOutputKeepBaseExtension", "true", 2);
+    WriteArray("CompilerInputFiles", Wrap(ObjectList.CompilerInputFiles), 2);
+    if (ObjectList.Hidden) {
+      WriteVariable("Hidden", "true", 2);
+    }
+  }
+  Indent(1);
+  *BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteLinker(
+  FastbuildLinkerNode const& LinkerNode, bool allowDistribution)
+{
+  WriteCommand(
+    LinkerNode.Type == FastbuildLinkerNode::EXECUTABLE         ? "Executable"
+      : LinkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY ? "DLL"
+                                                               : "Library",
+    LinkerNode.Name != LinkerNode.LinkerOutput ? Quote(LinkerNode.Name) : "",
+    1);
+  Indent(1);
+  *BuildFileStream << "{\n";
+  {
+    if (!LinkerNode.PreBuildDependencies.empty()) {
+      WriteArray("PreBuildDependencies", Wrap(LinkerNode.PreBuildDependencies),
+                 2);
+    }
+    if (!allowDistribution) {
+      WriteVariable("AllowDistribution", "false", 2);
+    }
+
+    if (!LinkerNode.Compiler.empty()) {
+      WriteVariable("Compiler", LinkerNode.Compiler, 2);
+      WriteVariable("CompilerOptions", Quote(LinkerNode.CompilerOptions), 2);
+      WriteVariable("CompilerOutputPath", Quote("."), 2);
+    }
+    if (!LocalEnvironment.empty()) {
+      WriteVariable("Environment", "." FASTBUILD_ENV_VAR_NAME, 2);
+    }
+
+    WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY
+                    ? "Librarian"
+                    : "Linker",
+                  Quote(LinkerNode.Linker), 2);
+
+    WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY
+                    ? "LibrarianOptions"
+                    : "LinkerOptions",
+                  Quote(LinkerNode.LinkerOptions), 2);
+
+    WriteVariable(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY
+                    ? "LibrarianOutput"
+                    : "LinkerOutput",
+                  Quote(LinkerNode.LinkerOutput), 2);
+
+    if (!LinkerNode.LibrarianAdditionalInputs.empty()) {
+      WriteArray(LinkerNode.Type == FastbuildLinkerNode::STATIC_LIBRARY
+                   ? "LibrarianAdditionalInputs"
+                   : "Libraries",
+                 Wrap(LinkerNode.LibrarianAdditionalInputs), 2);
+    }
+    if (!LinkerNode.Libraries2.empty()) {
+      WriteArray("Libraries2", Wrap(LinkerNode.Libraries2), 2);
+    }
+    if (!LinkerNode.LibrarianAdditionalInputs.empty()) {
+
+      if (!LinkerNode.LinkerType.empty()) {
+        WriteVariable("LinkerType", Quote(LinkerNode.LinkerType), 2);
+      }
+    }
+    if (LinkerNode.Type == FastbuildLinkerNode::EXECUTABLE ||
+        LinkerNode.Type == FastbuildLinkerNode::SHARED_LIBRARY) {
+      WriteVariable("LinkerLinkObjects",
+                    LinkerNode.LinkerLinkObjects ? "true" : "false", 2);
+
+      if (!LinkerNode.LinkerStampExe.empty()) {
+        WriteVariable("LinkerStampExe", Quote(LinkerNode.LinkerStampExe), 2);
+        if (!LinkerNode.LinkerStampExeArgs.empty()) {
+          WriteVariable("LinkerStampExeArgs",
+                        Quote(LinkerNode.LinkerStampExeArgs), 2);
+        }
+      }
+    }
+    Indent(1);
+    *BuildFileStream << "}\n";
+  }
+}
+
+void cmGlobalFastbuildGenerator::WriteAlias(FastbuildAliasNode const& Alias,
+                                            int indent)
+{
+  if (Alias.PreBuildDependencies.empty()) {
+    return;
+  }
+  auto const identPlus1 = indent + 1;
+  WriteCommand("Alias", Quote(Alias.Name), indent);
+  Indent(indent);
+  *BuildFileStream << "{\n";
+  WriteArray("Targets", Wrap(Alias.PreBuildDependencies), identPlus1);
+  if (Alias.Hidden) {
+    WriteVariable("Hidden", "true", identPlus1);
+  }
+  Indent(indent);
+  *BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteCopy(FastbuildCopyNode const& Copy)
+{
+  cmGlobalFastbuildGenerator::WriteCommand(
+    Copy.CopyDir ? "CopyDir" : "Copy",
+    cmGlobalFastbuildGenerator::Quote(Copy.Name), 1);
+  cmGlobalFastbuildGenerator::Indent(1);
+
+  *BuildFileStream << "{\n";
+  WriteVariable("PreBuildDependencies",
+                cmGlobalFastbuildGenerator::Quote(Copy.PreBuildDependencies),
+                2);
+  WriteVariable(Copy.CopyDir ? "SourcePaths" : "Source",
+                cmGlobalFastbuildGenerator::Quote(Copy.Source), 2);
+  WriteVariable("Dest", cmGlobalFastbuildGenerator::Quote(Copy.Dest), 2);
+  cmGlobalFastbuildGenerator::Indent(1);
+  *BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteTarget(FastbuildTarget const& target)
+{
+  for (auto const& val : target.Variables) {
+    auto const& key = val.first;
+    auto const& value = val.second;
+    WriteVariable(key, cmGlobalFastbuildGenerator::Quote(value), 1);
+  }
+  // add_custom_commands(...)
+  for (auto const& alias : { target.ExecNodes }) {
+    this->WriteAlias(alias);
+  }
+
+  // -deps Alias.
+  this->WriteAlias(target.DependenciesAlias);
+
+  // PRE_BUILD.
+  for (auto const& alias : { target.PreBuildExecNodes }) {
+    this->WriteAlias(alias);
+  }
+
+  // Copy commands.
+
+  for (FastbuildCopyNode const& node : target.CopyNodes) {
+    this->WriteCopy(node);
+  }
+
+  // Objects.
+  for (FastbuildObjectListNode const& objectList : target.ObjectListNodes) {
+    this->WriteObjectList(objectList, target.AllowDistribution);
+  }
+
+  if (!target.PreLinkExecNodes.Nodes.empty()) {
+    for (auto const& exec : target.PreLinkExecNodes.Nodes) {
+      this->WriteExec(exec);
+    }
+    this->WriteAlias(target.PreLinkExecNodes.Alias);
+  }
+
+  // Libraries / executables.
+  if (!target.LinkerNode.empty()) {
+    for (auto const& linkerNode : target.LinkerNode) {
+      this->WriteLinker(linkerNode, target.AllowDistribution);
+    }
+  }
+
+  // -build.
+  this->WriteAlias(target.BuildAlias);
+
+  if (!target.PostBuildExecNodes.Nodes.empty()) {
+    for (auto const& exec : target.PostBuildExecNodes.Nodes) {
+      this->WriteExec(exec);
+    }
+    this->WriteAlias(target.PostBuildExecNodes.Alias);
+  }
+
+  // Aliases (if any).
+  for (FastbuildAliasNode const& alias : target.AliasNodes) {
+    this->WriteAlias(alias);
+  }
+}
+void cmGlobalFastbuildGenerator::WriteIDEProjects()
+{
+  for (auto const& proj : IDEProjects) {
+    // VS
+    auto const& VSProj = proj.second.first;
+    WriteCommand("VCXProject", Quote(VSProj.Alias));
+    *this->BuildFileStream << "{\n";
+
+    WriteVariable("ProjectOutput", Quote(VSProj.ProjectOutput), 1);
+    WriteIDEProjectConfig(VSProj.ProjectConfigs);
+    WriteVSBuildCommands();
+    WriteIDEProjectCommon(VSProj);
+    *this->BuildFileStream << "}\n\n";
+
+    // XCode
+    // On Windows Xcode project's nested paths can become too long.
+#if !defined(_WIN32)
+    auto const& XCodeProj = proj.second.second;
+    WriteCommand("XCodeProject", Quote(XCodeProj.Alias), 0);
+    *this->BuildFileStream << "{\n";
+    WriteVariable("ProjectOutput", Quote(XCodeProj.ProjectOutput), 1);
+    WriteIDEProjectConfig(XCodeProj.ProjectConfigs);
+    WriteXCodeBuildCommands();
+
+    WriteIDEProjectCommon(XCodeProj);
+    *this->BuildFileStream << "}\n\n";
+#endif
+  }
+
+  this->WriteSolution();
+#if !defined(_WIN32)
+  this->WriteXCodeTopLevelProject();
+#endif
+}
+
+void cmGlobalFastbuildGenerator::WriteVSBuildCommands()
+{
+  WriteVariable("ProjectBuildCommand",
+                Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX +
+                      this->FastbuildCommand +
+                      FASTBUILD_IDE_BUILD_ARGS " ^$(ProjectName)"),
+                1);
+  WriteVariable("ProjectRebuildCommand",
+                Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX +
+                      this->FastbuildCommand +
+                      FASTBUILD_IDE_BUILD_ARGS "-clean ^$(ProjectName)"),
+                1);
+  WriteVariable("ProjectCleanCommand",
+                Quote(FASTBUILD_IDE_VS_COMMAND_PREFIX +
+                      this->FastbuildCommand + " -ide clean"),
+                1);
+}
+void cmGlobalFastbuildGenerator::WriteXCodeBuildCommands()
+{
+  WriteVariable("XCodeBuildToolPath", Quote(this->FastbuildCommand), 1);
+  WriteVariable("XCodeBuildToolArgs",
+                Quote(FASTBUILD_IDE_BUILD_ARGS "^$(FASTBUILD_TARGET)"), 1);
+  WriteVariable("XCodeBuildWorkingDir",
+                Quote(this->CMakeInstance->GetHomeOutputDirectory()), 1);
+}
+
+void cmGlobalFastbuildGenerator::WriteIDEProjectCommon(
+  IDEProjectCommon const& project)
+{
+  WriteVariable("ProjectBasePath", Quote(project.ProjectBasePath), 1);
+  // So Fastbuild will pick up files relative to CMakeLists.txt
+  WriteVariable("ProjectInputPaths", Quote(project.ProjectBasePath), 1);
+}
+
+void cmGlobalFastbuildGenerator::WriteIDEProjectConfig(
+  std::vector<IDEProjectConfig> const& configs, std::string const& keyName)
+{
+  std::vector<std::string> allConfigVariables;
+  for (auto const& config : configs) {
+    std::string configName = "Config" + config.Config;
+    WriteVariable(configName, "", 1);
+    Indent(1);
+    *this->BuildFileStream << "[\n";
+    WriteVariable("Config", Quote(config.Config), 2);
+    if (!config.Target.empty()) {
+      WriteVariable("Target", Quote(config.Target), 2);
+    }
+    if (!config.Platform.empty()) {
+      WriteVariable("Platform", Quote(config.Platform), 2);
+    }
+    Indent(1);
+    *this->BuildFileStream << "]\n";
+    allConfigVariables.emplace_back(std::move(configName));
+  }
+  WriteArray(keyName, Wrap(allConfigVariables, ".", ""), 1);
+}
+
+void cmGlobalFastbuildGenerator::AddTargetAll()
+{
+  FastbuildAliasNode allAliasNode;
+  allAliasNode.Name = FASTBUILD_ALL_TARGET_NAME;
+
+  FastbuildTarget allTarget;
+  allTarget.Name = allTarget.Name = FASTBUILD_ALL_TARGET_NAME;
+  allTarget.IsGlobal = true;
+
+  for (auto const& targetBase : FastbuildTargets) {
+    if (targetBase->Type == FastbuildTargetType::LINK) {
+      auto const& target = static_cast<FastbuildTarget const&>(*targetBase);
+      // Add non-global and non-excluded targets to "all"
+      if (!target.IsGlobal && !target.ExcludeFromAll) {
+        allAliasNode.PreBuildDependencies.emplace(target.Name +
+                                                  FASTBUILD_ALL_ALIAS_POSTFIX);
+        allTarget.PreBuildDependencies.emplace(target.Name);
+      }
+    } else if (targetBase->Type == FastbuildTargetType::ALIAS) {
+      auto const& target = static_cast<FastbuildAliasNode const&>(*targetBase);
+      if (!target.ExcludeFromAll) {
+        allAliasNode.PreBuildDependencies.emplace(target.Name);
+      }
+    }
+  }
+  if (allAliasNode.PreBuildDependencies.empty()) {
+    allAliasNode.PreBuildDependencies.emplace(FASTBUILD_NOOP_FILE_NAME);
+  }
+  allTarget.AliasNodes.emplace_back(std::move(allAliasNode));
+  this->AddTarget(std::move(allTarget));
+}
+
+void cmGlobalFastbuildGenerator::AddGlobCheckExec()
+{
+  // Tested in "RunCMake.file" test.
+  std::string const globScript =
+    this->GetCMakeInstance()->GetGlobVerifyScript();
+  if (!globScript.empty()) {
+
+    FastbuildExecNode globCheck;
+    globCheck.Name = FASTBUILD_GLOB_CHECK_TARGET;
+    globCheck.ExecExecutable = cmSystemTools::GetCMakeCommand();
+    globCheck.ExecArguments = "-P " FASTBUILD_1_INPUT_PLACEHOLDER;
+    globCheck.ExecInput = { this->ConvertToFastbuildPath(globScript) };
+    globCheck.ExecAlways = false;
+    globCheck.ExecUseStdOutAsOutput = false;
+    auto const cache = this->GetCMakeInstance()->GetGlobCacheEntries();
+    for (auto const& entry : cache) {
+      auto path = cmSystemTools::GetFilenamePath(entry.Expression);
+      auto expression = cmSystemTools::GetFilenameName(entry.Expression);
+      if (std::find(globCheck.ExecInputPath.begin(),
+                    globCheck.ExecInputPath.end(),
+                    path) == globCheck.ExecInputPath.end()) {
+        globCheck.ExecInputPath.emplace_back(std::move(path));
+      }
+      if (std::find(globCheck.ExecInputPattern.begin(),
+                    globCheck.ExecInputPattern.end(),
+                    expression) == globCheck.ExecInputPattern.end()) {
+        globCheck.ExecInputPattern.emplace_back(std::move(expression));
+      }
+    }
+    globCheck.ExecOutput = this->ConvertToFastbuildPath(
+      this->GetCMakeInstance()->GetGlobVerifyStamp());
+    this->AddTarget(std::move(globCheck));
+  }
+}
+void cmGlobalFastbuildGenerator::WriteSolution()
+{
+  std::string const solutionName = LocalGenerators[0]->GetProjectName();
+  std::vector<std::string> VSProjects;
+
+  for (auto const& IDEProj : IDEProjects) {
+    auto const VSProj = IDEProj.second.first;
+    VSProjects.emplace_back(VSProj.Alias);
+  }
+
+  WriteCommand("VSSolution", Quote("solution"));
+  *this->BuildFileStream << "{\n";
+
+  WriteVariable("SolutionOutput",
+                Quote(cmJoin({ "VisualStudio", solutionName + ".sln" }, "/")),
+                1);
+
+  auto const& configs = IDEProjects.begin()->second.first.ProjectConfigs;
+  WriteIDEProjectConfig(configs, "SolutionConfigs");
+  WriteArray("SolutionProjects", Wrap(VSProjects), 1);
+
+  *this->BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteXCodeTopLevelProject()
+{
+  std::string const projectName = LocalGenerators[0]->GetProjectName();
+  std::vector<std::string> XCodeProjects;
+  for (auto const& IDEProj : IDEProjects) {
+    auto const XCodeProj = IDEProj.second.second;
+    XCodeProjects.emplace_back(XCodeProj.Alias);
+  }
+
+  WriteCommand("XCodeProject", Quote("xcode"));
+  *this->BuildFileStream << "{\n";
+
+  WriteVariable(
+    "ProjectOutput",
+    Quote(
+      cmJoin({ "XCode", projectName + ".xcodeproj", "project.pbxproj" }, "/")),
+    1);
+  WriteVariable("ProjectBasePath", Quote(FASTBUILD_XCODE_BASE_PATH), 1);
+
+  auto const& configs = IDEProjects.begin()->second.second.ProjectConfigs;
+  WriteIDEProjectConfig(configs);
+  WriteArray("ProjectFiles", Wrap(XCodeProjects), 1);
+
+  *this->BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::LogMessage(std::string const& m) const
+{
+  static bool const verbose = GlobalSettingIsOn(FASTBUILD_VERBOSE_GENERATOR) ||
+    cmSystemTools::HasEnv(FASTBUILD_VERBOSE_GENERATOR);
+  if (verbose) {
+    cmSystemTools::Message(m);
+  }
+}
+void cmGlobalFastbuildGenerator::AddFileToClean(std::string const& file)
+{
+  AllFilesToClean.insert(file);
+}
+
+std::string cmGlobalFastbuildGenerator::GetExternalShellExecutable()
+{
+  // FindProgram is expensive - touches filesystem and makes syscalls, so cache
+  // it.
+  static std::string const cached =
+#ifdef _WIN32
+    cmSystemTools::FindProgram(
+      "cmd.exe", std::vector<std::string>{ "C:\\Windows\\System32" });
+#else
+    cmSystemTools::FindProgram("sh", std::vector<std::string>{ "/bin" });
+#endif
+  return cached;
+}
+
+void cmGlobalFastbuildGenerator::WriteTargetRebuildBFF()
+{
+  std::vector<std::string> implicitDeps;
+  for (auto& lg : LocalGenerators) {
+    std::vector<std::string> const& lf = lg->GetMakefile()->GetListFiles();
+    for (auto const& dep : lf) {
+      implicitDeps.push_back(this->ConvertToFastbuildPath(dep));
+    }
+  }
+  auto const* cmake = this->GetCMakeInstance();
+  std::string outDir = cmake->GetHomeOutputDirectory() + '/';
+
+  implicitDeps.push_back(outDir + "CMakeCache.txt");
+
+  FastbuildExecNode rebuildBFF;
+  rebuildBFF.Name = FASTBUILD_REBUILD_BFF_TARGET_NAME;
+  if (!this->GetCMakeInstance()->GetGlobVerifyScript().empty()) {
+    implicitDeps.emplace_back(this->GetCMakeInstance()->GetGlobVerifyStamp());
+  }
+
+  std::sort(implicitDeps.begin(), implicitDeps.end());
+  implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()),
+                     implicitDeps.end());
+  std::string args =
+    cmStrCat("--regenerate-during-build",
+             (this->GetCMakeInstance()->GetIgnoreCompileWarningAsError()
+                ? " --compile-no-warning-as-error"
+                : ""),
+             (this->GetCMakeInstance()->GetIgnoreLinkWarningAsError()
+                ? " --link-no-warning-as-error"
+                : ""),
+             " -S", QuoteIfHasSpaces(cmake->GetHomeDirectory()), " -B",
+             QuoteIfHasSpaces(cmake->GetHomeOutputDirectory()));
+
+  rebuildBFF.ExecArguments = std::move(args);
+  rebuildBFF.ExecInput = implicitDeps;
+  rebuildBFF.ExecExecutable = cmSystemTools::GetCMakeCommand();
+  rebuildBFF.ExecWorkingDir = outDir;
+  rebuildBFF.ExecOutput = outDir + FASTBUILD_BUILD_FILE;
+  this->WriteExec(rebuildBFF, 0);
+}
+
+void cmGlobalFastbuildGenerator::WriteTargetClean()
+{
+  WriteCommand("RemoveDir", Quote(FASTBUILD_CLEAN_TARGET_NAME));
+  *BuildFileStream << "{\n";
+
+  WriteVariable("RemoveDirs", "false", 1);
+  WriteVariable("RemovePathsRecurse", "false", 1);
+
+  std::vector<std::string> removePatterns{
+    "*" FASTBUILD_DUMMY_OUTPUT_EXTENSION
+  };
+  std::vector<std::string> langs;
+  this->GetEnabledLanguages(langs);
+  for (auto const& lang : langs) {
+    auto const extension = this->GetSafeGlobalSetting(
+      cmStrCat("CMAKE_", lang, "_OUTPUT_EXTENSION"));
+    if (!extension.empty() &&
+        std::find(removePatterns.begin(), removePatterns.end(),
+                  '*' + extension) == removePatterns.end()) {
+      removePatterns.emplace_back('*' + extension);
+    }
+  }
+  auto const PchExtension = this->GetSafeGlobalSetting("CMAKE_PCH_EXTENSION");
+  if (!PchExtension.empty()) {
+    removePatterns.emplace_back('*' + PchExtension);
+  }
+  auto const buildDir = this->GetCMakeInstance()->GetHomeOutputDirectory();
+  if (!AllFilesToClean.empty()) {
+    for (auto const& file : AllFilesToClean) {
+      auto const path = cmSystemTools::GetFilenamePath(file);
+      // Since we don't recurse (to not delete unrelated .obj), need to add
+      // full path to the file.
+      // Tested in "ExternalOBJ" test.
+      if (!path.empty()) {
+        AllFoldersToClean.emplace(cmStrCat(buildDir, '/', path));
+      }
+      removePatterns.emplace_back('*' + cmSystemTools::GetFilenameName(file));
+    }
+  }
+
+  WriteArray("RemovePatterns", Wrap(removePatterns), 1);
+
+  std::vector<std::string> tmp;
+
+  std::move(AllFilesToKeep.begin(), AllFilesToKeep.end(),
+            std::back_inserter(tmp));
+  if (!tmp.empty()) {
+    WriteArray("RemoveExcludeFiles", Wrap(tmp), 1);
+    tmp.clear();
+  }
+
+  tmp.emplace_back(buildDir);
+  std::move(AllFoldersToClean.begin(), AllFoldersToClean.end(),
+            std::back_inserter(tmp));
+  WriteArray("RemovePaths", Wrap(tmp), 1);
+
+  *BuildFileStream << "}\n";
+}
+
+void cmGlobalFastbuildGenerator::WriteTargets()
+{
+  std::string const outputDir = this->CMakeInstance->GetHomeOutputDirectory();
+  LogMessage("GetHomeOutputDirectory: " + outputDir);
+  // Noop file that 'all' can alias to if we don't have any other targets...
+  // The exact location of the "noop" file is verified in one of the tests in
+  // "RunCMake.CMakePresetsPackage" test suite.
+  cmSystemTools::Touch(cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(),
+                                '/', FASTBUILD_NOOP_FILE_NAME),
+                       true);
+  // Add "all" utility target before sorting, so we can correctly sort
+  // targets that depend on it
+  AddTargetAll();
+  TopologicalSort(FastbuildTargets);
+
+  AddGlobCheckExec();
+
+  for (auto const& targetBase : FastbuildTargets) {
+    this->WriteComment("Target definition: " + targetBase->Name);
+    // Target start.
+    *BuildFileStream << "{\n";
+
+    if (targetBase->Type == FastbuildTargetType::EXEC) {
+      this->WriteExec(static_cast<FastbuildExecNode const&>(*targetBase));
+    } else if (targetBase->Type == FastbuildTargetType::ALIAS) {
+      this->WriteAlias(static_cast<FastbuildAliasNode const&>(*targetBase));
+      // CustomCommandByproducts test.
+      FastbuildAliasNode alias;
+      alias.Name = targetBase->Name + FASTBUILD_ALL_ALIAS_POSTFIX;
+      alias.PreBuildDependencies.emplace(targetBase->Name);
+      this->WriteAlias(alias);
+    } else if (targetBase->Type == FastbuildTargetType::LINK) {
+      auto const& target = static_cast<FastbuildTarget const&>(*targetBase);
+      this->WriteTarget(target);
+    }
+    // Target end.
+    *BuildFileStream << "}\n";
+  }
+
+  if (!this->GetCMakeInstance()->GetIsInTryCompile()) {
+    if (!IDEProjects.empty()) {
+      this->WriteIDEProjects();
+    }
+  }
+  this->WriteTargetClean();
+  this->WriteTargetRebuildBFF();
+}
+
+std::string cmGlobalFastbuildGenerator::GetTargetName(
+  cmGeneratorTarget const* GeneratorTarget) const
+{
+  std::string targetName =
+    GeneratorTarget->GetLocalGenerator()->GetCurrentBinaryDirectory();
+  targetName += "/";
+  targetName += GeneratorTarget->GetName();
+  targetName = this->ConvertToFastbuildPath(targetName);
+  return targetName;
+}
+
+cm::optional<FastbuildTarget>
+cmGlobalFastbuildGenerator::GetTargetByOutputName(
+  std::string const& output) const
+{
+  for (auto const& targetBase : FastbuildTargets) {
+    if (targetBase->Type == FastbuildTargetType::LINK) {
+      auto const& target = static_cast<FastbuildTarget const&>(*targetBase);
+      if (std::any_of(target.LinkerNode.begin(), target.LinkerNode.end(),
+                      [&output](FastbuildLinkerNode const& target_) {
+                        return target_.LinkerOutput == output;
+                      })) {
+        return target;
+      }
+    }
+  }
+  return cm::nullopt;
+}
+
+void cmGlobalFastbuildGenerator::AddIDEProject(FastbuildTarget const& target,
+                                               std::string const& config)
+{
+  auto const& configs = GetConfigNames();
+  if (std::find(configs.begin(), configs.end(), config) == configs.end()) {
+    LogMessage("Config " + config + " doesn't exist, IDE projest for " +
+               target.Name + " won't be generated");
+    return;
+  }
+  auto& IDEProject = IDEProjects[target.BaseName];
+  // VS
+  auto& VSProject = IDEProject.first;
+  VSProject.Alias = target.BaseName + "-vcxproj";
+  VSProject.ProjectOutput =
+    cmStrCat("VisualStudio/Projects/", target.BaseName + ".vcxproj");
+  VSProject.ProjectBasePath = target.BasePath;
+  // XCode
+  auto& XCodeProject = IDEProject.second;
+  XCodeProject.Alias = target.BaseName + "-xcodeproj";
+  XCodeProject.ProjectOutput = cmStrCat(
+    "XCode/Projects/", target.BaseName + ".xcodeproj/project.pbxproj");
+  XCodeProject.ProjectBasePath = target.BasePath;
+
+  IDEProjectConfig VSConfig;
+  VSConfig.Platform = "X64";
+  IDEProjectConfig XCodeConfig;
+  VSConfig.Target = XCodeConfig.Target = target.Name;
+  VSConfig.Config = XCodeConfig.Config = config.empty() ? "DEFAULT" : config;
+
+  VSProject.ProjectConfigs.emplace_back(std::move(VSConfig));
+  XCodeProject.ProjectConfigs.emplace_back(std::move(XCodeConfig));
+}
+
+bool cmGlobalFastbuildGenerator::IsExcluded(cmGeneratorTarget* target)
+{
+  return cmGlobalGenerator::IsExcluded(LocalGenerators[0].get(), target);
+}
+std::vector<std::string> const& cmGlobalFastbuildGenerator::GetConfigNames()
+  const
+{
+  return static_cast<cmLocalFastbuildGenerator const*>(
+           this->LocalGenerators.front().get())
+    ->GetConfigNames();
+}
+
+bool cmGlobalFastbuildGenerator::Open(std::string const& bindir,
+                                      std::string const& projectName,
+                                      bool dryRun)
+{
+#ifdef _WIN32
+  std::string sln = bindir + "/VisualStudio/" + projectName + ".sln";
+
+  if (dryRun) {
+    return cmSystemTools::FileExists(sln, true);
+  }
+
+  sln = cmSystemTools::ConvertToOutputPath(sln);
+
+  auto OpenSolution = [](std::string pathToSolution) {
+    HRESULT comInitialized =
+      CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+    if (FAILED(comInitialized)) {
+      return false;
+    }
+
+    HINSTANCE hi = ShellExecuteA(NULL, "open", pathToSolution.c_str(), NULL,
+                                 NULL, SW_SHOWNORMAL);
+
+    CoUninitialize();
+
+    return reinterpret_cast<intptr_t>(hi) > 32;
+  };
+
+  return std::async(std::launch::async, OpenSolution, sln).get();
+#else
+  return cmGlobalCommonGenerator::Open(bindir, projectName, dryRun);
+#endif
+}

+ 602 - 0
Source/cmGlobalFastbuildGenerator.h

@@ -0,0 +1,602 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <cm/memory>
+#include <cm/optional>
+
+#include <assert.h>
+
+#include "cmBuildOptions.h"
+#include "cmGeneratedFileStream.h"
+#include "cmGlobalCommonGenerator.h"
+class cmFastbuildTargetGenerator;
+class cmGeneratorTarget;
+class cmGlobalGeneratorFactory;
+class cmLinkLineComputer;
+class cmLocalGenerator;
+class cmMakefile;
+class cmOutputConverter;
+class cmStateDirectory;
+class cmake;
+enum class cmDepfileFormat;
+struct cmDocumentationEntry;
+
+#define FASTBUILD_DOLLAR_TAG "FASTBUILD_DOLLAR_TAG"
+#define FASTBUILD_1_INPUT_PLACEHOLDER                                         \
+  FASTBUILD_DOLLAR_TAG "FB_INPUT_1_PLACEHOLDER" FASTBUILD_DOLLAR_TAG
+#define FASTBUILD_1_0_INPUT_PLACEHOLDER                                       \
+  FASTBUILD_DOLLAR_TAG "FB_INPUT_1_0_PLACEHOLDER" FASTBUILD_DOLLAR_TAG
+#define FASTBUILD_1_1_INPUT_PLACEHOLDER                                       \
+  FASTBUILD_DOLLAR_TAG "FB_INPUT_1_1_PLACEHOLDER" FASTBUILD_DOLLAR_TAG
+
+#define FASTBUILD_2_INPUT_PLACEHOLDER                                         \
+  FASTBUILD_DOLLAR_TAG "FB_INPUT_2_PLACEHOLDER" FASTBUILD_DOLLAR_TAG
+
+#define FASTBUILD_3_INPUT_PLACEHOLDER                                         \
+  FASTBUILD_DOLLAR_TAG "FB_INPUT_3_PLACEHOLDER" FASTBUILD_DOLLAR_TAG
+
+// Alias to artifacts that can be consumed by the linker (DLL or Library).
+#define FASTBUILD_LINK_ARTIFACTS_ALIAS_POSTFIX "-link-artifacts"
+// Alias to all the ObjectList nodes.
+#define FASTBUILD_OBJECTS_ALIAS_POSTFIX "-objects"
+// Alias to all the dependencies of the target.
+#define FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX "-deps"
+// Alias to build the target.
+#define FASTBUILD_BUILD_ALIAS_POSTFIX "-build"
+#define FASTBUILD_PRE_BUILD_ALIAS_POSTFIX "-pre-build"
+#define FASTBUILD_PRE_LINK_ALIAS_POSTFIX "-pre-link"
+#define FASTBUILD_POST_BUILD_ALIAS_POSTFIX "-post-build"
+// Alias to all other custom commands in the target.
+#define FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX "-custom-commands"
+// Alias to outputs produced by a custom command (since FASTBuild exec node
+// does not support more than 1 output).
+#define FASTBUILD_OUTPUTS_ALIAS_POSTFIX "-outputs"
+// Alias to byproducts produced by a custom command (since FASTBuild exec node
+// does not support more than 1 output).
+#define FASTBUILD_BYPRODUCTS_ALIAS_POSTFIX "-byproducts"
+// Alias to build & run all the custom commands.
+#define FASTBUILD_ALL_ALIAS_POSTFIX "-all"
+
+#define FASTBUILD_COMPILER_PREFIX "Compiler_"
+#define FASTBUILD_LAUNCHER_PREFIX "Launcher_"
+#define FASTBUILD_LINKER_LAUNCHER_PREFIX "LinkerLauncher_"
+
+#define FASTBUILD_RESTAT_FILE "FASTBUILD_RESTAT"
+
+#define FASTBUILD_UTIL_CONCURRENCY_GROUP_NAME "Utils"
+
+#define FASTBUILD_ALL_TARGET_NAME "all"
+#define FASTBUILD_CLEAN_TARGET_NAME "clean"
+
+#define FASTBUILD_NOOP_FILE_NAME "fbuild_noop"
+
+#define FASTBUILD_BUILD_FILE "fbuild.bff"
+
+#define FASTBUILD_DUMMY_OUTPUT_EXTENSION ".fbuild-cc-out"
+
+#if defined(_WIN32)
+#  define FASTBUILD_SCRIPT_FILE_EXTENSION ".bat"
+#  define FASTBUILD_SCRIPT_FILE_ARG "/C "
+#  define FASTBUILD_SCRIPT_CD "cd /D "
+#else
+#  define FASTBUILD_SCRIPT_FILE_EXTENSION ".sh"
+#  define FASTBUILD_SCRIPT_FILE_ARG ""
+#  define FASTBUILD_SCRIPT_CD "cd "
+#endif
+
+enum class FastbuildTargetDepType
+{
+  ALL,
+  BUILD,
+  ORDER_ONLY,
+  NONE,
+};
+struct FastbuildTargetDep
+{
+  std::string Name;
+  FastbuildTargetDepType Type = FastbuildTargetDepType::NONE;
+  FastbuildTargetDep(std::string n)
+    : Name(std::move(n))
+  {
+  }
+  bool operator==(FastbuildTargetDep const& rhs) const
+  {
+    return this->Name == rhs.Name;
+  }
+  bool operator<(FastbuildTargetDep const& rhs) const
+  {
+    return this->Name < rhs.Name;
+  }
+};
+
+enum class FastbuildTargetType
+{
+  ALIAS, // Alias node
+  EXEC,  // Exec node
+  LINK,  // Library, DLL or Executable
+  OBJECTLIST,
+};
+
+struct FastbuildTargetBase
+{
+  // Target name with config postfix.
+  std::string Name;
+  // Target name without config postfix, we use it to locate IDE project for
+  // the given target and add +1 config to it.
+  std::string BaseName;
+  std::string BasePath;
+  std::set<FastbuildTargetDep> PreBuildDependencies;
+  bool Hidden = true;
+  FastbuildTargetType Type;
+  explicit FastbuildTargetBase(FastbuildTargetType TargetType)
+    : Type(TargetType)
+  {
+  }
+};
+using FastbuildTargetPtrT = std::unique_ptr<FastbuildTargetBase>;
+
+struct FastbuildAliasNode : public FastbuildTargetBase
+{
+  bool ExcludeFromAll = false;
+  FastbuildAliasNode()
+    : FastbuildTargetBase(FastbuildTargetType::ALIAS)
+  {
+  }
+};
+
+struct FastbuildExecNode : public FastbuildTargetBase
+{
+  std::string ExecExecutable;
+  std::string ExecArguments;
+  std::string ScriptFile;
+  std::string ExecWorkingDir;
+  bool ExecUseStdOutAsOutput = false;
+  std::string ExecOutput;
+  std::vector<std::string> ExecInput;
+  std::vector<std::string> ExecInputPath;
+  std::vector<std::string> ExecInputPattern;
+  bool ExecInputPathRecurse = false;
+  bool ExecAlways = false;
+  FastbuildAliasNode OutputsAlias;
+  FastbuildAliasNode ByproductsAlias;
+  std::string ConcurrencyGroupName;
+  bool ExcludeFromAll = false;
+  FastbuildExecNode()
+    : FastbuildTargetBase(FastbuildTargetType::EXEC)
+  {
+  }
+
+  bool NeedsDepsCheckExec = false;
+};
+
+struct FastbuildCompiler
+{
+  std::map<std::string, std::string> ExtraVariables;
+  std::string Name;
+  std::string Executable;
+  std::string CmakeCompilerID;
+  std::string CmakeCompilerVersion;
+  std::string Language;
+  std::vector<std::string> ExtraFiles;
+  bool UseLightCache = false;
+  // Only used for launchers.
+  std::string Args;
+  bool DontUseEnv = false;
+};
+
+struct FastbuildObjectListNode : public FastbuildTargetBase
+{
+  std::string Compiler;
+  std::string CompilerOptions;
+  std::string CompilerOutputPath;
+  std::string CompilerOutputExtension;
+  std::string PCHInputFile;
+  std::string PCHOutputFile;
+  std::string PCHOptions;
+
+  std::vector<std::string> CompilerInputFiles;
+
+  std::set<std::string> ObjectOutputs;
+  std::set<std::string> ObjectDepends;
+
+  // Apple only.
+  std::string arch;
+  FastbuildObjectListNode()
+    : FastbuildTargetBase(FastbuildTargetType::OBJECTLIST)
+  {
+  }
+};
+
+struct IDEProjectConfig
+{
+  std::string Config;
+  std::string Target;
+  // VS only.
+  std::string Platform;
+
+  std::string XCodeBaseSDK;
+  std::string XCodeDebugWorkingDir;
+  std::string XCodeIphoneOSDeploymentTarget;
+};
+
+struct IDEProjectCommon
+{
+  std::string Alias;
+  std::string ProjectOutput;
+  std::string ProjectBasePath;
+
+  std::vector<IDEProjectConfig> ProjectConfigs;
+};
+
+struct XCodeProject : public IDEProjectCommon
+{
+};
+
+struct VCXProject : public IDEProjectCommon
+{
+};
+
+struct FastbuildLinkerNode
+{
+  enum
+  {
+    EXECUTABLE,
+    SHARED_LIBRARY,
+    STATIC_LIBRARY,
+    NONE
+  } Type = NONE;
+
+  std::string Name;
+  std::string Compiler;
+  std::string CompilerOptions;
+  std::string Linker;
+  std::string LinkerType;
+  std::string LinkerOutput;
+  std::string LinkerOptions;
+  std::vector<std::string> LibrarianAdditionalInputs;
+  // We only use Libraries2 for tracking dependencies.
+  std::vector<std::string> Libraries2;
+  std::set<std::string> PreBuildDependencies;
+  bool LinkerLinkObjects = false;
+  std::string LinkerStampExe;
+  std::string LinkerStampExeArgs;
+  // Apple only.
+  std::string Arch;
+};
+
+struct FastbuildCopyNode
+{
+  std::string Name;
+  std::string Source;
+  std::string Dest;
+  std::string PreBuildDependencies;
+  bool CopyDir = false;
+};
+
+struct FastbuildExecNodes
+{
+  std::vector<FastbuildExecNode> Nodes;
+  FastbuildAliasNode Alias;
+};
+
+struct FastbuildTarget : public FastbuildTargetBase
+{
+  std::map<std::string, std::string> Variables;
+  std::vector<FastbuildObjectListNode> ObjectListNodes;
+  // Potentially multiple libs for different archs (apple only);
+  std::vector<FastbuildLinkerNode> LinkerNode;
+  std::string RealOutput;
+  FastbuildAliasNode PreBuildExecNodes, ExecNodes;
+  std::vector<FastbuildAliasNode> AliasNodes;
+  // This alias must be written before all other nodes, since they might need
+  // to refer to it.
+  FastbuildAliasNode DependenciesAlias;
+  std::vector<FastbuildCopyNode> CopyNodes;
+  FastbuildExecNodes PreLinkExecNodes;
+  FastbuildExecNodes PostBuildExecNodes;
+  // Must be written after the build, but before custom commands, since post
+  // build commands need to depend on it.
+  FastbuildAliasNode BuildAlias;
+  bool IsGlobal = false;
+  bool ExcludeFromAll = false;
+  bool AllowDistribution = true;
+  FastbuildTarget()
+    : FastbuildTargetBase(FastbuildTargetType::LINK)
+  {
+  }
+
+  void GenerateAliases();
+};
+
+class cmGlobalFastbuildGenerator : public cmGlobalCommonGenerator
+{
+public:
+  cmGlobalFastbuildGenerator(cmake* cm);
+
+  void ProcessEnvironment();
+
+  static std::unique_ptr<cmGlobalGeneratorFactory> NewFactory();
+  void Generate() override;
+
+  bool FindMakeProgram(cmMakefile* mf) override;
+
+  void EnableLanguage(std::vector<std::string> const& lang, cmMakefile* mf,
+                      bool optional) override;
+
+  bool IsFastbuild() const override { return true; }
+
+  std::vector<GeneratedMakeCommand> GenerateBuildCommand(
+    std::string const& makeProgram, std::string const& projectName,
+    std::string const& projectDir, std::vector<std::string> const& targetNames,
+    std::string const& config, int jobs, bool verbose,
+    cmBuildOptions buildOptions = cmBuildOptions(),
+    std::vector<std::string> const& makeOptions =
+      std::vector<std::string>()) override;
+
+  std::unique_ptr<cmLocalGenerator> CreateLocalGenerator(
+    cmMakefile* makefile) override;
+  std::string GetName() const override
+  {
+    return cmGlobalFastbuildGenerator::GetActualName();
+  }
+
+  bool IsMultiConfig() const override { return false; }
+
+  void ComputeTargetObjectDirectory(cmGeneratorTarget*) const override;
+  void AppendDirectoryForConfig(std::string const& prefix,
+                                std::string const& config,
+                                std::string const& suffix,
+                                std::string& dir) override;
+
+  static std::string GetActualName() { return "FASTBuild"; }
+  static std::string RequiredFastbuildVersion() { return "1.14"; }
+
+  // Setup target names
+  char const* GetAllTargetName() const override
+  {
+    return FASTBUILD_ALL_TARGET_NAME;
+  }
+  char const* GetInstallTargetName() const override { return "install"; }
+  char const* GetCleanTargetName() const override
+  {
+    return FASTBUILD_CLEAN_TARGET_NAME;
+  }
+  char const* GetInstallLocalTargetName() const override
+  {
+    return "install/local";
+  }
+  char const* GetInstallStripTargetName() const override
+  {
+    return "install/strip";
+  }
+  char const* GetInstallParallelTargetName() const
+  {
+    return "install/parallel";
+  }
+  char const* GetTestTargetName() const override { return "test"; }
+  char const* GetPackageTargetName() const override { return "package"; }
+  char const* GetPackageSourceTargetName() const override
+  {
+    return "package_source";
+  }
+  char const* GetRebuildCacheTargetName() const override
+  {
+    return "rebuild_cache";
+  }
+  char const* GetCMakeCFGIntDir() const override { return "."; }
+
+  /// Overloaded methods. @see cmGlobalGenerator::GetDocumentation()
+  static cmDocumentationEntry GetDocumentation();
+
+  static bool SupportsToolset() { return false; }
+
+  static bool SupportsPlatform() { return false; }
+
+  bool IsIPOSupported() const override { return true; }
+
+  void OpenBuildFileStream();
+  void CloseBuildFileStream();
+
+  std::vector<std::string> const& GetConfigNames() const;
+
+  bool Open(std::string const& bindir, std::string const& projectName,
+            bool dryRun) override;
+
+  std::string ConvertToFastbuildPath(std::string const& path) const;
+  std::unique_ptr<cmLinkLineComputer> CreateLinkLineComputer(
+    cmOutputConverter* outputConverter,
+    cmStateDirectory const& /* stateDir */) const override;
+
+  bool SupportsCustomCommandDepfile() const override { return true; }
+  cm::optional<cmDepfileFormat> DepfileFormat() const override
+  {
+    return cm::nullopt;
+  }
+
+  static std::string Quote(std::string const& str,
+                           std::string const& quotation = "'");
+  static std::string QuoteIfHasSpaces(std::string str);
+
+  template <class T>
+  static std::vector<std::string> Wrap(T const& in,
+                                       std::string const& prefix = "'",
+                                       std::string const& suffix = "'",
+                                       bool escape_dollar = true);
+
+  void WriteDivider();
+  void WriteComment(std::string const& comment, int indent = 0);
+
+  /// Write @a count times INDENT level to output stream @a os.
+  void Indent(int count);
+
+  void WriteVariable(std::string const& key, std::string const& value,
+                     std::string const& op, int indent = 0);
+  void WriteVariable(std::string const& key, std::string const& value,
+                     int indent = 0);
+  void WriteCommand(std::string const& command,
+                    std::string const& value = std::string(), int indent = 0);
+  void WriteArray(std::string const& key,
+                  std::vector<std::string> const& values, int indent = 0);
+  void WriteStruct(
+    std::string const& name,
+    std::vector<std::pair<std::string, std::string>> const& variables,
+    int indent = 0);
+  void WriteArray(std::string const& key,
+                  std::vector<std::string> const& values,
+                  std::string const& op, int indent = 0);
+
+  template <typename T>
+  std::vector<std::string> ConvertToFastbuildPath(T const& container) const
+  {
+    std::vector<std::string> ret;
+    ret.reserve(container.size());
+    for (auto const& path : container) {
+      ret.push_back(ConvertToFastbuildPath(path));
+    }
+    return ret;
+  }
+
+  // Wrapper to sort array of conforming structs (which have .Name
+  // and .PreBuildDependencies fields).
+  template <class T>
+  static void TopologicalSort(std::vector<T>& nodes)
+  {
+    static_assert(std::is_base_of<FastbuildTargetBase, T>::value,
+                  "T must be derived from FastbuildTargetBase");
+    std::vector<FastbuildTargetPtrT> tmp;
+    tmp.reserve(nodes.size());
+    for (auto& node : nodes) {
+      tmp.emplace_back(cm::make_unique<T>(std::move(node)));
+    }
+    nodes.clear();
+    TopologicalSort(tmp);
+    for (auto& node : tmp) {
+      nodes.emplace_back(std::move(static_cast<T&>(*node)));
+    }
+  }
+  // Stable topological sort.
+  static void TopologicalSort(std::vector<FastbuildTargetPtrT>& nodes);
+
+  void WriteDisclaimer();
+  void WriteEnvironment();
+  void WriteSettings();
+  void WriteCompilers();
+  void WriteTargets();
+
+  void WriteTarget(FastbuildTarget const& target);
+  void WriteExec(FastbuildExecNode const& Exec, int indent = 1);
+  void WriteObjectList(FastbuildObjectListNode const& ObjectList,
+                       bool allowDistribution);
+  void WriteLinker(FastbuildLinkerNode const&, bool);
+  void WriteAlias(FastbuildAliasNode const& Alias, int indent = 1);
+  void WriteCopy(FastbuildCopyNode const& Copy);
+
+  void WriteIDEProjects();
+  void WriteVSBuildCommands();
+  void WriteXCodeBuildCommands();
+  void WriteIDEProjectCommon(IDEProjectCommon const& project);
+  void WriteIDEProjectConfig(std::vector<IDEProjectConfig> const& configs,
+                             std::string const& keyName = "ProjectConfigs");
+
+  void WriteSolution();
+  void WriteXCodeTopLevelProject();
+  void WriteTargetRebuildBFF();
+  void WriteTargetClean();
+
+  void AddTargetAll();
+  void AddGlobCheckExec();
+
+  void AddCompiler(std::string const& lang, cmMakefile* mf);
+  void AddLauncher(std::string const& prefix, std::string const& launcher,
+                   std::string const& lang, std::string const& args);
+  void AddIDEProject(FastbuildTarget const& target, std::string const& config);
+
+  template <class T>
+  void AddTarget(T target)
+  {
+    // Sometimes equivalent CCs are added to different targets. We try to
+    // de-dup it by assigning all execs a name which is a hash computed based
+    // on various properties (like input, output, deps.). Apparently, there are
+    // still some CCs intersection between different targets.
+    auto val = AllGeneratedCommands.emplace(target.Name);
+    if (val.second) {
+      FastbuildTargets.emplace_back(cm::make_unique<T>(std::move(target)));
+    }
+    // Get the intersection of CC's deps. Just mimicking what
+    // cmLocalNinjaGenerator::WriteCustomCommandBuildStatement does. (I don't
+    // think it's right in general case, each CC should be added only to 1
+    // target, not to multiple )
+    else {
+      auto it =
+        std::find_if(FastbuildTargets.begin(), FastbuildTargets.end(),
+                     [&target](FastbuildTargetPtrT const& existingTarget) {
+                       return existingTarget->Name == target.Name;
+                     });
+      assert(it != FastbuildTargets.end());
+
+      std::set<FastbuildTargetDep> intersection;
+      std::set_intersection(
+        target.PreBuildDependencies.begin(), target.PreBuildDependencies.end(),
+        (*it)->PreBuildDependencies.begin(), (*it)->PreBuildDependencies.end(),
+        std::inserter(intersection, intersection.end()));
+      (*it)->PreBuildDependencies = std::move(intersection);
+    }
+  }
+
+  static std::string GetExternalShellExecutable();
+  std::string GetTargetName(cmGeneratorTarget const* GeneratorTarget) const;
+  cm::optional<FastbuildTarget> GetTargetByOutputName(
+    std::string const& output) const;
+  void AskCMakeToMakeRebuildBFFUpToDate(std::string const& workingDir) const;
+  void ExecuteFastbuildTarget(
+    std::string const& dir, std::string const& target, std::string& output,
+    std::vector<std::string> const& fbuildOptions = {}) const;
+
+  bool IsExcluded(cmGeneratorTarget* target);
+
+  void LogMessage(std::string const& m) const;
+
+  void AddFileToClean(std::string const& file);
+
+  /// The set of compilers added to the generated build system.
+  std::map<std::string, FastbuildCompiler> Compilers;
+
+  std::vector<FastbuildTargetPtrT> FastbuildTargets;
+
+  /// The file containing the build statement.
+  std::unique_ptr<cmGeneratedFileStream> BuildFileStream;
+
+  std::string FastbuildCommand;
+  std::string FastbuildVersion;
+
+  std::map<std::string, std::unique_ptr<cmFastbuildTargetGenerator>> Targets;
+
+  std::unordered_set<std::string> AllFoldersToClean;
+  // Sometime we need to keep some files that are generated only during
+  // configuration (like .objs files used to create module definition from
+  // objects).
+  std::unordered_set<std::string> AllFilesToKeep;
+
+private:
+  std::unordered_set<std::string> AllFilesToClean;
+  // https://cmake.org/cmake/help/latest/module/ExternalProject.html#command:externalproject_add_steptargets
+  std::unordered_set<std::string /*exec name*/> AllGeneratedCommands;
+
+  std::unordered_map<std::string /*base target name (without -config)*/,
+                     std::pair<VCXProject, XCodeProject>>
+    IDEProjects;
+  // Env that we're going to embed to the generated file.
+  std::vector<std::string> LocalEnvironment;
+};

+ 1 - 0
Source/cmGlobalGenerator.cxx

@@ -140,6 +140,7 @@ cmGlobalGenerator::cmGlobalGenerator(cmake* cm)
   cm->GetState()->SetWindowsShell(false);
   cm->GetState()->SetWindowsVSIDE(false);
 
+  cm->GetState()->SetFastbuildMake(false);
 #if !defined(CMAKE_BOOTSTRAP)
   Json::StreamWriterBuilder wbuilder;
   wbuilder["indentation"] = "\t";

+ 2 - 0
Source/cmGlobalGenerator.h

@@ -564,6 +564,8 @@ public:
 
   virtual bool IsNinja() const { return false; }
 
+  virtual bool IsFastbuild() const { return false; }
+
   /** Return true if we know the exact location of object files for the given
      cmTarget. If false, store the reason in the given string. This is
      meaningful only after EnableLanguage has been called.  */

+ 1 - 1
Source/cmLinkLineComputer.h

@@ -50,6 +50,7 @@ public:
 
   std::string ComputeLinkLibraries(cmComputeLinkInformation& cli,
                                    std::string const& stdLibString);
+  std::string ComputeRPath(cmComputeLinkInformation& cli);
 
   virtual void ComputeLinkLibraries(
     cmComputeLinkInformation& cli, std::string const& stdLibString,
@@ -62,7 +63,6 @@ protected:
   std::string ComputeLinkLibs(cmComputeLinkInformation& cli);
   void ComputeLinkLibs(cmComputeLinkInformation& cli,
                        std::vector<BT<std::string>>& linkLibraries);
-  std::string ComputeRPath(cmComputeLinkInformation& cli);
 
   std::string ConvertToOutputFormat(std::string const& input);
   std::string ConvertToOutputForExisting(std::string const& input);

+ 96 - 0
Source/cmLocalFastbuildGenerator.cxx

@@ -0,0 +1,96 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmLocalFastbuildGenerator.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "cmFastbuildTargetGenerator.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalFastbuildGenerator.h"
+#include "cmList.h"
+#include "cmMakefile.h"
+#include "cmObjectLocation.h"
+#include "cmSystemTools.h"
+#include "cmValue.h"
+
+class cmGlobalGenerator;
+
+cmLocalFastbuildGenerator::cmLocalFastbuildGenerator(cmGlobalGenerator* gg,
+                                                     cmMakefile* makefile)
+  : cmLocalCommonGenerator(gg, makefile)
+{
+}
+
+void cmLocalFastbuildGenerator::Generate()
+{
+  auto const& targets = this->GetGeneratorTargets();
+  for (auto const& target : targets) {
+    if (!target->IsInBuildSystem()) {
+      continue;
+    }
+    for (std::string config : this->GetConfigNames()) {
+      cmFastbuildTargetGenerator* tg =
+        cmFastbuildTargetGenerator::New(target.get(), std::move(config));
+      if (tg) {
+        tg->Generate();
+        delete tg;
+      }
+      // Directory level.
+      this->AdditionalCleanFiles(config);
+    }
+  }
+}
+
+cmGlobalFastbuildGenerator const*
+cmLocalFastbuildGenerator::GetGlobalFastbuildGenerator() const
+{
+  return static_cast<cmGlobalFastbuildGenerator const*>(
+    this->GetGlobalGenerator());
+}
+
+cmGlobalFastbuildGenerator*
+cmLocalFastbuildGenerator::GetGlobalFastbuildGenerator()
+{
+  return static_cast<cmGlobalFastbuildGenerator*>(this->GetGlobalGenerator());
+}
+
+void cmLocalFastbuildGenerator::ComputeObjectFilenames(
+  std::map<cmSourceFile const*, cmObjectLocations>& mapping,
+  cmGeneratorTarget const* gt)
+{
+  for (auto& si : mapping) {
+    cmSourceFile const* sf = si.first;
+    si.second.LongLoc =
+      this->GetObjectFileNameWithoutTarget(*sf, gt->ObjectDirectory);
+  }
+}
+
+void cmLocalFastbuildGenerator::AppendFlagEscape(
+  std::string& flags, std::string const& rawFlag) const
+{
+  std::string escapedFlag = this->EscapeForShell(rawFlag);
+  // Other make systems will remove the double $ but
+  // fastbuild uses ^$ to escape it. So switch to that.
+  // cmSystemTools::ReplaceString(escapedFlag, "$$", "^$");
+  this->AppendFlags(flags, escapedFlag);
+}
+
+void cmLocalFastbuildGenerator::AdditionalCleanFiles(std::string const& config)
+{
+  if (cmValue prop_value =
+        this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
+    cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this,
+                                                       config) };
+    std::string const& binaryDir = this->GetCurrentBinaryDirectory();
+    auto* gg = this->GetGlobalFastbuildGenerator();
+    for (auto const& cleanFile : cleanFiles) {
+      // Support relative paths
+      gg->AddFileToClean(gg->ConvertToFastbuildPath(
+        cmSystemTools::CollapseFullPath(cleanFile, binaryDir)));
+    }
+  }
+}

+ 35 - 0
Source/cmLocalFastbuildGenerator.h

@@ -0,0 +1,35 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "cmLocalCommonGenerator.h"
+
+class cmGeneratorTarget;
+class cmGlobalFastbuildGenerator;
+class cmGlobalGenerator;
+class cmMakefile;
+class cmSourceFile;
+struct cmObjectLocations;
+
+class cmLocalFastbuildGenerator : public cmLocalCommonGenerator
+{
+public:
+  cmLocalFastbuildGenerator(cmGlobalGenerator* gg, cmMakefile* makefile);
+
+  void Generate() override;
+
+  void AppendFlagEscape(std::string& flags,
+                        std::string const& rawFlag) const override;
+
+  void ComputeObjectFilenames(
+    std::map<cmSourceFile const*, cmObjectLocations>& mapping,
+    cmGeneratorTarget const* gt = nullptr) override;
+
+  cmGlobalFastbuildGenerator const* GetGlobalFastbuildGenerator() const;
+  cmGlobalFastbuildGenerator* GetGlobalFastbuildGenerator();
+
+  void AdditionalCleanFiles(std::string const& config);
+};

+ 12 - 2
Source/cmLocalGenerator.cxx

@@ -2847,8 +2847,13 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
               }
 
               // Link to the pch object file
-              std::string pchSourceObj =
-                reuseTarget->GetPchFileObject(config, lang, arch);
+              std::string pchSourceObj;
+              // Fastbuild will propagate pch.obj for us, no need to link to it
+              // explicitly.
+              if (!this->GetGlobalGenerator()->IsFastbuild()) {
+                pchSourceObj =
+                  reuseTarget->GetPchFileObject(config, lang, arch);
+              }
 
               if (target->GetType() != cmStateEnums::OBJECT_LIBRARY) {
                 std::string linkerProperty = "LINK_FLAGS_";
@@ -2970,6 +2975,11 @@ void cmLocalGenerator::CopyPchCompilePdb(
   cc->SetStdPipesUTF8(true);
   cc->AppendDepends(
     { reuseTarget->GetPchFile(config, language), copy_script });
+  // Fastbuild needs to know that this custom command actually depends on a
+  // target that produces PCH, so it can sort by dependencies correctly.
+  if (this->GetGlobalGenerator()->IsFastbuild()) {
+    cc->AppendDepends({ reuseTarget->GetName() });
+  }
 
   if (this->GetGlobalGenerator()->IsVisualStudio()) {
     cc->SetByproducts(outputs);

+ 12 - 0
Source/cmOutputConverter.cxx

@@ -281,6 +281,10 @@ std::string cmOutputConverter::EscapeForShell(cm::string_view str,
   if (!this->GetState()->UseWindowsShell()) {
     flags |= Shell_Flag_IsUnix;
   }
+  if (this->GetState()->UseFastbuildMake()) {
+    // Fastbuild needs to escape very few characters.
+    flags = Shell_Flag_Fastbuild;
+  }
 
   return cmOutputConverter::EscapeForShell(str, flags);
 }
@@ -436,6 +440,10 @@ bool cmOutputConverter::Shell_CharNeedsQuotes(char c, int flags)
     return true;
   }
 
+  if (flags & Shell_Flag_Fastbuild) {
+    return false;
+  }
+
   /* Quote hyphens in response files */
   if (flags & Shell_Flag_IsResponse) {
     if (c == '-') {
@@ -645,6 +653,8 @@ std::string cmOutputConverter::Shell_GetArgument(cm::string_view in, int flags)
            quoting.  Either way the $ is isolated from surrounding
            text to avoid looking like a variable reference.  */
         out += "\"$\"";
+      } else if (flags & Shell_Flag_Fastbuild) {
+        out += "^$";
       } else {
         /* Otherwise a dollar is written just $. */
         out += '$';
@@ -684,6 +694,8 @@ std::string cmOutputConverter::Shell_GetArgument(cm::string_view in, int flags)
       } else {
         out += '\n';
       }
+    } else if (*cit == '^' && (flags & Shell_Flag_Fastbuild)) {
+      out += "^^";
     } else {
       /* Store this character.  */
       out += *cit;

+ 2 - 1
Source/cmOutputConverter.h

@@ -103,7 +103,8 @@ public:
     Shell_Flag_IsResponse = (1 << 10),
 
     /** The target shell is in a Ninja build file.  */
-    Shell_Flag_Ninja = (1 << 11)
+    Shell_Flag_Ninja = (1 << 11),
+    Shell_Flag_Fastbuild = (1 << 12),
   };
 
   std::string EscapeForShell(cm::string_view str, bool makeVars = false,

+ 10 - 0
Source/cmState.cxx

@@ -790,6 +790,16 @@ bool cmState::UseNinjaMulti() const
   return this->NinjaMulti;
 }
 
+void cmState::SetFastbuildMake(bool fastbuildMake)
+{
+  this->FastbuildMake = fastbuildMake;
+}
+
+bool cmState::UseFastbuildMake() const
+{
+  return this->FastbuildMake;
+}
+
 unsigned int cmState::GetCacheMajorVersion() const
 {
   return this->CacheManager->GetCacheMajorVersion();

+ 3 - 0
Source/cmState.h

@@ -225,6 +225,8 @@ public:
   bool UseNinja() const;
   void SetNinjaMulti(bool ninjaMulti);
   bool UseNinjaMulti() const;
+  void SetFastbuildMake(bool fastbuildMake);
+  bool UseFastbuildMake() const;
 
   unsigned int GetCacheMajorVersion() const;
   unsigned int GetCacheMinorVersion() const;
@@ -301,6 +303,7 @@ private:
   bool MSYSShell = false;
   bool Ninja = false;
   bool NinjaMulti = false;
+  bool FastbuildMake = false;
   Mode StateMode = Unknown;
   ProjectKind StateProjectKind = ProjectKind::Normal;
   cm::optional<cmDependencyProvider> DependencyProvider;

+ 21 - 0
Source/cmake.cxx

@@ -10,6 +10,7 @@
 #include <initializer_list>
 #include <iomanip>
 #include <iostream>
+#include <iterator>
 #include <sstream>
 #include <stdexcept>
 #include <utility>
@@ -100,6 +101,7 @@
 #    include <cmext/memory>
 
 #    include "cmGlobalBorlandMakefileGenerator.h"
+#    include "cmGlobalFastbuildGenerator.h"
 #    include "cmGlobalJOMMakefileGenerator.h"
 #    include "cmGlobalNMakeMakefileGenerator.h"
 #    include "cmGlobalVisualStudio14Generator.h"
@@ -123,6 +125,7 @@
 #elif defined(CMAKE_BOOTSTRAP_NINJA)
 #  include "cmGlobalNinjaGenerator.h"
 #endif
+#include "cmGlobalFastbuildGenerator.h"
 
 #if !defined(CMAKE_BOOTSTRAP)
 #  include "cmExtraCodeBlocksGenerator.h"
@@ -3032,6 +3035,23 @@ int cmake::Run(std::vector<std::string> const& args, bool noconfigure)
   if (!this->CheckBuildSystem()) {
     return 0;
   }
+  // After generating fbuild.bff, FastBuild sees rebuild-bff as outdated since
+  // it hasn’t built the target yet. To make it a no-op for future runs, we
+  // trigger a dummy fbuild invocation that creates this marker file and runs
+  // CMake, marking rebuild-bff as up-to-date.
+  std::string const FBuildRestatFile =
+    cmStrCat(this->GetHomeOutputDirectory(), '/', FASTBUILD_RESTAT_FILE);
+  if (cmSystemTools::FileExists(FBuildRestatFile)) {
+    cmsys::ifstream restat(FBuildRestatFile.c_str(),
+                           std::ios::in | std::ios::binary);
+    std::string const file((std::istreambuf_iterator<char>(restat)),
+                           std::istreambuf_iterator<char>());
+    // On Windows can not delete file if it's still opened.
+    restat.close();
+    cmSystemTools::Touch(file, true);
+    cmSystemTools::RemoveFile(FBuildRestatFile);
+    return 0;
+  }
 
 #ifdef CMake_ENABLE_DEBUGGER
   if (!this->StartDebuggerIfEnabled()) {
@@ -3253,6 +3273,7 @@ void cmake::AddDefaultGenerators()
   this->Generators.push_back(cmGlobalUnixMakefileGenerator3::NewFactory());
   this->Generators.push_back(cmGlobalNinjaGenerator::NewFactory());
   this->Generators.push_back(cmGlobalNinjaMultiGenerator::NewFactory());
+  this->Generators.push_back(cmGlobalFastbuildGenerator::NewFactory());
 #elif defined(CMAKE_BOOTSTRAP_NINJA)
   this->Generators.push_back(cmGlobalNinjaGenerator::NewFactory());
 #elif defined(CMAKE_BOOTSTRAP_MAKEFILES)

+ 12 - 0
Source/cmcmd.cxx

@@ -1381,6 +1381,18 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
     }
 
     // Internal CMake dependency scanning support.
+    // The format is: -E cmake_fastbuild_check_depends <dummy_file>
+    // <space_separated_list_of_real_outputs>
+    if (args[1] == "cmake_fastbuild_check_depends" && args.size() >= 3) {
+      auto const dummyFile = args[2];
+      for (auto const& arg : cmMakeRange(args).advance(3)) {
+        if (!cmSystemTools::FileExists(arg)) {
+          cmSystemTools::RemoveFile(dummyFile);
+          return 0;
+        }
+      }
+      return 0;
+    }
     if (args[1] == "cmake_depends" && args.size() >= 6) {
       bool const verbose = isCMakeVerbose();
 

+ 1 - 1
Tests/BuildDepends/CMakeLists.txt

@@ -43,7 +43,7 @@ if(WIN32 AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel")
   set(_cmake_options "-DCMAKE_EXE_LINKER_FLAGS=")
 endif()
 
-if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
+if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja|FASTBuild")
   set(TEST_LINK_DEPENDS ${BuildDepends_BINARY_DIR}/Project/linkdep.txt)
   file(WRITE ${TEST_LINK_DEPENDS} "1")
 endif()

+ 1 - 1
Tests/BuildDepends/Project/CMakeLists.txt

@@ -4,7 +4,7 @@ project(testRebuild)
 if(APPLE AND CMake_TEST_XCODE_VERSION)
   # only use multi-arch if the sysroot exists on this machine
   # Ninja needs -M which could not be used with multiple -arch flags
-  if(EXISTS "${CMAKE_OSX_SYSROOT}" AND NOT "${CMAKE_GENERATOR}" MATCHES "Ninja")
+  if(EXISTS "${CMAKE_OSX_SYSROOT}" AND NOT "${CMAKE_GENERATOR}" MATCHES "Ninja|FASTBuild")
     if(CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 12)
       set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)
     elseif(CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 10)

+ 5 - 6
Tests/CMakeLists.txt

@@ -113,7 +113,7 @@ if(BUILD_TESTING)
   # some old versions of make simply cannot handle spaces in paths
   if(MAKE_IS_GNU OR
       CMAKE_MAKE_PROGRAM MATCHES "nmake|gmake|wmake" OR
-      CMAKE_GENERATOR MATCHES "Visual Studio|Xcode|Borland|Ninja")
+      CMAKE_GENERATOR MATCHES "Visual Studio|Xcode|Borland|Ninja|FASTBuild")
     set(MAKE_SUPPORTS_SPACES 1)
   else()
     set(MAKE_SUPPORTS_SPACES 0)
@@ -786,7 +786,7 @@ if(BUILD_TESTING)
 
   # test for correct sub-project generation
   # not implemented in Xcode or Ninja
-  if(NOT CMAKE_GENERATOR MATCHES "Xcode|Ninja")
+  if(NOT CMAKE_GENERATOR MATCHES "Xcode|Ninja|FASTBuild")
     # run cmake and configure all of SubProject
     # but only build the independent executable car
     add_test(SubProject ${CMAKE_CTEST_COMMAND}
@@ -2113,8 +2113,7 @@ if(BUILD_TESTING)
 
   ADD_TEST_MACRO(CheckCompilerRelatedVariables CheckCompilerRelatedVariables)
 
-  if("${CMAKE_GENERATOR}" MATCHES "Makefile" OR
-     "${CMAKE_GENERATOR}" MATCHES "^Ninja$")
+  if("${CMAKE_GENERATOR}" MATCHES "^Ninja$|Makefile|FASTBuild")
     add_test(MakeClean ${CMAKE_CTEST_COMMAND}
       --build-and-test
       "${CMake_SOURCE_DIR}/Tests/MakeClean"
@@ -3131,7 +3130,7 @@ if(BUILD_TESTING)
         )
     endif()
 
-    if("${CMAKE_GENERATOR}" MATCHES "Makefiles" OR "${CMAKE_GENERATOR}" MATCHES "Ninja")
+    if("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja|FASTBuild")
       configure_file("${CMake_SOURCE_DIR}/Tests/CTestTestLaunchers/test.cmake.in"
         "${CMake_BINARY_DIR}/Tests/CTestTestLaunchers/test.cmake" @ONLY ESCAPE_QUOTES)
       add_test(CTestTestLaunchers ${CMAKE_CTEST_COMMAND}
@@ -3461,7 +3460,7 @@ if(BUILD_TESTING)
     --test-command IncludeDirectories)
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/IncludeDirectories")
 
-  if(CMAKE_GENERATOR MATCHES "^((Unix|MSYS) Makefiles|Ninja)$" AND
+  if(CMAKE_GENERATOR MATCHES "^((Unix|MSYS) Makefiles|Ninja|FASTBuild)$" AND
      ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.4)
       OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT "x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC")
       OR (CMAKE_CXX_COMPILER_ID STREQUAL "LCC")

+ 1 - 1
Tests/CheckSwift.cmake

@@ -1,4 +1,4 @@
-if(NOT CMAKE_GENERATOR MATCHES "Xcode|Ninja")
+if(NOT CMAKE_GENERATOR MATCHES "Xcode|Ninja|FASTBuild")
   set(CMAKE_Swift_COMPILER "")
   return()
 endif()

+ 6 - 0
Tests/CustomCommand/CMakeLists.txt

@@ -485,6 +485,12 @@ add_library(NormOutput "${gen_file}")
 string(APPEND gen_path "/bar")
 set(gen_file "${gen_path}/bar.cxx")
 
+# FASTBuild doesn't support directories as an output of custom commands,
+# so mark the output as SYMBOLIC to let FASTBuild know that it's not expected to appear on disk.
+if(CMAKE_GENERATOR MATCHES "FASTBuild")
+  set_property(SOURCE "${gen_path}" PROPERTY SYMBOLIC ON)
+endif()
+
 add_custom_command(
   OUTPUT "${gen_path}"
   COMMAND ${CMAKE_COMMAND} -E make_directory "${gen_path}"

+ 1 - 1
Tests/ExportImport/Export/CMakeLists.txt

@@ -721,7 +721,7 @@ export(TARGETS testLinkDirectories NAMESPACE bld_ APPEND FILE ExportBuildTree.cm
 
 #------------------------------------------------------------------------------
 # test export of INTERFACE_LINK_DEPENDS
-if(CMAKE_GENERATOR MATCHES "Make|Ninja")
+if(CMAKE_GENERATOR MATCHES "Make|Ninja|FASTBuild")
   add_library(testLinkDepends INTERFACE)
   set_property(TARGET testLinkDepends PROPERTY INTERFACE_LINK_DEPENDS
     $<BUILD_INTERFACE:BUILD_LINK_DEPENDS>

+ 2 - 2
Tests/ExportImport/Import/A/CMakeLists.txt

@@ -435,7 +435,7 @@ set_property(TARGET testSharedLibDepends APPEND PROPERTY
 if (((CMAKE_C_COMPILER_ID STREQUAL GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.4)
     OR CMAKE_C_COMPILER_ID MATCHES "LCC"
     OR (CMAKE_C_COMPILER_ID STREQUAL Clang AND NOT "x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC"))
-    AND (CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja"))
+    AND (CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "FASTBuild"))
   include(CheckCXXCompilerFlag)
   check_cxx_compiler_flag(-Wunused-variable run_sys_includes_test)
   if(run_sys_includes_test)
@@ -607,7 +607,7 @@ checkForProperty(Req::testLinkDirectories "INTERFACE_LINK_DIRECTORIES" "${CMAKE_
 
 #---------------------------------------------------------------------------------
 # check that imported libraries have the expected INTERFACE_LINK_DEPENDS property
-if(CMAKE_GENERATOR MATCHES "Make|Ninja")
+if(CMAKE_GENERATOR MATCHES "Make|Ninja|FASTBuild")
   checkForProperty(bld_testLinkDepends "INTERFACE_LINK_DEPENDS" "BUILD_LINK_DEPENDS")
   checkForProperty(Req::testLinkDepends "INTERFACE_LINK_DEPENDS" "${CMAKE_INSTALL_PREFIX}/INSTALL_LINK_DEPENDS")
 endif()

+ 4 - 0
Tests/FortranModules/CMakeLists.txt

@@ -1,6 +1,10 @@
 cmake_minimum_required(VERSION 3.10)
 project(FortranModules Fortran)
 
+if(CMAKE_GENERATOR MATCHES "FASTBuild")
+  return()
+endif()
+
 if("${CMAKE_Fortran_COMPILER_ID};${CMAKE_Fortran_SIMULATE_ID}" MATCHES "^Intel(LLVM)?;MSVC$")
   string(APPEND CMAKE_Fortran_FLAGS_DEBUG " -Z7")
   string(APPEND CMAKE_Fortran_FLAGS_RELWITHDEBINFO " -Z7")

+ 1 - 1
Tests/GeneratorExpression/CMakeLists.txt

@@ -405,7 +405,7 @@ add_executable(srcgenex_includes_basic "${CMAKE_CURRENT_BINARY_DIR}/srcgenex_inc
 # first include directory is useless but ensure list aspect is tested
 set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/srcgenex_includes_basic.c"
              PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/sf_includes_basic")
-if (CMAKE_GENERATOR MATCHES "Makefiles|Ninja|Watcom WMake")
+if (CMAKE_GENERATOR MATCHES "Makefiles|Ninja|Watcom WMake|FASTBuild")
   add_executable(srcgenex_includes_COMPILE_LANGUAGE "${CMAKE_CURRENT_BINARY_DIR}/srcgenex_includes_COMPILE_LANGUAGE.c")
   # first include directory is useless but ensure list aspect is tested
   set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/srcgenex_includes_COMPILE_LANGUAGE.c"

+ 1 - 0
Tests/IncludeDirectories/CMakeLists.txt

@@ -14,6 +14,7 @@ if (((CMAKE_C_COMPILER_ID STREQUAL GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREA
        CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "19.29.30036.3"))
     AND (CMAKE_GENERATOR STREQUAL "Unix Makefiles"
       OR CMAKE_GENERATOR STREQUAL "Ninja"
+      OR CMAKE_GENERATOR STREQUAL "FASTBuild"
       OR (CMAKE_GENERATOR STREQUAL "Xcode" AND NOT XCODE_VERSION VERSION_LESS 6.0)
       OR CMAKE_GENERATOR MATCHES "Visual Studio"))
   if ("x${CMAKE_C_COMPILER_ID}" STREQUAL "xMSVC")

+ 1 - 2
Tests/PDBDirectoryAndName/CMakeLists.txt

@@ -89,8 +89,7 @@ foreach(t ${my_targets})
   if(NOT pdb_dir)
     if (NOT with_compile)
       set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR})
-    elseif (CMAKE_GENERATOR MATCHES "Ninja" OR
-            CMAKE_GENERATOR MATCHES "Makefiles")
+    elseif (CMAKE_GENERATOR MATCHES "Ninja|Makefiles|FASTBuild")
       set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${t}.dir)
     elseif (CMAKE_GENERATOR MATCHES "Visual Studio")
       set(pdb_dir ${CMAKE_CURRENT_BINARY_DIR}/${t}.dir)

+ 1 - 2
Tests/PrecompiledHeader/CMakeLists.txt

@@ -42,8 +42,7 @@ set_source_files_properties(foo_precompile.c PROPERTIES
 
 # Setup dependencies for precompiled header creation and use.  The VS
 # IDE takes care of this automatically.
-if("${CMAKE_GENERATOR}" MATCHES "Makefile" OR
-   "${CMAKE_GENERATOR}" MATCHES "Ninja")
+if("${CMAKE_GENERATOR}" MATCHES "Makefile|Ninja|FASTBuild")
   # This source file creates the precompiled header as a side-effect.
   set_source_files_properties(foo_precompile.c PROPERTIES
     OBJECT_OUTPUTS "${PCH_DIR}/foo_precompiled.pch")

+ 6 - 1
Tests/Preprocess/CMakeLists.txt

@@ -28,6 +28,9 @@ endif()
 if("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
   set(PP_VS 1)
 endif()
+if("${CMAKE_GENERATOR}" MATCHES "FASTBuild")
+  set(PP_FASTBUILD 1)
+endif()
 if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND
    "x${CMAKE_C_SIMULATE_ID}" STREQUAL "xMSVC")
    set(CLANG_MSVC_WINDOWS 1)
@@ -107,9 +110,11 @@ if(NOT PP_BORLAND AND NOT PP_MINGW AND NOT PP_NMAKE)
 endif()
 
 set(EXPR_OP1 "/")
-if((NOT MSVC OR PP_NMAKE) AND
+if(NOT PP_FASTBUILD AND (NOT MSVC OR PP_NMAKE) AND
    NOT CMAKE_C_COMPILER_ID STREQUAL "Intel" AND
    NOT CLANG_MSVC_WINDOWS)
+  # FASTBuild does not support escaping of '%' character in the command line.
+  # See https://github.com/fastbuild/fastbuild/issues/390 for more details.
   # MSVC cl, Intel icl: %
   # When the cl compiler is invoked from the command line then % must
   # be written %% (to distinguish from %ENV% syntax).  However cl does

+ 1 - 1
Tests/RunCMake/AutoExportDll/RunCMakeTest.cmake

@@ -9,7 +9,7 @@ function (run_cmake_AutoExport name dir)
   run_cmake(${name})
   unset(RunCMake_TEST_OPTIONS)
   # don't run this test on Watcom or Borland make as it is not supported
-  if(RunCMake_GENERATOR MATCHES "Watcom WMake|Borland Makefiles")
+  if(RunCMake_GENERATOR MATCHES "Watcom WMake|Borland Makefiles|FASTBuild")
     return()
   endif()
   if(CMAKE_CXX_COMPILER_ID STREQUAL "OrangeC")

+ 2 - 2
Tests/RunCMake/Autogen_1/RunCMakeTest.cmake

@@ -181,11 +181,11 @@ if (DEFINED with_qt_version)
         "${RunCMake_BINARY_DIR}/AutogenDuplicateDependency-build-${LIB_LINK_COUNT}")
       run_cmake_with_options(AutogenDuplicateDependency ${RunCMake_TEST_OPTIONS} -DLIB_LINK_COUNT=${LIB_LINK_COUNT})
       set(RunCMake_TEST_NO_CLEAN 1)
-      set(RunCMake_TEST_EXPECT_stdout "fancy_generated.txt")
+      set(RunCMake-check-file "check-fancy-generated.cmake")
       run_cmake_command("AutogenDuplicateDependency-build"
         ${CMAKE_COMMAND} --build . --target App_autogen --verbose)
       unset(RunCMake_TEST_VARIANT_DESCRIPTION)
-      unset(RunCMake_TEST_EXPECT_stdout)
+      unset(RunCMake-check-file)
       unset(RunCMake_TEST_NO_CLEAN)
     endforeach()
   endblock()

+ 3 - 0
Tests/RunCMake/Autogen_1/check-fancy-generated.cmake

@@ -0,0 +1,3 @@
+if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/fancy_generated.txt")
+   set(RunCMake_TEST_FAILED "'${RunCMake_TEST_BINARY_DIR}/fancy_generated.txt' missing")
+endif()

+ 8 - 2
Tests/RunCMake/BuildDepends/RunCMakeTest.cmake

@@ -73,10 +73,16 @@ if(CMake_TEST_Fortran
     # FIXME(lfortran): The compiler fails on the test's includes.
     AND NOT CMAKE_Fortran_COMPILER_ID STREQUAL "LFortran"
     )
-  run_BuildDepends(FortranInclude)
+  if(NOT RunCMake_GENERATOR MATCHES "FASTBuild")
+    run_BuildDepends(FortranInclude)
+  endif()
 endif()
 
-run_BuildDepends(Custom-Symbolic-and-Byproduct)
+# By default, FASTBuild will only create "run always" commands if we only have SYMBOLIC output.
+# However, this declares a custom command with NON-SYMBOLIC output and expects it to always run.
+if(NOT RunCMake_GENERATOR MATCHES "FASTBuild")
+  run_BuildDepends(Custom-Symbolic-and-Byproduct)
+endif()
 run_BuildDepends(Custom-Always)
 
 set(RunCMake_TEST_OUTPUT_MERGE_save "${RunCMake_TEST_OUTPUT_MERGE}")

+ 1 - 1
Tests/RunCMake/BuiltinTargets/RunCMakeTest.cmake

@@ -1,6 +1,6 @@
 include(RunCMake)
 
-if(RunCMake_GENERATOR MATCHES "Make|Ninja")
+if(RunCMake_GENERATOR MATCHES "Make|Ninja|FASTBuild")
   set(test_target "test")
 else()
   set(test_target "RUN_TESTS")

+ 1 - 1
Tests/RunCMake/CMP0037/RunCMakeTest.cmake

@@ -11,6 +11,6 @@ run_cmake(NEW-cond-package)
 
 run_cmake(alias-test-NEW)
 
-if(RunCMake_GENERATOR MATCHES "Make|Ninja")
+if(RunCMake_GENERATOR MATCHES "Make|Ninja|FASTBuild")
   run_cmake(NEW-cond-package_source)
 endif()

+ 3 - 3
Tests/RunCMake/CMakeLists.txt

@@ -355,7 +355,7 @@ add_RunCMake_test(BuildDepends
   )
 set_property(TEST RunCMake.BuildDepends APPEND PROPERTY LABELS "Fortran")
 add_RunCMake_test(BuiltinTargets)
-if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")
+if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja|FASTBuild")
   add_RunCMake_test(Byproducts)
 endif()
 add_RunCMake_test(CMakeDependentOption)
@@ -371,7 +371,7 @@ add_RunCMake_test(CMakeRelease -DCMake_TEST_JQ=${CMake_TEST_JQ})
 if(CMAKE_GENERATOR MATCHES "Make|Ninja")
   add_RunCMake_test(Color)
 endif()
-if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")
+if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja|FASTBuild")
   add_RunCMake_test(CompilerChange)
 endif()
 add_RunCMake_test(CompilerNotFound)
@@ -1138,7 +1138,7 @@ add_executable(pseudo_iwyu pseudo_iwyu.c)
 add_executable(pseudo_cpplint pseudo_cpplint.c)
 add_executable(pseudo_cppcheck pseudo_cppcheck.c)
 
-if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
+if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja|FASTBuild")
   if(UNIX AND NOT CYGWIN)
     execute_process(COMMAND ldd --help
       OUTPUT_VARIABLE LDD_HELP

+ 1 - 1
Tests/RunCMake/CTest/RunCMakeTest.cmake

@@ -11,7 +11,7 @@ function(run_CMakeCTestArguments)
   run_cmake_with_options(CMakeCTestArguments "-DCMAKE_CTEST_ARGUMENTS=--quiet\\;--output-log\\;output-log.txt")
   set(RunCMake_TEST_NO_CLEAN 1)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMakeCTestArguments-build)
-  if(RunCMake_GENERATOR MATCHES "Make|Ninja")
+  if(RunCMake_GENERATOR MATCHES "Make|Ninja|FASTBuild")
     set(test "test")
   else()
     set(test "RUN_TESTS")

+ 3 - 0
Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake

@@ -12,6 +12,9 @@ function(run_compiler_launcher lang)
   if("${RunCMake_GENERATOR}" MATCHES "Ninja")
     set(verbose_args -- -v)
   endif()
+  if("${RunCMake_GENERATOR}" MATCHES "FASTBuild")
+    set(verbose_args -- -verbose)
+  endif()
   run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build . ${verbose_args})
 endfunction()
 

+ 1 - 1
Tests/RunCMake/ExternalProject/EnvVars-build-stdout.txt

@@ -27,6 +27,6 @@
  *-- Separator: ,
  *-- List: 7,8,9
 .*(Performing build step for 'DefaultCommandEnvVars'|DefaultCommandEnvVars-build).*
- *-- Stage: build
+.*-- Stage: build
  *-- Separator: ,
  *-- List: 7,8,9,10

+ 2 - 2
Tests/RunCMake/ExternalProject/LogOutputOnFailure-build-stdout.txt

@@ -1,8 +1,8 @@
 (  )?-- stdout output is:
 (  )?This is some dummy output with some long lines to ensure formatting is preserved
 (  )?    Including lines with leading spaces
-(  )?
+(  )?|
 (  )?And also blank lines[
- ]+
+ ]?
 (  )?-- stderr output is:
 (  )?cmake -E env: no command given

+ 2 - 2
Tests/RunCMake/ExternalProject/LogOutputOnFailureMerged-build-stdout.txt

@@ -1,7 +1,7 @@
 (  )?-- Log output is:
 (  )?This is some dummy output with some long lines to ensure formatting is preserved
 (  )?    Including lines with leading spaces
-(  )?
+(  )?|
 (  )?And also blank lines[
- ]+
+ ]?
 (  )?cmake -E env: no command given

+ 3 - 0
Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake

@@ -12,6 +12,9 @@ function(run_linker_launcher lang)
   if("${RunCMake_GENERATOR}" MATCHES "Ninja")
     set(verbose_args -- -v)
   endif()
+  if("${RunCMake_GENERATOR}" MATCHES "FASTBuild")
+    set(verbose_args -- -verbose)
+  endif()
   run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build . ${verbose_args})
 endfunction()
 

+ 1 - 1
Tests/RunCMake/MultiLint/C-Build-stdout.txt

@@ -2,7 +2,7 @@ Warning: include-what-you-use reported diagnostics:
 should add these lines:
 *
 #include <\.\.\.>
-+
+*
 .*Tests[/\]RunCMake[/\]MultiLint[/\]main\.c:0:0: warning: message \[checker\].*
 Total errors found: 0
 .*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 1
Tests/RunCMake/MultiLint/C-launch-Build-stdout.txt

@@ -2,7 +2,7 @@ Warning: include-what-you-use reported diagnostics:
 should add these lines:
 *
 #include <\.\.\.>
-+
+*
 .*Tests[/\]RunCMake[/\]MultiLint[/\]main\.c:0:0: warning: message \[checker\].*
 Total errors found: 0
 .*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 1
Tests/RunCMake/MultiLint/CXX-Build-stdout.txt

@@ -2,7 +2,7 @@ Warning: include-what-you-use reported diagnostics:
 should add these lines:
 *
 #include <\.\.\.>
-+
+?
 .*Tests[/\]RunCMake[/\]MultiLint[/\]main\.cxx:0:0: warning: message \[checker\].*
 Total errors found: 0
 .*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 1
Tests/RunCMake/MultiLint/CXX-launch-Build-stdout.txt

@@ -2,7 +2,7 @@ Warning: include-what-you-use reported diagnostics:
 should add these lines:
 *
 #include <\.\.\.>
-+
+?
 .*Tests[/\]RunCMake[/\]MultiLint[/\]main\.cxx:0:0: warning: message \[checker\].*
 Total errors found: 0
 .*Warning: cppcheck reported diagnostics.*error.*warning.*style.*performance.*information.*

+ 1 - 1
Tests/RunCMake/XcFramework/RunCMakeTest.cmake

@@ -87,7 +87,7 @@ set(xcframework_platforms macos ios ${maybe_ios_catalyst} tvos watchos ios-simul
 if(CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 15.2)
   list(APPEND xcframework_platforms visionos visionos-simulator)
 endif()
-if(CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 12)
+if(CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 12 AND NOT RunCMake_GENERATOR STREQUAL "FASTBuild")
   set(macos_archs_1 "x86_64\\;arm64")
   set(macos_archs_2 "x86_64\\\\;arm64")
   set(watch_sim_archs_2 "x86_64")

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

@@ -19,6 +19,8 @@ function(run_TargetOrder)
   run_cmake(TargetOrder)
   if(RunCMake_GENERATOR STREQUAL "Ninja")
     set(build_flags -j 1 -v)
+  elseif(RunCMake_GENERATOR STREQUAL "FASTBuild")
+    set(build_flags -j1)
   endif()
   run_cmake_command(TargetOrder-build ${CMAKE_COMMAND} --build . -- ${build_flags})
 endfunction()

+ 7 - 0
Tests/RunCMake/property_init/CompileSources.cmake

@@ -248,6 +248,13 @@ if (CMAKE_GENERATOR MATCHES "Ninja")
         pch_pool=1)
 endif ()
 
+# FASTBuild requires that launchers actually be available
+# at configure time
+if(CMAKE_GENERATOR MATCHES FASTBuild)
+  file(TOUCH "${dir}/ccache")
+  file(CHMOD "${dir}/ccache" PERMISSIONS OWNER_READ OWNER_EXECUTE)
+endif()
+
 prepare_target_types(can_compile_sources
   EXECUTABLE SHARED STATIC MODULE OBJECT)
 

+ 1 - 1
Tests/RunCMake/try_compile/ConfigureLog-config.txt

@@ -86,7 +86,7 @@ events:(
       variable: "COMPILE_RESULT"
       cached: true
       stdout: \|.*
-      exitCode: [1-9][0-9]*
+      exitCode: -?[1-9][0-9]*
   -
     kind: "try_compile-v1"
     backtrace:

+ 1 - 1
Tests/RunCMake/try_run/ConfigureLog-config.txt

@@ -80,7 +80,7 @@ events:(
       variable: "COMPILE_RESULT"
       cached: true
       stdout: \|.*
-      exitCode: [1-9][0-9]*
+      exitCode: -?[1-9][0-9]*
     runResult:
       variable: "RUN_RESULT"
       cached: true