CPack, install(CODE) and RPM

I’m seeing a peculiarity trying to use install(CODE) with CPack and RPM. The install code line I have is:

install(CODE “execute_process(COMMAND $<TARGET_FILE:buildpath_replace> "${CMAKE_INSTALL_PREFIX}/${dir}/${bpf}")”)

It’s purpose is to replace build paths used in various text configuration files when running from the build directory with the final install path for the package. This works for TGZ and TBZ2 targets, targeting the copies of the files in the (for example) _CPack_Packages/Linux/TGZ subdirectories. However, for RPM, the same line of code ends up trying (unsuccessfully) to target the final /usr file locations, rather than the correct locations in _CPack_Packages/Linux/RPM.

Is there something in particular I need to do for the RPM package generator to have install(CODE) work similarly to the other generators?

UPDATE: I’ve tracked this as far as an internal CMake behavior - with RPM, CPACK_SET_DESTDIR is always internally ON (I_ON) and CMAKE_INSTALL_PREFIX doesn’t end up getting the temporary path it does with the other generators. I tried using CPACK_PRE_BUILD_SCRIPTS to work around this - the documentation at https://cmake.org/cmake/help/latest/module/CPack.html indicates that during script execution CMAKE_INSTALL_PREFIX is “set to the staging install directory”. However, I see the same behavior as with install(CODE) - RPM doesn’t look in the right place.

Here’s a fairly minimal example. The below CMakeLists.txt file shows the following behavior (note the error message generated for the RPM case):

$ mkdir build && cd build && cmake .. && make package
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cyapp/cpack_test/build
[ 50%] Building CXX object CMakeFiles/txtprocess.dir/txtprocess.cpp.o
[100%] Linking CXX executable txtprocess
[100%] Built target txtprocess
Run CPack packaging tool...
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: CPT
CPack: - Install project: CPT []
CPack: Create package
CPack: - package: /home/user/cpack_test/build/CPT-0.1.1-Linux.tar.gz generated.
CPack: Create package using TBZ2
CPack: Install projects
CPack: - Run preinstall target for: CPT
CPack: - Install project: CPT []
CPack: Create package
CPack: - package: /home/user/cpack_test/build/CPT-0.1.1-Linux.tar.bz2 generated.
CPack: Create package using RPM
CPack: Install projects
CPack: - Run preinstall target for: CPT
CPack: - Install project: CPT []
Error: file /usr/share/file.txt could not be opened.
CPack: Create package
CPackRPM: Will use GENERATED spec file: /home/cyapp/cpack_test/build/_CPack_Packages/Linux/RPM/SPECS/cpt.spec
CPack: - package: /home/user/cpack_test/build/CPT-0.1.1-Linux.rpm generated.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.18)
project(CPT)

set(TXTP_SRC "
#include <iostream>
#include <fstream>
int main(int argc, const char **argv)
{
	if (argc < 2)
		return -1;
	std::ifstream fs;
	fs.open(argv[1]);

	if (!fs.is_open()) {
		std::cerr << \"Error: file \" << argv[1] << \" could not be opened.\\n\";
		return -1;
	}
	return 0;
}
"
)
file(WRITE "${CMAKE_BINARY_DIR}/txtprocess.cpp" "${TXTP_SRC}")
add_executable(txtprocess "${CMAKE_BINARY_DIR}/txtprocess.cpp")

execute_process(COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/file.txt)

install(FILES ${CMAKE_BINARY_DIR}/file.txt DESTINATION share)
install(CODE "execute_process(COMMAND $<TARGET_FILE:txtprocess>  \"\${CMAKE_INSTALL_PREFIX}/share/file.txt\")")

set(CPACK_GENERATOR TGZ TBZ2 RPM)
include(CPack)

UPDATE 2: Brad King found the problem - for this use case, we need to include $ENV{DESTDIR} the way the cmake install scripts do: https://gitlab.kitware.com/cmake/cmake/-/issues/24212#note_1283057