Browse Source

Merge topic 'symlinks-rebase-master'

58d10cf6f1 Alternative symlink-creating mode for file(INSTALL ...)

Acked-by: Kitware Robot <[email protected]>
Acked-by: Ben Boeckel <[email protected]>
Merge-request: !6396
Brad King 4 years ago
parent
commit
8706f7a617
38 changed files with 964 additions and 3 deletions
  1. 5 0
      Help/command/file.rst
  2. 4 0
      Help/command/install.rst
  3. 37 0
      Help/envvar/CMAKE_INSTALL_MODE.rst
  4. 1 0
      Help/manual/cmake-env-variables.7.rst
  5. 16 0
      Help/release/dev/cmake-install-mode-symlink.rst
  6. 3 2
      Source/cmFileCopier.h
  7. 118 0
      Source/cmFileInstaller.cxx
  8. 4 1
      Source/cmFileInstaller.h
  9. 17 0
      Source/cmInstallMode.h
  10. 48 0
      Tests/CMakeLists.txt
  11. 124 0
      Tests/InstallMode/CMakeLists.txt
  12. 43 0
      Tests/InstallMode/README.txt
  13. 76 0
      Tests/InstallMode/Subproject.cmake
  14. 38 0
      Tests/InstallMode/Test.cmake
  15. 60 0
      Tests/InstallMode/subpro_a_static_lib/CMakeLists.txt
  16. 8 0
      Tests/InstallMode/subpro_a_static_lib/cmake/PackageConfig.cmake.in
  17. 3 0
      Tests/InstallMode/subpro_a_static_lib/include/static_lib.h
  18. 10 0
      Tests/InstallMode/subpro_a_static_lib/src/static_lib.cpp
  19. 66 0
      Tests/InstallMode/subpro_b_shared_lib/CMakeLists.txt
  20. 8 0
      Tests/InstallMode/subpro_b_shared_lib/cmake/PackageConfig.cmake.in
  21. 3 0
      Tests/InstallMode/subpro_b_shared_lib/include/shared_lib.h
  22. 10 0
      Tests/InstallMode/subpro_b_shared_lib/src/shared_lib.cpp
  23. 10 0
      Tests/InstallMode/subpro_c_nested_lib/CMakeLists.txt
  24. 61 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/CMakeLists.txt
  25. 8 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/cmake/PackageConfig.cmake.in
  26. 3 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/include/c1_lib.h
  27. 10 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/src/c1_lib.cpp
  28. 68 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/CMakeLists.txt
  29. 11 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/cmake/PackageConfig.cmake.in
  30. 3 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/include/c2_lib.h
  31. 12 0
      Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/src/c2_lib.cpp
  32. 24 0
      Tests/InstallMode/subpro_d_executable/CMakeLists.txt
  33. 13 0
      Tests/InstallMode/subpro_d_executable/src/main.cpp
  34. 29 0
      Tests/InstallMode/superpro/CMakeLists.txt
  35. 1 0
      Tests/InstallMode/superpro/file_copy.txt
  36. 1 0
      Tests/InstallMode/superpro/file_copy_file.txt
  37. 2 0
      Tests/InstallMode/superpro/file_create_link_symbolic.txt
  38. 6 0
      Tests/InstallMode/superpro/file_install.txt

+ 5 - 0
Help/command/file.rst

@@ -843,6 +843,11 @@ and ``NO_SOURCE_PERMISSIONS`` is default.
 Installation scripts generated by the :command:`install` command
 use this signature (with some undocumented options for internal use).
 
+.. versionchanged:: 3.22
+
+  The environment variable :envvar:`CMAKE_INSTALL_MODE` can override the
+  default copying behavior of :command:`file(INSTALL)`.
+
 .. _SIZE:
 
 .. code-block:: cmake

+ 4 - 0
Help/command/install.rst

@@ -30,6 +30,10 @@ are executed in order during installation.
   with those in the parent directory to run in the order declared (see
   policy :policy:`CMP0082`).
 
+.. versionchanged:: 3.22
+  The environment variable :envvar:`CMAKE_INSTALL_MODE` can override the
+  default copying behavior of :command:`install()`.
+
 There are multiple signatures for this command.  Some of them define
 installation options for files and targets.  Options common to
 multiple signatures are covered here but they are valid only for

+ 37 - 0
Help/envvar/CMAKE_INSTALL_MODE.rst

@@ -0,0 +1,37 @@
+CMAKE_INSTALL_MODE
+------------------
+
+.. versionadded:: 3.22
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_INSTALL_MODE`` environment variable allows users to operate
+CMake in an alternate mode of :command:`file(INSTALL)` and :command:`install()`.
+
+The default behavior for an installation is to copy a source file from a
+source directory into a destination directory. This environment variable
+however allows the user to override this behavior, causing CMake to create
+symbolic links instead.
+
+.. note::
+  A symbolic link consists of a reference file path rather than contents of its own,
+  hence there are two ways to express the relation, either by a relative or an absolute path.
+
+The following values are allowed for ``CMAKE_INSTALL_MODE``:
+
+* empty, unset or ``COPY``: default behavior, duplicate the file at its destination
+* ``ABS_SYMLINK``: create an *absolute* symbolic link to the source file at the destination *or fail*
+* ``ABS_SYMLINK_OR_COPY``: like ``ABS_SYMLINK`` but silently copy on error
+* ``REL_SYMLINK``: create an *relative* symbolic link to the source file at the destination *or fail*
+* ``REL_SYMLINK_OR_COPY``: like ``REL_SYMLINK`` but silently copy on error
+* ``SYMLINK``: try as if through ``REL_SYMLINK`` and fall back to ``ABS_SYMLINK`` if the referenced
+  file cannot be expressed using a relative path. Fail on error.
+* ``SYMLINK_OR_COPY``: like ``SYMLINK`` but silently copy on error
+
+Installing symbolic links rather than copying files can help conserve storage space because files do
+not have to be duplicated on disk. However, modifications applied to the source immediately affects
+the symbolic link and vice versa. *Use with caution*.
+
+.. note:: ``CMAKE_INSTALL_MODE`` only affects files, *not* directories.
+
+.. note:: Symbolic links are not available on all platforms.

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

@@ -38,6 +38,7 @@ Environment Variables that Control the Build
    /envvar/CMAKE_GENERATOR_INSTANCE
    /envvar/CMAKE_GENERATOR_PLATFORM
    /envvar/CMAKE_GENERATOR_TOOLSET
+   /envvar/CMAKE_INSTALL_MODE
    /envvar/CMAKE_LANG_COMPILER_LAUNCHER
    /envvar/CMAKE_LANG_LINKER_LAUNCHER
    /envvar/CMAKE_MSVCIDE_RUN_PATH

+ 16 - 0
Help/release/dev/cmake-install-mode-symlink.rst

@@ -0,0 +1,16 @@
+cmake-install-mode-symlink
+--------------------------
+
+* The :envvar:`CMAKE_INSTALL_MODE` environment variable was added to
+  allow users to override the default file-copying behavior of
+  :command:`install` and :command:`file(INSTALL)` into creating
+  symbolic links. This can aid in lowering storage space requirements
+  and avoiding redundancy.
+
+* The :command:`file(INSTALL)` can now be affected / modified by the
+  :envvar:`CMAKE_INSTALL_MODE` environment variable causing installation
+  of symbolic links instead of copying of files.
+
+* The :command:`install` can now be affected / modified by the
+  :envvar:`CMAKE_INSTALL_MODE` environment variable causing installation
+  of symbolic links instead of copying of files.

+ 3 - 2
Source/cmFileCopier.h

@@ -67,8 +67,9 @@ protected:
 
   bool InstallSymlinkChain(std::string& fromFile, std::string& toFile);
   bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
-  bool InstallFile(const std::string& fromFile, const std::string& toFile,
-                   MatchProperties match_properties);
+  virtual bool InstallFile(const std::string& fromFile,
+                           const std::string& toFile,
+                           MatchProperties match_properties);
   bool InstallDirectory(const std::string& source,
                         const std::string& destination,
                         MatchProperties match_properties);

+ 118 - 0
Source/cmFileInstaller.cxx

@@ -3,7 +3,12 @@
 
 #include "cmFileInstaller.h"
 
+#include <map>
 #include <sstream>
+#include <utility>
+
+#include <cm/string_view>
+#include <cmext/string_view>
 
 #include "cm_sys_stat.h"
 
@@ -18,6 +23,7 @@ using namespace cmFSPermissions;
 cmFileInstaller::cmFileInstaller(cmExecutionStatus& status)
   : cmFileCopier(status, "INSTALL")
   , InstallType(cmInstallType_FILES)
+  , InstallMode(cmInstallMode::COPY)
   , Optional(false)
   , MessageAlways(false)
   , MessageLazy(false)
@@ -82,6 +88,93 @@ bool cmFileInstaller::Install(const std::string& fromFile,
   return this->cmFileCopier::Install(fromFile, toFile);
 }
 
+bool cmFileInstaller::InstallFile(const std::string& fromFile,
+                                  const std::string& toFile,
+                                  MatchProperties match_properties)
+{
+  if (this->InstallMode == cmInstallMode::COPY) {
+    return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties);
+  }
+
+  std::string newFromFile;
+
+  if (this->InstallMode == cmInstallMode::REL_SYMLINK ||
+      this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
+      this->InstallMode == cmInstallMode::SYMLINK ||
+      this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
+    // Try to get a relative path.
+    std::string toDir = cmSystemTools::GetParentDirectory(toFile);
+    newFromFile = cmSystemTools::ForceToRelativePath(toDir, fromFile);
+
+    // Double check that we can restore the original path.
+    std::string reassembled =
+      cmSystemTools::CollapseFullPath(newFromFile, toDir);
+    if (!cmSystemTools::ComparePath(reassembled, fromFile)) {
+      if (this->InstallMode == cmInstallMode::SYMLINK ||
+          this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
+        // User does not mind, silently proceed with absolute path.
+        newFromFile = fromFile;
+      } else if (this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY) {
+        // User expects a relative symbolic link or a copy.
+        // Since an absolute symlink won't do, copy instead.
+        return this->cmFileCopier::InstallFile(fromFile, toFile,
+                                               match_properties);
+      } else {
+        // We cannot meet user's expectation (REL_SYMLINK)
+        auto e = cmStrCat(this->Name,
+                          " cannot determine relative path for symlink to \"",
+                          newFromFile, "\" at \"", toFile, "\".");
+        this->Status.SetError(e);
+        return false;
+      }
+    }
+  } else {
+    newFromFile = fromFile; // stick with absolute path
+  }
+
+  // Compare the symlink value to that at the destination if not
+  // always installing.
+  bool copy = true;
+  if (!this->Always) {
+    std::string oldSymlinkTarget;
+    if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+      if (newFromFile == oldSymlinkTarget) {
+        copy = false;
+      }
+    }
+  }
+
+  // Inform the user about this file installation.
+  this->ReportCopy(toFile, TypeLink, copy);
+
+  if (copy) {
+    // Remove the destination file so we can always create the symlink.
+    cmSystemTools::RemoveFile(toFile);
+
+    // Create destination directory if it doesn't exist
+    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
+
+    // Create the symlink.
+    if (!cmSystemTools::CreateSymlink(newFromFile, toFile)) {
+      if (this->InstallMode == cmInstallMode::ABS_SYMLINK_OR_COPY ||
+          this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
+          this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
+        // Failed to create a symbolic link, fall back to copying.
+        return this->cmFileCopier::InstallFile(newFromFile, toFile,
+                                               match_properties);
+      }
+
+      auto e = cmStrCat(this->Name, " cannot create symlink to \"",
+                        newFromFile, "\" at \"", toFile,
+                        "\": ", cmSystemTools::GetLastSystemError(), "\".");
+      this->Status.SetError(e);
+      return false;
+    }
+  }
+
+  return true;
+}
+
 void cmFileInstaller::DefaultFilePermissions()
 {
   this->cmFileCopier::DefaultFilePermissions();
@@ -141,6 +234,31 @@ bool cmFileInstaller::Parse(std::vector<std::string> const& args)
     return false;
   }
 
+  static const std::map<cm::string_view, cmInstallMode> install_mode_dict{
+    { "ABS_SYMLINK"_s, cmInstallMode::ABS_SYMLINK },
+    { "ABS_SYMLINK_OR_COPY"_s, cmInstallMode::ABS_SYMLINK_OR_COPY },
+    { "REL_SYMLINK"_s, cmInstallMode::REL_SYMLINK },
+    { "REL_SYMLINK_OR_COPY"_s, cmInstallMode::REL_SYMLINK_OR_COPY },
+    { "SYMLINK"_s, cmInstallMode::SYMLINK },
+    { "SYMLINK_OR_COPY"_s, cmInstallMode::SYMLINK_OR_COPY }
+  };
+
+  std::string install_mode;
+  cmSystemTools::GetEnv("CMAKE_INSTALL_MODE", install_mode);
+  if (install_mode.empty() || install_mode == "COPY"_s) {
+    this->InstallMode = cmInstallMode::COPY;
+  } else {
+    auto it = install_mode_dict.find(install_mode);
+    if (it != install_mode_dict.end()) {
+      this->InstallMode = it->second;
+    } else {
+      auto e = cmStrCat("Unrecognized value '", install_mode,
+                        "' for environment variable CMAKE_INSTALL_MODE");
+      this->Status.SetError(e);
+      return false;
+    }
+  }
+
   return true;
 }
 

+ 4 - 1
Source/cmFileInstaller.h

@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "cmFileCopier.h"
+#include "cmInstallMode.h"
 #include "cmInstallType.h"
 
 class cmExecutionStatus;
@@ -19,6 +20,7 @@ struct cmFileInstaller : public cmFileCopier
 
 protected:
   cmInstallType InstallType;
+  cmInstallMode InstallMode;
   bool Optional;
   bool MessageAlways;
   bool MessageLazy;
@@ -35,7 +37,8 @@ protected:
   bool ReportMissing(const std::string& fromFile) override;
   bool Install(const std::string& fromFile,
                const std::string& toFile) override;
-
+  bool InstallFile(const std::string& fromFile, const std::string& toFile,
+                   MatchProperties match_properties) override;
   bool Parse(std::vector<std::string> const& args) override;
   enum
   {

+ 17 - 0
Source/cmInstallMode.h

@@ -0,0 +1,17 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/**
+ * Enumerate types known to file(INSTALL).
+ */
+enum class cmInstallMode
+{
+  COPY,
+  ABS_SYMLINK,
+  ABS_SYMLINK_OR_COPY,
+  REL_SYMLINK,
+  REL_SYMLINK_OR_COPY,
+  SYMLINK,
+  SYMLINK_OR_COPY
+};

+ 48 - 0
Tests/CMakeLists.txt

@@ -1648,6 +1648,54 @@ if(BUILD_TESTING)
     WORKING_DIRECTORY ${CMake_SOURCE_DIR}/Tests/ExternalProjectUpdate
     DEPENDS ExternalProjectUpdateSetup )
 
+  execute_process(
+    COMMAND ${CMAKE_CMAKE_COMMAND}
+    "-E" create_symlink
+    "${CMake_SOURCE_DIR}/Tests/CMakeLists.txt"        # random source file that exists
+    "${CMake_BINARY_DIR}/Tests/try_to_create_symlink" # random target file in existing directory
+    RESULT_VARIABLE _failed
+    )
+  if(_failed)
+    message("Failed to create a simple symlink on this machine. Skipping InstallMode tests.")
+  else()
+    function(add_installmode_test _mode)
+      set(ENV{CMAKE_INSTALL_MODE} _mode)
+      set(_maybe_InstallMode_CTEST_OPTIONS)
+      set(_maybe_BUILD_OPTIONS)
+      if(_isMultiConfig)
+        set(_maybe_CTEST_OPTIONS -C $<CONFIGURATION>)
+      else()
+        set(_maybe_BUILD_OPTIONS "-DCMAKE_BUILD_TYPE=$<CONFIGURATION>")
+      endif()
+      add_test(
+        NAME "InstallMode-${_mode}"
+        COMMAND
+          ${CMAKE_CTEST_COMMAND} -V ${_maybe_CTEST_OPTIONS}
+        --build-and-test
+          "${CMake_SOURCE_DIR}/Tests/InstallMode"
+          "${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}"
+        ${build_generator_args}
+        --build-project superpro
+        --build-exe-dir "${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}"
+        --force-new-ctest-process
+        --build-options
+          ${_maybe_BUILD_OPTIONS}
+          "-DCMAKE_INSTALL_PREFIX:PATH=${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}/install"
+        )
+      list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}")
+      unset(ENV{CMAKE_INSTALL_MODE})
+    endfunction()
+
+    add_installmode_test(COPY)
+    add_installmode_test(REL_SYMLINK)
+    add_installmode_test(REL_SYMLINK_OR_COPY)
+    add_installmode_test(ABS_SYMLINK)
+    add_installmode_test(ABS_SYMLINK_OR_COPY)
+    add_installmode_test(SYMLINK)
+    add_installmode_test(SYMLINK_OR_COPY)
+  endif()
+
+
   # do each of the tutorial steps
   function(add_tutorial_test step_name use_mymath tutorial_arg pass_regex)
     set(tutorial_test_name Tutorial${step_name})

+ 124 - 0
Tests/InstallMode/CMakeLists.txt

@@ -0,0 +1,124 @@
+cmake_minimum_required(VERSION 3.20.0)
+
+project(superpro LANGUAGES NONE)
+
+add_subdirectory(superpro)
+
+include(Subproject.cmake)
+add_subproject(static_lib   DIR subpro_a_static_lib)
+add_subproject(shared_lib   DIR subpro_b_shared_lib)
+add_subproject(nested_lib   DIR subpro_c_nested_lib NO_INSTALL)
+add_subproject(executable   DIR subpro_d_executable
+  DEPENDS
+    static_lib
+    shared_lib
+    nested_lib
+)
+
+include(CTest)
+if(BUILD_TESTING)
+  enable_language(CXX)  # required by GNUInstallDirs
+  include(GNUInstallDirs)
+
+  macro(testme _name _path _symlink)
+    add_test(
+      NAME "${_name}"
+      WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+      COMMAND
+        "${CMAKE_COMMAND}"
+        "-DFILE_PATH=${CMAKE_INSTALL_PREFIX}/${_path}"
+        "-DEXPECT_SYMLINK:BOOL=${_symlink}"
+        "-DEXPECT_ABSOLUTE:BOOL=${ARGN}"
+        "-P" "${CMAKE_SOURCE_DIR}/Test.cmake"
+    )
+  endmacro()
+
+  set(_mode $ENV{CMAKE_INSTALL_MODE})
+  if(NOT "${_mode}" OR "${_mode}" STREQUAL "COPY")
+    set(expect_symlink NO)
+  elseif("${_mode}" MATCHES "(REL_)?SYMLINK(_OR_COPY)?")
+    set(expect_symlink YES)
+    set(expect_absolute NO)
+  elseif("${_mode}" MATCHES "ABS_SYMLINK(_OR_COPY)?")
+    set(expect_symlink YES)
+    set(expect_absolute YES)
+  endif()
+
+  # toplevel project should respect CMAKE_INSTALL_MODE
+
+  testme(superproj_file_copy
+    "file_copy.txt" NO)
+  testme(superproj_file_copy_file
+    "file_copy_file.txt" NO)
+  testme(superproj_file_install
+    "file_install.txt"
+    ${expect_symlink}
+    ${expect_absolute})
+  testme(superproj_file_create_link_symbolic
+    "file_create_link_symbolic.txt" YES YES)
+
+  # subprojects should receive and respect CMAKE_INSTALL_MODE too
+
+  testme(subpro_a_static_lib_header
+    "${CMAKE_INSTALL_INCLUDEDIR}/static_lib.h"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+  testme(subpro_a_static_lib_libfile
+    "${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}the_static_lib${CMAKE_STATIC_LIBRARY_SUFFIX}"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+
+  testme(subpro_b_shared_lib_header
+    "${CMAKE_INSTALL_INCLUDEDIR}/shared_lib.h"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+
+  if(CMAKE_SHARED_LIBRARY_SONAME_CXX_FLAG AND
+      "${CMAKE_CXX_CREATE_SHARED_MODULE}" MATCHES "SONAME_FLAG")
+    # due to semver, this is always a link
+    testme(subpro_b_shared_lib_libfile
+      "${CMAKE_INSTALL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}the_shared_lib${CMAKE_SHARED_LIBRARY_SUFFIX}"
+      YES
+      ${expect_absolute}
+    )
+    # this is the actual shared lib, so should follow CMAKE_INSTALL_MODE rules
+    testme(subpro_b_shared_lib_libfile_versuffix
+      "${CMAKE_INSTALL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}the_shared_lib${CMAKE_SHARED_LIBRARY_SUFFIX}.2.3.4"
+      ${expect_symlink}
+      ${expect_absolute}
+    )
+  endif()
+
+  testme(subpro_d_executable_exefile
+    "${CMAKE_INSTALL_BINDIR}/the_executable${CMAKE_EXECUTABLE_SUFFIX}"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+
+  # nested subprojects should receive and respect CMAKE_INSTALL_MODE too
+
+  testme(subsubpro_c1_header
+    "${CMAKE_INSTALL_INCLUDEDIR}/c1_lib.h"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+  testme(subsubpro_c1_libfile
+    "${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}the_c1_lib${CMAKE_STATIC_LIBRARY_SUFFIX}"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+
+  testme(subsubpro_c2_header
+    "${CMAKE_INSTALL_INCLUDEDIR}/c2_lib.h"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+  testme(subsubpro_c2_libfile
+    "${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}the_c2_lib${CMAKE_STATIC_LIBRARY_SUFFIX}"
+    ${expect_symlink}
+    ${expect_absolute}
+  )
+endif()

+ 43 - 0
Tests/InstallMode/README.txt

@@ -0,0 +1,43 @@
+This is an example superbuild project to demonstrate the use of the
+CMAKE_INSTALL_MODE environment variable on.
+
+The project hierarchy is like (B = Builds / D = Link Dependency):
+
++---------------------------------------------------------------------+
+|                           Superbuild (Top)                          |
++---------------------------------------------------------------------+
+        |                 |                 |                 |
+        |                 |                 |                 |
+       (B)               (B)               (B)               (B)
+        |                 |                 |                 |
+        v                 v                 v                 v
++---------------+ +---------------+ +---------------+ +---------------+
+| A: Static Lib | | B: Shared Lib | | C: Nested     | | D: Executable |
+|    Project    | |    Project    | |    Superbuild | |    Project    |
++---------------+ +---------------+ +---------------+ +---------------+
+        ^               ^               |       |        |   |   |
+        |               |              (B)     (B)       |   |   |
+        |               |               |       |        |   |   |
+        |               |               v       |        |   |   |
+        |               |  +----------------+   |        |   |   |
+        |               |  | C1: Static Lib |   |        |   |   |
+        |               |  |     Project    |   |       (D) (D) (D)
+        |               |  +----------------+   |        |   |   |
+        |               |       ^               |        |   |   |
+        |               |       |               v        |   |   |
+        |               |      (D) +----------------+    |   |   |
+        |               |       |  | C2: Static Lib |<---+   |   |
+        |               |       +--|     Project    |        |   |
+        |               |          +----------------+        |   |
+        |               |                                    |   |
+        |               +------------------------------------+   |
+        |                                                        |
+        +--------------------------------------------------------+
+
+The superbuild system is built on top of ExternalProject_Add().
+
+NOTE that the subprojects will configure, build and install
+during the build phase ('make') of the top-level project.
+There is no install target in the top-level project!
+The CMAKE_INSTALL_PREFIX is therefore populated during the build
+phase already.

+ 76 - 0
Tests/InstallMode/Subproject.cmake

@@ -0,0 +1,76 @@
+include(ExternalProject)
+
+# add_subproject(<name> [NO_INSTALL] [DIR <dirname>] [DEPENDS [subpro_dep ...]])
+function(add_subproject _name)
+  cmake_parse_arguments(_arg "NO_INSTALL" "DIR" "DEPENDS" ${ARGN})
+
+  if(_arg_UNPARSED_ARGUMENTS)
+    message(FATAL_ERROR "There are unparsed arguments")
+  endif()
+
+  set(_maybe_NO_INSTALL)
+  if(_arg_NO_INSTALL)
+    set(_maybe_NO_INSTALL "INSTALL_COMMAND")
+  else()
+    # This is a trick to get a valid call.
+    # Since we set UPDATE_COMMAND to ""
+    # explicitly below, this won't harm.
+    set(_maybe_NO_INSTALL "UPDATE_COMMAND")
+  endif()
+
+  if(CMAKE_GENERATOR MATCHES "Ninja Multi-Config")
+    # Replace list separator before passing on to ExternalProject_Add
+    string(REPLACE ";" "|" _CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}")
+    string(REPLACE ";" "|" _CROSS_CONFIGS "${CMAKE_CROSS_CONFIGS}")
+    string(REPLACE ";" "|" _DEFAULT_CONFIGS "${CMAKE_DEFAULT_CONFIGS}")
+
+    set(_maybe_NINJA_MULTICONFIG_ARGS
+      "-DCMAKE_CONFIGURATION_TYPES:STRINGS=${_CONFIGURATION_TYPES}"
+      "-DCMAKE_CROSS_CONFIGS:STRINGS=${_CROSS_CONFIGS}"
+      "-DCMAKE_DEFAULT_BUILD_TYPE:STRING=${CMAKE_DEFAULT_BUILD_TYPE}"
+      "-DCMAKE_DEFAULT_CONFIGS:STRINGS=${_DEFAULT_CONFIGS}"
+    )
+  endif()
+
+  ExternalProject_Add("${_name}"
+    DOWNLOAD_COMMAND      ""
+    UPDATE_COMMAND        ""
+    ${_maybe_NO_INSTALL}  ""
+
+    BUILD_ALWAYS          ON
+
+    LOG_DOWNLOAD          OFF
+    LOG_UPDATE            OFF
+    LOG_PATCH             OFF
+    LOG_CONFIGURE         OFF
+    LOG_BUILD             OFF
+    LOG_INSTALL           OFF
+
+    SOURCE_DIR            "${PROJECT_SOURCE_DIR}/${_arg_DIR}"
+
+    # Private build directory per subproject
+    BINARY_DIR            "${PROJECT_BINARY_DIR}/subproject/${_arg_DIR}"
+
+    # Common install directory, populated immediately
+    # during build (during build - not install - of superproject)
+    INSTALL_DIR           "${CMAKE_INSTALL_PREFIX}"
+
+    DEPENDS
+      ${_arg_DEPENDS}
+
+    LIST_SEPARATOR "|"
+    CMAKE_ARGS
+      "-DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>"
+
+      # We can rely on ExternalProject to pick the right
+      # generator (and architecture/toolset where applicable),
+      # however, we need to explicitly inherit other parent
+      # project's build settings.
+      "-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}"
+      ${_maybe_NINJA_MULTICONFIG_ARGS}
+
+      # Subproject progress reports clutter up the output, disable
+      "-DCMAKE_TARGET_MESSAGES:BOOL=OFF"
+      "-DCMAKE_RULE_MESSAGES:BOOL=OFF"
+  )
+endfunction()

+ 38 - 0
Tests/InstallMode/Test.cmake

@@ -0,0 +1,38 @@
+message("Testing...")
+message("FILE_PATH =       ${FILE_PATH}")
+message("EXPECT_SYMLINK =  ${EXPECT_SYMLINK}")
+message("EXPECT_ABSOLUTE = ${EXPECT_ABSOLUTE}")
+
+if(NOT DEFINED FILE_PATH)
+  message(FATAL_ERROR "FILE_PATH variable must be defined")
+endif()
+
+if(NOT EXISTS "${FILE_PATH}")
+  message(FATAL_ERROR "File ${FILE_PATH} does not exist")
+endif()
+
+if(NOT DEFINED EXPECT_SYMLINK)
+  message(FATAL_ERROR "EXPECT_SYMLINK must be defined")
+endif()
+
+if(EXPECT_SYMLINK)
+  if(NOT DEFINED EXPECT_ABSOLUTE)
+    message(FATAL_ERROR "EXPECT_ABSOLUTE variable must be defined")
+  endif()
+
+  if(NOT IS_SYMLINK "${FILE_PATH}")
+    message(FATAL_ERROR "${FILE_PATH} must be a symlink")
+  endif()
+
+  file(READ_SYMLINK "${FILE_PATH}" TARGET_PATH)
+
+  if(EXPECT_ABSOLUTE AND NOT IS_ABSOLUTE "${TARGET_PATH}")
+    message(FATAL_ERROR "${FILE_PATH} must be an absolute symlink")
+  elseif(NOT EXPECT_ABSOLUTE AND IS_ABSOLUTE "${TARGET_PATH}")
+    message(FATAL_ERROR "${FILE_PATH} must be a relative symlink")
+  endif()
+else()
+  if(IS_SYMLINK "${FILE_PATH}")
+    message(FATAL_ERROR "${FILE_PATH} must NOT be a symlink")
+  endif()
+endif()

+ 60 - 0
Tests/InstallMode/subpro_a_static_lib/CMakeLists.txt

@@ -0,0 +1,60 @@
+# This CMakeLists.txt is part of the subproject A (ExternalProject_Add).
+
+cmake_minimum_required(VERSION 3.20)
+project(static_lib_project VERSION 1.2.3 LANGUAGES CXX)
+
+include(GNUInstallDirs)
+
+add_library(the_static_lib STATIC
+  "include/static_lib.h"
+  "src/static_lib.cpp"
+)
+
+target_include_directories(the_static_lib PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+)
+
+install(
+  DIRECTORY   "${CMAKE_CURRENT_SOURCE_DIR}/include/"
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+)
+
+install(
+  TARGETS
+    the_static_lib
+  EXPORT main
+  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+)
+
+set(INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
+
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+  "cmake/PackageConfig.cmake.in"
+  "${PROJECT_NAME}Config.cmake"
+  INSTALL_DESTINATION "${INSTALL_CMAKE_DIR}"
+  PATH_VARS
+    CMAKE_INSTALL_INCLUDEDIR
+    CMAKE_INSTALL_LIBDIR
+)
+
+write_basic_package_version_file("${PROJECT_NAME}Version.cmake"
+  VERSION       "${PROJECT_VERSION}"
+  COMPATIBILITY SameMajorVersion
+)
+
+install(
+  EXPORT      main
+  FILE        "${PROJECT_NAME}Targets.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)
+
+install(
+  FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Version.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)

+ 8 - 0
Tests/InstallMode/subpro_a_static_lib/cmake/PackageConfig.cmake.in

@@ -0,0 +1,8 @@
+set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
+
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
+
+set_and_check(@PROJECT_NAME@_INCLUDE_DIR  "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(@PROJECT_NAME@_LIB_DIR      "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

+ 3 - 0
Tests/InstallMode/subpro_a_static_lib/include/static_lib.h

@@ -0,0 +1,3 @@
+#pragma once
+
+void static_hello();

+ 10 - 0
Tests/InstallMode/subpro_a_static_lib/src/static_lib.cpp

@@ -0,0 +1,10 @@
+#include <iostream>
+
+#include <static_lib.h>
+
+using namespace std;
+
+void static_hello()
+{
+  cout << "Hello from static_lib" << endl;
+}

+ 66 - 0
Tests/InstallMode/subpro_b_shared_lib/CMakeLists.txt

@@ -0,0 +1,66 @@
+# This CMakeLists.txt is part of the subproject B (ExternalProject_Add).
+
+cmake_minimum_required(VERSION 3.20)
+project(shared_lib_project VERSION 2.3.4 LANGUAGES CXX)
+
+include(GNUInstallDirs)
+
+add_library(the_shared_lib SHARED
+  "include/shared_lib.h"
+  "src/shared_lib.cpp"
+)
+
+set_target_properties(the_shared_lib
+  PROPERTIES
+    VERSION   "${PROJECT_VERSION}"
+    SOVERSION "${PROJECT_VERSION}"
+)
+
+target_include_directories(the_shared_lib PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+)
+
+install(
+  DIRECTORY   "${CMAKE_CURRENT_SOURCE_DIR}/include/"
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+)
+
+install(
+  TARGETS
+    the_shared_lib
+  EXPORT main
+  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+)
+
+set(INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
+
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+  "cmake/PackageConfig.cmake.in"
+  "${PROJECT_NAME}Config.cmake"
+  INSTALL_DESTINATION "${INSTALL_CMAKE_DIR}"
+  PATH_VARS
+    CMAKE_INSTALL_INCLUDEDIR
+    CMAKE_INSTALL_LIBDIR
+)
+
+write_basic_package_version_file("${PROJECT_NAME}Version.cmake"
+  VERSION       "${PROJECT_VERSION}"
+  COMPATIBILITY SameMajorVersion
+)
+
+install(
+  EXPORT      main
+  FILE        "${PROJECT_NAME}Targets.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)
+
+install(
+  FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Version.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)

+ 8 - 0
Tests/InstallMode/subpro_b_shared_lib/cmake/PackageConfig.cmake.in

@@ -0,0 +1,8 @@
+set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
+
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
+
+set_and_check(@PROJECT_NAME@_INCLUDE_DIR  "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(@PROJECT_NAME@_LIB_DIR      "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

+ 3 - 0
Tests/InstallMode/subpro_b_shared_lib/include/shared_lib.h

@@ -0,0 +1,3 @@
+#pragma once
+
+void shared_hello();

+ 10 - 0
Tests/InstallMode/subpro_b_shared_lib/src/shared_lib.cpp

@@ -0,0 +1,10 @@
+#include <iostream>
+
+#include <shared_lib.h>
+
+using namespace std;
+
+void shared_hello()
+{
+  cout << "Hello from shared_lib" << endl;
+}

+ 10 - 0
Tests/InstallMode/subpro_c_nested_lib/CMakeLists.txt

@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.20.0)
+
+project(subpro_c_nested_lib LANGUAGES NONE)
+
+include(../Subproject.cmake)
+add_subproject(c1_lib   DIR subsubpro_c1_lib)
+add_subproject(c2_lib   DIR subsubpro_c2_lib
+  DEPENDS
+    c1_lib
+)

+ 61 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/CMakeLists.txt

@@ -0,0 +1,61 @@
+# This CMakeLists.txt is a nested subproject of the
+# subproject C (ExternalProject_Add).
+
+cmake_minimum_required(VERSION 3.20)
+project(c1_lib_project VERSION 1.2.3 LANGUAGES CXX)
+
+include(GNUInstallDirs)
+
+add_library(the_c1_lib STATIC
+  "include/c1_lib.h"
+  "src/c1_lib.cpp"
+)
+
+target_include_directories(the_c1_lib PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+)
+
+install(
+  DIRECTORY   "${CMAKE_CURRENT_SOURCE_DIR}/include/"
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+)
+
+install(
+  TARGETS
+    the_c1_lib
+  EXPORT main
+  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+)
+
+set(INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
+
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+  "cmake/PackageConfig.cmake.in"
+  "${PROJECT_NAME}Config.cmake"
+  INSTALL_DESTINATION "${INSTALL_CMAKE_DIR}"
+  PATH_VARS
+    CMAKE_INSTALL_INCLUDEDIR
+    CMAKE_INSTALL_LIBDIR
+)
+
+write_basic_package_version_file("${PROJECT_NAME}Version.cmake"
+  VERSION       "${PROJECT_VERSION}"
+  COMPATIBILITY SameMajorVersion
+)
+
+install(
+  EXPORT      main
+  FILE        "${PROJECT_NAME}Targets.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)
+
+install(
+  FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Version.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)

+ 8 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/cmake/PackageConfig.cmake.in

@@ -0,0 +1,8 @@
+set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
+
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
+
+set_and_check(@PROJECT_NAME@_INCLUDE_DIR  "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(@PROJECT_NAME@_LIB_DIR      "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

+ 3 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/include/c1_lib.h

@@ -0,0 +1,3 @@
+#pragma once
+
+void c1_hello();

+ 10 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c1_lib/src/c1_lib.cpp

@@ -0,0 +1,10 @@
+#include <iostream>
+
+#include <c1_lib.h>
+
+using namespace std;
+
+void c1_hello()
+{
+  cout << "Hello from c1_lib" << endl;
+}

+ 68 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/CMakeLists.txt

@@ -0,0 +1,68 @@
+# This CMakeLists.txt is a nested subproject of the
+# subproject C (ExternalProject_Add).
+
+cmake_minimum_required(VERSION 3.20)
+project(c2_lib_project VERSION 1.2.3 LANGUAGES CXX)
+
+find_package(c1_lib_project REQUIRED)
+
+include(GNUInstallDirs)
+
+add_library(the_c2_lib STATIC
+  "include/c2_lib.h"
+  "src/c2_lib.cpp"
+)
+
+target_link_libraries(the_c2_lib
+  PUBLIC
+    the_c1_lib
+)
+
+target_include_directories(the_c2_lib PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+)
+
+install(
+  DIRECTORY   "${CMAKE_CURRENT_SOURCE_DIR}/include/"
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+)
+
+install(
+  TARGETS
+    the_c2_lib
+  EXPORT main
+  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+)
+
+set(INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
+
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+  "cmake/PackageConfig.cmake.in"
+  "${PROJECT_NAME}Config.cmake"
+  INSTALL_DESTINATION "${INSTALL_CMAKE_DIR}"
+  PATH_VARS
+    CMAKE_INSTALL_INCLUDEDIR
+    CMAKE_INSTALL_LIBDIR
+)
+
+write_basic_package_version_file("${PROJECT_NAME}Version.cmake"
+  VERSION       "${PROJECT_VERSION}"
+  COMPATIBILITY SameMajorVersion
+)
+
+install(
+  EXPORT      main
+  FILE        "${PROJECT_NAME}Targets.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)
+
+install(
+  FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Version.cmake"
+  DESTINATION "${INSTALL_CMAKE_DIR}"
+)

+ 11 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/cmake/PackageConfig.cmake.in

@@ -0,0 +1,11 @@
+set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
+
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
+
+set_and_check(@PROJECT_NAME@_INCLUDE_DIR  "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(@PROJECT_NAME@_LIB_DIR      "@PACKAGE_CMAKE_INSTALL_LIBDIR@")
+
+include(CMakeFindDependencyMacro)
+find_dependency(c1_lib_project REQUIRED)

+ 3 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/include/c2_lib.h

@@ -0,0 +1,3 @@
+#pragma once
+
+void c2_hello();

+ 12 - 0
Tests/InstallMode/subpro_c_nested_lib/subsubpro_c2_lib/src/c2_lib.cpp

@@ -0,0 +1,12 @@
+#include <iostream>
+
+#include <c1_lib.h>
+#include <c2_lib.h>
+
+using namespace std;
+
+void c2_hello()
+{
+  cout << "Hello from c2_lib and also..." << endl;
+  c1_hello();
+}

+ 24 - 0
Tests/InstallMode/subpro_d_executable/CMakeLists.txt

@@ -0,0 +1,24 @@
+# This CMakeLists.txt is part of the subproject B (ExternalProject_Add).
+
+cmake_minimum_required(VERSION 3.20)
+project(subpro_d_executable LANGUAGES CXX)
+
+find_package(static_lib_project REQUIRED)
+find_package(shared_lib_project REQUIRED)
+find_package(c2_lib_project REQUIRED)
+
+add_executable(the_executable
+  "src/main.cpp"
+)
+
+target_link_libraries(the_executable PRIVATE
+  the_static_lib
+  the_shared_lib
+  the_c2_lib
+)
+
+install(
+  TARGETS
+    the_executable
+  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)

+ 13 - 0
Tests/InstallMode/subpro_d_executable/src/main.cpp

@@ -0,0 +1,13 @@
+#include <cstdlib>
+
+#include <c2_lib.h>
+#include <shared_lib.h>
+#include <static_lib.h>
+
+int main()
+{
+  static_hello();
+  shared_hello();
+  c2_hello();
+  return EXIT_SUCCESS;
+}

+ 29 - 0
Tests/InstallMode/superpro/CMakeLists.txt

@@ -0,0 +1,29 @@
+# This CMakeLists.txt is part of the superproject (add_subdirectory).
+
+# Below file transfers are executed at configuration time!
+
+file(
+  COPY
+    "file_copy.txt"
+  DESTINATION
+    "${CMAKE_INSTALL_PREFIX}"
+)
+
+file(COPY_FILE
+  "${CMAKE_CURRENT_SOURCE_DIR}/file_copy_file.txt"
+  "${CMAKE_INSTALL_PREFIX}/file_copy_file.txt"
+)
+
+file(
+  INSTALL
+    "file_install.txt"
+  DESTINATION
+    "${CMAKE_INSTALL_PREFIX}"
+)
+
+file(
+  CREATE_LINK
+    "${CMAKE_CURRENT_SOURCE_DIR}/file_create_link_symbolic.txt"
+    "${CMAKE_INSTALL_PREFIX}/file_create_link_symbolic.txt"
+  SYMBOLIC
+)

+ 1 - 0
Tests/InstallMode/superpro/file_copy.txt

@@ -0,0 +1 @@
+This file should always be copied into CMAKE_INSTALL_PREFIX.

+ 1 - 0
Tests/InstallMode/superpro/file_copy_file.txt

@@ -0,0 +1 @@
+This file should always be copied into CMAKE_INSTALL_PREFIX.

+ 2 - 0
Tests/InstallMode/superpro/file_create_link_symbolic.txt

@@ -0,0 +1,2 @@
+This file should always be installed into CMAKE_INSTALL_PREFIX
+as a symbolic link to the original file.

+ 6 - 0
Tests/InstallMode/superpro/file_install.txt

@@ -0,0 +1,6 @@
+This file should be placed in CMAKE_INSTALL_PREFIX
+as a copy if the CMAKE_INSTALL_MODE environment variable
+is unset or equals "COPY".
+If the variable's value is "SYMLINK" or "SYMLINK_OR_COPY",
+the CMAKE_INSTALL_PREFIX should rather receive a symbolic
+link to this file.