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

An new environment variable 'CMAKE_INSTALL_MODE' is introduced,
which can be used to ask CMake to create symbolic links
instead of copying files during a file(INSTALL ...).

The operation is at the file level only, directory trees are
still created using actual directories, not links.

Signed-off-by: Felix Lelchuk <felix.lelchuk@gmx.de>
This commit is contained in:
Felix Lelchuk
2021-08-02 19:42:26 +02:00
parent c15bb6f8b8
commit 58d10cf6f1
38 changed files with 964 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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
Source/cmInstallMode.h Normal file
View File

@ -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
};

View File

@ -1641,6 +1641,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})

View File

@ -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()

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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}"
)

View File

@ -0,0 +1,8 @@
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
set_and_check(@PROJECT_NAME@_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

View File

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

View File

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

View File

@ -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}"
)

View File

@ -0,0 +1,8 @@
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
set_and_check(@PROJECT_NAME@_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

View File

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

View File

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

View File

@ -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
)

View File

@ -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}"
)

View File

@ -0,0 +1,8 @@
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
set_and_check(@PROJECT_NAME@_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@")

View File

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

View File

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

View File

@ -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}"
)

View File

@ -0,0 +1,11 @@
set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@)
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
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)

View File

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

View File

@ -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();
}

View File

@ -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}"
)

View File

@ -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;
}

View File

@ -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
)

View File

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

View File

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

View File

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

View File

@ -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.