Merge topic 'link-options-propagation'

bbba701899 Link properties: must be transitive over private dependency on static library

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !4120
diff --git a/Help/command/foreach.rst b/Help/command/foreach.rst
index ecbfed3..a01a104 100644
--- a/Help/command/foreach.rst
+++ b/Help/command/foreach.rst
@@ -82,3 +82,46 @@
   -- X=6
   -- X=7
   -- X=8
+
+
+.. code-block:: cmake
+
+  foreach(<loop_var>... IN ZIP_LISTS <lists>)
+
+In this variant, ``<lists>`` is a whitespace or semicolon
+separated list of list-valued variables. The ``foreach``
+command iterates over each list simultaneously setting the
+iteration variables as follows:
+
+- if the only ``loop_var`` given, then it sets a series of
+  ``loop_var_N`` variables to the current item from the
+  corresponding list;
+- if multiple variable names passed, their count should match
+  the lists variables count;
+- if any of the lists are shorter, the corresponding iteration
+  variable is not defined for the current iteration.
+
+.. code-block:: cmake
+
+  list(APPEND English one two three four)
+  list(APPEND Bahasa satu dua tiga)
+
+  foreach(num IN ZIP_LISTS English Bahasa)
+      message(STATUS "num_0=${num_0}, num_1=${num_1}")
+  endforeach()
+
+  foreach(en ba IN ZIP_LISTS English Bahasa)
+      message(STATUS "en=${en}, ba=${ba}")
+  endforeach()
+
+yields
+::
+
+  -- num_0=one, num_1=satu
+  -- num_0=two, num_1=dua
+  -- num_0=three, num_1=tiga
+  -- num_0=four, num_1=
+  -- en=one, ba=satu
+  -- en=two, ba=dua
+  -- en=three, ba=tiga
+  -- en=four, ba=
diff --git a/Help/envvar/CMAKE_LANG_COMPILER_LAUNCHER.rst b/Help/envvar/CMAKE_LANG_COMPILER_LAUNCHER.rst
new file mode 100644
index 0000000..4f91e9a
--- /dev/null
+++ b/Help/envvar/CMAKE_LANG_COMPILER_LAUNCHER.rst
@@ -0,0 +1,10 @@
+CMAKE_<LANG>_COMPILER_LAUNCHER
+------------------------------
+
+.. include:: ENV_VAR.txt
+
+Default compiler launcher to use for the specified language. Will only be used
+by CMake to initialize the variable on the first configuration. Afterwards, it
+is available through the cache setting of the variable of the same name. For
+any configuration run (including the first), the environment variable will be
+ignored if the :variable:`CMAKE_<LANG>_COMPILER_LAUNCHER` variable is defined.
diff --git a/Help/manual/cmake-env-variables.7.rst b/Help/manual/cmake-env-variables.7.rst
index c98f18f..adfc39e 100644
--- a/Help/manual/cmake-env-variables.7.rst
+++ b/Help/manual/cmake-env-variables.7.rst
@@ -28,6 +28,7 @@
    /envvar/CMAKE_GENERATOR_INSTANCE
    /envvar/CMAKE_GENERATOR_PLATFORM
    /envvar/CMAKE_GENERATOR_TOOLSET
+   /envvar/CMAKE_LANG_COMPILER_LAUNCHER
    /envvar/CMAKE_MSVCIDE_RUN_PATH
    /envvar/CMAKE_NO_VERBOSE
    /envvar/CMAKE_OSX_ARCHITECTURES
diff --git a/Help/release/dev/FindLibArchive-target.rst b/Help/release/dev/FindLibArchive-target.rst
new file mode 100644
index 0000000..8998dae
--- /dev/null
+++ b/Help/release/dev/FindLibArchive-target.rst
@@ -0,0 +1,5 @@
+FindLibArchive-target
+---------------------
+
+* The :module:`FindLibArchive` module now returns an ``IMPORTED`` target
+  for libarchive.
diff --git a/Help/release/dev/compiler-launcher-env.rst b/Help/release/dev/compiler-launcher-env.rst
new file mode 100644
index 0000000..58519d9
--- /dev/null
+++ b/Help/release/dev/compiler-launcher-env.rst
@@ -0,0 +1,5 @@
+compiler-launcher-env
+---------------------
+
+* The :envvar:`CMAKE_<LANG>_COMPILER_LAUNCHER` environment variable may now be
+  used to initialize the :variable:`CMAKE_<LANG>_COMPILER_LAUNCHER` variable.
diff --git a/Help/release/dev/foreach-ZIP_LISTS.rst b/Help/release/dev/foreach-ZIP_LISTS.rst
new file mode 100644
index 0000000..d45d9b9
--- /dev/null
+++ b/Help/release/dev/foreach-ZIP_LISTS.rst
@@ -0,0 +1,5 @@
+foreach-ZIP_LISTS
+-----------------
+
+* The :command:`foreach` learned a new option ``ZIP_LISTS`` to iterate
+  over multiple lists simultaneously.
diff --git a/Help/variable/CMAKE_LANG_COMPILER_LAUNCHER.rst b/Help/variable/CMAKE_LANG_COMPILER_LAUNCHER.rst
index e6c8bb5..e5dda60 100644
--- a/Help/variable/CMAKE_LANG_COMPILER_LAUNCHER.rst
+++ b/Help/variable/CMAKE_LANG_COMPILER_LAUNCHER.rst
@@ -5,3 +5,6 @@
 This variable is used to initialize the property on each target as it is
 created.  This is done only when ``<LANG>`` is ``C``, ``CXX``, ``Fortran``,
 or ``CUDA``.
+
+This variable is initialized to the :envvar:`CMAKE_<LANG>_COMPILER_LAUNCHER`
+environment variable if it is set.
diff --git a/Modules/CMakeCInformation.cmake b/Modules/CMakeCInformation.cmake
index df43559..1e08bb7 100644
--- a/Modules/CMakeCInformation.cmake
+++ b/Modules/CMakeCInformation.cmake
@@ -110,6 +110,11 @@
   mark_as_advanced(CMAKE_C_STANDARD_LIBRARIES)
 endif()
 
+if(NOT CMAKE_C_COMPILER_LAUNCHER AND DEFINED ENV{CMAKE_C_COMPILER_LAUNCHER})
+  set(CMAKE_C_COMPILER_LAUNCHER "$ENV{CMAKE_C_COMPILER_LAUNCHER}"
+    CACHE STRING "Compiler launcher for C.")
+endif()
+
 include(CMakeCommonLanguageInclude)
 
 # now define the following rule variables
diff --git a/Modules/CMakeCUDAInformation.cmake b/Modules/CMakeCUDAInformation.cmake
index b0d80d1..974f5fa 100644
--- a/Modules/CMakeCUDAInformation.cmake
+++ b/Modules/CMakeCUDAInformation.cmake
@@ -82,6 +82,11 @@
   mark_as_advanced(CMAKE_CUDA_STANDARD_LIBRARIES)
 endif()
 
+if(NOT CMAKE_CUDA_COMPILER_LAUNCHER AND DEFINED ENV{CMAKE_CUDA_COMPILER_LAUNCHER})
+  set(CMAKE_CUDA_COMPILER_LAUNCHER "$ENV{CMAKE_CUDA_COMPILER_LAUNCHER}"
+    CACHE STRING "Compiler launcher for CUDA.")
+endif()
+
 include(CMakeCommonLanguageInclude)
 
 # now define the following rules:
@@ -93,9 +98,7 @@
 # CMAKE_CUDA_LINK_EXECUTABLE
 
 if(CMAKE_CUDA_HOST_COMPILER)
-  set(CMAKE_CUDA_HOST_FLAGS "-ccbin=<CMAKE_CUDA_HOST_COMPILER>")
-else()
-  set(CMAKE_CUDA_HOST_FLAGS "")
+  string(APPEND _CMAKE_CUDA_EXTRA_FLAGS " -ccbin=<CMAKE_CUDA_HOST_COMPILER>")
 endif()
 
 set(__IMPLICT_LINKS )
@@ -135,26 +138,26 @@
 #Specify how to compile when ptx has been requested
 if(NOT CMAKE_CUDA_COMPILE_PTX_COMPILATION)
   set(CMAKE_CUDA_COMPILE_PTX_COMPILATION
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -ptx <SOURCE> -o <OBJECT>")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -ptx <SOURCE> -o <OBJECT>")
 endif()
 
 #Specify how to compile when separable compilation has been requested
 if(NOT CMAKE_CUDA_COMPILE_SEPARABLE_COMPILATION)
   set(CMAKE_CUDA_COMPILE_SEPARABLE_COMPILATION
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -dc <SOURCE> -o <OBJECT>")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -dc <SOURCE> -o <OBJECT>")
 endif()
 
 #Specify how to compile when whole compilation has been requested
 if(NOT CMAKE_CUDA_COMPILE_WHOLE_COMPILATION)
   set(CMAKE_CUDA_COMPILE_WHOLE_COMPILATION
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -c <SOURCE> -o <OBJECT>")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -c <SOURCE> -o <OBJECT>")
 endif()
 
-if(CMAKE_GENERATOR STREQUAL "Ninja")
+if(CMAKE_GENERATOR STREQUAL "Ninja" AND NOT CMAKE_DEPFILE_FLAGS_CUDA )
   set(CMAKE_CUDA_COMPILE_DEPENDENCY_DETECTION
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -M <SOURCE> -MT <OBJECT> -o $DEP_FILE")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -M <SOURCE> -MT <OBJECT> -o $DEP_FILE")
   #The Ninja generator uses the make file dependency files to determine what
-  #files need to be recompiled. Unfortunately, nvcc doesn't support building
+  #files need to be recompiled. Unfortunately, nvcc < 10.2 doesn't support building
   #a source file and generating the dependencies of said file in a single
   #invocation. Instead we have to state that you need to chain two commands.
   #
@@ -171,13 +174,6 @@
     "<CMAKE_CUDA_HOST_LINK_LAUNCHER> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>${__IMPLICT_LINKS}")
 endif()
 
-if( CMAKE_CUDA_COMPILER_ID STREQUAL "NVIDIA" AND
-    CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "8.0.0")
-  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "-Wno-deprecated-gpu-targets")
-else()
-  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "")
-endif()
-
 # Add implicit host link directories that contain device libraries
 # to the device link line.
 set(__IMPLICT_DLINK_DIRS ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
@@ -192,14 +188,15 @@
 endforeach()
 unset(__IMPLICT_DLINK_DIRS)
 
+
 #These are used when linking relocatable (dc) cuda code
 if(NOT CMAKE_CUDA_DEVICE_LINK_LIBRARY)
   set(CMAKE_CUDA_DEVICE_LINK_LIBRARY
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <LANGUAGE_COMPILE_FLAGS> ${CMAKE_CUDA_COMPILE_OPTIONS_PIC} ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES>${__IMPLICT_DLINK_FLAGS}")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <LANGUAGE_COMPILE_FLAGS> ${CMAKE_CUDA_COMPILE_OPTIONS_PIC} ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES>${__IMPLICT_DLINK_FLAGS}")
 endif()
 if(NOT CMAKE_CUDA_DEVICE_LINK_EXECUTABLE)
   set(CMAKE_CUDA_DEVICE_LINK_EXECUTABLE
-    "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <FLAGS> ${CMAKE_CUDA_COMPILE_OPTIONS_PIC} ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES>${__IMPLICT_DLINK_FLAGS}")
+    "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <FLAGS> ${CMAKE_CUDA_COMPILE_OPTIONS_PIC} ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES>${__IMPLICT_DLINK_FLAGS}")
 endif()
 
 unset(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS)
diff --git a/Modules/CMakeCXXInformation.cmake b/Modules/CMakeCXXInformation.cmake
index a896b99..da7440a 100644
--- a/Modules/CMakeCXXInformation.cmake
+++ b/Modules/CMakeCXXInformation.cmake
@@ -207,6 +207,11 @@
   mark_as_advanced(CMAKE_CXX_STANDARD_LIBRARIES)
 endif()
 
+if(NOT CMAKE_CXX_COMPILER_LAUNCHER AND DEFINED ENV{CMAKE_CXX_COMPILER_LAUNCHER})
+  set(CMAKE_CXX_COMPILER_LAUNCHER "$ENV{CMAKE_CXX_COMPILER_LAUNCHER}"
+    CACHE STRING "Compiler launcher for CXX.")
+endif()
+
 include(CMakeCommonLanguageInclude)
 
 # now define the following rules:
diff --git a/Modules/CMakeFortranInformation.cmake b/Modules/CMakeFortranInformation.cmake
index ffa6a24..e80716b 100644
--- a/Modules/CMakeFortranInformation.cmake
+++ b/Modules/CMakeFortranInformation.cmake
@@ -163,6 +163,11 @@
 
 cmake_initialize_per_config_variable(CMAKE_Fortran_FLAGS "Flags used by the Fortran compiler")
 
+if(NOT CMAKE_Fortran_COMPILER_LAUNCHER AND DEFINED ENV{CMAKE_Fortran_COMPILER_LAUNCHER})
+  set(CMAKE_Fortran_COMPILER_LAUNCHER "$ENV{CMAKE_Fortran_COMPILER_LAUNCHER}"
+    CACHE STRING "Compiler launcher for Fortran.")
+endif()
+
 include(CMakeCommonLanguageInclude)
 
 # now define the following rule variables
diff --git a/Modules/Compiler/NVIDIA-CUDA.cmake b/Modules/Compiler/NVIDIA-CUDA.cmake
index b59deda..2b24fa5 100644
--- a/Modules/Compiler/NVIDIA-CUDA.cmake
+++ b/Modules/Compiler/NVIDIA-CUDA.cmake
@@ -2,6 +2,29 @@
 set(CMAKE_CUDA_VERBOSE_FLAG "-v")
 set(CMAKE_CUDA_VERBOSE_COMPILE_FLAG "-Xcompiler=-v")
 
+if (NOT CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 10.2)
+  # The -forward-unknown-to-host-compiler flag was only
+  # added to nvcc in 10.2 so before that we had no good
+  # way to invoke the CUDA compiler and propagate unknown
+  # flags such as -pthread to the host compiler
+  set(_CMAKE_CUDA_EXTRA_FLAGS "-forward-unknown-to-host-compiler")
+else()
+  set(_CMAKE_CUDA_EXTRA_FLAGS "")
+endif()
+
+if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "8.0.0")
+  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "-Wno-deprecated-gpu-targets")
+else()
+  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "")
+endif()
+
+if (NOT CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 10.2)
+  # The -MD flag was only added to nvcc in 10.2 so
+  # before that we had to invoke the compiler twice
+  # to get header dependency information
+  set(CMAKE_DEPFILE_FLAGS_CUDA "-MD -MT <OBJECT> -MF <DEPFILE>")
+endif()
+
 if(NOT "x${CMAKE_CUDA_SIMULATE_ID}" STREQUAL "xMSVC")
   set(CMAKE_CUDA_COMPILE_OPTIONS_PIE -Xcompiler=-fPIE)
   set(CMAKE_CUDA_COMPILE_OPTIONS_PIC -Xcompiler=-fPIC)
diff --git a/Modules/FindLibArchive.cmake b/Modules/FindLibArchive.cmake
index ef27b7d..ce3c8b8 100644
--- a/Modules/FindLibArchive.cmake
+++ b/Modules/FindLibArchive.cmake
@@ -16,18 +16,26 @@
   LibArchive_INCLUDE_DIRS - include search path
   LibArchive_LIBRARIES    - libraries to link
   LibArchive_VERSION      - libarchive 3-component version number
+
+The module defines the following ``IMPORTED`` targets:
+
+::
+
+  LibArchive::LibArchive  - target for linking against libarchive
 #]=======================================================================]
 
 find_path(LibArchive_INCLUDE_DIR
   NAMES archive.h
   PATHS
   "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\LibArchive;InstallPath]/include"
+  DOC "libarchive include directory"
   )
 
 find_library(LibArchive_LIBRARY
   NAMES archive libarchive
   PATHS
   "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\LibArchive;InstallPath]/lib"
+  DOC "libarchive library"
   )
 
 mark_as_advanced(LibArchive_INCLUDE_DIR LibArchive_LIBRARY)
@@ -58,4 +66,11 @@
 if(LibArchive_FOUND)
   set(LibArchive_INCLUDE_DIRS ${LibArchive_INCLUDE_DIR})
   set(LibArchive_LIBRARIES    ${LibArchive_LIBRARY})
+
+  if (NOT TARGET LibArchive::LibArchive)
+    add_library(LibArchive::LibArchive UNKNOWN IMPORTED)
+    set_target_properties(LibArchive::LibArchive PROPERTIES
+      IMPORTED_LOCATION "${LibArchive_LIBRARY}"
+      INTERFACE_INCLUDE_DIRECTORIES "${LibArchive_INCLUDE_DIR}")
+  endif ()
 endif()
diff --git a/Modules/Internal/CPack/CPackNuGet.cmake b/Modules/Internal/CPack/CPackNuGet.cmake
index b46a7b1..1f4bcfd 100644
--- a/Modules/Internal/CPack/CPackNuGet.cmake
+++ b/Modules/Internal/CPack/CPackNuGet.cmake
@@ -239,20 +239,13 @@
 
         if(_ver)
             _cpack_nuget_debug("  got `${_dep}` dependency version ${_ver}")
-            list(APPEND _collected_deps "<dependency id=\"${_dep}\" version=\"${_ver}\" />")
+            string(CONCAT _collected_deps "${_collected_deps}" "            <dependency id=\"${_dep}\" version=\"${_ver}\" />\n")
         endif()
     endforeach()
 
     # Render deps into the variable
     if(_collected_deps)
-        set(_CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n")
-        foreach(_line IN LISTS _collected_deps)
-            string(
-                APPEND _CPACK_NUGET_DEPENDENCIES_TAG
-                "            ${_line}\n"
-              )
-        endforeach()
-        string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG "        </dependencies>")
+        string(CONCAT _CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n" "${_collected_deps}" "        </dependencies>")
     endif()
 
     # Render the spec file
diff --git a/Modules/Platform/Windows-NVIDIA-CUDA.cmake b/Modules/Platform/Windows-NVIDIA-CUDA.cmake
index 94d77b9..30b5aa9 100644
--- a/Modules/Platform/Windows-NVIDIA-CUDA.cmake
+++ b/Modules/Platform/Windows-NVIDIA-CUDA.cmake
@@ -1,11 +1,11 @@
 include(Platform/Windows-MSVC)
 
 set(CMAKE_CUDA_COMPILE_PTX_COMPILATION
-  "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -ptx <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
+  "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -ptx <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
 set(CMAKE_CUDA_COMPILE_SEPARABLE_COMPILATION
-  "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -dc <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
+  "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -dc <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
 set(CMAKE_CUDA_COMPILE_WHOLE_COMPILATION
-  "<CMAKE_CUDA_COMPILER> ${CMAKE_CUDA_HOST_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -c <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
+  "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <DEFINES> <INCLUDES> <FLAGS> -x cu -c <SOURCE> -o <OBJECT> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS")
 
 set(__IMPLICT_LINKS )
 foreach(dir ${CMAKE_CUDA_HOST_IMPLICIT_LINK_DIRECTORIES})
@@ -30,11 +30,6 @@
 unset(_CMAKE_VS_LINK_EXE)
 unset(_CMAKE_VS_LINK_EXE)
 
-if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "8.0.0")
-  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "-Wno-deprecated-gpu-targets")
-else()
-  set(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS "")
-endif()
 
 # Add implicit host link directories that contain device libraries
 # to the device link line.
@@ -51,11 +46,9 @@
 unset(__IMPLICT_DLINK_DIRS)
 
 set(CMAKE_CUDA_DEVICE_LINK_LIBRARY
-  "<CMAKE_CUDA_COMPILER> <LANGUAGE_COMPILE_FLAGS> ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS${__IMPLICT_DLINK_FLAGS}")
+  "<CMAKE_CUDA_COMPILER> ${_CMAKE_CUDA_EXTRA_FLAGS} <LANGUAGE_COMPILE_FLAGS> ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS${__IMPLICT_DLINK_FLAGS}")
 set(CMAKE_CUDA_DEVICE_LINK_EXECUTABLE
-  "<CMAKE_CUDA_COMPILER> <FLAGS> ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS${__IMPLICT_DLINK_FLAGS}")
-
-unset(_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS)
+  "<CMAKE_CUDA_COMPILER>  ${_CMAKE_CUDA_EXTRA_FLAGS} <FLAGS> ${_CMAKE_CUDA_EXTRA_DEVICE_LINK_FLAGS} -shared -dlink <OBJECTS> -o <TARGET> <LINK_LIBRARIES> -Xcompiler=-Fd<TARGET_COMPILE_PDB>,-FS${__IMPLICT_DLINK_FLAGS}")
 unset(__IMPLICT_DLINK_FLAGS)
 
 string(REPLACE "/D" "-D" _PLATFORM_DEFINES_CUDA "${_PLATFORM_DEFINES}${_PLATFORM_DEFINES_CXX}")
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index d050fb7..ff313d6 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -693,6 +693,8 @@
 
   cmDuration.h
   cmDuration.cxx
+
+  bindexplib.cxx
   )
 
 SET_PROPERTY(SOURCE cmProcessOutput.cxx APPEND PROPERTY COMPILE_DEFINITIONS
@@ -715,7 +717,6 @@
   set(SRCS ${SRCS}
     cmCallVisualStudioMacro.cxx
     cmCallVisualStudioMacro.h
-    bindexplib.cxx
     )
 
   if(NOT UNIX)
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index b373350..cdd0893 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 16)
-set(CMake_VERSION_PATCH 20191207)
+set(CMake_VERSION_PATCH 20191209)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/bindexplib.cxx b/Source/bindexplib.cxx
index b85cc33..0b2750d 100644
--- a/Source/bindexplib.cxx
+++ b/Source/bindexplib.cxx
@@ -64,32 +64,36 @@
  */
 #include "bindexplib.h"
 
-#include <iostream>
+#include <cstddef>
 #include <sstream>
 #include <vector>
 
-#include <windows.h>
+#ifdef _WIN32
+#  include <windows.h>
 
-#include "cmsys/Encoding.hxx"
+#  include "cmsys/Encoding.hxx"
+#endif
+
 #include "cmsys/FStream.hxx"
 
 #include "cmSystemTools.h"
 
-#ifndef IMAGE_FILE_MACHINE_ARM
-#  define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
-#endif
+#ifdef _WIN32
+#  ifndef IMAGE_FILE_MACHINE_ARM
+#    define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
+#  endif
 
-#ifndef IMAGE_FILE_MACHINE_THUMB
-#  define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
-#endif
+#  ifndef IMAGE_FILE_MACHINE_THUMB
+#    define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
+#  endif
 
-#ifndef IMAGE_FILE_MACHINE_ARMNT
-#  define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
-#endif
+#  ifndef IMAGE_FILE_MACHINE_ARMNT
+#    define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
+#  endif
 
-#ifndef IMAGE_FILE_MACHINE_ARM64
-#  define IMAGE_FILE_MACHINE_ARM64 0xaa64 // ARM64 Little-Endian
-#endif
+#  ifndef IMAGE_FILE_MACHINE_ARM64
+#    define IMAGE_FILE_MACHINE_ARM64 0xaa64 // ARM64 Little-Endian
+#  endif
 
 typedef struct cmANON_OBJECT_HEADER_BIGOBJ
 {
@@ -306,6 +310,7 @@
   SymbolTableType* SymbolTable;
   bool IsI386;
 };
+#endif
 
 bool DumpFileWithLlvmNm(std::string const& nmPath, const char* filename,
                         std::set<std::string>& symbols,
@@ -315,15 +320,15 @@
   // break up command line into a vector
   std::vector<std::string> command;
   command.push_back(nmPath);
-  command.push_back("--no-weak");
-  command.push_back("--defined-only");
-  command.push_back("--format=posix");
-  command.push_back(filename);
+  command.emplace_back("--no-weak");
+  command.emplace_back("--defined-only");
+  command.emplace_back("--format=posix");
+  command.emplace_back(filename);
 
   // run the command
   int exit_code = 0;
-  cmSystemTools::RunSingleCommand(command, &output, &output, &exit_code, "",
-                                  cmSystemTools::OUTPUT_NONE);
+  cmSystemTools::RunSingleCommand(command, &output, &output, &exit_code,
+                                  nullptr, cmSystemTools::OUTPUT_NONE);
 
   if (exit_code != 0) {
     fprintf(stderr, "llvm-nm returned an error: %s\n", output.c_str());
@@ -336,7 +341,7 @@
     if (line.empty()) { // last line
       continue;
     }
-    size_t sym_end = line.find(" ");
+    size_t sym_end = line.find(' ');
     if (sym_end == std::string::npos) {
       fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n",
               line.c_str());
@@ -366,6 +371,9 @@
               std::set<std::string>& symbols,
               std::set<std::string>& dataSymbols)
 {
+#ifndef _WIN32
+  return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
+#else
   HANDLE hFile;
   HANDLE hFileMapping;
   LPVOID lpFileBase;
@@ -446,6 +454,7 @@
   CloseHandle(hFileMapping);
   CloseHandle(hFile);
   return true;
+#endif
 }
 
 bool bindexplib::AddObjectFile(const char* filename)
diff --git a/Source/cmAlgorithms.h b/Source/cmAlgorithms.h
index e83f160..def3ac7 100644
--- a/Source/cmAlgorithms.h
+++ b/Source/cmAlgorithms.h
@@ -146,6 +146,14 @@
 }
 
 template <typename T>
+void cmAppend(std::vector<std::unique_ptr<T>>& v,
+              std::vector<std::unique_ptr<T>>&& r)
+{
+  std::transform(r.begin(), r.end(), std::back_inserter(v),
+                 [](std::unique_ptr<T>& item) { return std::move(item); });
+}
+
+template <typename T>
 void cmAppend(std::vector<T*>& v, std::vector<std::unique_ptr<T>> const& r)
 {
   std::transform(r.begin(), r.end(), std::back_inserter(v),
diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx
index 91cd4ef..ac48287 100644
--- a/Source/cmForEachCommand.cxx
+++ b/Source/cmForEachCommand.cxx
@@ -3,12 +3,15 @@
 #include "cmForEachCommand.h"
 
 #include <algorithm>
+#include <cassert>
 #include <cstddef>
 // NOTE The declaration of `std::abs` has moved to `cmath` since C++17
 // See https://en.cppreference.com/w/cpp/numeric/math/abs
 // ALERT But IWYU used to lint `#include`s do not "understand"
 // conditional compilation (i.e. `#if __cplusplus >= 201703L`)
 #include <cstdlib>
+#include <iterator>
+#include <map>
 #include <utility>
 
 #include <cm/memory>
@@ -29,7 +32,7 @@
 class cmForEachFunctionBlocker : public cmFunctionBlocker
 {
 public:
-  cmForEachFunctionBlocker(cmMakefile* mf);
+  explicit cmForEachFunctionBlocker(cmMakefile* mf);
   ~cmForEachFunctionBlocker() override;
 
   cm::string_view StartCommandName() const override { return "foreach"_s; }
@@ -41,10 +44,33 @@
   bool Replay(std::vector<cmListFileFunction> functions,
               cmExecutionStatus& inStatus) override;
 
+  void SetIterationVarsCount(const std::size_t varsCount)
+  {
+    this->IterationVarsCount = varsCount;
+  }
+  void SetZipLists() { this->ZipLists = true; }
+
   std::vector<std::string> Args;
 
 private:
+  struct InvokeResult
+  {
+    bool Restore;
+    bool Break;
+  };
+
+  bool ReplayItems(std::vector<cmListFileFunction> const& functions,
+                   cmExecutionStatus& inStatus);
+
+  bool ReplayZipLists(std::vector<cmListFileFunction> const& functions,
+                      cmExecutionStatus& inStatus);
+
+  InvokeResult invoke(std::vector<cmListFileFunction> const& functions,
+                      cmExecutionStatus& inStatus, cmMakefile& mf);
+
   cmMakefile* Makefile;
+  std::size_t IterationVarsCount = 0u;
+  bool ZipLists = false;
 };
 
 cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
@@ -70,70 +96,240 @@
 bool cmForEachFunctionBlocker::Replay(
   std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
 {
-  cmMakefile& mf = inStatus.GetMakefile();
-  // at end of for each execute recorded commands
+  return this->ZipLists ? this->ReplayZipLists(functions, inStatus)
+                        : this->ReplayItems(functions, inStatus);
+}
+
+bool cmForEachFunctionBlocker::ReplayItems(
+  std::vector<cmListFileFunction> const& functions,
+  cmExecutionStatus& inStatus)
+{
+  assert("Unexpected number of iteration variables" &&
+         this->IterationVarsCount == 1);
+
+  auto& mf = inStatus.GetMakefile();
+
+  // At end of for each execute recorded commands
   // store the old value
   std::string oldDef;
   if (mf.GetDefinition(this->Args.front())) {
     oldDef = mf.GetDefinition(this->Args.front());
   }
 
+  auto restore = false;
   for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
-    // set the variable to the loop value
+    // Set the variable to the loop value
     mf.AddDefinition(this->Args.front(), arg);
     // Invoke all the functions that were collected in the block.
-    for (cmListFileFunction const& func : functions) {
-      cmExecutionStatus status(mf);
-      mf.ExecuteCommand(func, status);
-      if (status.GetReturnInvoked()) {
-        inStatus.SetReturnInvoked();
-        // restore the variable to its prior value
-        mf.AddDefinition(this->Args.front(), oldDef);
-        return true;
-      }
-      if (status.GetBreakInvoked()) {
-        // restore the variable to its prior value
-        mf.AddDefinition(this->Args.front(), oldDef);
-        return true;
-      }
-      if (status.GetContinueInvoked()) {
-        break;
-      }
-      if (cmSystemTools::GetFatalErrorOccured()) {
-        return true;
-      }
+    auto r = this->invoke(functions, inStatus, mf);
+    restore = r.Restore;
+    if (r.Break) {
+      break;
     }
   }
 
-  // restore the variable to its prior value
-  mf.AddDefinition(this->Args.front(), oldDef);
+  if (restore) {
+    // restore the variable to its prior value
+    mf.AddDefinition(this->Args.front(), oldDef);
+  }
   return true;
 }
 
-bool HandleInMode(std::vector<std::string> const& args, cmMakefile& makefile)
+bool cmForEachFunctionBlocker::ReplayZipLists(
+  std::vector<cmListFileFunction> const& functions,
+  cmExecutionStatus& inStatus)
 {
+  assert("Unexpected number of iteration variables" &&
+         this->IterationVarsCount >= 1);
+
+  auto& mf = inStatus.GetMakefile();
+
+  // Expand the list of list-variables into a list of lists of strings
+  std::vector<std::vector<std::string>> values;
+  values.reserve(this->Args.size() - this->IterationVarsCount);
+  // Also track the longest list size
+  std::size_t maxItems = 0u;
+  for (auto const& var :
+       cmMakeRange(this->Args).advance(this->IterationVarsCount)) {
+    std::vector<std::string> items;
+    auto const& value = mf.GetSafeDefinition(var);
+    if (!value.empty()) {
+      cmExpandList(value, items, true);
+    }
+    maxItems = std::max(maxItems, items.size());
+    values.emplace_back(std::move(items));
+  }
+
+  // Form the list of iteration variables
+  std::vector<std::string> iterationVars;
+  if (this->IterationVarsCount > 1) {
+    // If multiple iteration variables has given,
+    // just copy them to the `iterationVars` list.
+    iterationVars.reserve(values.size());
+    std::copy(this->Args.begin(),
+              this->Args.begin() + this->IterationVarsCount,
+              std::back_inserter(iterationVars));
+  } else {
+    // In case of the only iteration variable,
+    // generate names as `var_name_N`,
+    // where `N` is the count of lists to zip
+    iterationVars.resize(values.size());
+    const auto iter_var_prefix = this->Args.front() + "_";
+    auto i = 0u;
+    std::generate(
+      iterationVars.begin(), iterationVars.end(),
+      [&]() -> std::string { return iter_var_prefix + std::to_string(i++); });
+  }
+  assert("Sanity check" && iterationVars.size() == values.size());
+
+  // Store old values for iteration variables
+  std::map<std::string, std::string> oldDefs;
+  for (auto i = 0u; i < values.size(); ++i) {
+    if (mf.GetDefinition(iterationVars[i])) {
+      oldDefs.emplace(iterationVars[i], mf.GetDefinition(iterationVars[i]));
+    }
+  }
+
+  // Form a vector of current positions in all lists (Ok, vectors) of values
+  std::vector<decltype(values)::value_type::iterator> positions;
+  positions.reserve(values.size());
+  std::transform(
+    values.begin(), values.end(), std::back_inserter(positions),
+    // Set the initial position to the beginning of every list
+    [](decltype(values)::value_type& list) { return list.begin(); });
+  assert("Sanity check" && positions.size() == values.size());
+
+  auto restore = false;
+  // Iterate over all the lists simulateneously
+  for (auto i = 0u; i < maxItems; ++i) {
+    // Declare iteration variables
+    for (auto j = 0u; j < values.size(); ++j) {
+      // Define (or not) the iteration variable if the current position
+      // still not at the end...
+      if (positions[j] != values[j].end()) {
+        mf.AddDefinition(iterationVars[j], *positions[j]);
+        ++positions[j];
+      } else {
+        mf.RemoveDefinition(iterationVars[j]);
+      }
+    }
+    // Invoke all the functions that were collected in the block.
+    auto r = this->invoke(functions, inStatus, mf);
+    restore = r.Restore;
+    if (r.Break) {
+      break;
+    }
+  }
+
+  // Restore the variables to its prior value
+  if (restore) {
+    for (auto const& p : oldDefs) {
+      mf.AddDefinition(p.first, p.second);
+    }
+  }
+  return true;
+}
+
+auto cmForEachFunctionBlocker::invoke(
+  std::vector<cmListFileFunction> const& functions,
+  cmExecutionStatus& inStatus, cmMakefile& mf) -> InvokeResult
+{
+  InvokeResult result = { true, false };
+  // Invoke all the functions that were collected in the block.
+  for (cmListFileFunction const& func : functions) {
+    cmExecutionStatus status(mf);
+    mf.ExecuteCommand(func, status);
+    if (status.GetReturnInvoked()) {
+      inStatus.SetReturnInvoked();
+      result.Break = true;
+      break;
+    }
+    if (status.GetBreakInvoked()) {
+      result.Break = true;
+      break;
+    }
+    if (status.GetContinueInvoked()) {
+      break;
+    }
+    if (cmSystemTools::GetFatalErrorOccured()) {
+      result.Restore = false;
+      result.Break = true;
+      break;
+    }
+  }
+  return result;
+}
+
+bool HandleInMode(std::vector<std::string> const& args,
+                  std::vector<std::string>::const_iterator kwInIter,
+                  cmMakefile& makefile)
+{
+  assert("A valid iterator expected" && kwInIter != args.end());
+
   auto fb = cm::make_unique<cmForEachFunctionBlocker>(&makefile);
-  fb->Args.push_back(args.front());
+
+  // Copy iteration variable names first
+  std::copy(args.begin(), kwInIter, std::back_inserter(fb->Args));
+  // Remember the count of given iteration variable names
+  const auto varsCount = fb->Args.size();
+  fb->SetIterationVarsCount(varsCount);
 
   enum Doing
   {
     DoingNone,
     DoingLists,
-    DoingItems
+    DoingItems,
+    DoingZipLists
   };
   Doing doing = DoingNone;
-  for (std::string const& arg : cmMakeRange(args).advance(2)) {
+  // Iterate over arguments past the "IN" keyword
+  for (std::string const& arg : cmMakeRange(++kwInIter, args.end())) {
     if (arg == "LISTS") {
+      if (doing == DoingZipLists) {
+        makefile.IssueMessage(MessageType::FATAL_ERROR,
+                              "ZIP_LISTS can not be used with LISTS or ITEMS");
+        return true;
+      }
+      if (varsCount != 1u) {
+        makefile.IssueMessage(
+          MessageType::FATAL_ERROR,
+          "ITEMS or LISTS require exactly one iteration variable");
+        return true;
+      }
       doing = DoingLists;
+
     } else if (arg == "ITEMS") {
+      if (doing == DoingZipLists) {
+        makefile.IssueMessage(MessageType::FATAL_ERROR,
+                              "ZIP_LISTS can not be used with LISTS or ITEMS");
+        return true;
+      }
+      if (varsCount != 1u) {
+        makefile.IssueMessage(
+          MessageType::FATAL_ERROR,
+          "ITEMS or LISTS require exactly one iteration variable");
+        return true;
+      }
       doing = DoingItems;
+
+    } else if (arg == "ZIP_LISTS") {
+      if (doing != DoingNone) {
+        makefile.IssueMessage(MessageType::FATAL_ERROR,
+                              "ZIP_LISTS can not be used with LISTS or ITEMS");
+        return true;
+      }
+      doing = DoingZipLists;
+      fb->SetZipLists();
+
     } else if (doing == DoingLists) {
       auto const& value = makefile.GetSafeDefinition(arg);
       if (!value.empty()) {
         cmExpandList(value, fb->Args, true);
       }
-    } else if (doing == DoingItems) {
+
+    } else if (doing == DoingItems || doing == DoingZipLists) {
       fb->Args.push_back(arg);
+
     } else {
       makefile.IssueMessage(MessageType::FATAL_ERROR,
                             cmStrCat("Unknown argument:\n", "  ", arg, "\n"));
@@ -141,6 +337,18 @@
     }
   }
 
+  // If `ZIP_LISTS` given and variables count more than 1,
+  // make sure the given lists count matches variables...
+  if (doing == DoingZipLists && varsCount > 1u &&
+      (2u * varsCount) != fb->Args.size()) {
+    makefile.IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Expected ", std::to_string(varsCount),
+               " list variables, but given ",
+               std::to_string(fb->Args.size() - varsCount)));
+    return true;
+  }
+
   makefile.AddFunctionBlocker(std::move(fb));
 
   return true;
@@ -155,8 +363,9 @@
     status.SetError("called with incorrect number of arguments");
     return false;
   }
-  if (args.size() > 1 && args[1] == "IN") {
-    return HandleInMode(args, status.GetMakefile());
+  auto kwInIter = std::find(args.begin(), args.end(), "IN");
+  if (kwInIter != args.end()) {
+    return HandleInMode(args, kwInIter, status.GetMakefile());
   }
 
   // create a function blocker
@@ -216,6 +425,8 @@
   } else {
     fb->Args = args;
   }
+
+  fb->SetIterationVarsCount(1u);
   status.GetMakefile().AddFunctionBlocker(std::move(fb));
 
   return true;
diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx
index de43d3e..81d1e46 100644
--- a/Source/cmGeneratorExpression.cxx
+++ b/Source/cmGeneratorExpression.cxx
@@ -8,7 +8,6 @@
 
 #include "cmsys/RegularExpression.hxx"
 
-#include "cmAlgorithms.h"
 #include "cmGeneratorExpressionContext.h"
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorExpressionEvaluator.h"
@@ -22,6 +21,8 @@
 {
 }
 
+cmCompiledGeneratorExpression::~cmCompiledGeneratorExpression() = default;
+
 cmGeneratorExpression::~cmGeneratorExpression() = default;
 
 std::unique_ptr<cmCompiledGeneratorExpression> cmGeneratorExpression::Parse(
@@ -86,7 +87,7 @@
 
   this->Output.clear();
 
-  for (const cmGeneratorExpressionEvaluator* it : this->Evaluators) {
+  for (const auto& it : this->Evaluators) {
     this->Output += it->Evaluate(&context, dagChecker);
 
     this->SeenTargetProperties.insert(context.SeenTargetProperties.cbegin(),
@@ -129,11 +130,6 @@
   }
 }
 
-cmCompiledGeneratorExpression::~cmCompiledGeneratorExpression()
-{
-  cmDeleteAll(this->Evaluators);
-}
-
 std::string cmGeneratorExpression::StripEmptyListElements(
   const std::string& input)
 {
diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h
index cd35e1e..c4be3a1 100644
--- a/Source/cmGeneratorExpression.h
+++ b/Source/cmGeneratorExpression.h
@@ -163,7 +163,7 @@
   friend class cmGeneratorExpression;
 
   cmListFileBacktrace Backtrace;
-  std::vector<cmGeneratorExpressionEvaluator*> Evaluators;
+  std::vector<std::unique_ptr<cmGeneratorExpressionEvaluator>> Evaluators;
   const std::string Input;
   bool NeedsEvaluation;
   bool EvaluateForBuildsystem;
diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx
index e0ae170..4129a0c 100644
--- a/Source/cmGeneratorExpressionEvaluator.cxx
+++ b/Source/cmGeneratorExpressionEvaluator.cxx
@@ -2,10 +2,8 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmGeneratorExpressionEvaluator.h"
 
-#include <algorithm>
 #include <sstream>
 
-#include "cmAlgorithms.h"
 #include "cmGeneratorExpressionContext.h"
 #include "cmGeneratorExpressionNode.h"
 
@@ -16,6 +14,8 @@
 {
 }
 
+GeneratorExpressionContent::~GeneratorExpressionContent() = default;
+
 std::string GeneratorExpressionContent::GetOriginalExpression() const
 {
   return std::string(this->StartContent, this->ContentLength);
@@ -25,14 +25,13 @@
   const cmGeneratorExpressionNode* node, const std::string& identifier,
   cmGeneratorExpressionContext* context,
   cmGeneratorExpressionDAGChecker* dagChecker,
-  std::vector<std::vector<cmGeneratorExpressionEvaluator*>>::const_iterator
-    pit) const
+  std::vector<cmGeneratorExpressionEvaluatorVector>::const_iterator pit) const
 {
   std::string result;
 
   const auto pend = this->ParamChildren.end();
   for (; pit != pend; ++pit) {
-    for (cmGeneratorExpressionEvaluator* pExprEval : *pit) {
+    for (auto& pExprEval : *pit) {
       if (node->RequiresLiteralInput()) {
         if (pExprEval->GetType() != cmGeneratorExpressionEvaluator::Text) {
           reportError(context, this->GetOriginalExpression(),
@@ -64,8 +63,7 @@
 {
   std::string identifier;
   {
-    for (cmGeneratorExpressionEvaluator* pExprEval :
-         this->IdentifierChildren) {
+    for (auto& pExprEval : this->IdentifierChildren) {
       identifier += pExprEval->Evaluate(context, dagChecker);
       if (context->HadError) {
         return std::string();
@@ -126,7 +124,7 @@
         return std::string();
       }
       std::string parameter;
-      for (cmGeneratorExpressionEvaluator* pExprEval : *pit) {
+      for (auto& pExprEval : *pit) {
         parameter += pExprEval->Evaluate(context, dagChecker);
         if (context->HadError) {
           return std::string();
@@ -174,10 +172,3 @@
   }
   return std::string();
 }
-
-GeneratorExpressionContent::~GeneratorExpressionContent()
-{
-  cmDeleteAll(this->IdentifierChildren);
-  std::for_each(this->ParamChildren.begin(), this->ParamChildren.end(),
-                cmDeleteAll<std::vector<cmGeneratorExpressionEvaluator*>>);
-}
diff --git a/Source/cmGeneratorExpressionEvaluator.h b/Source/cmGeneratorExpressionEvaluator.h
index b10bb5b..10496fd 100644
--- a/Source/cmGeneratorExpressionEvaluator.h
+++ b/Source/cmGeneratorExpressionEvaluator.h
@@ -6,6 +6,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <cstddef>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -36,6 +37,9 @@
                                cmGeneratorExpressionDAGChecker*) const = 0;
 };
 
+using cmGeneratorExpressionEvaluatorVector =
+  std::vector<std::unique_ptr<cmGeneratorExpressionEvaluator>>;
+
 struct TextContent : public cmGeneratorExpressionEvaluator
 {
   TextContent(const char* start, size_t length)
@@ -68,13 +72,13 @@
 {
   GeneratorExpressionContent(const char* startContent, size_t length);
 
-  void SetIdentifier(std::vector<cmGeneratorExpressionEvaluator*> identifier)
+  void SetIdentifier(cmGeneratorExpressionEvaluatorVector&& identifier)
   {
     this->IdentifierChildren = std::move(identifier);
   }
 
   void SetParameters(
-    std::vector<std::vector<cmGeneratorExpressionEvaluator*>> parameters)
+    std::vector<cmGeneratorExpressionEvaluatorVector>&& parameters)
   {
     this->ParamChildren = std::move(parameters);
   }
@@ -102,12 +106,12 @@
     const cmGeneratorExpressionNode* node, const std::string& identifier,
     cmGeneratorExpressionContext* context,
     cmGeneratorExpressionDAGChecker* dagChecker,
-    std::vector<std::vector<cmGeneratorExpressionEvaluator*>>::const_iterator
-      pit) const;
+    std::vector<cmGeneratorExpressionEvaluatorVector>::const_iterator pit)
+    const;
 
 private:
-  std::vector<cmGeneratorExpressionEvaluator*> IdentifierChildren;
-  std::vector<std::vector<cmGeneratorExpressionEvaluator*>> ParamChildren;
+  cmGeneratorExpressionEvaluatorVector IdentifierChildren;
+  std::vector<cmGeneratorExpressionEvaluatorVector> ParamChildren;
   const char* StartContent;
   size_t ContentLength;
 };
diff --git a/Source/cmGeneratorExpressionParser.cxx b/Source/cmGeneratorExpressionParser.cxx
index d6cc6ab..4159a7b 100644
--- a/Source/cmGeneratorExpressionParser.cxx
+++ b/Source/cmGeneratorExpressionParser.cxx
@@ -6,6 +6,9 @@
 #include <cstddef>
 #include <utility>
 
+#include <cm/memory>
+#include <cmext/memory>
+
 #include "cmAlgorithms.h"
 #include "cmGeneratorExpressionEvaluator.h"
 
@@ -17,7 +20,7 @@
 }
 
 void cmGeneratorExpressionParser::Parse(
-  std::vector<cmGeneratorExpressionEvaluator*>& result)
+  cmGeneratorExpressionEvaluatorVector& result)
 {
   it = this->Tokens.begin();
 
@@ -27,40 +30,38 @@
 }
 
 static void extendText(
-  std::vector<cmGeneratorExpressionEvaluator*>& result,
+  cmGeneratorExpressionEvaluatorVector& result,
   std::vector<cmGeneratorExpressionToken>::const_iterator it)
 {
   if (!result.empty() &&
       (*(result.end() - 1))->GetType() ==
         cmGeneratorExpressionEvaluator::Text) {
-    TextContent* textContent = static_cast<TextContent*>(*(result.end() - 1));
-    textContent->Extend(it->Length);
+    cm::static_reference_cast<TextContent>(*(result.end() - 1))
+      .Extend(it->Length);
   } else {
-    TextContent* textContent = new TextContent(it->Content, it->Length);
-    result.push_back(textContent);
+    auto textContent = cm::make_unique<TextContent>(it->Content, it->Length);
+    result.push_back(std::move(textContent));
   }
 }
 
 static void extendResult(
-  std::vector<cmGeneratorExpressionEvaluator*>& result,
-  const std::vector<cmGeneratorExpressionEvaluator*>& contents)
+  cmGeneratorExpressionParser::cmGeneratorExpressionEvaluatorVector& result,
+  cmGeneratorExpressionParser::cmGeneratorExpressionEvaluatorVector&& contents)
 {
   if (!result.empty() &&
       (*(result.end() - 1))->GetType() ==
         cmGeneratorExpressionEvaluator::Text &&
       contents.front()->GetType() == cmGeneratorExpressionEvaluator::Text) {
-    TextContent* textContent = static_cast<TextContent*>(*(result.end() - 1));
-    textContent->Extend(
-      static_cast<TextContent*>(contents.front())->GetLength());
-    delete contents.front();
-    cmAppend(result, contents.begin() + 1, contents.end());
-  } else {
-    cmAppend(result, contents);
+    cm::static_reference_cast<TextContent>(*(result.end() - 1))
+      .Extend(
+        cm::static_reference_cast<TextContent>(contents.front()).GetLength());
+    contents.erase(contents.begin());
   }
+  cmAppend(result, std::move(contents));
 }
 
 void cmGeneratorExpressionParser::ParseGeneratorExpression(
-  std::vector<cmGeneratorExpressionEvaluator*>& result)
+  cmGeneratorExpressionEvaluatorVector& result)
 {
   assert(this->it != this->Tokens.end());
   unsigned int nestedLevel = this->NestingLevel;
@@ -68,7 +69,7 @@
 
   auto startToken = this->it - 1;
 
-  std::vector<cmGeneratorExpressionEvaluator*> identifier;
+  cmGeneratorExpressionEvaluatorVector identifier;
   while (this->it->TokenType != cmGeneratorExpressionToken::EndExpression &&
          this->it->TokenType != cmGeneratorExpressionToken::ColonSeparator) {
     if (this->it->TokenType == cmGeneratorExpressionToken::CommaSeparator) {
@@ -87,18 +88,18 @@
 
   if (this->it != this->Tokens.end() &&
       this->it->TokenType == cmGeneratorExpressionToken::EndExpression) {
-    GeneratorExpressionContent* content = new GeneratorExpressionContent(
+    auto content = cm::make_unique<GeneratorExpressionContent>(
       startToken->Content,
       this->it->Content - startToken->Content + this->it->Length);
     assert(this->it != this->Tokens.end());
     ++this->it;
     --this->NestingLevel;
     content->SetIdentifier(std::move(identifier));
-    result.push_back(content);
+    result.push_back(std::move(content));
     return;
   }
 
-  std::vector<std::vector<cmGeneratorExpressionEvaluator*>> parameters;
+  std::vector<cmGeneratorExpressionEvaluatorVector> parameters;
   std::vector<std::vector<cmGeneratorExpressionToken>::const_iterator>
     commaTokens;
   std::vector<cmGeneratorExpressionToken>::const_iterator colonToken;
@@ -169,7 +170,7 @@
     // treat the '$<' as having been plain text, along with the
     // corresponding : and , tokens that might have been found.
     extendText(result, startToken);
-    extendResult(result, identifier);
+    extendResult(result, std::move(identifier));
     if (!parameters.empty()) {
       extendText(result, colonToken);
 
@@ -179,7 +180,7 @@
       assert(parameters.size() > commaTokens.size());
       for (; pit != pend; ++pit, ++commaIt) {
         if (!pit->empty() && !emptyParamTermination) {
-          extendResult(result, *pit);
+          extendResult(result, std::move(*pit));
         }
         if (commaIt != commaTokens.end()) {
           extendText(result, *commaIt);
@@ -193,15 +194,15 @@
 
   size_t contentLength =
     ((this->it - 1)->Content - startToken->Content) + (this->it - 1)->Length;
-  GeneratorExpressionContent* content =
-    new GeneratorExpressionContent(startToken->Content, contentLength);
+  auto content = cm::make_unique<GeneratorExpressionContent>(
+    startToken->Content, contentLength);
   content->SetIdentifier(std::move(identifier));
   content->SetParameters(std::move(parameters));
-  result.push_back(content);
+  result.push_back(std::move(content));
 }
 
 void cmGeneratorExpressionParser::ParseContent(
-  std::vector<cmGeneratorExpressionEvaluator*>& result)
+  cmGeneratorExpressionEvaluatorVector& result)
 {
   assert(this->it != this->Tokens.end());
   switch (this->it->TokenType) {
@@ -213,17 +214,16 @@
           // A comma in 'plain text' could have split text that should
           // otherwise be continuous. Extend the last text content instead of
           // creating a new one.
-          TextContent* textContent =
-            static_cast<TextContent*>(*(result.end() - 1));
-          textContent->Extend(this->it->Length);
+          cm::static_reference_cast<TextContent>(*(result.end() - 1))
+            .Extend(this->it->Length);
           assert(this->it != this->Tokens.end());
           ++this->it;
           return;
         }
       }
-      cmGeneratorExpressionEvaluator* n =
-        new TextContent(this->it->Content, this->it->Length);
-      result.push_back(n);
+      auto n =
+        cm::make_unique<TextContent>(this->it->Content, this->it->Length);
+      result.push_back(std::move(n));
       assert(this->it != this->Tokens.end());
       ++this->it;
       return;
diff --git a/Source/cmGeneratorExpressionParser.h b/Source/cmGeneratorExpressionParser.h
index e663496..1ba1654 100644
--- a/Source/cmGeneratorExpressionParser.h
+++ b/Source/cmGeneratorExpressionParser.h
@@ -5,6 +5,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <vector>
 
 #include "cmGeneratorExpressionLexer.h"
@@ -15,11 +16,14 @@
 {
   cmGeneratorExpressionParser(std::vector<cmGeneratorExpressionToken> tokens);
 
-  void Parse(std::vector<cmGeneratorExpressionEvaluator*>& result);
+  using cmGeneratorExpressionEvaluatorVector =
+    std::vector<std::unique_ptr<cmGeneratorExpressionEvaluator>>;
+
+  void Parse(cmGeneratorExpressionEvaluatorVector& result);
 
 private:
-  void ParseContent(std::vector<cmGeneratorExpressionEvaluator*>&);
-  void ParseGeneratorExpression(std::vector<cmGeneratorExpressionEvaluator*>&);
+  void ParseContent(cmGeneratorExpressionEvaluatorVector&);
+  void ParseGeneratorExpression(cmGeneratorExpressionEvaluatorVector&);
 
 private:
   std::vector<cmGeneratorExpressionToken>::const_iterator it;
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 29593d9..a4a074f 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -2513,11 +2513,11 @@
   info.WindowsExportAllSymbols =
     this->Makefile->IsOn("CMAKE_SUPPORT_WINDOWS_EXPORT_ALL_SYMBOLS") &&
     this->GetPropertyAsBool("WINDOWS_EXPORT_ALL_SYMBOLS");
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
+#if !defined(CMAKE_BOOTSTRAP)
   info.DefFileGenerated =
     info.WindowsExportAllSymbols || info.Sources.size() > 1;
 #else
-  // Our __create_def helper is only available on Windows.
+  // Our __create_def helper is not available during CMake bootstrap.
   info.DefFileGenerated = false;
 #endif
   if (info.DefFileGenerated) {
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 8dfc0ce..0e782f2 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1566,13 +1566,24 @@
   for (cmLocalGenerator* lg : this->LocalGenerators) {
     lg->CreateEvaluationFileOutputs();
     for (const auto& gt : lg->GetGeneratorTargets()) {
-      if (gt->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+      if (gt->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
+          gt->GetType() == cmStateEnums::UTILITY ||
+          gt->GetType() == cmStateEnums::GLOBAL_TARGET) {
         continue;
       }
       lg->AddUnityBuild(gt.get());
       lg->AddPchDependencies(gt.get());
     }
   }
+  // The above transformations may have changed the classification of sources.
+  // Clear the source list and classification cache (KindedSources) of all
+  // targets so that it will be recomputed correctly by the generators later
+  // now that the above transformations are done for all targets.
+  for (cmLocalGenerator* lg : this->LocalGenerators) {
+    for (const auto& gt : lg->GetGeneratorTargets()) {
+      gt->ClearSourcesCache();
+    }
+  }
   return true;
 }
 
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 7451ff3..c58603f 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -2393,6 +2393,7 @@
   }
   const std::string buildType = cmSystemTools::UpperCase(config);
 
+  // FIXME: Refactor collection of sources to not evaluate object libraries.
   std::vector<cmSourceFile*> sources;
   target->GetSourceFiles(sources, buildType);
 
@@ -2467,9 +2468,25 @@
                 target->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/",
                 target->GetName(), ".dir/${PDB_PREFIX}");
 
-              file << "if (EXISTS \"" << from_file << "\")\n";
+              const std::string to_file =
+                cmStrCat(to_dir, pchReuseFrom, extension);
+
+              std::string dest_file = to_file;
+
+              const std::string prefix = target->GetSafeProperty("PREFIX");
+              if (!prefix.empty()) {
+                dest_file = cmStrCat(to_dir, prefix, pchReuseFrom, extension);
+              }
+
+              file << "if (EXISTS \"" << from_file << "\" AND \"" << from_file
+                   << "\" IS_NEWER_THAN \"" << dest_file << "\")\n";
               file << "  file(COPY \"" << from_file << "\""
                    << " DESTINATION \"" << to_dir << "\")\n";
+              if (!prefix.empty()) {
+                file << "  file(REMOVE \"" << dest_file << "\")\n";
+                file << "  file(RENAME \"" << to_file << "\" \"" << dest_file
+                     << "\")\n";
+              }
               file << "endif()\n";
             }
 
@@ -2549,6 +2566,7 @@
     cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles/",
              target->GetName(), ".dir/Unity/");
 
+  // FIXME: Refactor collection of sources to not evaluate object libraries.
   std::vector<cmSourceFile*> sources;
   target->GetSourceFiles(sources, buildType);
 
@@ -2598,12 +2616,7 @@
         for (; begin != end; ++begin) {
           cmSourceFile* sf = filtered_sources[begin];
 
-          // Only in Visual Studio generator we keep the source files
-          // for explicit processing.
-          if (!this->GetGlobalGenerator()->IsMultiConfig() ||
-              this->GetGlobalGenerator()->IsXcode()) {
-            target->AddSourceFileToUnityBatch(sf->ResolveFullPath());
-          }
+          target->AddSourceFileToUnityBatch(sf->ResolveFullPath());
           sf->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
 
           if (beforeInclude) {
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 5ffa576..8fc40a79 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -2160,7 +2160,6 @@
         this->WriteExtraSource(e1, si.Source);
         break;
       case cmGeneratorTarget::SourceKindHeader:
-      case cmGeneratorTarget::SourceKindUnityBatched:
         this->WriteHeaderSource(e1, si.Source);
         break;
       case cmGeneratorTarget::SourceKindIDL:
@@ -2172,6 +2171,7 @@
       case cmGeneratorTarget::SourceKindModuleDefinition:
         tool = "None";
         break;
+      case cmGeneratorTarget::SourceKindUnityBatched:
       case cmGeneratorTarget::SourceKindObjectSource: {
         const std::string& lang = si.Source->GetLanguage();
         if (lang == "C" || lang == "CXX") {
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index c2fbb43..d6df49c 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -21,16 +21,15 @@
 
 #if !defined(CMAKE_BOOTSTRAP)
 #  include "cmDependsFortran.h" // For -E cmake_copy_f90_mod callback.
+#  include "cmFileTime.h"
 #  include "cmServer.h"
 #  include "cmServerConnection.h"
+
+#  include "bindexplib.h"
 #endif
 
 #if !defined(CMAKE_BOOTSTRAP) && defined(_WIN32)
 #  include "cmsys/ConsoleBuf.hxx"
-
-#  include "cmFileTime.h"
-
-#  include "bindexplib.h"
 #endif
 
 #if !defined(CMAKE_BOOTSTRAP) && defined(_WIN32) && !defined(__CYGWIN__)
@@ -581,11 +580,11 @@
       return 0;
     }
 
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
-    else if (args[1] == "__create_def") {
+#if !defined(CMAKE_BOOTSTRAP)
+    if (args[1] == "__create_def") {
       if (args.size() < 4) {
         std::cerr << "__create_def Usage: -E __create_def outfile.def "
-                     "objlistfile [-nm=nm-path]\n";
+                     "objlistfile [--nm=nm-path]\n";
         return 1;
       }
       cmsys::ifstream fin(args[3].c_str(), std::ios::in | std::ios::binary);
@@ -612,7 +611,7 @@
           return 0;
         }
       }
-      FILE* fout = cmsys::SystemTools::Fopen(args[2].c_str(), "w+");
+      FILE* fout = cmsys::SystemTools::Fopen(args[2], "w+");
       if (!fout) {
         std::cerr << "could not open output .def file: " << args[2].c_str()
                   << "\n";
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index b45782e..0d9e43f 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -1405,6 +1405,7 @@
             ICU
             JPEG
             JsonCpp
+            LibArchive
             LibLZMA
             LibRHash
             Libinput
diff --git a/Tests/FindLibArchive/CMakeLists.txt b/Tests/FindLibArchive/CMakeLists.txt
new file mode 100644
index 0000000..f532ef2
--- /dev/null
+++ b/Tests/FindLibArchive/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_test(NAME FindLibArchive.Test COMMAND
+  ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+  --build-and-test
+  "${CMake_SOURCE_DIR}/Tests/FindLibArchive/Test"
+  "${CMake_BINARY_DIR}/Tests/FindLibArchive/Test"
+  ${build_generator_args}
+  --build-project TestFindLibArchive
+  --build-options ${build_options}
+  --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+  )
diff --git a/Tests/FindLibArchive/Test/CMakeLists.txt b/Tests/FindLibArchive/Test/CMakeLists.txt
new file mode 100644
index 0000000..35843bb
--- /dev/null
+++ b/Tests/FindLibArchive/Test/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.12)
+project(TestFindLibArchive C)
+include(CTest)
+
+find_package(LibArchive REQUIRED)
+
+add_executable(test_libarchive_tgt main.c)
+target_link_libraries(test_libarchive_tgt LibArchive::LibArchive)
+add_test(NAME test_libarchive_tgt COMMAND test_libarchive_tgt)
+
+add_executable(test_libarchive_var main.c)
+target_include_directories(test_libarchive_var PRIVATE ${LibArchive_INCLUDE_DIRS})
+target_link_libraries(test_libarchive_var PRIVATE ${LibArchive_LIBRARIES})
+add_test(NAME test_libarchive_var COMMAND test_libarchive_var)
diff --git a/Tests/FindLibArchive/Test/main.c b/Tests/FindLibArchive/Test/main.c
new file mode 100644
index 0000000..03e7ece
--- /dev/null
+++ b/Tests/FindLibArchive/Test/main.c
@@ -0,0 +1,7 @@
+#include <archive.h>
+
+int main(void)
+{
+  archive_read_free(archive_read_new());
+  return 0;
+}
diff --git a/Tests/RunCMake/CompilerLauncher/C-common.cmake b/Tests/RunCMake/CompilerLauncher/C-common.cmake
new file mode 100644
index 0000000..96b004b
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/C-common.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+add_executable(main main.c)
diff --git a/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/C-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-env-launch-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/C-env-launch-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/C-env.cmake b/Tests/RunCMake/CompilerLauncher/C-env.cmake
new file mode 100644
index 0000000..09b5167
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/C-env.cmake
@@ -0,0 +1 @@
+include(C-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/C-launch-env.cmake b/Tests/RunCMake/CompilerLauncher/C-launch-env.cmake
new file mode 100644
index 0000000..68abcb5
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/C-launch-env.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(C-env.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/C.cmake b/Tests/RunCMake/CompilerLauncher/C.cmake
index 67bf7c4..481e74d 100644
--- a/Tests/RunCMake/CompilerLauncher/C.cmake
+++ b/Tests/RunCMake/CompilerLauncher/C.cmake
@@ -1,4 +1,2 @@
-enable_language(C)
 set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
-set(CMAKE_VERBOSE_MAKEFILE TRUE)
-add_executable(main main.c)
+include(C-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-common.cmake b/Tests/RunCMake/CompilerLauncher/CUDA-common.cmake
new file mode 100644
index 0000000..6f7fc86
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-common.cmake
@@ -0,0 +1,3 @@
+enable_language(CUDA)
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+add_executable(main main.cu)
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CUDA-env-launch-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-env-launch-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-env.cmake b/Tests/RunCMake/CompilerLauncher/CUDA-env.cmake
new file mode 100644
index 0000000..cefbe9e
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-env.cmake
@@ -0,0 +1 @@
+include(CUDA-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-launch-env.cmake b/Tests/RunCMake/CompilerLauncher/CUDA-launch-env.cmake
new file mode 100644
index 0000000..d0d777a
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-launch-env.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(CUDA-env.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA.cmake b/Tests/RunCMake/CompilerLauncher/CUDA.cmake
index fe5560b..7f1652b 100644
--- a/Tests/RunCMake/CompilerLauncher/CUDA.cmake
+++ b/Tests/RunCMake/CompilerLauncher/CUDA.cmake
@@ -1,4 +1,2 @@
-enable_language(CUDA)
 set(CMAKE_CUDA_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
-set(CMAKE_VERBOSE_MAKEFILE TRUE)
-add_executable(main main.cu)
+include(CUDA-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-common.cmake b/Tests/RunCMake/CompilerLauncher/CXX-common.cmake
new file mode 100644
index 0000000..3d2ee00
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CXX-common.cmake
@@ -0,0 +1,3 @@
+enable_language(CXX)
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+add_executable(main main.cxx)
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CXX-env-launch-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CXX-env-launch-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-env.cmake b/Tests/RunCMake/CompilerLauncher/CXX-env.cmake
new file mode 100644
index 0000000..db36956
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CXX-env.cmake
@@ -0,0 +1 @@
+include(CXX-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-launch-env.cmake b/Tests/RunCMake/CompilerLauncher/CXX-launch-env.cmake
new file mode 100644
index 0000000..a65cc89
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/CXX-launch-env.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(CXX-env.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CXX.cmake b/Tests/RunCMake/CompilerLauncher/CXX.cmake
index cdd3478..1f9a12b 100644
--- a/Tests/RunCMake/CompilerLauncher/CXX.cmake
+++ b/Tests/RunCMake/CompilerLauncher/CXX.cmake
@@ -1,4 +1,2 @@
-enable_language(CXX)
 set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
-set(CMAKE_VERBOSE_MAKEFILE TRUE)
-add_executable(main main.cxx)
+include(CXX-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-common.cmake b/Tests/RunCMake/CompilerLauncher/Fortran-common.cmake
new file mode 100644
index 0000000..e33c2ca
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-common.cmake
@@ -0,0 +1,3 @@
+enable_language(Fortran)
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+add_executable(main main.F)
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/Fortran-env-launch-Build-stdout.txt
new file mode 100644
index 0000000..3313e31
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-env-launch-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-env.cmake b/Tests/RunCMake/CompilerLauncher/Fortran-env.cmake
new file mode 100644
index 0000000..3dc27c3
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-env.cmake
@@ -0,0 +1 @@
+include(Fortran-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-launch-env.cmake b/Tests/RunCMake/CompilerLauncher/Fortran-launch-env.cmake
new file mode 100644
index 0000000..30a196c
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-launch-env.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(Fortran-env.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran.cmake b/Tests/RunCMake/CompilerLauncher/Fortran.cmake
index 72cc03e..dc46173 100644
--- a/Tests/RunCMake/CompilerLauncher/Fortran.cmake
+++ b/Tests/RunCMake/CompilerLauncher/Fortran.cmake
@@ -1,4 +1,2 @@
-enable_language(Fortran)
 set(CMAKE_Fortran_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
-set(CMAKE_VERBOSE_MAKEFILE TRUE)
-add_executable(main main.F)
+include(Fortran-common.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
index bb8da03..f86e8ea 100644
--- a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
@@ -15,6 +15,13 @@
   run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build . ${verbose_args})
 endfunction()
 
+function(run_compiler_launcher_env lang)
+  string(REGEX REPLACE "-.*" "" core_lang "${lang}")
+  set(ENV{CMAKE_${core_lang}_COMPILER_LAUNCHER} "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
+  run_compiler_launcher(${lang})
+  unset(ENV{CMAKE_${core_lang}_COMPILER_LAUNCHER})
+endfunction()
+
 set(langs C CXX)
 if(CMake_TEST_CUDA)
   list(APPEND langs CUDA)
@@ -25,7 +32,9 @@
 
 foreach(lang ${langs})
   run_compiler_launcher(${lang})
+  run_compiler_launcher_env(${lang}-env)
   if (NOT RunCMake_GENERATOR STREQUAL "Watcom WMake")
     run_compiler_launcher(${lang}-launch)
+    run_compiler_launcher_env(${lang}-launch-env)
   endif()
 endforeach()
diff --git a/Tests/RunCMake/ObjectLibrary/OwnSources-stderr.txt b/Tests/RunCMake/ObjectLibrary/OwnSources-stderr.txt
index 208f3c9..cb1a2e5 100644
--- a/Tests/RunCMake/ObjectLibrary/OwnSources-stderr.txt
+++ b/Tests/RunCMake/ObjectLibrary/OwnSources-stderr.txt
@@ -4,4 +4,10 @@
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
 +
+CMake Error at OwnSources.cmake:[0-9]+ \(add_library\):
+  The SOURCES of "A" use a generator expression that depends on the SOURCES
+  themselves.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
++
 CMake Generate step failed\.  Build files cannot be regenerated correctly\.$
diff --git a/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed-build-stderr.txt b/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed-build-stderr.txt
new file mode 100644
index 0000000..8cdcfd9
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed-build-stderr.txt
@@ -0,0 +1,2 @@
+^(|Warning #670: precompiled header file [^
+]* was not generated in this directory)$
diff --git a/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed.cmake b/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed.cmake
new file mode 100644
index 0000000..e306d8e
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/PchReuseFromPrefixed.cmake
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.15)
+project(PchReuseFromPrefixed C)
+
+if(CMAKE_C_COMPILE_OPTIONS_USE_PCH)
+  add_definitions(-DHAVE_PCH_SUPPORT)
+endif()
+
+add_library(empty empty.c)
+target_precompile_headers(empty PRIVATE
+  <stdio.h>
+  <string.h>
+)
+target_include_directories(empty PUBLIC include)
+
+add_library(foo foo.c)
+target_include_directories(foo PUBLIC include)
+target_precompile_headers(foo REUSE_FROM empty)
+
+# Visual Studio 2017 and greater
+if (NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND CMAKE_C_COMPILER_VERSION VERSION_LESS_EQUAL 19.10))
+  set_target_properties(foo PROPERTIES PREFIX "lib" IMPORT_PREFIX "lib")
+endif()
+
+add_executable(foobar foobar.c)
+target_link_libraries(foobar foo )
+set_target_properties(foobar PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM empty)
+
+enable_testing()
+add_test(NAME foobar COMMAND foobar)
diff --git a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
index ec13663..8d2f4f9 100644
--- a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
+++ b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
@@ -17,5 +17,6 @@
 run_cmake(PchPrologueEpilogue)
 run_test(SkipPrecompileHeaders)
 run_test(PchReuseFrom)
+run_test(PchReuseFromPrefixed)
 run_test(PchReuseFromSubdir)
 run_cmake(PchMultilanguage)
diff --git a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
index 8e484d0..24daa64 100644
--- a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
+++ b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
@@ -21,3 +21,4 @@
 endfunction()
 
 run_test(unitybuild_runtest)
+run_test(unitybuild_object_library)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_object_library.cmake b/Tests/RunCMake/UnityBuild/unitybuild_object_library.cmake
new file mode 100644
index 0000000..b400517
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_object_library.cmake
@@ -0,0 +1,13 @@
+project(unitybuild_object_library C)
+
+set(CMAKE_UNITY_BUILD ON) # This tests that the variable works in addition to the property
+
+add_library(lib OBJECT func.c)
+
+add_library(other-lib STATIC func.c)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE lib)
+
+enable_testing()
+add_test(NAME main COMMAND main)
diff --git a/Tests/RunCMake/foreach/RunCMakeTest.cmake b/Tests/RunCMake/foreach/RunCMakeTest.cmake
index 0f1fdd4..8f50203 100644
--- a/Tests/RunCMake/foreach/RunCMakeTest.cmake
+++ b/Tests/RunCMake/foreach/RunCMakeTest.cmake
@@ -2,3 +2,13 @@
 
 run_cmake(BadRangeInFunction)
 run_cmake(foreach-all-test)
+run_cmake(foreach-ITEMS-multiple-iter-vars-test)
+run_cmake(foreach-LISTS-multiple-iter-vars-test)
+run_cmake(foreach-ZIP_LISTS-test)
+run_cmake(foreach-ITEMS-with-ZIP_LISTS-mix-test)
+run_cmake(foreach-LISTS-with-ZIP_LISTS-mix-test)
+run_cmake(foreach-ZIP_LISTS-with-ITEMS-mix-test)
+run_cmake(foreach-ZIP_LISTS-with-LISTS-mix-test)
+run_cmake(foreach-ZIP_LISTS-multiple-iter-vars-test)
+run_cmake(foreach-ZIP_LISTS-iter-vars-mismatch-test-1)
+run_cmake(foreach-ZIP_LISTS-iter-vars-mismatch-test-2)
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-result.txt b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-stderr.txt b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-stderr.txt
new file mode 100644
index 0000000..d174bb1
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ITEMS-multiple-iter-vars-test.cmake:1 \(foreach\):
+  ITEMS or LISTS require exactly one iteration variable
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test.cmake b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test.cmake
new file mode 100644
index 0000000..55d33a8
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-multiple-iter-vars-test.cmake
@@ -0,0 +1,2 @@
+foreach(one two IN ITEMS one two)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-result.txt b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-stderr.txt b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-stderr.txt
new file mode 100644
index 0000000..f7d5ae7
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ITEMS-with-ZIP_LISTS-mix-test.cmake:1 \(foreach\):
+  ZIP_LISTS can not be used with LISTS or ITEMS
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test.cmake b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test.cmake
new file mode 100644
index 0000000..28099a0
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ITEMS-with-ZIP_LISTS-mix-test.cmake
@@ -0,0 +1,2 @@
+foreach(i IN ITEMS one two three ZIP_LISTS blah)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-result.txt b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-stderr.txt b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-stderr.txt
new file mode 100644
index 0000000..f2f83c2
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-LISTS-multiple-iter-vars-test.cmake:1 \(foreach\):
+  ITEMS or LISTS require exactly one iteration variable
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test.cmake b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test.cmake
new file mode 100644
index 0000000..78f3847
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-multiple-iter-vars-test.cmake
@@ -0,0 +1,2 @@
+foreach(one two IN LISTS one two)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-result.txt b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-stderr.txt b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-stderr.txt
new file mode 100644
index 0000000..42f8d1e
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-LISTS-with-ZIP_LISTS-mix-test.cmake:1 \(foreach\):
+  ZIP_LISTS can not be used with LISTS or ITEMS
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test.cmake b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test.cmake
new file mode 100644
index 0000000..8a919dd
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-LISTS-with-ZIP_LISTS-mix-test.cmake
@@ -0,0 +1,2 @@
+foreach(i IN LISTS one two three ZIP_LISTS blah)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-result.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-stderr.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-stderr.txt
new file mode 100644
index 0000000..fa51e46
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ZIP_LISTS-iter-vars-mismatch-test-1.cmake:1 \(foreach\):
+  Expected 3 list variables, but given 4
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1.cmake
new file mode 100644
index 0000000..458b6ca
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-1.cmake
@@ -0,0 +1,2 @@
+foreach(less than lists IN ZIP_LISTS list_1 list_2 list_3 list_4)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-result.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-stderr.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-stderr.txt
new file mode 100644
index 0000000..7b6b484
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ZIP_LISTS-iter-vars-mismatch-test-2.cmake:1 \(foreach\):
+  Expected 3 list variables, but given 2
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2.cmake
new file mode 100644
index 0000000..d24d99c
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-iter-vars-mismatch-test-2.cmake
@@ -0,0 +1,2 @@
+foreach(greater than lists IN ZIP_LISTS list_1 list_2)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test-stdout.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test-stdout.txt
new file mode 100644
index 0000000..e009d15
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test-stdout.txt
@@ -0,0 +1,6 @@
+-- foreach\(\.\.\. IN ZIP_LISTS\):
+--   Begin output
+--   | one, satu, raz
+--   | two, dua, dva
+--   | three, tiga, tri
+--   End output
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test.cmake
new file mode 100644
index 0000000..9647dea
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-multiple-iter-vars-test.cmake
@@ -0,0 +1,42 @@
+function(foreachTest result list_var_1 list_var_2 list_var_3)
+    set(_options MUTE)
+    set(_one_value_args)
+    set(_multi_value_args)
+    cmake_parse_arguments(PARSE_ARGV 3 _arg "${_options}" "${_one_value_args}" "${_multi_value_args}")
+
+    set(_has_any_output FALSE)
+    list(APPEND CMAKE_MESSAGE_INDENT "| ")
+    foreach(first second third IN ZIP_LISTS ${list_var_1} ${list_var_2} ${list_var_3})
+        if(NOT first)
+            set(first "[undefiend]")
+        endif()
+        if(NOT second)
+            set(second "[undefiend]")
+        endif()
+        if(NOT third)
+            set(third "[undefiend]")
+        endif()
+        if(NOT _arg_MUTE)
+            message(STATUS "${first}, ${second}, ${third}")
+        endif()
+        set(_has_any_output TRUE)
+    endforeach()
+    set(${result} ${_has_any_output} PARENT_SCOPE)
+endfunction()
+
+function(foreachTestDecorated list_var_1 list_var_2 list_var_3)
+    list(APPEND CMAKE_MESSAGE_INDENT "  ")
+    message(STATUS "Begin output")
+    foreachTest(_has_any_output ${list_var_1} ${list_var_2} ${list_var_3})
+    if(NOT _has_any_output)
+        message(STATUS "--> empty-output <--")
+    endif()
+    message(STATUS "End output")
+endfunction()
+
+list(APPEND english one two three)
+list(APPEND bahasa satu dua tiga)
+list(APPEND russian raz dva tri)
+
+message(STATUS "foreach(... IN ZIP_LISTS):")
+foreachTestDecorated(english bahasa russian)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test-stdout.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test-stdout.txt
new file mode 100644
index 0000000..25433fd
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test-stdout.txt
@@ -0,0 +1,19 @@
+-- foreach\(IN ZIP_LISTS\):
+--   <<< empty lists case >>>
+--     Begin output
+--     --> empty-output <--
+--     End output
+--   <<< same lengths lists case >>>
+--     Begin output
+--     | one, satu, raz
+--     | two, dua, dva
+--     | three, tiga, tri
+--     End output
+--   <<< different lengths lists case >>>
+--     Begin output
+--     | one, satu, raz
+--     | two, dua, dva
+--     | three, tiga, tri
+--     | \[undefiend\], empat, \[undefiend\]
+--     End output
+--   <<< test variable value restored -- PASSED >>>
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test.cmake
new file mode 100644
index 0000000..56cfe64
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-test.cmake
@@ -0,0 +1,68 @@
+function(foreachTest result list_var_1 list_var_2 list_var_3)
+    set(_options MUTE)
+    set(_one_value_args)
+    set(_multi_value_args)
+    cmake_parse_arguments(PARSE_ARGV 3 _arg "${_options}" "${_one_value_args}" "${_multi_value_args}")
+
+    set(_has_any_output FALSE)
+    list(APPEND CMAKE_MESSAGE_INDENT "| ")
+    foreach(num IN ZIP_LISTS ${list_var_1} ${list_var_2} ${list_var_3})
+        foreach(i RANGE 2)
+            if(NOT num_${i})
+                set(num_${i} "[undefiend]")
+            endif()
+        endforeach()
+        if(NOT _arg_MUTE)
+            message(STATUS "${num_0}, ${num_1}, ${num_2}")
+        endif()
+        set(_has_any_output TRUE)
+    endforeach()
+    set(${result} ${_has_any_output} PARENT_SCOPE)
+endfunction()
+
+function(foreachTestDecorated list_var_1 list_var_2 list_var_3)
+    list(APPEND CMAKE_MESSAGE_INDENT "  ")
+    message(STATUS "Begin output")
+    foreachTest(_has_any_output ${list_var_1} ${list_var_2} ${list_var_3})
+    if(NOT _has_any_output)
+        message(STATUS "--> empty-output <--")
+    endif()
+    message(STATUS "End output")
+endfunction()
+
+message(STATUS "foreach(IN ZIP_LISTS):")
+list(APPEND CMAKE_MESSAGE_INDENT "  ")
+
+set(english)
+set(bahasa)
+set(russian)
+
+message(STATUS "<<< empty lists case >>>")
+foreachTestDecorated(english bahasa russian)
+
+list(APPEND english one two three)
+list(APPEND bahasa satu dua tiga)
+list(APPEND russian raz dva tri)
+
+message(STATUS "<<< same lengths lists case >>>")
+foreachTestDecorated(english bahasa russian)
+
+list(APPEND bahasa empat)
+
+message(STATUS "<<< different lengths lists case >>>")
+foreachTestDecorated(english bahasa russian)
+
+set(num_0 "old-0")
+set(num_1 "old-1")
+set(num_2 "old-2")
+foreachTest(_ english bahasa russian MUTE)
+set(check PASSED)
+foreach(i RANGE 2)
+    if(NOT "${num_${i}}" STREQUAL "old-${i}")
+        message(SEND_ERROR "num_${i} value is corrupted")
+        set(check FAILED)
+    endif()
+endforeach()
+message(STATUS "<<< test variable value restored -- ${check} >>>")
+
+list(POP_BACK CMAKE_MESSAGE_INDENT)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-result.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-stderr.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-stderr.txt
new file mode 100644
index 0000000..0dcab01
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ZIP_LISTS-with-ITEMS-mix-test.cmake:1 \(foreach\):
+  ZIP_LISTS can not be used with LISTS or ITEMS
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test.cmake
new file mode 100644
index 0000000..71ed842
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-ITEMS-mix-test.cmake
@@ -0,0 +1,2 @@
+foreach(i IN ZIP_LISTS blah ITEMS blah)
+endforeach()
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-result.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-stderr.txt b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-stderr.txt
new file mode 100644
index 0000000..a6b6e9c
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at foreach-ZIP_LISTS-with-LISTS-mix-test.cmake:1 \(foreach\):
+  ZIP_LISTS can not be used with LISTS or ITEMS
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test.cmake b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test.cmake
new file mode 100644
index 0000000..11a97b2
--- /dev/null
+++ b/Tests/RunCMake/foreach/foreach-ZIP_LISTS-with-LISTS-mix-test.cmake
@@ -0,0 +1,2 @@
+foreach(i IN ZIP_LISTS blah LISTS blah)
+endforeach()