Installation broken on MacOS, when used in combination with `CMAKE_INSTALL_RPATH`
Dear CMake Team,
unfortunately, we encountered some bugs on MacOS when installing files with RPATH
entries.
Consider the following simple project containing two files to illustrate the bugs:
project/CMakeLists.txt
:
cmake_minimum_required(VERSION 3.11)
project(test LANGUAGES CXX)
set(CMAKE_INSTALL_RPATH "@loader_path/../lib")
add_executable(test test.cxx)
install(TARGETS test RUNTIME DESTINATION bin)
project/test.cxx
:
int main () {}
Let's configure the project:
project$ mkdir build && cd build
project/build$ cmake -DCMAKE_INSTALL_PREFIX=./install ..
-- The CXX compiler identification is AppleClang 8.0.0.8000042
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/dklein/project/build
CMake generates an install script project/build/cmake_install.cmake
with the following simplified logic:
# ...
file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" TYPE EXECUTABLE FILES "/Users/dklein/project/build/test")
# ...
execute_process(COMMAND /usr/bin/install_name_tool
-add_rpath "@loader_path/../lib"
"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/test")
# ...
From here we can produce two different scenarios that IMHO are both bugged.
Scenario A: We build and install the test executable sufficiently fast:
project/build$ make && make install // or just make install
Scanning dependencies of target test
[ 50%] Building CXX object CMakeFiles/test.dir/test.cxx.o
[100%] Linking CXX executable test
[100%] Built target test
[100%] Built target test
Install the project...
-- Install configuration: ""
-- Installing: /Users/dklein/project/build/install/bin/test
project/build$ make install
[100%] Built target test
Install the project...
-- Install configuration: ""
-- Up-to-date: /Users/dklein/project/build/install/bin/test
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: for: /Users/dklein/project/build/install/bin/test (for architecture x86_64) option "-add_rpath @loader_path/../lib" would duplicate path, file already has LC_RPATH for: @loader_path/../lib
Analysis Scenario A: On the second make install
, the file(INSTALL ...)
command confirmed, that the installed file is already "Up-to-date". This is the expected behaviour, because the implementation of cmFileCopier::InstallFile
explicitely synchronizes the modification timestamp of the installed file with the original file. This is also confirmed by install_name_tool -add_rpath
complaining about adding a duplicate RPATH
entry.
Bug A: The generated RPATH
relocation code is not idempotent in all cases.
Scenario B: We build, wait a second and then call the install target
project/build$ make && sleep 1 && make install
Scanning dependencies of target test
[ 50%] Building CXX object CMakeFiles/test.dir/test.cxx.o
[100%] Linking CXX executable test
[100%] Built target test
[100%] Built target test
Install the project...
-- Install configuration: ""
-- Installing: /Users/dklein/project/build/install/bin/test
project/build$ make install
[100%] Built target test
Install the project...
-- Install configuration: ""
-- Installing: /Users/dklein/project/build/install/bin/test
Analysis Scenario B: On the second make install
the file(INSTALL ...)
decided to reinstall the executable, which is not expected, because we have not change anything. The reason is, that install_name_tool
has changed the modification timestamp of the installed file sufficiently late after building, so the file got installed again. Because the installed file is a fresh copy, install_name_tool --add_rpath
succeeds.
Bug B: Executables and Libraries affected by CMAKE_INSTALL_RPATH
are installed on every invocation of the install
target, even if they have not changed.
Linux is not affected in both cases, because it uses the idempotent file(RPATH_CHANGE ...)
command, which explicitely resynchs the modification timestamps of the build file with the installed file.
Looking at the current implementation, I feel, we should generate the same cmake_install.cmake
scripts on MacOS and Linux and switch implementations of file(RPATH_CHANGE/RPATH_CHECK/...)
depending on the platform.
While it is easy to workaround Bug A, do you have a suggestion how we can workaround Bug B for now?
Best regards, Dennis