CMAKE: a custom target to install at different location - makefile

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.

Related

How to install Criterion to specific folder?

I have Criterion framework (https://github.com/Snaipe/Criterion), I want to build it from source and install to specific directory so headers of criterion should be located in
/usr/include/
and libraries libcriterion.so in /usr/lib64 folder
I use this command to build and install:
mkdir build
cd build
cmake ..
cmake --build .
make install
it installs .so files to /usr/local/ folder, bash command find / -name "*criterion*" shows this:
/usr/local/lib/libcriterion.so
/usr/local/lib/libcriterion.so.3
/usr/local/lib/libcriterion.so.3.1.0
/usr/local/share/pkgconfig/criterion.pc
/usr/local/include/criterion
/usr/local/include/criterion/criterion.h
How can I fix my command so that after installation libcriterion.so was located in /usr/lib64 directory ?
Using CMake command line options does not allow changing the library install location as requested. You could change use the -DCMAKE_INSTALL_PREFIX=... option, but that would affect the include location, as well.
However, since you have access to the source code, you could simply modify the CMakeLists.txt file and set the library install location by adding a command like this:
install(TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION lib64
)
For further details, I may link to the CMake documentation.
By the way: There is no need to create the build directory explicitly, your script could look like this
cmake -H. -Bbuild
cmake --build build
cmake --build build --target install

How to install subdirectories to different locations with CMake?

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)

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.

Can't install OpenCv on Mac: there are no files in /usr/local

I've downloaded tar.gz files from official site(versions 2.4.3, 2.4.7, 2.4.8). Then unzipped them somewhere.
mac-mini-olia:Data olia$ cd opencv-2.4.6.1/
mac-mini-olia:opencv-2.4.6.1 olia$ mkdir build
mac-mini-olia:opencv-2.4.6.1 olia$ cd build/
mac-mini-olia:build olia$ cmake -G "Unix Makefiles" ..
mac-mini-olia:build olia$ make -j8
after the last command output is
3910 warnings and 12 errors generated.
Errors are like
/Volumes/Data/opencv-2.4.6.1/3rdparty/libjpeg/._jcapimin.c:1:4096: error: source file is not valid UTF-8
and
/Volumes/Data/opencv-2.4.6.1/3rdparty/libpng/._pngerror.c:1:2: error: expected identifier or '('
And after that in /usr/local/lib and /usr/local/include there are no files of opencv.
There are multiple ways to install OpenCV on OSX.
You can use MacPorts
Make sure you have XCode installed with it's Command Line Tools first.
(An easy way to test that is to see if xcodebuild if a found command in Terminal)
After you install MacPorts simply do
sudo port install opencv
This will take care of building the project from source and installing it for you.
(The path might be /opt/local/lib /opt/local/include though, haven't used it in a while)
There are also port variants: opencv with options. For example if you plan to use openni 1.5.x and have it integrated with opencv you can try
sudo port install opencv +openni
If you do
sudo port variants opencv
you should get a list of all the options(e.g. python support, qt support, etc.)
If you want to build from source yourself, I recommend installing the ccmake command (I think macports can also do that for you) or use the CMake gui tool. This will allow you to easily configure the build and setup your install location(/use/local/...and so on)
So you can try someting like this:
cd /path/to/your/opencv_folder
mkdir build && cd build
ccmake ..
At this stage you should see something like this:
hit Enter to change an option and Enter again to exit edit mode and use the up/down keys to scroll through the options. When you're happy with the settings, press C to configure. Once that's done, you can press G to generate. This will generate the makefiles for you so you can do this:
make
sudo make install
make install will actually copy the built libraries/headers to the /usr/ folder.
You might run into errors when running make, depending on your setup(e.g. if you're missing dependencies, etc.), but the cool thing about ccmake is that you can go back, run it again, disable the things you don't want to build right now and go back to the make stage.

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