mirror of
https://github.com/Kitware/CMake.git
synced 2026-02-22 15:10:20 -06:00
Merge topic 'command_file_link'
0f08ed8936cmSystemTools: Silence CreateLink and CreateSymlink errors593d986470Tests: Avoid cross-device links in CREATE_LINK test9a3d85cfc5Tests: Skip symlink tests on Windowse68ea269d7Tests: CREATE_LINK subcommand negative test case45aa9c65a1Tests: file CREATE_LINK subcommand test cases8bb7562f1aHelp: Add documentation for file(CREATE_LINK) subcommand81650e488ccmFileCommand: Add CREATE_LINK subcommand Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !2759
This commit is contained in:
@@ -27,6 +27,7 @@ Synopsis
|
||||
file({`COPY`_ | `INSTALL`_} <file>... DESTINATION <dir> [...])
|
||||
file(`SIZE`_ <filename> <out-var>)
|
||||
file(`READ_SYMLINK`_ <filename> <out-var>)
|
||||
file(`CREATE_LINK`_ <file> <new-file> [...])
|
||||
|
||||
`Path Conversion`_
|
||||
file(`RELATIVE_PATH`_ <out-var> <directory> <file>)
|
||||
@@ -368,6 +369,28 @@ could do something like this:
|
||||
set(result "${dir}/${result}")
|
||||
endif()
|
||||
|
||||
.. _CREATE_LINK:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
file(CREATE_LINK <file> <new-file>
|
||||
[RESULT <result>] [COPY_ON_ERROR] [SYMBOLIC])
|
||||
|
||||
Create a link to ``<file>`` at ``<new-file>``.
|
||||
|
||||
It is a hard link by default. This can be changed to symbolic links by
|
||||
using ``SYMBOLIC``. The original file needs to exist for hard links.
|
||||
|
||||
The ``<result>`` variable, if specified, gets the status of the operation.
|
||||
It is set to ``0`` in case of success. Otherwise, it contains the error
|
||||
generated. In case of failures, if ``RESULT`` is not specified, a fatal error
|
||||
is emitted.
|
||||
|
||||
Specifying ``COPY_ON_ERROR`` enables copying the file as a fallback if
|
||||
creating the link fails.
|
||||
|
||||
Overwrites the ``<new-file>`` if it exists.
|
||||
|
||||
Path Conversion
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -185,6 +185,9 @@ bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
|
||||
if (subCommand == "READ_SYMLINK") {
|
||||
return this->HandleReadSymlinkCommand(args);
|
||||
}
|
||||
if (subCommand == "CREATE_LINK") {
|
||||
return this->HandleCreateLinkCommand(args);
|
||||
}
|
||||
|
||||
std::string e = "does not recognize sub-command " + subCommand;
|
||||
this->SetError(e);
|
||||
@@ -3670,3 +3673,119 @@ bool cmFileCommand::HandleReadSymlinkCommand(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmFileCommand::HandleCreateLinkCommand(
|
||||
std::vector<std::string> const& args)
|
||||
{
|
||||
if (args.size() < 3) {
|
||||
this->SetError("CREATE_LINK must be called with at least two additional "
|
||||
"arguments");
|
||||
return false;
|
||||
}
|
||||
|
||||
cmCommandArgumentsHelper argHelper;
|
||||
cmCommandArgumentGroup group;
|
||||
|
||||
cmCAString linkArg(&argHelper, "CREATE_LINK");
|
||||
cmCAString fileArg(&argHelper, nullptr);
|
||||
cmCAString newFileArg(&argHelper, nullptr);
|
||||
|
||||
cmCAString resultArg(&argHelper, "RESULT", &group);
|
||||
cmCAEnabler copyOnErrorArg(&argHelper, "COPY_ON_ERROR", &group);
|
||||
cmCAEnabler symbolicArg(&argHelper, "SYMBOLIC", &group);
|
||||
|
||||
linkArg.Follows(nullptr);
|
||||
fileArg.Follows(&linkArg);
|
||||
newFileArg.Follows(&fileArg);
|
||||
group.Follows(&newFileArg);
|
||||
|
||||
std::vector<std::string> unconsumedArgs;
|
||||
argHelper.Parse(&args, &unconsumedArgs);
|
||||
|
||||
if (!unconsumedArgs.empty()) {
|
||||
this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fileName = fileArg.GetString();
|
||||
std::string newFileName = newFileArg.GetString();
|
||||
|
||||
// Output variable for storing the result.
|
||||
const std::string& resultVar = resultArg.GetString();
|
||||
|
||||
// The system error message generated in the operation.
|
||||
std::string result;
|
||||
|
||||
// Check if the paths are distinct.
|
||||
if (fileName == newFileName) {
|
||||
result = "CREATE_LINK cannot use same file and newfile";
|
||||
if (!resultVar.empty()) {
|
||||
this->Makefile->AddDefinition(resultVar, result.c_str());
|
||||
return true;
|
||||
}
|
||||
this->SetError(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hard link requires original file to exist.
|
||||
if (!symbolicArg.IsEnabled() && !cmSystemTools::FileExists(fileName)) {
|
||||
result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
|
||||
if (!resultVar.empty()) {
|
||||
this->Makefile->AddDefinition(resultVar, result.c_str());
|
||||
return true;
|
||||
}
|
||||
this->SetError(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the new file already exists and remove it.
|
||||
if ((cmSystemTools::FileExists(newFileName) ||
|
||||
cmSystemTools::FileIsSymlink(newFileName)) &&
|
||||
!cmSystemTools::RemoveFile(newFileName)) {
|
||||
std::ostringstream e;
|
||||
e << "Failed to create link '" << newFileName
|
||||
<< "' because existing path cannot be removed: "
|
||||
<< cmSystemTools::GetLastSystemError() << "\n";
|
||||
|
||||
if (!resultVar.empty()) {
|
||||
this->Makefile->AddDefinition(resultVar, e.str().c_str());
|
||||
return true;
|
||||
}
|
||||
this->SetError(e.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether the operation completed successfully.
|
||||
bool completed = false;
|
||||
|
||||
// Check if the command requires a symbolic link.
|
||||
if (symbolicArg.IsEnabled()) {
|
||||
completed = cmSystemTools::CreateSymlink(fileName, newFileName, &result);
|
||||
} else {
|
||||
completed = cmSystemTools::CreateLink(fileName, newFileName, &result);
|
||||
}
|
||||
|
||||
// Check if copy-on-error is enabled in the arguments.
|
||||
if (!completed && copyOnErrorArg.IsEnabled()) {
|
||||
completed =
|
||||
cmSystemTools::cmCopyFile(fileName.c_str(), newFileName.c_str());
|
||||
if (!completed) {
|
||||
result = "Copy failed: " + cmSystemTools::GetLastSystemError();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the operation was successful.
|
||||
if (completed) {
|
||||
result = "0";
|
||||
} else if (resultVar.empty()) {
|
||||
// The operation failed and the result is not reported in a variable.
|
||||
this->SetError(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!resultVar.empty()) {
|
||||
this->Makefile->AddDefinition(resultVar, result.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ protected:
|
||||
bool HandleLockCommand(std::vector<std::string> const& args);
|
||||
bool HandleSizeCommand(std::vector<std::string> const& args);
|
||||
bool HandleReadSymlinkCommand(std::vector<std::string> const& args);
|
||||
bool HandleCreateLinkCommand(std::vector<std::string> const& args);
|
||||
|
||||
private:
|
||||
void AddEvaluationFile(const std::string& inputName,
|
||||
|
||||
@@ -3114,7 +3114,8 @@ std::string cmSystemTools::EncodeURL(std::string const& in, bool escapeSlashes)
|
||||
}
|
||||
|
||||
bool cmSystemTools::CreateSymlink(const std::string& origName,
|
||||
const std::string& newName)
|
||||
const std::string& newName,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
uv_fs_t req;
|
||||
int flags = 0;
|
||||
@@ -3128,7 +3129,32 @@ bool cmSystemTools::CreateSymlink(const std::string& origName,
|
||||
if (err) {
|
||||
std::string e =
|
||||
"failed to create symbolic link '" + newName + "': " + uv_strerror(err);
|
||||
cmSystemTools::Error(e.c_str());
|
||||
if (errorMessage) {
|
||||
*errorMessage = std::move(e);
|
||||
} else {
|
||||
cmSystemTools::Error(e.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmSystemTools::CreateLink(const std::string& origName,
|
||||
const std::string& newName,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
uv_fs_t req;
|
||||
int err =
|
||||
uv_fs_link(nullptr, &req, origName.c_str(), newName.c_str(), nullptr);
|
||||
if (err) {
|
||||
std::string e =
|
||||
"failed to create link '" + newName + "': " + uv_strerror(err);
|
||||
if (errorMessage) {
|
||||
*errorMessage = std::move(e);
|
||||
} else {
|
||||
cmSystemTools::Error(e.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -528,7 +528,14 @@ public:
|
||||
/** Create a symbolic link if the platform supports it. Returns whether
|
||||
creation succeeded. */
|
||||
static bool CreateSymlink(const std::string& origName,
|
||||
const std::string& newName);
|
||||
const std::string& newName,
|
||||
std::string* errorMessage = nullptr);
|
||||
|
||||
/** Create a hard link if the platform supports it. Returns whether
|
||||
creation succeeded. */
|
||||
static bool CreateLink(const std::string& origName,
|
||||
const std::string& newName,
|
||||
std::string* errorMessage = nullptr);
|
||||
|
||||
private:
|
||||
static bool s_ForceUnixPaths;
|
||||
|
||||
11
Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake
Normal file
11
Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake
Normal file
@@ -0,0 +1,11 @@
|
||||
# Use COPY_ON_ERROR to handle the case where the source and destination
|
||||
# directory are on different devices. Cross-device links are not permitted
|
||||
# and the following command falls back to copying the file if link fails.
|
||||
file(CREATE_LINK
|
||||
${CMAKE_CURRENT_LIST_FILE} TestCreateLink.cmake
|
||||
RESULT result
|
||||
COPY_ON_ERROR
|
||||
)
|
||||
if(NOT result STREQUAL "0")
|
||||
message(SEND_ERROR "COPY_ON_ERROR failed: '${result}'")
|
||||
endif()
|
||||
4
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake
Normal file
4
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
file(CREATE_LINK does_not_exist.txt TestSymLink.txt RESULT sym_result SYMBOLIC)
|
||||
if(NOT sym_result STREQUAL "0")
|
||||
message("Symlink fail: ${sym_result}")
|
||||
endif()
|
||||
4
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake
Normal file
4
Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
file(CREATE_LINK ${CMAKE_CURRENT_LIST_FILE} TestSymLink.cmake RESULT sym_result SYMBOLIC)
|
||||
if(NOT sym_result STREQUAL "0")
|
||||
message(SEND_ERROR "Symlink result='${sym_result}'")
|
||||
endif()
|
||||
1
Tests/RunCMake/file/CREATE_LINK-noarg-result.txt
Normal file
1
Tests/RunCMake/file/CREATE_LINK-noarg-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
4
Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt
Normal file
4
Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
CMake Error at CREATE_LINK-noarg\.cmake:[0-9]+ \(file\):
|
||||
file CREATE_LINK must be called with at least two additional arguments
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
1
Tests/RunCMake/file/CREATE_LINK-noarg.cmake
Normal file
1
Tests/RunCMake/file/CREATE_LINK-noarg.cmake
Normal file
@@ -0,0 +1 @@
|
||||
file(CREATE_LINK ${CMAKE_CURRENT_LIST_FILE})
|
||||
1
Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt
Normal file
1
Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hard link error: Cannot hard link 'does_not_exist.txt' as it does not exist.
|
||||
4
Tests/RunCMake/file/CREATE_LINK-noexist.cmake
Normal file
4
Tests/RunCMake/file/CREATE_LINK-noexist.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
file(CREATE_LINK does_not_exist.txt TestLink.txt RESULT result)
|
||||
if(NOT result STREQUAL "0")
|
||||
message("Hard link error: ${result}")
|
||||
endif()
|
||||
11
Tests/RunCMake/file/CREATE_LINK.cmake
Normal file
11
Tests/RunCMake/file/CREATE_LINK.cmake
Normal file
@@ -0,0 +1,11 @@
|
||||
# start with a file in the same directory to avoid cross-device links
|
||||
set(test_file ${CMAKE_CURRENT_BINARY_DIR}/CreateLinkTest.txt)
|
||||
file(TOUCH ${test_file})
|
||||
|
||||
file(CREATE_LINK
|
||||
${test_file} ${CMAKE_CURRENT_BINARY_DIR}/TestCreateLink.txt
|
||||
RESULT result
|
||||
)
|
||||
if(NOT result STREQUAL "0")
|
||||
message(SEND_ERROR "Hard link result='${result}'")
|
||||
endif()
|
||||
@@ -1,5 +1,9 @@
|
||||
include(RunCMake)
|
||||
|
||||
run_cmake(CREATE_LINK)
|
||||
run_cmake(CREATE_LINK-COPY_ON_ERROR)
|
||||
run_cmake(CREATE_LINK-noarg)
|
||||
run_cmake(CREATE_LINK-noexist)
|
||||
run_cmake(DOWNLOAD-hash-mismatch)
|
||||
run_cmake(DOWNLOAD-unused-argument)
|
||||
run_cmake(DOWNLOAD-httpheader-not-set)
|
||||
@@ -53,6 +57,8 @@ run_cmake_command(GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE ${CMAKE_COMMAND} -P
|
||||
${RunCMake_SOURCE_DIR}/GLOB-error-CONFIGURE_DEPENDS-SCRIPT_MODE.cmake)
|
||||
|
||||
if(NOT WIN32 OR CYGWIN)
|
||||
run_cmake(CREATE_LINK-SYMBOLIC)
|
||||
run_cmake(CREATE_LINK-SYMBOLIC-noexist)
|
||||
run_cmake(GLOB_RECURSE-cyclic-recursion)
|
||||
run_cmake(INSTALL-SYMLINK)
|
||||
run_cmake(READ_SYMLINK)
|
||||
|
||||
Reference in New Issue
Block a user