Installing only one target (and its dependencies) out of a complex project with cmake (open to better solutions) - cmake

Let's say I have a project made of several subprojects A, B, C, D...
All subprojects depends on A, which changes rather frequently.
Plus, there might be some further dependencies:
in this example, D depends on B.
Now: many people are working on these projects. The main CMakeLists.txt file should include all directories, so that the build all builds everything. But people would like also to be able to work only on one of these projects, and not having to build/install everything everytime.
If I am working on D, I can easily build "only" D by calling
cmake --build . --target D -- -j7
or
ninja -j7 D
This will also build A and B if something for them has changed. Perfect.
But how can I call install only for D without triggering build all?
I would like that if I call:
ninja -j7 D install
it only built D (and dependencies) and then installed only D and its dependencies (A and B).
Instead, it builds the target all and install all.
I would like to keep that the target all keep building everything. So EXCLUDE_FROM_ALL wouldn't be an option. But going in that direction I couldn't find any solution.
So I am thinking of the following strategy:
Apart from subproject A, all other targets are set to EXCLUDE_FROM_ALL, and OPTIONAL at installation.
I add one extra subproject that simply depends from all other sub-projects (maybe I make each target publish its name by using some variable set at PARENT_SCOPE), and people will have to call that when they want to build and install everything.
Is it going to work? Is there any better solution?
We would like to avoid that everybody has to edit the main CMakeLists.txt file to exclude projects he is not interested in. The solution should be portable to different OSs.
Edit:
I tried the strategy I proposed, but it didn't work: in my case, putting an install statement for a target (even if specified as OPTIONAL) will make ineffective EXCLUDE_FROM_ALL. Reading better in the documentation I found out that:
Installing a target with EXCLUDE_FROM_ALL set to true has undefined behavior.
I also get this warning:
Target <targetname> has EXCLUDE_FROM_ALL set and will not be built by default but an install rule has been provided for it. CMake does not define behavior for this case.
Edit 2:
I tried putting EXCLUDE_FROM_ALL as an option of add_subdirectory (instead of add_library/add_executable), but then all the install statements in those sub-directory seem to be ignored: only install statements in non excluded-from-all subdirectories will be installed.
Edit 3:
Even if I activate CMAKE_SKIP_INSTALL_ALL_DEPENDENCY:
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY true)
in the main CMakeLists.txt file, and I omit all EXCLUDE_FROM_ALL, put installation of as many targets as I want optional (in my case, all but A), and if building of specific targets precede installation, yet the command:
ninja -j7 D && ninja install
for some reason will fail, stating that C (whose installation was set to OPTIONAL) does not exist (it was not created because D depended only on A and B)...
file INSTALL cannot find "<name of dll file for C>"
Edit 4:
It looks like a cmake bug to me. (I am using 2.8.11 under Windows, also tested 2.8.10)
This INSTALL command
install(TARGETS ${targetname} RUNTIME DESTINATION . LIBRARY DESTINATION . OPTIONAL)
is converted in the cmake_install.cmake as:
IF(NOT CMAKE_INSTALL_COMPONENT OR "${CMAKE_INSTALL_COMPONENT}" STREQUAL "Unspecified")
FILE(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/." TYPE SHARED_LIBRARY FILES *path_to_dll*)
IF(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/./" AND NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/./*dll_name*")
IF(CMAKE_INSTALL_DO_STRIP)
EXECUTE_PROCESS(COMMAND "C:/Programs/MinGW/bin/strip.exe" "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/./*dll_name*")
ENDIF(CMAKE_INSTALL_DO_STRIP) ENDIF() ENDIF(NOT CMAKE_INSTALL_COMPONENT OR "${CMAKE_INSTALL_COMPONENT}" STREQUAL "Unspecified")
with the command FILE missing OPTIONAL! If I add OPTIONAL manually, it works!
(note: I have edited here to put *dll_name* and *path_to_dll* placeholders)
Edit 5:
I confirm it's a bug of cmake, or at least wrong documentation. I will report this.
The situation solved either putting a more simple
install(TARGETS ${targetname} DESTINATION . OPTIONAL)
(but this in my case will also install .lib.a files that I don't want)
or moving in front the OPTIONAL flag:
install(TARGETS ${targetname} OPTIONAL RUNTIME DESTINATION . LIBRARY DESTINATION .)
What one understands from the cmake documentation is that OPTIONAL should be put as last option.

What works:
Remove dependency of "install" target to "all" target (once, in the main CMakeLists.txt):
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY true)
Set to OPTIONAL installation of all targets you do not want to always build:
install(TARGETS <<targetname>> DESTINATION . OPTIONAL)
Build the targets you want to install
ninja -j7 <<list of targets>>, or more generally:
<<your builder>> <<your options>> <<list of targets>>
This will build all the targets listed and their dependencies
Call the installer
ninja install. This will install all the libraries you have built, those you mentioned explicitly and those to which they depended. This solves the problem of installing a specific target together with its dependencies!
To concatenate the commands, both on Unix and Windows you can use
ninja -j7 <<list of targets>> && ninja install
Note that, at least for ninja, you cannot simply prepend "install" to the list of targets, as the target "install" does not depend anymore on any target and ninja in parallelizing the job will run install while you are still building your targets. A replacement to the old ninja -j7 install is
ninja -j7 && ninja install.
The target "all" is still available (and it is still the default target).
If you need to create a list of targets you want to build together, you can define a custom target:
add_custom_target(<<collective target name>> DEPENDS <<list of targets>>)
This will not be included in the target all. Adding also a COMMAND would also allow to create an install target for as many as target as we want, for example ninja -j7 install_D, but I think now we are beyond the scope of this question.
Further considerations:
(Note, July 2018: This might be outdated) If you use RUNTIME DESTINATION, LIBRARY DESTINATION etc., most likely because of a CMake bug the OPTIONAL keyword should be put exactly in the position indicated below:
install(TARGETS <<targetname>> OPTIONAL RUNTIME DESTINATION <<some dir>> LIBRARY DESTINATION <<some (other) dir>>)
This contradicts what written in the documentation, and I will proceed to report it as a bug to the CMake developers.
Using EXCLUDE_FROM_ALL in combination with INSTALL + OPTIONAL is a bad idea
Installing a target with EXCLUDE_FROM_ALL set to true has undefined behavior.
(and cmake tries to warn you when it parses the code)

As I answered here,
recent versions of Ninja provide the option to build only targets in a subdirectory.
The same feature holds for installs. So if your target D from your question were in a subdirectory D you can run this command:
ninja D/install
Find more info at the CMake documentation to the Ninja Generator here.

If you have used add_subdirectory to add your sub-projects to your CMakeLists.txt you will see, that in your build-directory you have subdirectories for all your subprojects.
If you only want to install the targets from D just do:
cd build-dir/D
make install
this will only install the D-targets.
Of course you have to have built your project before in build-dir. I'm using it is this way using gnu-make. I don't know if it works with Ninja.

As mentioned by unapiedra the ninja build file generated by cmake includes rules for building/testing/installing/packaging what's inside a particular directory.
This is fine but you cannot do:
ninja <targetName>/install
You can only do
ninja path/where/targetName/is/install
If you don't know where the targetName is you may use:
ninja -t query <targetName>
to see outputs.

Related

cmake add-custom-target doesn't create a target

I want to build a package that contains a list of files (configuration files that my main task uses). So I am adding these in my CMakeLists.txt
add_custom_target(my-configs)
install(
FILES
file1.cfg
DESTINATION data/task
COMPONENT my-configs
EXCLUDE_FROM_ALL)
...
But when I run :
make -C ../cmake-build/linux_64_static_make_RelWithDebInfo/task/ my-configs
I get :
make: Entering directory '/development/cmake-build/linux_64_static_make_RelWithDebInfo/task'
make: *** No rule to make target 'my-configs'. Stop.
Why is that? Shouldn't the above create the target?
EDIT
This component doesn't do anything apart from copying files into the specified location. In that case, do I need a custom_target at all?
Or could I just go ahead and do cmake install? If I do install I see :
cd ../cmake-build/linux_64_static_make_RelWithDebInfo && DESTDIR=../../cmake-install/linux_64_static_make_RelWithDebInfo cmake -DCOMPONENT=my_configs -P cmake_install.cmake
-- Install configuration: "RelWithDebInfo"
-- Install component: "my_configs"
but nothing gets installed in the DESTDIR as expected - which is why I thought I needed a target so I can regenerate the cmake-build tree? Otherwise how will it know about the new component?
One use case for add_custom_target() can be like a .PHONY target in a Makefile.
You need to add dependencies to your custom target so that CMake knows how to fill the order. Here some examples:
add_custom_target(libs DEPENDS library_1 library_2 ...)
So now you have a target libs that will build your set of libraries when you specify that target to be built or some other target depends on libs to be built.
Another example:
add_custom_target(unmount_server COMMAND "umount /mnt/deployment")
This would provide a target that would unmount a server drive. Very much a non-portable operation as I have written it.

cmake not rebuilding a non-download external project after manually editing its sources

I'm working on some modifications to the openEMS project. This project uses cmake to build all of its components. The top level CMakeLists.txt file contains the following:
# ...
ExternalProject_Add( openEMS
DEPENDS fparser CSXCAD
SOURCE_DIR ${PROJECT_SOURCE_DIR}/openEMS
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DFPARSER_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DCSXCAD_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DWITH_MPI=${WITH_MPI} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
)
# ...
Inside the openEMS directory, there's another CMakeLists.txt with the following:
# ...
set(SOURCES
openems.cpp
)
# ...
add_library( openEMS SHARED ${SOURCES})
# ...
After building the project successfully once, make does not rebuild anything when, for example, openems.cpp is modified. Why?
$ mkdir build
$ cd build
$ cmake -DBUILD_APPCSXCAD=NO
$ make
[builds all files]
$ touch ../openEMS/openems.cpp
$ make
[ 33%] Built target fparser
[ 66%] Built target CSXCAD
[100%] Built target openEMS
(noting is built)
I have checked and the modification date of openems.cpp is newer than the target. Even deleting the produced library files and binaries, both in the install directory and in the build directory, does not cause it to rebuild anything. The only way I can get it to rebuild is by deleting everything in the build directory and re-running cmake which, of course, rebuilds everything.
This looks like a case of the following. Quoting from the docs for ExternalProject_Add at the section titled "Build Step Options":
BUILD_ALWAYS <bool>
Enabling this option forces the build step to always be run. This can be the easiest way to robustly ensure that the external project's own build dependencies are evaluated rather than relying on the default success timestamp-based method. This option is not normally needed unless developers are expected to modify something the external project's build depends on in a way that is not detectable via the step target dependencies (e.g. SOURCE_DIR is used without a download method and developers might modify the sources in SOURCE_DIR).
If that's the case, the solution would be to add the BUILD_ALWAYS argument to the ExternalProject_Add call like.
ExternalProject_Add( openEMS
DEPENDS fparser CSXCAD
SOURCE_DIR ${PROJECT_SOURCE_DIR}/openEMS
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DFPARSER_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DCSXCAD_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DWITH_MPI=${WITH_MPI} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
BUILD_ALWAYS TRUE
)
If you confirm that this solves the issue, you might want to raise this as an issue to the maintainers of openEMS.
Also note that since the external project there is using CMake as a buildsystem, you could also add the CONFIGURE_HANDLED_BY_BUILD TRUE to the argument list. See the docs for more info.
Edit: The asker opened a GitHub Pull-Request.

CMake - how to block executing installation scripts during packaging?

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.

Ninja equivalent of Make's "build from this directory down" feature (with CMake)?

When building a project using CMake and Make, you can execute make from a subdirectory of your build tree (i.e. from a directory below whatever directory contains your top-level Makefile), and make will (as far as I can tell) build all targets at or below that directory. This is because CMake generates a Makefile for every directory that contains targets, so when you're in a directory with targets, make finds the Makefile for building those targets.
When CMake generates Ninja files, however, it only generates one build.ninja file, which is at the top level of the build tree. So calling ninja from a directory other than the top-level directory fails (even the -f option doesn't work because ninja can't find the rules.ninja file).
Is there any way to emulate the "make-like" behavior of building targets at and below a directory? As far as I can tell, there are no Ninja targets that correspond to "all targets at and below a particular directory." (This could be emulated using phony targets named after each directory that depend on all targets at and below that directory, but CMake does not generate such targets by default.)
ninja <DIR>/all works with recent versions of Ninja (1.7.2). Version 1.3.4 does not allow this.
I could not find a reference to this on the manual. However, CMake has this documented here:
Recent versions of the ninja program can build the project through the “all” target. An “install” target is also provided.
For each subdirectory sub/dir of the project, additional targets are generated:
sub/dir/all
Depends on all targets required by the subdirectory.
sub/dir/install
Runs the install step in the subdirectory, if any.
sub/dir/test
Runs the test step in the subdirectory, if any.
sub/dir/package
Runs the package step in the subdirectory, if any.
This worked for me:
cd <build-root>
DIRECTORY=<path-relative-to-build-root>
ninja -t targets all | egrep "^${DIRECTORY}/" | egrep CXX_EXECUTABLE_LINKER | \
sed -e 's/:.*//g' | xargs ninja
ninja -t targets all - lists all targets (including target type)
egrep "^${DIRECTORY}/" - filters list of targets to only include those in desired directory
egrep CXX_EXECUTABLE_LINKER - limits the targets to just C++ executables. You can remove or tweak this to get the set of targets you're interested in.
sed -e 's/:.*//g' - removes the target type e.g. ": CXX_EXECUTABLE_LINKER"
xargs ninja - invokes ninja to build the targets
Good question. I would like to know the answer if you find it.
I am just in the process of transitioning to cmake+ninja myself.
I found that I could not create targets with the same name at different levels
(if there is a way I would be interested to know).
So I adopted a naming convention for different targets
E.g.
name - builds program or library
test.name - runs tests for the named program or library
doxygen.name - build doxygen for the named program or library
For deeper hierarchies you can do something like:
doxygen.subproject
doxygen.subproject.name
Using this pattern you can control precisely what is built but you have to issue the command from the top-level build directory.
I think after I get used to this I will find it more productive as there is no need to change directory before you build or run something and though there is sometimes a little extra typing required the shell history generally has it covered.
This is implemented under the hood by using add_custom_target() and adding appropriate dependencies. I use a macro to do this automatically so that
a macro "add_doxygen()" will add the doxygen target for the program and make the doxygen target at each higher level depend on it using add_dependencies().

cmake exclude executable during install

How to configure/hack cmake to build particular executable added with add_executable() but do not install it?
The executable is a unit test and will eventually be handled with add_test but for now I just want to strip test binaries off the release touching as little as possible.
Thanks
Since EXCLUDE_FROM_ALL has undefined behavior if combined with INSTALL (cmake tries to warn you, it doesn't matter if OPTIONAL is set), a solution guaranteed to work is more complicated.
You should:
Remove dependency of "install" target to "all" target (once, in the main CMakeLists.txt):
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY true)
Add OPTIONAL to the INSTALL statements in your test libraries. Be aware of the possible bug of CMake I reported here
Collect separately all the targets you want to include in a custom "all_but_tests"
CMake add_custom_target depending on whole project being built (most difficult step)
Create a custom all_but_tests target:
add_custom_target(all_but_tests DEPENDS <<list of targets>>)
Add a dependency of the target install to all_but_tests
add_dependency(install all_but_tests)
(Sorry, this I have never tried, feedback is welcome)
Create a custom tests target:
add_custom_target(my_tests DEPENDS <<list of tests>>)
Then (supposing you are using make, but will work also for ninja):
You can call make install, which triggers make all_but_tests, and installs once built is complete.
You can call make my_tests, and then make install, in this case it will install everything. You can concatenate the commands like this
make my_tests && make install
or, since there is not difference in this case:
make [all] && make install
I was attracted by this question because I recently had to face a similar problem: Installing only one target and its dependencies
Edit:
add_dependency(install all_but_tests) will probably not work.
So, either you use an adequate workaround, or you call
make all_but_tests && make install
everytime you want to install "all_but_tests"
CMake will only install an executable target if you apply the install function to it, i.e.:
install(TARGETS ExecutableTest RUNTIME DESTINATION "bin")
To prevent the installation of the ExecutableTest for a Release build, add a CONFIGURATIONS restriction:
install(TARGETS ExecutableTest RUNTIME DESTINATION "bin" CONFIGURATIONS Debug)
Alternatively, you can make ExecutableTest an optional target, which is not built by default:
add_executable(ExecutableTest EXCLUDE_FROM_ALL ${ExecutableTestFiles})
and then optionally only install the ExecutableTest if it has been built explicitly:
install(TARGETS ExecutableTest RUNTIME DESTINATION "bin" OPTIONAL)
All optional test targets can be pooled in a super target to allow for building them in one step:
add_custom_target(MyTests DEPENDS ExecutableTest ExecutableTest2 ExecutableTest3)