Commit 3bd8ed22 authored by Marc Chevrier's avatar Marc Chevrier Committed by Kitware Robot

Merge topic 'file-install-follow-symlink-chain'

e3ff7ced file(INSTALL): Add FOLLOW_SYMLINK_CHAIN argument
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: Brad King's avatarBrad King <brad.king@kitware.com>
Merge-request: !3332
parents 8ada0598 e3ff7ced
Pipeline #137290 passed with stage
in 0 seconds
......@@ -311,6 +311,7 @@ Create the given directories and their parents as needed.
[FILE_PERMISSIONS <permissions>...]
[DIRECTORY_PERMISSIONS <permissions>...]
[NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
[FOLLOW_SYMLINK_CHAIN]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS <permissions>...]] [...])
......@@ -324,6 +325,32 @@ at the destination with the same timestamp. Copying preserves input
permissions unless explicit permissions or ``NO_SOURCE_PERMISSIONS``
are given (default is ``USE_SOURCE_PERMISSIONS``).
If ``FOLLOW_SYMLINK_CHAIN`` is specified, ``COPY`` will recursively resolve
the symlinks at the paths given until a real file is found, and install
a corresponding symlink in the destination for each symlink encountered. For
each symlink that is installed, the resolution is stripped of the directory,
leaving only the filename, meaning that the new symlink points to a file in
the same directory as the symlink. This feature is useful on some Unix systems,
where libraries are installed as a chain of symlinks with version numbers, with
less specific versions pointing to more specific versions.
``FOLLOW_SYMLINK_CHAIN`` will install all of these symlinks and the library
itself into the destination directory. For example, if you have the following
directory structure:
* ``/opt/foo/lib/libfoo.so.1.2.3``
* ``/opt/foo/lib/libfoo.so.1.2 -> libfoo.so.1.2.3``
* ``/opt/foo/lib/libfoo.so.1 -> libfoo.so.1.2``
* ``/opt/foo/lib/libfoo.so -> libfoo.so.1``
and you do:
.. code-block:: cmake
file(COPY /opt/foo/lib/libfoo.so DESTINATION lib FOLLOW_SYMLINK_CHAIN)
This will install all of the symlinks and ``libfoo.so.1.2.3`` itself into
``lib``.
See the :command:`install(DIRECTORY)` command for documentation of
permissions, ``FILES_MATCHING``, ``PATTERN``, ``REGEX``, and
``EXCLUDE`` options. Copying directories preserves the structure
......
file-install-follow-symlink-chain
---------------------------------
* The :command:`file(INSTALL)` command learned a new argument,
``FOLLOW_SYMLINK_CHAIN``, which can be used to recursively resolve and
install symlinks.
......@@ -31,6 +31,7 @@ cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name)
, UseGivenPermissionsFile(false)
, UseGivenPermissionsDir(false)
, UseSourcePermissions(true)
, FollowSymlinkChain(false)
, Doing(DoingNone)
{
}
......@@ -249,6 +250,9 @@ bool cmFileCopier::CheckKeyword(std::string const& arg)
this->Doing = DoingPattern;
} else if (arg == "REGEX") {
this->Doing = DoingRegex;
} else if (arg == "FOLLOW_SYMLINK_CHAIN") {
this->FollowSymlinkChain = true;
this->Doing = DoingNone;
} else if (arg == "EXCLUDE") {
// Add this property to the current match rule.
if (this->CurrentMatchRule) {
......@@ -464,16 +468,69 @@ bool cmFileCopier::Install(const std::string& fromFile,
if (cmSystemTools::SameFile(fromFile, toFile)) {
return true;
}
if (cmSystemTools::FileIsSymlink(fromFile)) {
return this->InstallSymlink(fromFile, toFile);
std::string newFromFile = fromFile;
std::string newToFile = toFile;
if (this->FollowSymlinkChain &&
!this->InstallSymlinkChain(newFromFile, newToFile)) {
return false;
}
if (cmSystemTools::FileIsDirectory(fromFile)) {
return this->InstallDirectory(fromFile, toFile, match_properties);
if (cmSystemTools::FileIsSymlink(newFromFile)) {
return this->InstallSymlink(newFromFile, newToFile);
}
if (cmSystemTools::FileExists(fromFile)) {
return this->InstallFile(fromFile, toFile, match_properties);
if (cmSystemTools::FileIsDirectory(newFromFile)) {
return this->InstallDirectory(newFromFile, newToFile, match_properties);
}
return this->ReportMissing(fromFile);
if (cmSystemTools::FileExists(newFromFile)) {
return this->InstallFile(newFromFile, newToFile, match_properties);
}
return this->ReportMissing(newFromFile);
}
bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
std::string& toFile)
{
std::string newFromFile;
std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
if (!cmSystemTools::FileIsFullPath(newFromFile)) {
std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
newFromFile = fromFilePath + "/" + newFromFile;
}
std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
bool copy = true;
if (!this->Always) {
std::string oldSymlinkTarget;
if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
if (symlinkTarget == oldSymlinkTarget) {
copy = false;
}
}
}
this->ReportCopy(toFile, TypeLink, copy);
if (copy) {
cmSystemTools::RemoveFile(toFile);
cmSystemTools::MakeDirectory(toFilePath);
if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
std::ostringstream e;
e << this->Name << " cannot create symlink \"" << toFile << "\".";
this->FileCommand->SetError(e.str());
return false;
}
}
fromFile = newFromFile;
toFile = toFilePath + "/" + symlinkTarget;
}
return true;
}
bool cmFileCopier::InstallSymlink(const std::string& fromFile,
......
......@@ -64,6 +64,7 @@ protected:
// Translate an argument to a permissions bit.
bool CheckPermissions(std::string const& arg, mode_t& permissions);
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);
......@@ -86,6 +87,7 @@ protected:
bool UseGivenPermissionsFile;
bool UseGivenPermissionsDir;
bool UseSourcePermissions;
bool FollowSymlinkChain;
std::string Destination;
std::string FilesFromDir;
std::vector<std::string> Files;
......
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/dest1")
file(TOUCH "${CMAKE_BINARY_DIR}/file1.txt")
file(CREATE_LINK file1.txt "${CMAKE_BINARY_DIR}/file1.txt.sym" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/dest1/file1.txt.sym")
file(TOUCH "${CMAKE_BINARY_DIR}/file2.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file2")
file(CREATE_LINK ../file2.txt "${CMAKE_BINARY_DIR}/file2/file2.txt.sym" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/file3.txt")
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file3.txt" "${CMAKE_BINARY_DIR}/file3.txt.sym" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/file4.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file4")
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file4.txt" "${CMAKE_BINARY_DIR}/file4/file4.txt.sym" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/file5.txt")
file(TOUCH "${CMAKE_BINARY_DIR}/file6.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file6/file6")
file(CREATE_LINK file6.txt "${CMAKE_BINARY_DIR}/file6.txt.sym.1" SYMBOLIC)
file(CREATE_LINK ../file6.txt.sym.1 "${CMAKE_BINARY_DIR}/file6/file6.txt.sym.2" SYMBOLIC)
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file6/file6.txt.sym.2" "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.3" SYMBOLIC)
file(CREATE_LINK file6.txt.sym.3 "${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/file7.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file7")
file(TOUCH "${CMAKE_BINARY_DIR}/file8.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file8")
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file8/../file8.txt" "${CMAKE_BINARY_DIR}/file8/file8.txt.sym" SYMBOLIC)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file9")
file(TOUCH "${CMAKE_BINARY_DIR}/file9/file9.txt")
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file9" "${CMAKE_BINARY_DIR}/file9.sym" SYMBOLIC)
file(TOUCH "${CMAKE_BINARY_DIR}/file10.txt")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/file10")
file(CREATE_LINK "." "${CMAKE_BINARY_DIR}/file10/file10" SYMBOLIC)
file(CREATE_LINK "${CMAKE_BINARY_DIR}/file10/file10/../file10.txt" "${CMAKE_BINARY_DIR}/file10/file10.txt.sym" SYMBOLIC)
file(INSTALL
"${CMAKE_BINARY_DIR}/file1.txt.sym"
DESTINATION "${CMAKE_BINARY_DIR}/dest1"
FOLLOW_SYMLINK_CHAIN
)
file(INSTALL
"${CMAKE_BINARY_DIR}/file1.txt.sym"
"${CMAKE_BINARY_DIR}/file2/file2.txt.sym"
"${CMAKE_BINARY_DIR}/file3.txt.sym"
"${CMAKE_BINARY_DIR}/file4/file4.txt.sym"
"${CMAKE_BINARY_DIR}/file5.txt"
"${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4"
"${CMAKE_BINARY_DIR}/file8/file8.txt.sym"
"${CMAKE_BINARY_DIR}/file7/../file7.txt"
"${CMAKE_BINARY_DIR}/file8.txt"
"${CMAKE_BINARY_DIR}/file9.sym/file9.txt"
"${CMAKE_BINARY_DIR}/file10/file10/file10.txt.sym"
DESTINATION "${CMAKE_BINARY_DIR}/dest2"
FOLLOW_SYMLINK_CHAIN
)
set(resolved_file1.txt.sym file1.txt)
set(resolved_file10.txt.sym file10.txt)
set(resolved_file2.txt.sym file2.txt)
set(resolved_file3.txt.sym file3.txt)
set(resolved_file4.txt.sym file4.txt)
set(resolved_file6.txt.sym.1 file6.txt)
set(resolved_file6.txt.sym.2 file6.txt.sym.1)
set(resolved_file6.txt.sym.3 file6.txt.sym.2)
set(resolved_file6.txt.sym.4 file6.txt.sym.3)
set(resolved_file8.txt.sym file8.txt)
set(syms)
foreach(f
file1.txt
file1.txt.sym
file10.txt
file10.txt.sym
file2.txt
file2.txt.sym
file3.txt
file3.txt.sym
file4.txt
file4.txt.sym
file5.txt
file6.txt
file6.txt.sym.1
file6.txt.sym.2
file6.txt.sym.3
file6.txt.sym.4
file7.txt
file8.txt
file8.txt.sym
file9.txt
)
string(REPLACE "." "\\." r "${f}")
list(APPEND syms "[^;]*/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/dest2/${r}")
set(filename "${CMAKE_BINARY_DIR}/dest2/${f}")
if(DEFINED resolved_${f})
file(READ_SYMLINK "${filename}" resolved)
if(NOT resolved STREQUAL "${resolved_${f}}")
message(SEND_ERROR "Expected symlink resolution for ${f}: ${resolved_${f}}\nActual resolution: ${resolved}")
endif()
elseif(NOT EXISTS "${filename}" OR IS_SYMLINK "${filename}" OR IS_DIRECTORY "${filename}")
message(SEND_ERROR "${f} should be a regular file")
endif()
endforeach()
file(GLOB_RECURSE actual_syms LIST_DIRECTORIES true "${CMAKE_BINARY_DIR}/dest2/*")
if(NOT actual_syms MATCHES "^${syms}$")
message(SEND_ERROR "Expected files:\n\n ^${syms}$\n\nActual files:\n\n ${actual_syms}")
endif()
file(INSTALL
"${CMAKE_BINARY_DIR}/file1.txt.sym"
"${CMAKE_BINARY_DIR}/file2/file2.txt.sym"
"${CMAKE_BINARY_DIR}/file3.txt.sym"
"${CMAKE_BINARY_DIR}/file4/file4.txt.sym"
"${CMAKE_BINARY_DIR}/file5.txt"
"${CMAKE_BINARY_DIR}/file6/file6/file6.txt.sym.4"
"${CMAKE_BINARY_DIR}/file8/file8.txt.sym"
"${CMAKE_BINARY_DIR}/file7/../file7.txt"
"${CMAKE_BINARY_DIR}/file8.txt"
"${CMAKE_BINARY_DIR}/file9.sym/file9.txt"
"${CMAKE_BINARY_DIR}/file10/file10/file10.txt.sym"
DESTINATION "${CMAKE_BINARY_DIR}/dest3"
)
set(resolved_file1.txt.sym [[^file1\.txt$]])
set(resolved_file10.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file10/file10/\.\./file10\.txt$]])
set(resolved_file2.txt.sym [[^\.\./file2\.txt$]])
set(resolved_file3.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file3\.txt$]])
set(resolved_file4.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file4\.txt$]])
set(resolved_file6.txt.sym.4 [[^file6\.txt\.sym\.3$]])
set(resolved_file8.txt.sym [[/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/file8/\.\./file8\.txt$]])
set(syms)
foreach(f
file1.txt.sym
file10.txt.sym
file2.txt.sym
file3.txt.sym
file4.txt.sym
file5.txt
file6.txt.sym.4
file7.txt
file8.txt
file8.txt.sym
file9.txt
)
string(REPLACE "." "\\." r "${f}")
list(APPEND syms "[^;]*/Tests/RunCMake/file/INSTALL-FOLLOW_SYMLINK_CHAIN-build/dest3/${r}")
set(filename "${CMAKE_BINARY_DIR}/dest3/${f}")
if(DEFINED resolved_${f})
file(READ_SYMLINK "${filename}" resolved)
if(NOT resolved MATCHES "${resolved_${f}}")
message(SEND_ERROR "Expected symlink resolution for ${f}: ${resolved_${f}}\nActual resolution: ${resolved}")
endif()
elseif(NOT EXISTS "${filename}" OR IS_SYMLINK "${filename}" OR IS_DIRECTORY "${filename}")
message(SEND_ERROR "${f} should be a regular file")
endif()
endforeach()
file(GLOB_RECURSE actual_syms LIST_DIRECTORIES true "${CMAKE_BINARY_DIR}/dest3/*")
if(NOT actual_syms MATCHES "^${syms}$")
message(SEND_ERROR "Expected files:\n\n ^${syms}$\n\nActual files:\n\n ${actual_syms}")
endif()
......@@ -64,6 +64,7 @@ if(NOT WIN32 OR CYGWIN)
run_cmake(READ_SYMLINK)
run_cmake(READ_SYMLINK-noexist)
run_cmake(READ_SYMLINK-notsymlink)
run_cmake(INSTALL-FOLLOW_SYMLINK_CHAIN)
endif()
if(RunCMake_GENERATOR STREQUAL "Ninja")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment