Commit 77e59e44 authored by Kyle Edwards's avatar Kyle Edwards Committed by Kitware Robot

Merge topic 'escape-install-rpath'

4caefbb4 cmInstallTargetGenerator: Add tests for the RPATH_CHANGE rule
749ce48e cmInstallTargetGenerator: Escape generated OLD_RPATH argument
9e84c7c5 cmInstallTargetGenerator: Introduce CMP0095
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Merge-request: !3383
parents 2c2c5753 4caefbb4
Pipeline #139284 passed with stage
in 0 seconds
......@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.15
.. toctree::
:maxdepth: 1
CMP0095: RPATH entries are properly escaped in the intermediary CMake install script. </policy/CMP0095>
CMP0094: FindPython3, FindPython2 and FindPython use LOCATION for lookup strategy. </policy/CMP0094>
CMP0093: FindBoost reports Boost_VERSION in x.y.z format. </policy/CMP0093>
CMP0092: MSVC warning flags are not in CMAKE_{C,CXX}_FLAGS by default. </policy/CMP0092>
......
CMP0095
-------
``RPATH`` entries are properly escaped in the intermediary CMake install script.
In CMake 3.15 and earlier, ``RPATH`` entries set via
:variable:`CMAKE_INSTALL_RPATH` or via :prop_tgt:`INSTALL_RPATH` have not been
escaped before being inserted into the ``cmake_install.cmake`` script. Dynamic
linkers on ELF-based systems (e.g. Linux and FreeBSD) allow certain keywords in
``RPATH`` entries, such as ``${ORIGIN}`` (More details are available in the
``ld.so`` man pages on those systems). The syntax of these keywords can match
CMake's variable syntax. In order to not be substituted (usually to an empty
string) already by the intermediary ``cmake_install.cmake`` script, the user had
to double-escape such ``RPATH`` keywords, e.g.
``set(CMAKE_INSTALL_RPATH "\\\${ORIGIN}/../lib")``. Since the intermediary
``cmake_install.cmake`` script is an implementation detail of CMake, CMake 3.16
and later will make sure ``RPATH`` entries are inserted literally by escaping
any coincidental CMake syntax.
The ``OLD`` behavior of this policy is to not escape ``RPATH`` entries in the
intermediary ``cmake_install.cmake`` script. The ``NEW`` behavior is to properly
escape coincidental CMake syntax in ``RPATH`` entries when generating the
intermediary ``cmake_install.cmake`` script.
This policy was introduced in CMake version 3.16. CMake version |release| warns
when the policy is not set and detected usage of CMake-like syntax and uses
``OLD`` behavior. Use the :command:`cmake_policy` command to set it to ``OLD``
or ``NEW`` explicitly.
.. include:: DEPRECATED.txt
CMP0095
-------
* ``RPATH`` entries are properly escaped in the intermediary CMake install script.
See policy :policy:`CMP0095`.
......@@ -16,6 +16,8 @@
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmOutputConverter.h"
#include "cmPolicies.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
......@@ -632,17 +634,34 @@ void cmInstallTargetGenerator::AddRPathCheckRule(
return;
}
// Get the install RPATH from the link information.
std::string newRpath = cli->GetChrpathString();
// Write a rule to remove the installed file if its rpath is not the
// new rpath. This is needed for existing build/install trees when
// the installed rpath changes but the file is not rebuilt.
/* clang-format off */
os << indent << "file(RPATH_CHECK\n"
<< indent << " FILE \"" << toDestDirPath << "\"\n"
<< indent << " RPATH \"" << newRpath << "\")\n";
/* clang-format on */
<< indent << " FILE \"" << toDestDirPath << "\"\n";
// CMP0095: ``RPATH`` entries are properly escaped in the intermediary
// CMake install script.
switch (this->Target->GetPolicyStatusCMP0095()) {
case cmPolicies::WARN:
// No author warning needed here, we warn later in
// cmInstallTargetGenerator::AddChrpathPatchRule().
CM_FALLTHROUGH;
case cmPolicies::OLD: {
// Get the install RPATH from the link information.
std::string newRpath = cli->GetChrpathString();
os << indent << " RPATH \"" << newRpath << "\")\n";
break;
}
default: {
// Get the install RPATH from the link information and
// escape any CMake syntax in the install RPATH.
std::string escapedNewRpath =
cmOutputConverter::EscapeForCMake(cli->GetChrpathString());
os << indent << " RPATH " << escapedNewRpath << ")\n";
break;
}
}
}
void cmInstallTargetGenerator::AddChrpathPatchRule(
......@@ -731,11 +750,28 @@ void cmInstallTargetGenerator::AddChrpathPatchRule(
return;
}
// Escape any CMake syntax in the RPATHs.
std::string escapedOldRpath = cmOutputConverter::EscapeForCMake(oldRpath);
std::string escapedNewRpath = cmOutputConverter::EscapeForCMake(newRpath);
// Write a rule to run chrpath to set the install-tree RPATH
os << indent << "file(RPATH_CHANGE\n"
<< indent << " FILE \"" << toDestDirPath << "\"\n"
<< indent << " OLD_RPATH \"" << oldRpath << "\"\n"
<< indent << " NEW_RPATH \"" << newRpath << "\")\n";
<< indent << " OLD_RPATH " << escapedOldRpath << "\n";
// CMP0095: ``RPATH`` entries are properly escaped in the intermediary
// CMake install script.
switch (this->Target->GetPolicyStatusCMP0095()) {
case cmPolicies::WARN:
this->IssueCMP0095Warning(newRpath);
CM_FALLTHROUGH;
case cmPolicies::OLD:
os << indent << " NEW_RPATH \"" << newRpath << "\")\n";
break;
default:
os << indent << " NEW_RPATH " << escapedNewRpath << ")\n";
break;
}
}
}
......@@ -838,3 +874,26 @@ void cmInstallTargetGenerator::AddUniversalInstallRule(
<< "\"" << this->Target->Target->GetName() << "\" "
<< "\"" << toDestDirPath << "\")\n";
}
void cmInstallTargetGenerator::IssueCMP0095Warning(
const std::string& unescapedRpath)
{
// Reduce warning noise to cases where used RPATHs may actually be affected
// by CMP0095. This filter is meant to skip warnings in cases when
// non-curly-braces syntax (e.g. $ORIGIN) or no keyword is used which has
// worked already before CMP0095. We intend to issue a warning in all cases
// with curly-braces syntax, even if the workaround of double-escaping is in
// place, since we deprecate the need for it with CMP0095.
const bool potentially_affected(unescapedRpath.find("${") !=
std::string::npos);
if (potentially_affected) {
std::ostringstream w;
w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0095) << "\n";
w << "RPATH entries for target '" << this->Target->GetName() << "' "
<< "will not be escaped in the intermediary "
<< "cmake_install.cmake script.";
this->Target->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
MessageType::AUTHOR_WARNING, w.str(), this->GetBacktrace());
}
}
......@@ -104,6 +104,7 @@ protected:
const std::string& toDestDirPath);
void AddUniversalInstallRule(std::ostream& os, Indent indent,
const std::string& toDestDirPath);
void IssueCMP0095Warning(const std::string& unescapedRpath);
std::string TargetName;
cmGeneratorTarget* Target;
......
......@@ -279,7 +279,11 @@ class cmMakefile;
SELECT(POLICY, CMP0094, \
"FindPython3, FindPython2 and FindPyton use " \
"LOCATION for lookup strategy.", \
3, 15, 0, cmPolicies::WARN)
3, 15, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0095, \
"RPATH entries are properly escaped in the intermediary CMake " \
"install script.", \
3, 16, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
......@@ -307,7 +311,8 @@ class cmMakefile;
F(CMP0073) \
F(CMP0076) \
F(CMP0081) \
F(CMP0083)
F(CMP0083) \
F(CMP0095)
/** \class cmPolicies
* \brief Handles changes in CMake behavior and policies
......
......@@ -408,7 +408,12 @@ else()
set(NO_NAMELINK 0)
endif()
add_RunCMake_test(install -DNO_NAMELINK=${NO_NAMELINK} -DCYGWIN=${CYGWIN} -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
add_RunCMake_test(install -DNO_NAMELINK=${NO_NAMELINK} -DCYGWIN=${CYGWIN} -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
-DCMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN=${CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN}
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-DCMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG=${CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG}
-DCMAKE_EXECUTABLE_FORMAT=${CMAKE_EXECUTABLE_FORMAT})
add_RunCMake_test(CPackCommandLine)
add_RunCMake_test(CPackConfig)
add_RunCMake_test(CPackInstallProperties)
......
......@@ -27,6 +27,7 @@
\* CMP0076
\* CMP0081
\* CMP0083
\* CMP0095
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
......@@ -48,6 +48,22 @@ in directory:
endif()
endfunction()
# Wrapper for run_cmake() that skips platforms that are non-ELF or have no RPATH support
function(run_cmake_ELFRPATH_only case)
if(UNIX AND CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG AND CMAKE_EXECUTABLE_FORMAT STREQUAL "ELF")
run_cmake(${case})
else()
# Sanity check against a platform known to be ELF-based
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "Expected platform Linux to advertize itself as ELF-based, but it did not.")
else()
message(STATUS "${case} - SKIPPED (No ELF-based platform found)")
endif()
endif()
endfunction()
run_cmake(TARGETS-FILE_RPATH_CHANGE-old_rpath)
run_cmake_ELFRPATH_only(TARGETS-FILE_RPATH_CHANGE-new_rpath)
run_cmake(DIRECTORY-MESSAGE_NEVER)
run_cmake(DIRECTORY-PATTERN-MESSAGE_NEVER)
run_cmake(DIRECTORY-message)
......
file(READ ${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake install_script)
#message(STATUS ${install_script})
set(wsnl " *[\n\r]+ *") # whitespace + single newline + whitespace
set(wssl " *[\n\r]+[^\n\r]*[\n\r]+ *") # ws nl skipline nl ws
string(CONCAT prefix [[file\(RPATH_CHANGE]])
set(_msg "cmake_install.cmake does not match ")
macro(check)
if(NOT install_script MATCHES "${regex}")
message(STATUS "${test} - check \"${target}\" - FAILED:")
string(CONCAT RunCMake_TEST_FAILED "${_msg}" ">>>${regex}<<<")
return()
else()
message(STATUS "${test} - check \"${target}\" - PASSED")
endif()
endmacro()
macro(skip_without_rpath_change_rule)
# Not all platforms generate a file(RPATH_CHANGE) rule
if(NOT install_script MATCHES [[file\(RPATH_CHANGE]])
# Sanity check against a platform known to generate a file(RPATH_CHANGE) rule
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "Expected generated file(RPATH_CHANGE) rule on platform Linux.")
else()
message(STATUS "${test} - All checks skipped. No file(RPATH_CHANGE) rule found on this platform.")
return()
endif()
endif()
endmacro()
include(${RunCMake_SOURCE_DIR}/TARGETS-FILE_RPATH_CHANGE-check-common.cmake)
skip_without_rpath_change_rule()
string(APPEND prefix "${wsnl}" [[FILE "[^"]*/]])
set(target "exe1_cmp0095_old")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar]])
check()
set(target "exe1_cmp0095_warn")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar]])
check()
set(target "exe1_cmp0095_new")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar]])
check()
set(target "exe2_cmp0095_old")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\$ORIGIN/../lib]])
check()
set(target "exe2_cmp0095_warn")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\$ORIGIN/../lib]])
check()
set(target "exe2_cmp0095_new")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\\\$ORIGIN/../lib]])
check()
set(target "exe3_cmp0095_old")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\${ORIGIN}/../lib]])
check()
set(target "exe3_cmp0095_warn")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\${ORIGIN}/../lib]])
check()
set(target "exe3_cmp0095_new")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "\\\${ORIGIN}/../lib]])
check()
set(target "exe4_cmp0095_old")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar/\${PLATFORM}]])
check()
set(target "exe4_cmp0095_warn")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar/\${PLATFORM}]])
check()
set(target "exe4_cmp0095_new")
string(CONCAT regex "${prefix}${target}\"${wssl}"
[[NEW_RPATH "/foo/bar/\\\${PLATFORM}]])
check()
^CMake Warning \(dev\) at TARGETS-FILE_RPATH_CHANGE-new_rpath\.cmake:[0-9]+ \(install\):
Policy CMP0095 is not set: RPATH entries are properly escaped in the
intermediary CMake install script\. Run "cmake --help-policy CMP0095" for
policy details\. Use the cmake_policy command to set the policy and
suppress this warning\.
RPATH entries for target 'exe3_cmp0095_warn' will not be escaped in the
intermediary cmake_install\.cmake script\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
This warning is for project developers\. Use -Wno-dev to suppress it\.
CMake Warning \(dev\) at TARGETS-FILE_RPATH_CHANGE-new_rpath\.cmake:[0-9]+ \(install\):
Policy CMP0095 is not set: RPATH entries are properly escaped in the
intermediary CMake install script\. Run "cmake --help-policy CMP0095" for
policy details\. Use the cmake_policy command to set the policy and
suppress this warning\.
RPATH entries for target 'exe4_cmp0095_warn' will not be escaped in the
intermediary cmake_install\.cmake script\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
This warning is for project developers\. Use -Wno-dev to suppress it\.$
cmake_minimum_required(VERSION 3.14)
enable_language(C)
# test matrix
#
# A :=
# | no cmake syntax | cmake syntax |
# -----------------------+-----------------+--------------+
# absolute install RPATH | exe1 | exe4 |
# relative install RPATH | exe2 | exe3 |
#
# all := A * CMP005_OLD + A * CMP0095_WARN + A * CMP0095_NEW
add_library(utils SHARED obj1.c)
set(targets utils)
set(exe1_install_rpath "/foo/bar")
set(exe2_install_rpath "\$ORIGIN/../lib")
set(exe3_install_rpath "\${ORIGIN}/../lib")
set(exe4_install_rpath "/foo/bar/\${PLATFORM}")
macro(A_CMP0095 policy_value)
cmake_policy(PUSH)
if(NOT "x${policy_value}x" STREQUAL "xWARNx")
cmake_policy(SET CMP0095 ${policy_value})
endif()
string(TOLOWER "${policy_value}" p)
# exe1: absolute install RPATH, no cmake syntax
set(case "exe1")
set(target "${case}_cmp0095_${p}")
list(APPEND targets ${target})
add_executable(${target} main.c)
target_link_libraries(${target} PRIVATE utils)
set_target_properties(${target} PROPERTIES
INSTALL_RPATH "${${case}_install_rpath}")
# exe2: relative install RPATH, no cmake syntax
set(case "exe2")
set(target "${case}_cmp0095_${p}")
list(APPEND targets ${target})
add_executable(${target} main.c)
target_link_libraries(${target} PRIVATE utils)
set_target_properties(${target} PROPERTIES
INSTALL_RPATH "${${case}_install_rpath}")
# exe3: relative install RPATH, cmake syntax
set(case "exe3")
set(target "${case}_cmp0095_${p}")
list(APPEND targets ${target})
add_executable(${target} main.c)
target_link_libraries(${target} PRIVATE utils)
set_target_properties(${target} PROPERTIES
INSTALL_RPATH "${${case}_install_rpath}")
# exe4: absolute install RPATH, cmake syntax
set(case "exe4")
set(target "${case}_cmp0095_${p}")
list(APPEND targets ${target})
add_executable(${target} main.c)
target_link_libraries(${target} PRIVATE utils)
set_target_properties(${target} PROPERTIES
INSTALL_RPATH "${${case}_install_rpath}")
cmake_policy(POP)
endmacro()
A_CMP0095("OLD")
A_CMP0095("WARN") # exe3 and exe4 are expected to issue an author warning
A_CMP0095("NEW")
install(TARGETS ${targets})
include(${RunCMake_SOURCE_DIR}/TARGETS-FILE_RPATH_CHANGE-check-common.cmake)
skip_without_rpath_change_rule()
string(APPEND prefix "${wsnl}" [[FILE "[^"]*/]])
set(target "exe1")
string(CONCAT regex "${prefix}${target}\"${wsnl}"
[[OLD_RPATH "]] "${RunCMake_BINARY_DIR}")
check()
if("x${CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN}" STREQUAL "x\$ORIGIN")
set(target "exe2")
string(CONCAT regex "${prefix}${target}\"${wsnl}"
[[OLD_RPATH "\\\$ORIGIN]])
check()
endif()
cmake_minimum_required(VERSION 3.14)
enable_language(C)
add_library(utils SHARED obj1.c)
# exe1: absolute build RPATH, no cmake syntax
set(CMAKE_BUILD_RPATH_USE_ORIGIN OFF)
set(CMAKE_INSTALL_RPATH "/foo/bar")
add_executable(exe1 main.c)
target_link_libraries(exe1 PRIVATE utils)
# exe2: relative build RPATH, no cmake syntax
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
set(CMAKE_INSTALL_RPATH "/foo/bar")
add_executable(exe2 main.c)
target_link_libraries(exe2 PRIVATE utils)
install(TARGETS utils exe1 exe2)
enable_language(C)
cmake_policy(SET CMP0095 NEW)
file(WRITE "${CMAKE_BINARY_DIR}/test.c" "void test(void) {}\n")
file(WRITE "${CMAKE_BINARY_DIR}/main.c" [[extern void test(void);
......@@ -13,7 +14,7 @@ int main(void)
add_library(test SHARED "${CMAKE_BINARY_DIR}/test.c")
add_executable(exe "${CMAKE_BINARY_DIR}/main.c")
target_link_libraries(exe PRIVATE test)
set_property(TARGET exe PROPERTY INSTALL_RPATH "\\\${ORIGIN}/../lib")
set_property(TARGET exe PROPERTY INSTALL_RPATH "\${ORIGIN}/../lib")
install(TARGETS exe DESTINATION bin)
......
enable_language(C)
cmake_policy(SET CMP0095 NEW)
set(test_rpath_names
preexcluded
......@@ -52,8 +53,8 @@ target_link_libraries(test_rpath PRIVATE ${test_rpath_names})
set_property(TARGET test_rpath PROPERTY INSTALL_RPATH
"${CMAKE_BINARY_DIR}/root-all/lib/rpath_postexcluded"
"${CMAKE_BINARY_DIR}/root-all/lib/rpath"
"\\\$ORIGIN/rpath_origin_postexcluded"
"\\\${ORIGIN}/rpath_origin" # This must be double-escaped because of issue #19225.
"\$ORIGIN/rpath_origin_postexcluded"
"\${ORIGIN}/rpath_origin"
"${CMAKE_BINARY_DIR}/root-all/lib/conflict"
)
target_link_options(test_rpath PRIVATE -Wl,--disable-new-dtags)
......@@ -88,8 +89,8 @@ set_property(TARGET test_runpath PROPERTY INSTALL_RPATH
"${CMAKE_BINARY_DIR}/root-all/lib/runpath/../rpath" # Ensure that files that don't conflict are treated correctly
"${CMAKE_BINARY_DIR}/root-all/lib/runpath_postexcluded"
"${CMAKE_BINARY_DIR}/root-all/lib/runpath"
"\\\${ORIGIN}/runpath_origin_postexcluded" # This must be double-escaped because of issue #19225.
"\\\$ORIGIN/runpath_origin"
"\${ORIGIN}/runpath_origin_postexcluded"
"\$ORIGIN/runpath_origin"
"${CMAKE_BINARY_DIR}/root-all/lib/conflict2"
)
target_link_options(test_runpath PRIVATE -Wl,--enable-new-dtags)
......
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