mirror of
https://github.com/Kitware/CMake.git
synced 2025-12-31 19:00:54 -06:00
file(CREATE_LINK): Implement COPY_ON_ERROR for directories
Add policy `CMP0205` for compatibility with projects not expecting this. Fixes: #27294
This commit is contained in:
committed by
hanna.rusakovich
parent
f719a36bc0
commit
a73ddd2ddb
@@ -615,11 +615,12 @@ Filesystem
|
||||
emitted.
|
||||
|
||||
Specifying ``COPY_ON_ERROR`` enables copying the file as a fallback if
|
||||
creating the link fails. If the source is a directory, the destination
|
||||
directory will be created if it does not exist, but no files will be copied
|
||||
the from source one. It can be useful for handling situations such as
|
||||
creating the link fails. It can be useful for handling situations such as
|
||||
``<original>`` and ``<linkname>`` being on different drives or mount points,
|
||||
which would make them unable to support a hard link.
|
||||
If the source is a directory, the destination directory will be created if
|
||||
it does not exist. Contents of the source directory will be copied to the
|
||||
destination directory unless policy :policy:`CMP0205` is not set to ``NEW``.
|
||||
|
||||
.. signature::
|
||||
file(CHMOD <files>... <directories>...
|
||||
|
||||
@@ -92,6 +92,14 @@ Supported Policies
|
||||
|
||||
The following policies are supported.
|
||||
|
||||
Policies Introduced by CMake 4.3
|
||||
--------------------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
CMP0205: file(CREATE_LINK) with COPY_ON_ERROR copies directory content. </policy/CMP0205>
|
||||
|
||||
Policies Introduced by CMake 4.2
|
||||
--------------------------------
|
||||
|
||||
|
||||
23
Help/policy/CMP0205.rst
Normal file
23
Help/policy/CMP0205.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
CMP0205
|
||||
-------
|
||||
|
||||
.. versionadded:: 4.3
|
||||
|
||||
:command:`file(CREATE_LINK)` with ``COPY_ON_ERROR`` copies directory content.
|
||||
|
||||
The :command:`file(CREATE_LINK)` command's ``COPY_ON_ERROR`` option copies
|
||||
the source file to the destination as a fallback if linking it fails.
|
||||
If the source is a directory, CMake 4.2 and below create the destination
|
||||
directory but do not copy its contents. CMake 4.3 and above prefer to
|
||||
copy the directory contents too. This policy provides compatibility with
|
||||
projects that have not been updated to expect the contents to be copied.
|
||||
|
||||
The ``OLD`` behavior for this policy is to create the destination directory
|
||||
without copying contents. The ``NEW`` behavior for this policy to create
|
||||
the destination directory and copy contents from the source directory.
|
||||
|
||||
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.3
|
||||
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
|
||||
.. include:: include/STANDARD_ADVICE.rst
|
||||
|
||||
.. include:: include/DEPRECATED.rst
|
||||
@@ -3221,8 +3221,13 @@ bool HandleCreateLinkCommand(std::vector<std::string> const& args,
|
||||
return false;
|
||||
}
|
||||
|
||||
cmPolicies::PolicyStatus const cmp0205 =
|
||||
status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0205);
|
||||
|
||||
// Hard link requires original file to exist.
|
||||
if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
|
||||
if (!arguments.Symbolic &&
|
||||
(!cmSystemTools::PathExists(fileName) ||
|
||||
(cmp0205 != cmPolicies::NEW && !cmSystemTools::FileExists(fileName)))) {
|
||||
result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
|
||||
if (!arguments.Result.empty()) {
|
||||
status.GetMakefile().AddDefinition(arguments.Result, result);
|
||||
@@ -3234,11 +3239,17 @@ bool HandleCreateLinkCommand(std::vector<std::string> const& args,
|
||||
|
||||
// Check if the new file already exists and remove it.
|
||||
if (cmSystemTools::PathExists(newFileName)) {
|
||||
cmsys::Status rmStatus = cmSystemTools::RemoveFile(newFileName);
|
||||
cmsys::Status rmStatus;
|
||||
if (cmp0205 == cmPolicies::NEW &&
|
||||
cmSystemTools::FileIsDirectory(newFileName)) {
|
||||
rmStatus = cmSystemTools::RepeatedRemoveDirectory(newFileName);
|
||||
} else {
|
||||
rmStatus = cmSystemTools::RemoveFile(newFileName);
|
||||
}
|
||||
if (!rmStatus) {
|
||||
auto err = cmStrCat("Failed to create link '", newFileName,
|
||||
"' because existing path cannot be removed: ",
|
||||
rmStatus.GetString(), '\n');
|
||||
std::string err = cmStrCat("Failed to create link '", newFileName,
|
||||
"' because existing path cannot be removed: ",
|
||||
rmStatus.GetString(), '\n');
|
||||
|
||||
if (!arguments.Result.empty()) {
|
||||
status.GetMakefile().AddDefinition(arguments.Result, err);
|
||||
@@ -3249,6 +3260,8 @@ bool HandleCreateLinkCommand(std::vector<std::string> const& args,
|
||||
}
|
||||
}
|
||||
|
||||
bool const sourceIsDirectory = cmSystemTools::FileIsDirectory(fileName);
|
||||
|
||||
// Whether the operation completed successfully.
|
||||
bool completed = false;
|
||||
|
||||
@@ -3263,20 +3276,54 @@ bool HandleCreateLinkCommand(std::vector<std::string> const& args,
|
||||
"': ", linked.GetString());
|
||||
}
|
||||
} else {
|
||||
cmsys::Status linked =
|
||||
cmSystemTools::CreateLinkQuietly(fileName, newFileName);
|
||||
if (linked) {
|
||||
completed = true;
|
||||
} else {
|
||||
result = cmStrCat("failed to create link '", newFileName,
|
||||
"': ", linked.GetString());
|
||||
bool needToTry = true;
|
||||
if (sourceIsDirectory) {
|
||||
if (cmp0205 == cmPolicies::NEW) {
|
||||
needToTry = false;
|
||||
} else if (cmp0205 == cmPolicies::WARN) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::AUTHOR_WARNING,
|
||||
cmStrCat("Path\n ", fileName,
|
||||
"\nis directory. Hardlinks creation is not supported for "
|
||||
"directories.\n",
|
||||
cmPolicies::GetPolicyWarning(cmPolicies::CMP0205)));
|
||||
}
|
||||
}
|
||||
|
||||
if (needToTry) {
|
||||
cmsys::Status linked =
|
||||
cmSystemTools::CreateLinkQuietly(fileName, newFileName);
|
||||
if (linked) {
|
||||
completed = true;
|
||||
} else {
|
||||
result = cmStrCat("failed to create link '", newFileName,
|
||||
"': ", linked.GetString());
|
||||
}
|
||||
} else {
|
||||
result =
|
||||
cmStrCat("failed to create link '", newFileName, "': not supported");
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments.CopyOnError && cmp0205 == cmPolicies::WARN &&
|
||||
sourceIsDirectory) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::AUTHOR_WARNING,
|
||||
cmStrCat("Path\n ", fileName,
|
||||
"\nis directory. It will be copied recursively when NEW policy "
|
||||
"behavior applies for CMP0205.\n",
|
||||
cmPolicies::GetPolicyWarning(cmPolicies::CMP0205)));
|
||||
}
|
||||
|
||||
// Check if copy-on-error is enabled in the arguments.
|
||||
if (!completed && arguments.CopyOnError) {
|
||||
cmsys::Status copied =
|
||||
cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
|
||||
cmsys::Status copied;
|
||||
if (cmp0205 == cmPolicies::NEW && sourceIsDirectory) {
|
||||
copied = cmsys::SystemTools::CopyADirectory(fileName, newFileName);
|
||||
} else {
|
||||
copied = cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
|
||||
}
|
||||
|
||||
if (copied) {
|
||||
completed = true;
|
||||
} else {
|
||||
|
||||
@@ -612,7 +612,10 @@ class cmMakefile;
|
||||
4, 2, 0, WARN) \
|
||||
SELECT(POLICY, CMP0204, \
|
||||
"A character set is always defined when targeting the MSVC ABI.", 4, \
|
||||
2, 0, WARN)
|
||||
2, 0, WARN) \
|
||||
SELECT(POLICY, CMP0205, \
|
||||
"file(CREATE_LINK) with COPY_ON_ERROR copies directory content.", 4, \
|
||||
3, 0, WARN)
|
||||
|
||||
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
|
||||
#define CM_FOR_EACH_POLICY_ID(POLICY) \
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name HardLink)
|
||||
set(maybe_SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-NEW.cmake")
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name HardLink)
|
||||
set(maybe_SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-OLD.cmake")
|
||||
@@ -0,0 +1,38 @@
|
||||
^CMake Warning \(dev\) at [^
|
||||
]*/CMP0205-common\.cmake:[0-9]+ \(file\):
|
||||
Path
|
||||
|
||||
[^
|
||||
]*[\\|/]file-CREATE_LINK[\\|/]CMP0205
|
||||
|
||||
is directory. Hardlinks creation is not supported for directories.
|
||||
|
||||
Policy CMP0205 is not set: file\(CREATE_LINK\) with COPY_ON_ERROR copies
|
||||
directory content\. Run "cmake --help-policy CMP0205" for policy details\.
|
||||
Use the cmake_policy command to set the policy and suppress this warning\.
|
||||
Call Stack \(most recent call first\):
|
||||
[^
|
||||
]*/CMP0205-common-WARN\.cmake:[0-9]+ \(include\)
|
||||
[^
|
||||
]*/CMP0205-HardLink-WARN\.cmake:[0-9]+ \(include\)
|
||||
This warning is for project developers\. Use -Wno-dev to suppress it\.
|
||||
|
||||
CMake Warning \(dev\) at [^
|
||||
]*/CMP0205-common\.cmake:[0-9]+ \(file\):
|
||||
Path
|
||||
|
||||
[^
|
||||
]*[\\|/]file-CREATE_LINK[\\|/]CMP0205
|
||||
|
||||
is directory. It will be copied recursively when NEW policy behavior
|
||||
applies for CMP0205\.
|
||||
|
||||
Policy CMP0205 is not set: file\(CREATE_LINK\) with COPY_ON_ERROR copies
|
||||
directory content\. Run "cmake --help-policy CMP0205" for policy details\.
|
||||
Use the cmake_policy command to set the policy and suppress this warning\.
|
||||
Call Stack \(most recent call first\):
|
||||
[^
|
||||
]*/CMP0205-common-WARN\.cmake:[0-9]+ \(include\)
|
||||
[^
|
||||
]*/CMP0205-HardLink-WARN\.cmake:[0-9]+ \(include\)
|
||||
This warning is for project developers\. Use -Wno-dev to suppress it\.$
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name HardLink)
|
||||
set(maybe_SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-WARN.cmake")
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name SymLink)
|
||||
set(maybe_SYMBOLIC SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-NEW.cmake")
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name SymLink)
|
||||
set(maybe_SYMBOLIC SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-OLD.cmake")
|
||||
@@ -0,0 +1,19 @@
|
||||
^CMake Warning \(dev\) at [^
|
||||
]*/CMP0205-common\.cmake:[0-9]+ \(file\):
|
||||
Path
|
||||
|
||||
[^
|
||||
]*[\\|/]file-CREATE_LINK[\\|/]CMP0205
|
||||
|
||||
is directory. It will be copied recursively when NEW policy behavior
|
||||
applies for CMP0205\.
|
||||
|
||||
Policy CMP0205 is not set: file\(CREATE_LINK\) with COPY_ON_ERROR copies
|
||||
directory content\. Run "cmake --help-policy CMP0205" for policy details\.
|
||||
Use the cmake_policy command to set the policy and suppress this warning\.
|
||||
Call Stack \(most recent call first\):
|
||||
[^
|
||||
]*/CMP0205-common-WARN\.cmake:[0-9]+ \(include\)
|
||||
[^
|
||||
]*/CMP0205-SymLink-WARN\.cmake:[0-9]+ \(include\)
|
||||
This warning is for project developers\. Use -Wno-dev to suppress it\.$
|
||||
@@ -0,0 +1,3 @@
|
||||
set(link_name SymLink)
|
||||
set(maybe_SYMBOLIC SYMBOLIC)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common-WARN.cmake")
|
||||
12
Tests/RunCMake/file-CREATE_LINK/CMP0205-common-NEW.cmake
Normal file
12
Tests/RunCMake/file-CREATE_LINK/CMP0205-common-NEW.cmake
Normal file
@@ -0,0 +1,12 @@
|
||||
cmake_policy(SET CMP0205 NEW)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common.cmake")
|
||||
|
||||
if(NOT allFilesDst)
|
||||
message(SEND_ERROR "Destination directory is empty: '${allFilesDst}'")
|
||||
endif()
|
||||
|
||||
if(NOT "${allFilesSrc}" STREQUAL "${allFilesDst}")
|
||||
message(SEND_ERROR "Source and destination directories are not equal")
|
||||
message(SEND_ERROR "Source files: '${allFilesSrc}'")
|
||||
message(SEND_ERROR "Destination files: '${allFilesDst}'")
|
||||
endif()
|
||||
6
Tests/RunCMake/file-CREATE_LINK/CMP0205-common-OLD.cmake
Normal file
6
Tests/RunCMake/file-CREATE_LINK/CMP0205-common-OLD.cmake
Normal file
@@ -0,0 +1,6 @@
|
||||
cmake_policy(SET CMP0205 OLD)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common.cmake")
|
||||
|
||||
if(allFilesDst)
|
||||
message(SEND_ERROR "Directory is not empty: '${allFilesDst}'")
|
||||
endif()
|
||||
@@ -0,0 +1,6 @@
|
||||
# CMP0205 is unset
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/CMP0205-common.cmake")
|
||||
|
||||
if(allFilesDst)
|
||||
message(SEND_ERROR "Directory is not empty: '${allFilesDst}'")
|
||||
endif()
|
||||
14
Tests/RunCMake/file-CREATE_LINK/CMP0205-common.cmake
Normal file
14
Tests/RunCMake/file-CREATE_LINK/CMP0205-common.cmake
Normal file
@@ -0,0 +1,14 @@
|
||||
# Use COPY_ON_ERROR to handle the case where the source and destination
|
||||
# directory are on different devices and empty.
|
||||
file(CREATE_LINK
|
||||
${CMAKE_CURRENT_LIST_DIR}/CMP0205 ${CMAKE_CURRENT_BINARY_DIR}/CMP0205-${link_name}
|
||||
${maybe_SYMBOLIC}
|
||||
RESULT result
|
||||
COPY_ON_ERROR
|
||||
)
|
||||
if(NOT result STREQUAL "0")
|
||||
message(SEND_ERROR "COPY_ON_ERROR failed: '${result}'")
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE allFilesSrc LIST_DIRECTORIES true RELATIVE "${CMAKE_CURRENT_LIST_DIR}/CMP0205" "${CMAKE_CURRENT_LIST_DIR}/CMP0205/*")
|
||||
file(GLOB_RECURSE allFilesDst LIST_DIRECTORIES true RELATIVE "${CMAKE_CURRENT_BINARY_DIR}/CMP0205-${link_name}" "${CMAKE_CURRENT_BINARY_DIR}/CMP0205-${link_name}/*")
|
||||
0
Tests/RunCMake/file-CREATE_LINK/CMP0205/test.txt
Normal file
0
Tests/RunCMake/file-CREATE_LINK/CMP0205/test.txt
Normal file
@@ -1,6 +1,7 @@
|
||||
# Use COPY_ON_ERROR to handle the case where the source and destination
|
||||
# directory are on different devices. Cross-device links are not permitted
|
||||
# file are on different devices. Cross-device links are not permitted
|
||||
# and the following command falls back to copying the file if link fails.
|
||||
# Check only command result.
|
||||
file(CREATE_LINK
|
||||
${CMAKE_CURRENT_LIST_FILE} TestCreateLink.cmake
|
||||
RESULT result
|
||||
@@ -1,7 +1,7 @@
|
||||
include(RunCMake)
|
||||
|
||||
run_cmake(CREATE_LINK)
|
||||
run_cmake(CREATE_LINK-COPY_ON_ERROR)
|
||||
run_cmake(CREATE_LINK-COPY_ON_ERROR-file)
|
||||
run_cmake(CREATE_LINK-noarg)
|
||||
run_cmake(CREATE_LINK-noexist)
|
||||
|
||||
@@ -11,3 +11,34 @@ if(NOT WIN32
|
||||
run_cmake(CREATE_LINK-SYMBOLIC)
|
||||
run_cmake(CREATE_LINK-SYMBOLIC-noexist)
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY ${RunCMake_BINARY_DIR}/CMP0205-Inspect/Dest)
|
||||
|
||||
file(REMOVE_RECURSE ${RunCMake_BINARY_DIR}/CMP0205-Inspect-SymLink)
|
||||
file(CREATE_LINK
|
||||
${RunCMake_BINARY_DIR}/CMP0205-Inspect/Dest ${RunCMake_BINARY_DIR}/CMP0205-Inspect-SymLink
|
||||
SYMBOLIC
|
||||
RESULT SymLink_RESULT
|
||||
)
|
||||
if(SymLink_RESULT STREQUAL "0")
|
||||
message(STATUS "CMP0205-SymLink-* skipped: directory symbolic link creation works")
|
||||
file(REMOVE ${RunCMake_BINARY_DIR}/CMP0205-Inspect-SymLink)
|
||||
else()
|
||||
run_cmake_script(CMP0205-SymLink-WARN)
|
||||
run_cmake_script(CMP0205-SymLink-OLD)
|
||||
run_cmake_script(CMP0205-SymLink-NEW)
|
||||
endif()
|
||||
|
||||
file(REMOVE_RECURSE ${RunCMake_BINARY_DIR}/CMP0205-Inspect-HardLink)
|
||||
file(CREATE_LINK
|
||||
${RunCMake_BINARY_DIR}/CMP0205-Inspect/Dest ${RunCMake_BINARY_DIR}/CMP0205-Inspect-HardLink
|
||||
RESULT HardLink_RESULT
|
||||
)
|
||||
if(HardLink_RESULT STREQUAL "0")
|
||||
message(STATUS "CMP0205-HardLink-* skipped: directory hard link creation works")
|
||||
file(REMOVE_RECURSE ${RunCMake_BINARY_DIR}/CMP0205-Inspect-HardLink)
|
||||
else()
|
||||
run_cmake_script(CMP0205-HardLink-WARN)
|
||||
run_cmake_script(CMP0205-HardLink-OLD)
|
||||
run_cmake_script(CMP0205-HardLink-NEW)
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user