How to install subdirectories to different locations with CMake? - installation

I have a couple of CMake projects which have their own install logic defined. Now, I want to add a global CMakeLists.txt that adds those projects using add_subdirectory() in order to build and install all of them at once. How can I provide a different ${CMAKE_INSTALL_PREFIX} to each of them from the outside?
What I tried is changing the install prefix between the calls to add the subdirectories but they are installed when actually calling make install and at that time, the install prefix is already set to the last assigned value.

If your subprojects require same variable to have different values(at global scope) you cannot build them using add_subdirectory within single global project.
Instead, global project may install them using cmake call either via execute_process or ExternalProject_Add. Sometimes, install(SCRIPT) command may be used for that.
Installing using execute_process makes subproject available immediately at configuration stage.
It can be used when subproject has some packaging mechanism (see CMake tutorials about packaging), so global object may execute find_package(<subproject-name>) and link with libraries, defined in subproject, in the most simple way(using variables, filled outside).
# Configure subproject into <subproject-build-dir>
execute_process(COMMAND ${CMAKE_COMMAND} -D<var>=<value> <subproject-source-dir>
WORKING_DIRECTORY <subproject-build-dir>)
# Build and install subproject
execute_process(COMMAND ${CMAKE_COMMAND} --build <subproject-build-dir> --target install)
# Extract variables, related to subproject for link with it.
find_package(<subproject-name>)
# Create target in global project and link with subproject
include_directories(${<subproject-name>_INCLUDE_DIRS})
link_directories(${<subproject-name>_LIBRARY_DIRS})
add_executable(global_program main.cpp)
target_link_libraries(global_program ${<subproject-name>_LIBRARIES})
Installing using ExternalProject_add assigns target to the subproject, which will be installed at build stage. Linking with subproject in that case is also possible, but requires to fill variables manually.
# Configure, build and install subproject
ExternalProject_Add(<subproject_target>
SOURCE_DIR <subproject-source-dir>
CMAKE_CACHE_ARGS -D<var>=<value>
BINARY_DIR <subproject-build-dir>
INSTALL_DIR <CMAKE_INSTALL_PREFIX-for-subproject>
)
# Create target in global project and link with subproject
include_directories(<subproject-include-files-location>)
link_directories(<subproject-libraries-location>)
add_executable(global_program main.cpp)
target_link_libraries(global_program <subproject-libraries-name>)
# Make sure that subproject will be built before executable.
add_dependencies(global_program <subproject_target>)
Installing using install(SCRIPT) executes script at install stage. This approach can be used when there is no build-dependencies between global project and subproject.
subproject_install.cmake:
# Configure subproject into <subproject-build-dir>
execute_process(COMMAND ${CMAKE_COMMAND} -D<var>=<value> <subproject-source-dir>
WORKING_DIRECTORY <subproject-build-dir>)
# Build and install subproject
execute_process(COMMAND ${CMAKE_COMMAND} --build <subproject-build-dir> --target install)
CMakeLists.txt:
install(SCRIPT subproject_install.cmake)

Related

Conan boost dlls not installed on Windows -> runtime exception

I have a conan recipe of a package, named Package, that requires boost as a shared library:
def requirements(self):
self.requires("boost/1.79.0#")
self.options["boost"].shared = True
self.options["boost"].bzip2 = False
self.options["boost"].without_stacktrace = True
The used generators are CMakeDeps and CMakeToolchain.
def generate(self):
tc = CMakeToolchain(self)
tc.variables['BUILD_SHARED_LIBS'] = "ON" if self.options.shared == True else "OFF"
tc.variables['CMAKE_FIND_ROOT_PATH_MODE_PACKAGE'] = 'NEVER'
tc.variables['CMAKE_POSITION_INDEPENDENT_CODE'] = 'ON'
tc.generate()
The unit tests of this conan package use a CMakeLists.txt which defines a PackageTests target that links against Package and boost::boost.
Building the Package and PackageTests works fine for both Ubuntu and Windows but only on Ubuntu the tests run without issues. On Windows I get exceptions for all the tests because the boost dlls are not found. Using ldd PackageTests and readelf -d PackageTests on Ubuntu shows that boost so files are used from the conan cache.
Using conans VirtualRunEnv generator and then activating the generated environement helps to also run the PackageTests.exe on Windows but I would like to know if there is another way using for example pure CMake to install/copy the required boost dlls to the bin/PackageTests.exe folder? Or is there a way to extend the conan recipe to install the dlls on Windows?
Why are boost shared libraries found correctly in the conan cache but not on Windows? Is there some extra manual work needed or shouldn't this be handeled by conan as well?
Edit:
Trying to use the following to copy the dlls results in a cmake command usage error because the TARGET_RUNTIME_DLLS generator expression is empty.
add_custom_command(TARGET PackageTest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:PackageTest > $<TARGET_FILE_DIR:PackageTest>
COMMAND_EXPAND_LISTS
)
Also IMPORTED_LOCATION property for the following targets are *-NOTFOUND:
get_target_property(VAR-boost boost::boost IMPORTED_LOCATION)
message(${VAR-boost})
> VAR-boost-NOTFOUND
get_target_property(VAR-Package Package IMPORTED_LOCATION)
message(${VAR-Package})
> VAR-Package-NOTFOUND
get_target_property(VAR-Package PackageTest IMPORTED_LOCATION)
message(${VAR-Package})
> VAR-PackageTest-NOTFOUND
From the boost conan recipe package_info() I can see that the CMakeDeps generator will only create the BoostConfig.cmake and BoostTargets.cmake scripts, which is also the case for Package. There is no FindBoost.cmake generated. By default CMakeDeps only creates config scripts, except the recipes define cmake_find_mode to be both. Not sure though if adding both to the recipe could help. Even if it would help, this is no immediate solution as this is not directly in my control (hosted on conan-center-index repo). I am still not able to see the reason why everything works fine on Ubuntu but on Windows the dlls are not found/copied at all by conan.

How can i setup meson and ninja on Ubuntu-Linux to produce the expected .a file by use of MakeFile?

Some years ago on Ubuntu 16.0.4 I've used this library: git clone https://github.com/Beckhoff/ADS and using only the make command I got build, compile and finally on the main directory I found a file called AdsLib-Linux.a and maybe nothing more than this.
Now I'm on Ubuntu 20.04 I need this library once again but this times make dosn't produce the same output and looking forth to the ReadMe instructions I finally used that instead of make:
meson build
ninja -C build
That now create a new directory build but no .a file as before on the root directory. Instead a new file in the build directory libADSLib.a is there. The same thing happens using right the make command.
Maybe the author changed over the years something on the config files or the behavior of the tools have changed, but I cannot get the former file anymore and I need it for other referencing code that now is not executing anymore.
Looking to the MakeFile I found that in the example folder, differently from the one on the parent directory, the MakeFile has something like that:
$(warning ATTENTION make is deprecated and superseeded by meson)
...
${PROGRAM}: LIB_NAME = ../AdsLib-${OS_NAME}.a
...
But all i've tried reading the guides on meson and ninja about setup, configure, build, and so on, did not produce anymore that file.
I've tried also to first build and then copy all files form the example folder to the parent directory and then build again, but again no .a file there.
How's the right way to configure the build process corectly so that this -Linux.a file is created. Or if not possibile anymore, what does it now produce I can use instead of what produced before?
Meson is a build system generator, similar to CMake or somewhat like ./configure, you need to run meson, then run ninja to actually build something.
You need to run both meson and ninja:
meson setup builddir
ninja -C builddir
Once you do that successfully, there will be a libAdsLib.a inside the builddir directory.
Let me correct a bit #dcbaker, according to their README you should setup build as build directory:
# configure meson to build the library into "build" dir
meson build
# let ninja build the library
ninja -C build
Of course, in general, it shouldn't be specific, but their example code is written in a weird way so this path is hard-coded. So, to use the example:
# configure meson to build example into "build" dir
meson example/build example
# let ninja build the example
ninja -C example/build
# and run the example
./example/build/example
About the library: it's now libAdsLib.a and produced in build directory. The name is set here and it's now in linux naming style, the old one - not. So, you have options:
Update your configuration/build files (Makefile?) where you use it
Copy or make symbolic link, e.g.
$ ln -s <>/build/libAdsLib.a <target_path>/AdsLib-Linux.a
Above it's very dependent on your development environment, do you have installation or setup scripts for it? do you permissions to modify/configure parameters for target application? do you need to support both old and new names? - many questions not related to original question about meson.

CMAKE: a custom target to install at different location

I am porting our product from using Makefiles to CMAKE.
With Makefile we have 'install' target to move stuff to some location on user's machine and 'deploy' - to some fixed location on a server. I don't want to run 'cmake -DCMAKE_INSTALL_PREFIX=...' to reconfigure each time I need to switch a destination and prefer to minimize extra typing on a command line. Therefore, in my CMakeLists, I have
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/../../_install)
Now, I am trying to use 'add_custom_target' but not sure how to do this correctly:
add_custom_target(DEPLOY
COMMAND "${CMAKE_COMMAND}" --build . --target install --install ${SITE}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
Any idea?
Here's a full example:
cmake_minimum_required(VERSION 3.21)
project(test)
set(SITE "${CMAKE_SOURCE_DIR}/_deploy"
CACHE PATH "Alternate install prefix for 'deploy'")
add_executable(main main.cpp)
include(GNUInstallDirs)
install(TARGETS main)
add_custom_target(
deploy
COMMAND "${CMAKE_COMMAND}"
--install "${CMAKE_BINARY_DIR}"
--config "$<CONFIG>"
--prefix "${SITE}"
)
Which reduces to
cmake --build /path/to/build --config ... --target deploy
Be warned that none of this will work if your install rules store the value of ${CMAKE_INSTALL_PREFIX}. You have to be careful to use relative paths only. If you do make this mistake, you'll have no choice but to fix your code or reconfigure.

Add 'install' target to 'all' in CMake

I have a CMake project with the following directory tree:
build/
assets/
dest/
<other files>
dest is a directory where all installed files should go:
The executable, which goes to dest/ with a simple make, this is controlled with CMAKE_RUNTIME_OUTPUT_DIRECTORY
The assets, located on assets/, which go to dest/ after a make install.
But I don't want to issue make install to copy all files do the dest/ dir: I want a simple make to do this.
In this sense, how do I add the install target to the default one (all)? Or, is there a better way to solve this?
Using the following wont cause recursion. Requires CMake >= 3.15.
add_custom_command(
TARGET ${MY_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --config $<CONFIG>
)
Extra : You may want to provide a default (local) install path so this doesn't fail on Windows.
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "install" CACHE PATH "Default install path." FORCE)
endif()
This will execute the install target after building <target_name> (which could be all):
add_custom_command(
TARGET <target_name>
POST_BUILD
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target install
)
I've solved this problem by using the following macro to generate data dependency rules for the specified target. These rules require only a make and not a make install.
The macro maps an arbitrary file from your source tree into a "staging" tree, where each tree structure may be different, and you may optionally rename the file. The staged data is kept up-to-date, so if you change it in your source tree then it'll be updated the next time you make.
# Macro used to create dependencies for staging data files (e.g. config files, assets) and keeping them up-to-date.
# The given "source" file is copied (and possibly renamed) to "staged" for the given "target".
#
# It works by creating a rule that creates "staged" by copying "source", then creating a target that depends upon "staged",
# then making the given "target" depend upon the new target. Or in makefile speak:
#
# staged: source
# cp source staged
#
# targetData1: staged
#
# target: <existing dependencies...> targetData1
# <existing commands...>
#
# The intermediate rule is used for parallel build robustness. For details, see:
# http://www.cmake.org/cmake/help/v2.8.12/cmake.html#command:add_custom_command
#
# Example:
# target = myExeTarget
# source = "${CMAKE_CURRENT_SOURCE_DIR}/../../data/images/bush1.png"
# staged = "${STAGING_DATA_DIR}/images/bush1.png"
macro(add_data_dependency target source staged)
add_custom_command(
OUTPUT "${staged}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${source}" "${staged}" # Dir(s) will be created if needed.
DEPENDS "${source}"
)
if (NOT DEFINED add_data_dependency_counter)
#message(status "Setting tmp counter......")
set(add_data_dependency_counter "0")
endif()
math(EXPR add_data_dependency_counter "${add_data_dependency_counter} + 1")
#message(status "tmp counter is ${add_data_dependency_counter}......")
set(customTarget "${target}Data${add_data_dependency_counter}")
add_custom_target(${customTarget} DEPENDS "${staged}")
add_dependencies(${target} ${customTarget})
endmacro()
In your case, usage would be something like:
add_data_dependency(myTarget "${CMAKE_CURRENT_SOURCE_DIR}/assets/file1.ext" "${CMAKE_CURRENT_SOURCE_DIR}/dest/file1.ext")
add_data_dependency(myTarget "${CMAKE_CURRENT_SOURCE_DIR}/assets/file2.ext" "${CMAKE_CURRENT_SOURCE_DIR}/dest/file2.ext")
add_data_dependency(myTarget "${CMAKE_CURRENT_SOURCE_DIR}/assets/rename_me.ext" "${CMAKE_CURRENT_SOURCE_DIR}/dest/renamed.ext")
:
:
Then whenever you make, the files will be copied if missing or out-of-date (assuming that myTarget is part of all).
The normal way to use CMake is to create a build directory outside your project and all compiled binaries are put there. When you are finished developing and want to install some binaries into your system then you call make install. In this way you keep your project source folder free from all compiler generated stuff.
Example file directory structure:
my_project/
my_project_build/
from my_project_build you call cmake ../my_project to generate build files. Call make to build it and all binaries will be in my_project_build.

CMake: Exclude custom install target(s) from 'make install [all]'

I have a library that is built and linked to as part of my project. I want to provide the facility to OPTIONALLY have the library installed system-wide (or wherever ${CMAKE_INSTALL_PREFIX} is set). Otherwise, by default, the project's final build products will be linked statically to the library, and the former get installed, but the library binaries stay in the build directory.
In other words:
$ make
$ make install
will build and install, the programs, but only something like
$ make install.foo
will install the library to ${CMAKE_INSTALL_PREFIX}, building it first if needed.
I have something like this so far (simplified from the actual script, so there might be errors):
INCLUDE_DIRECTORIES( "${CMAKE_CURRENT_LIST_DIR}")
SET (FOO_LIBRARY "foo")
# Following builds library and makes it available to
# be linked other targets within project by:
# TARGET_LINK_LIBRARIES(${progname} ${FOO_LIBRARY})
ADD_LIBRARY(${FOO_LIBRARY}
foo/foo.cpp # and other sources ...
)
###########################################################
# Approach #1
# -----------
# Optionally allow users to install it by invoking:
#
# cmake .. -DINSTALL_FOO="yes"
#
# This works, but it means that users will have to run
# cmake again to switch back and forth between the libary
# installation and non-library installation.
#
OPTION(INSTALL_FOO "Install foo" OFF)
IF (INSTALL_FOO)
INSTALL(TARGETS ${FOO_LIBRARY} DESTINATION lib/foo)
SET(FOO_HEADERS foo/foo.h)
INSTALL(FILES ${FOO_HEADERS} DESTINATION include/foo)
UNSET(INSTALL_FOO CACHE)
ENDIF()
###########################################################
###########################################################
# Approach #2
# -----------
# Optionally allow users to install it by invoking:
#
# make install.foo
#
# Unfortunately, this gets installed by "make install",
# which I want to avoid
SET(FOO_INSTALL "install.foo")
ADD_CUSTOM_TARGET(${FOO_INSTALL}
COMMAND ${CMAKE_COMMAND}
-D COMPONENT=foo
-P cmake_install.cmake)
ADD_DEPENDENCIES(${FOO_INSTALL} ${FOO_LIBRARY})
INSTALL(TARGETS ${FOO_LIBRRARY}
DESTINATION lib/foo COMPONENT foo)
SET(FOO_HEADERS foo/foo.h)
INSTALL(FILES ${FOO_HEADERS}
DESTINATION include/foo COMPONENT foo)
###########################################################
As can be seen, approach #1 sort of works, but the required steps to install the library are:
$ cmake .. -DINSTALL_FOO="yes"
$ make && make install
And then, to go back to "normal" builds, the user has to remember to run cmake again without the "-DINSTALL_FOO" option, otherwise the library will be installed on the next "make install".
The second approach works when I run "make install.foo", but it also install the library if I run "make install". I would like to avoid the latter.
Does anyone have any suggestions on how to go about achieving this?
You are on the right track with approach #2. You can trick CMake into avoiding the installation of the FOO related files by using the OPTIONAL switch of the install command.
For the FOO library target, the commands have to modified in the following way:
SET (FOO_LIBRARY "foo")
ADD_LIBRARY(${FOO_LIBRARY} EXCLUDE_FROM_ALL
foo/foo.cpp
)
INSTALL(TARGETS ${FOO_LIBRARY}
DESTINATION lib/foo COMPONENT foo OPTIONAL)
The EXCLUDE_FROM_ALL is added to ADD_LIBRARY to prevent the library from building when you run a plain make. The installation of FOO_LIBRARY is made optional by adding the OPTIONAL switch.
Making the installation of the FOO_HEADERS optional requires the following changes:
SET(FOO_HEADERS foo/foo.h)
SET(BINARY_FOO_HEADERS "")
FOREACH (FOO_HEADER ${FOO_HEADERS})
ADD_CUSTOM_COMMAND(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FOO_HEADER}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${FOO_HEADER} ${CMAKE_CURRENT_BINARY_DIR}/${FOO_HEADER}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FOO_HEADER})
LIST (APPEND BINARY_FOO_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${FOO_HEADER})
ENDFOREACH()
INSTALL(FILES ${BINARY_FOO_HEADERS}
DESTINATION include/foo COMPONENT foo OPTIONAL)
The FOREACH loop sets up custom commands which copy the FOO_HEADERS verbatim to the corresponding binary dir. Instead of directly using the headers in the current source dir the INSTALL(FILES ... command picks up the copied headers from the binary dir. The paths of the headers in the binary dir are collected in the variable BINARY_FOO_HEADERS.
Finally the FOO_INSTALL target has to be set up in the following way:
SET(FOO_INSTALL "install.foo")
ADD_CUSTOM_TARGET(${FOO_INSTALL}
COMMAND ${CMAKE_COMMAND}
-D COMPONENT=foo
-P cmake_install.cmake
DEPENDS ${BINARY_FOO_HEADERS})
ADD_DEPENDENCIES(${FOO_INSTALL} ${FOO_LIBRARY})
The custom FOO_INSTALL is added with a dependency on BINARY_FOO_HEADERS to trigger the copying of the header files. A target level dependency on FOO_LIBRARY triggers the building of the library when you run make install.foo.
The solution however has the following drawbacks:
Upon configuring CMake will issues a warning Target "foo" has EXCLUDE_FROM_ALL set and will not be built by default but an install rule has been provided for it. CMake will however do the right thing anyway.
Once you have run make install.foo a subsequent make install command will also install all FOO related files, because the built FOO library and the headers exist in the binary directory.
Check if this solution works for you.
You would have to use different commands to those you said you would like to use, but I think it addresses the problem fairly well.

Resources