CMake - how to block executing installation scripts during packaging? - cmake

My CMakeLists.txt file contains commands, which should be executed by make install, and all this works fine. The sample CMakeLists.txt below is a short excerpt from my actual CMake file (the tm0001.cpp content is not important here - it might be any C++ program):
cmake_minimum_required(VERSION 3.12)
project(tm0001)
set(CMAKE_CXX_STANDARD 11)
add_executable(${PROJECT_NAME} tm0001.cpp)
install(
TARGETS ${PROJECT_NAME}
DESTINATION /usr/local/bin
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
)
install(CODE "message(\"-- This must be called during installation only\")")
set(CPACK_PACKAGE_CONTACT "HEKTO")
set(CPACK_GENERATOR "DEB")
include(CPack)
I see the message command is executed by make package as well, which is not I want.
How to tell CMake not to execute installation scripts by the make package command? I couldn't find any way to do that with the CMake if command.

As it already said in the comment, it's an extremely bad idea to "work w/ systemd" (and doing anything not related to build or packaging of your project) from install commands. The install command (even SCRIPT and CODE signatures) are intended to be used for install actions and not for any other side effects.
The correct way to act here is to produce a native package (DEB/RPM) w/ post-install script, where using the system-provided macros (like described here), you can install your package properly. Take a look to CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA for the way to provide package install actions.
The other bad thing is to use the hardcoded path (/usr/bin/). And BTW, a better place for the (pure) daemon app I suggest /usr/sbin/. Take a look to GNUInstallDirs module shipped w/ CMake for further references.

What I did was to specify install commands with CODE/SCRIPT as separate component e.g. install(CODE ... COMPONENT post-install).
Then also added other non-code install commands as a different component e.g. install(FILES ... COMPONENT files-install)
The CPack then needs to be configured to package only files-install component (solution to this can be found easily - hint: use CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE, CPACK_COMPONENTS_ALL and CPACK_(RPM/DEB/...)_COMPONENT_INSTALL variables).
Of course then the resulting package won't run these CODE components during installing the package - they need to be added separately as a post install script.

I'm answering my own question, because the existing answer doesn't address my main problem. I couldn't find any way (on the CMake level) to block install commands from running during make package - even the postinst script is called by this command.
Fortunately, I could modify the postinst script itself to do nothing in case it's called not by the dpkg:
if [ -z ${DPKG_ADMINDIR} ]; then
echo "postinst: missing 'dpkg' environment (not an error during packaging)"
exit 0
fi
It's a trick of course, but it worked for me.

Related

How to make the install target dependend on generated (protobuf) files

We have a project, where we have different applications, which come with protobuf definitions, for which we provide python bindings as a side effect. We ran into the problem, that when we want to install them along with the rest of the software, cmake complains that the binding to be created does not exist.
This is a minimal sample which behaves wrong:
// foo.proto
message Foo {
uint32 foo = 1;
}
# CMakeLists.txt
project(foobar)
cmake_minimum_required(VERSION 3.16)
find_package(Protobuf REQUIRED)
protobuf_generate_cpp(FOO_SRC FOO_HDR foo.proto)
protobuf_generate_python(FOO_PY foo.proto)
install(FILES ${FOO_PY} DESTINATION "share/proto/")
$ make install
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/foobar
Install the project...
-- Install configuration: ""
CMake Error at cmake_install.cmake:41 (file):
file INSTALL cannot find "/tmp/foobar/foo_pb2.py": No such file or
directory.
How can I convince CMake to build the bindings when I run make install?
Functions protobuf_generate_cpp and protobuf_generate_python are actually create custom commands (add_custom_command). This is noted in the documentation for these functions.
For make a custom command to work, it should be some target which consumes (or depends on) the files created by the custom command.
In case of protobuf_generate_cpp, consumption is usually performed with add_library/add_executable commands.
But it is possible to create a custom target which depends on given files:
protobuf_generate_cpp(FOO_SRC FOO_HDR foo.proto)
protobuf_generate_python(FOO_PY foo.proto)
add_custom_target(proto_target ALL DEPENDS ${FOO_SRC} ${FOO_HDR} ${FOO_PY})

How to with CPack generate the WIX package?

I used cmake 3.12.0. There are exists one cmake project that creates one console application. I add the ability of package generation to that cmake project:
# ... above cmake code for one console application creation
# below code that I add:
# pack
set (A_PACK_DESCRIPTION_SUMMARY "${PROJECT_NAME} - CMake Assistant Solution")
set (A_INSTALL_PREFIX Consolas)
set(CPACK_WIX_PRODUCT_GUID "F9AAAAE2-D6AF-4EA4-BF46-B3E265400CC8")
set(CPACK_WIX_UPGRADE_GUID "F9AAAAE2-D6AF-4EA4-BF46-B3E265400CC7")
set(CPACK_GENERATOR "WIX")
include(CPack)
With other generators (NSIS, 7Z, ZIP, DEB) all works fine but with WIX appears followed error:
...path\files.wxs(11) : error LGHT0091 : Duplicate symbol 'Component:CM_C_EMPTY_INSTALL_ROOT' found. This typically means that an Id is duplicated. Check to make sure all your identifiers of a given type (File, Component, Feature) are unique.
Why it happens and how to fix it?
This appears to be caused by this bug
Basically you use add_subdirectory(xxx EXCLUDE_FROM_ALL) where the subdirectory has a install(... COMPONENT ...) call. The installed files are excluded from the subdirectory, but still creates COMPONENTs, which are now empty and break wix.
As a workaround, you can add:
set(CPACK_COMPONENTS_ALL Unspecified)
in CMakeLists.txt to exclude all the empty components.
If it helps any, I had this problem and found that I had INSTALL commands that specified only Release Configurations but then tried to build the package using the Debug build configuration. By just switching to building the package specifying the Release configuration all went as expected (Wasted most of an afternoon before I figured this out!)

cpack and install(CODE ...) - CPACK_PACKAGING_INSTALL_PREFIX vs CMAKE_INSTALL_PREFIX

as a "post-install hook" I need to execute an install command like
install(CODE "execute_process(COMMAND some_command ${CMAKE_INSTALL_PREFIX}/some_folder"))
which creates a file in some_folder based on the files which were previously installed into some_folder (it compiles an index/cache of those files).
This works fine for the install target, however as soon as using cpack ${CMAKE_INSTALL_PREFIX} is not the correct location anymore.
Is there a variable like ${CMAKE_CURRENT_INSTALL_PREFIX} that always points towards the current installation directory, regardless of wether the default install target or cpack is used and can be used for this purpose?
The only alternative I see is to try to execute the command at an earlier stage on the original files, create a temporary file and install the temporary file. Unfortunately this is much more error prone, as some_command should be run on the "final" files after installation (in order to create a valid cache)
The answer turns out to be extremely simple (kudos to Nils Gladitz from IRC):
Escaping the variable ${CMAKE_INSTALL_PREFIX} with a backslash delays its expansion until install time at which it holds the correct value also for installs via CPack:
install(CODE "execute_process(COMMAND some_command \${CMAKE_INSTALL_PREFIX}/some_folder"))

cmake run script for install target?

I have a project where "installing" the code is not quite as simple as just copying some files. With a traditional Makefile, I would just create a make install target that runs a series of shell commands to do what I need.
But googling around has resulting in no examples of this (some things close, but not quite... i think). So basically, I want a custom command, that depends on the target executables, but produces nothing and runs a script that need not be portable to accomplish the "install"
Anyone have any examples of something like this?
CMake's install command allows for custom scripts. See the official documentation: install - Custom Installation Logic:
install([[SCRIPT <file>] [CODE <code>]]
[COMPONENT <component>] [...])
The SCRIPT form will invoke the given CMake script files during installation. If the script file name is a relative path it will be interpreted with respect to the current source directory. The CODE form will invoke the given CMake code during installation. Code is specified as a single argument inside a double-quoted string. For example, the code
install(CODE "MESSAGE(\"Sample install message.\")")
will print a message during installation.
To run custom shell script (or whatever program), combine install(CODE ...) with execute_process:
install(CODE "execute_process(COMMAND my_script.sh)")
This worked for me: use add_custom_target, then add the main target as a dependency to the custom target target.
# create custom target for setcap to be executed
add_custom_target(setcap ALL
WORKING_DIRECTORY ${OUTPUT_DIR}/bin
COMMAND ${CMAKE_COMMAND} -E 'sudo setcap cap_net_raw,cap_net_admin+eip ${}/bin/<executable name>)
# create a dependency on the custom target for main target, setcap depends on ${proj_name}
add_dependencies(setcap ${proj_name})

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)