summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2019-01-21 12:48:19 (GMT)
committerKitware Robot <kwrobot@kitware.com>2019-01-21 12:48:26 (GMT)
commitd704cc34071f0c6f95099670c94c7f1940ab7fa2 (patch)
tree1a815a2dcb0446d290d6b80b8828ed7bbef2594b
parentd1aa2e11092acdcc22b333e219187026d38549a5 (diff)
parent0f08ed89362d207e18b06e806f127cd683b79141 (diff)
downloadCMake-d704cc34071f0c6f95099670c94c7f1940ab7fa2.zip
CMake-d704cc34071f0c6f95099670c94c7f1940ab7fa2.tar.gz
CMake-d704cc34071f0c6f95099670c94c7f1940ab7fa2.tar.bz2
Merge topic 'command_file_link'
0f08ed8936 cmSystemTools: Silence CreateLink and CreateSymlink errors 593d986470 Tests: Avoid cross-device links in CREATE_LINK test 9a3d85cfc5 Tests: Skip symlink tests on Windows e68ea269d7 Tests: CREATE_LINK subcommand negative test case 45aa9c65a1 Tests: file CREATE_LINK subcommand test cases 8bb7562f1a Help: Add documentation for file(CREATE_LINK) subcommand 81650e488c cmFileCommand: Add CREATE_LINK subcommand Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !2759
-rw-r--r--Help/command/file.rst23
-rw-r--r--Source/cmFileCommand.cxx119
-rw-r--r--Source/cmFileCommand.h1
-rw-r--r--Source/cmSystemTools.cxx30
-rw-r--r--Source/cmSystemTools.h9
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake11
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake4
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake4
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-noarg-result.txt1
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt4
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-noarg.cmake1
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt1
-rw-r--r--Tests/RunCMake/file/CREATE_LINK-noexist.cmake4
-rw-r--r--Tests/RunCMake/file/CREATE_LINK.cmake11
-rw-r--r--Tests/RunCMake/file/RunCMakeTest.cmake6
15 files changed, 226 insertions, 3 deletions
diff --git a/Help/command/file.rst b/Help/command/file.rst
index 6e2a6dd..db4d6fc 100644
--- a/Help/command/file.rst
+++ b/Help/command/file.rst
@@ -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
^^^^^^^^^^^^^^^
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index db2fde8..999af54 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -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;
+}
diff --git a/Source/cmFileCommand.h b/Source/cmFileCommand.h
index fe05c98..12c5115 100644
--- a/Source/cmFileCommand.h
+++ b/Source/cmFileCommand.h
@@ -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,
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index be65853..52957c1 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -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;
}
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index c0999e7..489811d 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -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;
diff --git a/Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake b/Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake
new file mode 100644
index 0000000..777ef4e
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-COPY_ON_ERROR.cmake
@@ -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()
diff --git a/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake b/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake
new file mode 100644
index 0000000..61aaf38
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC-noexist.cmake
@@ -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()
diff --git a/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake b/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake
new file mode 100644
index 0000000..77b899c
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-SYMBOLIC.cmake
@@ -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()
diff --git a/Tests/RunCMake/file/CREATE_LINK-noarg-result.txt b/Tests/RunCMake/file/CREATE_LINK-noarg-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-noarg-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt b/Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt
new file mode 100644
index 0000000..12494f8
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-noarg-stderr.txt
@@ -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\)
diff --git a/Tests/RunCMake/file/CREATE_LINK-noarg.cmake b/Tests/RunCMake/file/CREATE_LINK-noarg.cmake
new file mode 100644
index 0000000..65002fa
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-noarg.cmake
@@ -0,0 +1 @@
+file(CREATE_LINK ${CMAKE_CURRENT_LIST_FILE})
diff --git a/Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt b/Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt
new file mode 100644
index 0000000..97eee4f
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-noexist-stderr.txt
@@ -0,0 +1 @@
+Hard link error: Cannot hard link 'does_not_exist.txt' as it does not exist.
diff --git a/Tests/RunCMake/file/CREATE_LINK-noexist.cmake b/Tests/RunCMake/file/CREATE_LINK-noexist.cmake
new file mode 100644
index 0000000..5ee2580
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK-noexist.cmake
@@ -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()
diff --git a/Tests/RunCMake/file/CREATE_LINK.cmake b/Tests/RunCMake/file/CREATE_LINK.cmake
new file mode 100644
index 0000000..ca61646
--- /dev/null
+++ b/Tests/RunCMake/file/CREATE_LINK.cmake
@@ -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()
diff --git a/Tests/RunCMake/file/RunCMakeTest.cmake b/Tests/RunCMake/file/RunCMakeTest.cmake
index b872824..128e8f3 100644
--- a/Tests/RunCMake/file/RunCMakeTest.cmake
+++ b/Tests/RunCMake/file/RunCMakeTest.cmake
@@ -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)