cpack conditonal behaviour based on generator - cmake

I have a build which is currently set up with steps as follows:
cmake
make
cpack -G TGZ
cpack -G RPM
I now have a problem in that there are files I wish to include in the RPM but not the tarball. Is there a way to make the install command conditional according to the generator used?
The simple and obvious way is wrong:
if (NOT ${PACKAGE_TYPE} STREQUAL "TGZ")
message("HELLO ${PACKAGE_TYPE}")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/foobar DESTINATION "/usr/lib" COMPONENT core RENAME "/usr/lib/only-install-me-for-RPM")
endif()
I believe it is wrong because the configure stage (running cmake) evaluates the conditional but cpack does not.
I do not want two builds as the install stage is the only part different. I do want more than one kind of installation package.
Background
Why do I want to do such an odd thing? I can think of other legitimate reasons but in this case it is because of the introduction of /usr/lib/.build-id.
It is not possible to disable this behaviour from cmake (though it is possible in the .spec file see https://bugzilla.redhat.com/show_bug.cgi?id=1724153)
In RHEL8 rpmbuild installs files (actually links) in /usr/lib/.build-id which I have not specificed myself.
In order to persuade cmake to make /usr/lib relocatable I have to install a dummy file in /usr/lib - see https://gitlab.kitware.com/cmake/cmake/-/issues/20691
This is not necessary for the tarball.

Currently used CPack generator can be retrieved from CPACK_GENERATOR variable. But this meaning works only inside a script specified with CPACK_PROJECT_CONFIG_FILE variable. Inside CMakeLists.txt the variable CPACK_GENERATOR has other meaning.
Because install command can only be issued in CMakeLists.txt, this command cannot be made conditional (based on CPack generator). But you may assign a COMPONENT for this installation. This component can be excluded from the components list later.
CMakeLists.txt:
# Assign 'core_special' COMPONENT for installation
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/foobar DESTINATION "/usr/lib" COMPONENT core_special RENAME "/usr/lib/only-install-me-for-RPM")
# ...
# Set config script for CPack.
set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_SOURCE_DIR}/cpack_project_config.cmake")
cpack_project_config.cmake:
# Exclude component "core_special" for all CPack generators except TGZ.
if (NOT CPACK_GENERATOR STREQUAL "TGZ")
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "core_special")
endif()
# Need to set 'CMAKE_<GENERATOR>_COMPONENT_INSTALL' to ON, otherwise CPack ignores CPACK_COMPONENTS_ALL variable
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_RPM_COMPONENT_INSTALL ON)
# E.g create single archive/package from all components
# (other values - "IGNORE", "ONE_PER_GROUP" - will also work)
set(CPACK_COMPONENTS_GROUPING "ALL_COMPONENTS_IN_ONE")

Related

CMake variable contents dependent on build/install

Using the $<INSTALL_INTERFACE:...> and $<BUILD_INTERFACE:...> generator expressions I can set target properties to different values depending on whether the target is exported in the current build directory or installed globally. I am writing a custom macro to accompany my CMake package and targets and would like to make the macro behave differently depending on where it is exported (in the build directory) or installed. The macro is contained in a <package>-macros.cmake.in which is included from my <package>-config.cmake file and is configured into the build directory using configure_file and later installed. I tried using the generator expressions in variables set using the configure_file command, but obviously they are not intended to work that way. I assume my requirement is not that uncommon, how is it usually done using CMake?
Just create different <package>-config.cmake files for export() and for install(EXPORT). In that files you may have a variable which differentiate them.
You may even create both files from the same pattern using configure_file command with different CMake environment(variables):
<package>-config.cmake.in:
set(IS_BUILD_INTERFACE #IS_BUILD_INTERFACE#)
# other commands, inclusion of other files, etc.
<package>-macros.cmake:
if(IS_BUILD_INTERFACE)
# Part of build interface
else()
# Part of install interface
endif()
CMakeLists.txt:
# Prepare the file for build interface exporting
set(IS_BUILD_INTERFACE ON)
configure_file(<package>-config.cmake.in <package>-config.cmake #ONLY)
export(PACKAGE <package>)
# Prepare the file for install interface exporting
set(IS_BUILD_INTERFACE OFF)
configure_file(<package>-config.cmake.in <package>-config.cmake.install #ONLY)
install(FILES <package>-config.cmake.install DESTINATION cmake)

overwriting CMAKE_INSTALL_PREFIX [duplicate]

How do I set CMAKE_INSTALL_PREFIX in my root CMakeLists.txt file?
I have been doing
cmake_minimum_required(VERSION 2.8)
project(MyProject)
# Set default install prefix
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
with the hopes that by installations would be destined to folders in the source tree. That is,
install(TARGETS my_exe DESTINATION bin/)
would install to ${CMAKE_SOURCE_DIR}/bin/. Instead, it keeps trying to write to /usr/local/bin (the default for Ubuntu 14.04).
I tried the answers to this question, but I still get the standard usr/local/ as my CMAKE_INSTALL_PREFIX when I check CMakeCache.txt.
The only working solution I have is to do
install(TARGETS my_exe DESTINATION "${CMAKE_SOURCE_DIR}/bin/")
but this then removes the user's ability to specify where the bin directory to install is.
tl;dr I would like make install to automatically install to ${CMAKE_SOURCE_DIR} by default, rather than /usr/local/.
CMake developers suggest to use given pattern for change default value of CMAKE_INSTALL_PREFIX inside CMakeLists.txt:
# Use this snippet *after* PROJECT(xxx):
IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
SET(CMAKE_INSTALL_PREFIX <path> CACHE PATH <comment> FORCE)
ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
Using that approach
# Use this snippet *before* PROJECT(xxx):
SET(CMAKE_INSTALL_PREFIX <path> CACHE PATH <comment>)
is not recommended:
.. solution depends on the implementation details of the PROJECT command and is very fragile since it works "accidentally" for some versions of CMake. I don't consider it to be an option at all.

How to pass variable to cpack?

I have a cmake project which one of the install targets is a collection of files. This files change depending on the configuration (Release, Debug...).
I would like to be able to install the files like so:
install(DIRECTORY $<TARGET_FILE_DIR:tgt>
DESTINATION bin
COMPONENT files)
But cmake does not support that. Generator variables do not apply to DIRECTORY. So I was wondering if there is a way to either save the directory somewhere. Either the cache or a file and then load it into cpack.
So I guess the question is how to pass a variable from cmake to cpack?
This is a rather late answer, but I happened upon this question trying to solve a somewhat different problem that could also be summarized as: "How do I pass a variable to CPack?" In my case, I was making this call from a customized version of CPackDeb.cmake copied to my workspace:
find_program(OPKG_CMD NAMES opkg-build HINTS "${OPKG_HINT}")
# ^^^^^^^^^^^^
# This is what I wanted to pass to CPack
I was setting OPKG_HINT in a file included from my top-level CMakeLists.txt, but it was not getting passed through to cpack; the above find_program() invocation was seeing an empty string for OPKG_HINT.
The solution turned out to be stupid simple: just prepend CPACK_ to the variable name!
If I do this in CMakeLists.txt:
set(CPACK_OPKG_HINT "${_sysroot_top}/aarch64-poky-linux/usr/bin")
then I can put this in my CPackDeb.cmake file and it works fine:
find_program(OPKG_CMD NAMES opkg-build HINTS "${CPACK_OPKG_HINT}")
Anyway, this wound up being a bit of an X-Y problem for the OP, but... if you really need to set a variable at CMake time in such a way that it's accessible to cpack, prefixing the variable name with CPACK_ seems to do the trick nicely...
The following setup work if you use a "single-configuration generators (such as make and Ninja)" and call CMake with
cmake -DCMAKE_BUILD_TYPE=Release <source_dir>
https://cmake.org/cmake/help/v3.0/variable/CMAKE_BUILD_TYPE.html
You can define the ${dir} variable in another way if you like.
IF (CMAKE_BUILD_TYPE STREQUAL "Release")
SET(dir release_dir)
ELSE()
SET(dir debug_dir)
ENDIF()
INSTALL(DIRECTORY ${dir} DESTINATION bin COMPONENT files)
Until now this seems to be the best answer (from someone on the cmake mail list)
install(DIRECTORY path/to/Debug/dir
DESTINATION bin
CONFIGURATIONS Debug
COMPONENT files
)
install(DIRECTORY path/to/Release/dir
DESTINATION bin
CONFIGURATIONS Release
COMPONENT files
)
CMake 3.5 supports generator expressions for the DIRECTORY arguments. See installing directories.

How to get the list of files that will be installed when installing a CMake component

Is there any way to know programmatically (in CMake) what files will be installed if a COMPONENT is installed (something like a get_property of component)?
Currently, I am installing a COMPONENT to a temporary location for packaging (not using CPack for packaging) and then packaging using custom commands. I'm invoking the following command during packaging in CMake.
cmake -DCOMPONENT=my_test_component
-DCMAKE_INSTALL_PREFIX=${TMP_PACKAGING_ROOT}
-P ${CMAKE_BINARY_DIR}/cmake_install.cmake
I wanted to know if it is possible to get the list of files so that I can only include those files explicitly in the package? Or possibly add them as outputs to the custom command?
The only way to know it seems to be by reading the install_manifest_${component}.txt which will have all the list of files that will be installed when we install a CMake component.
CMake does not have a get_property() function (or similar equivalent) for the COMPONENT descriptor; CMake properties are reserved for targets, directories, source files, etc. (full list here). However, there are ways to programmatically list the files associated with a COMPONENT.
In general, the COMPONENT option is often specified with install() to essentially categorize targets into specific install groups. These "component" groupings are typically used with CPack:
For certain kinds of binary installers (including the graphical installers on macOS and Windows), CPack generates installers that allow users to select individual application components to install. The contents of each of the components are identified by the COMPONENT argument of CMake’s INSTALL command.
However, if you're not using CPack, CMake still honors the COMPONENT groupings; they are just harder to manage. You can iterate through each install rule in the cmake_install.cmake file, and filter out those that pertain a specific COMPONENT. This must be done after the CMake generate stage (after the cmake_install.cmake file is generated), as the full path to each target is not known at configure time. As the question above suggests, you can create a custom target to call the generated CMake install script yourself, filtering based on COMPONENT:
# Define install rule for MyExecutable target (grouping it in MyComponent).
install(TARGETS MyExecutable
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/installation
CONFIGURATIONS Release
COMPONENT MyComponent
)
# Add custom target to filter and install MyComponent files.
add_custom_target(MyInstallTarget
COMMAND "${CMAKE_COMMAND}" -DCOMPONENT=MyComponent -P cmake_install.cmake
DEPENDS MyExecutable
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
After building this custom target (e.g. make MyInstallTarget), the manifest file install_manifest_MyComponent.txt will be created, containing a list of all the files associated with that COMPONENT. (It is not necessarily created by building the CMake-predefined INSTALL target.)
However, this manifest file is not very useful by itself. To use it programmatically, we can expand our custom target to read these component-specific files into a CMake variable.
add_custom_target(MyInstallTarget
COMMAND "${CMAKE_COMMAND}" -DCOMPONENT=MyComponent -P cmake_install.cmake
COMMAND "${CMAKE_COMMAND}" -DCOMPONENT=MyComponent -P ../my_install_script.cmake
DEPENDS MyExecutable
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
Inside my_install_script.cmake, the logic is largely dependent on what you want to do with the list of files. The script below will read the files into a CMake list variable, then copies them to an install destination using configure_file():
# Check if an install COMPONENT was provided.
if(COMPONENT)
# Read the manifest file.
file(READ "install_manifest_${COMPONENT}.txt" MY_INSTALL_FILES)
# Create a list from the component files.
string(REPLACE "\n" ";" MY_INSTALL_FILES ${MY_INSTALL_FILES})
# Loop through each file, placing it in the installation directory.
foreach(curFile ${MY_INSTALL_FILES})
message("Installing file: " ${curFile})
configure_file(${curFile} /your/final/install/folder COPYONLY)
endforeach()
endif(COMPONENT)

cmake: install executables and create links to them

I'm using cmake and cpack to build my project and build packages. I'm creating a few executables in my project, let's call them EXE1 and EXE2.
When creating different versions of these executables, I want to name to reflect the version of the executable (let's say EXE1_1.0.0). I can change the name of the output for a target by doing set_target_properties.
However, now when doing an install, I want to do create a symlink to this versioned name of the executable, i.e. I want to have
the "versioned" executable installed in bin directory, i.e. EXE1_1.0.0
create a symlink to the "versioned" executable, i.e. create symlink EXE1, which points to EXE1_1.0.0
Can someone suggest me how to do this?
Second question is:
How to install configuration files /etc/MYPROJECT/ directory? What DESTINATION I need to use for configuration files, like I use bin for executables and lib for libraries? Is using an absolute path like /etc an acceptable practice with cmake?
I asked this question on cmake mailing list subsequently, and this is the response I received:
The validity of the answer will depend on which CMake version you use
and which set of platform you want to support.
Symlinks are not that portable
a) Creation may not be [currently] done portably but if you are
targeting Unix you can use cmake -E create_symlink to create one.
b) Depending on the CPack generator you use and CMake/CPack version
symlinks may be embedded in the package or not.
i.e. CPack pre 2.8.7 cannot create ZIP archive which contains
symlinks CPack 2.8.8 can do that now.
Then you can use an install(SCRIPT ... or install(CODE ...) to do that
at install time.
Another option if you are using RPM is to use package specific post
install script. cpack --help-variable
CPACK_RPM_POST_INSTALL_SCRIPT_FILE
this last solution will off course only work for CPack RPM.
For second question
You can use absolute destination path, they should be handled just
fine by CPack DEB and RPM, I don't know for other.
If your software should be installed on Windows this is won't work
with archive generator (ZIP, TGZ, etc...) and/or NSIS.
May be you can do something like:
if(UNIX AND NOT APPLE) set(CONFDEST "/etc/${CMAKE_PROJECT_NAME}")
else() set(CONFDEST "etc") endif()
install(FILES yourconffile DESTINATION ${CONFDEST})