Have CMake rebuild custom targets when transitive dependency outdated - cmake

I have a multi-directory CMake project where I generate two files b and c. Then I have an executable d_target that needs c.
File b depends on some configuration file a
File c depends on file b
Executable target d_target needs c, it's a generated header file.
I added two add_custom_command() calls to generate the files and added dependencies.
Then, I added two add_custom_target() calls to make the targets available throughout the multi-directory project.
file a > file b > b_target > file c > c_target > d_target
The first time that I build c_target, all dependencies are built and the file c is equal to b and b equal to a.
Now, when I change the 'configuration' a and build d_target, d_target is not rebuilt.
How can I have CMake rebuild targets when any of their dependencies change?
cmake_minimum_required(VERSION 3.15)
project(abcd)
# ./b/CMakeLists.txt
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/b
COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/a > ${CMAKE_CURRENT_BINARY_DIR}/b
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/a)
add_custom_target(b_target DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/b)
# ./c/CMakeLists.txt
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c
COMMAND cat ${CMAKE_CURRENT_BINARY_DIR}/b > ${CMAKE_CURRENT_BINARY_DIR}/c
DEPENDS b_target ${CMAKE_CURRENT_BINARY_DIR}/b)
add_custom_target(c_target DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/c)
# ./d/CMakeLists.txt
add_executable(d_target ${CMAKE_CURRENT_SOURCE_DIR}/d.cpp)
add_dependencies(d_target c_target)
# will not generate:
# add_dependencies(d_target c_target ${CMAKE_CURRENT_BINARY_DIR}/c)
In my specific situation, the commands for b and c and d are in CMakeLists.txt in their respective subdirectories.
Demonstration:
$ make d_target
[ 20%] Generating b
[ 20%] Built target b_target
[ 40%] Generating c
[ 60%] Built target c_target
[ 80%] Building CXX object CMakeFiles/d_target.dir/d.cpp.o
[100%] Linking CXX executable d_target
[100%] Built target d_target
$ touch ../a
$ make d_target
[ 20%] Generating b
[ 20%] Built target b_target
[ 40%] Generating c
[ 60%] Built target c_target
[100%] Built target d_target
When I try to add the c file to add_dependencies(d_target ...), CMake fails:
CMake Error at CMakeLists.txt:21 (add_dependencies):
The dependency target "/home/tom/tmp/abcd/build/c" of target
"d_target" does not exist.

Below is a solution with explanatory comments in the c/CMakeLists.txt snippet.
You can change the value to SOME_DEFINE inside of b/a and re-build to see it update.
root CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(abcd)
add_subdirectory(b)
add_subdirectory(c)
add_subdirectory(d)
b/a
#define SOME_DEFINE 4
b/CMakeLists.txt
set(B_INPUT "${CMAKE_CURRENT_SOURCE_DIR}/a")
set(B_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/b")
add_custom_command(
OUTPUT ${B_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E copy ${B_INPUT} ${B_OUTPUT}
DEPENDS ${B_INPUT}
)
add_custom_target(b_target DEPENDS ${B_OUTPUT})
c/CMakeLists.txt
get_target_property(B_OUTPUT_DIR b_target BINARY_DIR)
set(C_INPUT "${B_OUTPUT_DIR}/b")
set(C_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/c")
add_custom_command(
OUTPUT ${C_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E copy ${C_INPUT} ${C_OUTPUT}
DEPENDS ${C_INPUT}
)
# Using "_c_target" as a local target and intending the non underscore prefix
# for use outside this CMakeLists.txt file
add_custom_target(_c_target DEPENDS ${C_OUTPUT})
# This forces `b_target` to be built prior to `_c_target`. Since the custom
# command to build `C_INPUT` is in a different CMakeLists, CMake doesn't
# understand that `C_INPUT` is created by a custom command and thus doesn't know
# to generate it.
# This is only needed because the custom command for `C_INPUT` is in another
# file. If the custom command was here, in this file, CMake would implicitly
# hook up the dependency for us through the custom commands and we wouldn't need
# `b_target`.
add_dependencies(_c_target b_target)
# Since `C_OUTPUT` is a header file, we create a header only library that the
# executable can link to. This header only library provides the include
# directory of the generated file.
add_library(c_target INTERFACE)
target_include_directories(c_target INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
# Per docs for add_dependencies
#
# Dependencies added to an imported target or an interface library are
# followed transitively"
#
# This ensures that the `_c_target` will be built prior to anything that may end
# up using `c_target`.
add_dependencies(c_target _c_target)
d/CMakeLists.txt
add_executable(d_target ${CMAKE_CURRENT_SOURCE_DIR}/d.cpp)
target_link_libraries(d_target PRIVATE c_target)
d/d.cpp
#include <stdio.h>
#include "c"
int main(int argc, char *argv[]) {
printf("The value is %d.\n", SOME_DEFINE);
}

Related

CMake EXCLUDE_FROM_ALL argument ignored when running 'make all'

Introduction
Using CMake, I am trying to build a library using a special make target. To this end, I use the EXCLUDE_FROM_ALL argument to ensure that the library is not built by running make (which defaults to make all). Unfortunately, the EXCLUDE_FROM_ALL option seems to be ignored by cmake.
Consider the following file tree:
.
├── CMakeLists.txt
├── foo
│   ├── CMakeLists.txt
│   ├── headers
│   │   └── foo.h
│   └── src
│   └── foo.cpp
└── src
└── main.cpp
4 directories, 5 files
In this file tree, you can see that I basically have two parts:
The foo library, for which all files are located in ./foo/.
The main executable, for which the source code is located in ./src/main.cpp.
Below, I will further explain these two parts and the behaviour I am observing.
The foo library
First of all, the header code located in ./foo/headers/foo.h is:
#ifndef _FOO_H_
#define _FOO_H_
#include <iostream>
void foo(void);
#endif // _FOO_H_
The corresponding source code, located in ./foo/src/foo.cpp, is:
#include "foo.h"
void foo(void) {
std::printf("Hello foo!\n");
}
As you can see, the foo library only contains the function foo(). This function simply prints Hello, foo! to the terminal. To build the foo library, I have the following CMakeLists.txt file defined (in ./foo/CMakeLists.txt):
cmake_minimum_required(VERSION 3.10)
project(foo
LANGUAGES CXX
)
# Create a foo library target
add_library(foo EXCLUDE_FROM_ALL
"src/foo.cpp"
)
target_include_directories(foo
PUBLIC "headers"
)
Note that I use the EXCLUDE_FROM_ALL argument in the add_library() function.
To test the behaviour of the EXCLUDE_FROM_ALL argument, I will now build the foo library as a stand-alone (using the previously shown CMakeLists.txt). Starting from the top directory (.), I use the following workflow:
$ cd foo
$ mkdir build
$ cd build
$ cmake ..
After this is finished, I run the make command:
$ make
(nothing happens)
As expected, nothing happens (since the foo library is excluded from the all target). Now, when I use make foo, I get the following output:
$ make foo
Scanning dependencies of target foo
[ 50%] Building CXX object CMakeFiles/foo.dir/src/foo.cpp.o
[100%] Linking CXX static library libfoo.a
[100%] Built target foo
So far, so good. Before moving on to building the main executable, I completely remove the ./foo/build directory and go back to the top-level directory:
$ cd ..
$ rm -rf build
$ cd ..
The main executable
The source code for the main executable is located in ./src/main.cpp:
#include "foo.h"
int main() {
foo();
return 0;
}
The top-level CMakeLists.txt contains:
cmake_minimum_required(VERSION 3.10)
project(TestProject
LANGUAGES CXX
)
# Include the foo library
add_subdirectory("foo" EXCLUDE_FROM_ALL)
# Create the main executable
add_executable(TestProject
"src/main.cpp"
)
target_link_libraries(TestProject
PRIVATE foo
)
Note that I again use the EXCLUDE_FROM_ALL argument, but now for the add_subdirectory() function.
To generate a makefile for the main executable, I use the following workflow:
$ mkdir build
$ cd build
$ cmake ..
When I now run make, I get the following output:
$ make
Scanning dependencies of target foo
[ 25%] Building CXX object foo/CMakeFiles/foo.dir/src/foo.cpp.o
[ 50%] Linking CXX static library libfoo.a
[ 50%] Built target foo
Scanning dependencies of target TestProject
[ 75%] Building CXX object CMakeFiles/TestProject.dir/src/main.cpp.o
[100%] Linking CXX executable TestProject
[100%] Built target TestProject
As you can see, it first builds the foo library. However, this is not what I expected! I explicitly stated to exclude the foo library from the all target. Hence, I would expect the need to first run make foo before running make.
This brings me to my main question(s): does someone know why it ignores the EXCLUDE_FROM_ALL option in the add_subdirectory() function in the top-level CMakeLists.txt? It did work when I built the foo library as a stand-alone. Is this a bug in cmake? If not, how do I achieve my expected behaviour (i.e. the need to run make foo before running make)?
This is not a bug. CMake does not include the foo lib in the all target directly. It does however add the foo target as dependency to TestProject and since TestProject is included in the all target, foo gets built too, if you build all.
This needs to be done this way. How else would you be able to link the foo function into TestProject?
If you want to use a lib that is not built as part of all, you need to use a imported lib. Doing this is and including the lib in the project seems a bit nonsensical though, but here you go.
add_library(foo STATIC EXCLUDE_FROM_ALL
"src/foo.cpp"
)
cmake_minimum_required(VERSION 3.10)
project(TestProject
LANGUAGES CXX
)
# Include the foo library
add_subdirectory("foo" EXCLUDE_FROM_ALL)
# Create the main executable
add_executable(TestProject
"src/main.cpp"
)
# add copy of foo as imported lib
add_library(foo_imported STATIC IMPORTED)
target_include_directories(foo_imported INTERFACE $<TARGET_PROPERTY:foo,INTERFACE_INCLUDE_DIRECTORIES>)
set_target_properties(foo_imported PROPERTIES IMPORTED_LOCATION $<TARGET_FILE:foo>)
target_link_libraries(TestProject
PRIVATE foo_imported
)

CMake: require building of files in addition to building targets

In this simple CMakefile, the first script list.sh outputs a list of 2 generated files file1.proto;file2.proto, instructing CMake that they can be built from source source.xml (using the second script gen.sh).
cmake_minimum_required(VERSION 3.13)
set(source "source.xml")
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/list.sh ${source}
OUTPUT_VARIABLE protos
)
message("${protos}: ${source}")
add_custom_command(
OUTPUT ${protos}
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen.sh ${source}
DEPENDS ${source}
)
add_custom_target(my_target DEPENDS ${protos})
Everything works well if I run:
$ cmake ..
file1.proto;file2.proto: source.xml
-- Configuring done
-- Generating done
-- Build files have been written to: /build
$ make my_target
[100%] Generating file1.proto, file2.proto
[100%] Built target my_target
What should I add to be able to also run the code generation with:
$ make file1.proto
[EDIT] autocomplete suggests only the following for command make:
$ make (TAB TAB)
all cmake_force edit_cache/ preinstall
clean default_target help preinstall/
clean/ depend my_target rebuild_cache
cmake_check_build_system edit_cache my_target/ rebuild_cache/
Solution from #KamilCuk :
Adding the following makes it possible to build each proto file individually
(it works, but then cmake complains about circular dependencies!)
foreach(p ${protos})
add_custom_target(${p} DEPENDS {CMAKE_CURRENT_BINARY_DIR}/${p})
endforeach()

How can I include a dependency file generated by a custom command in cmake for GNU make?

I have integrated a tool that generates some code from a text file in CMake based build environment.
This tool is also able to generate the dependency file in the GNU make format (pretty much like gcc -MD does it for c files).
I would like to include this dependency file in makefiles generated by CMake in order to correctly rebuild when necessary. Unfortunately, I could not find the correct way to do this.
I tried:
Generated Dependency Files in CMake , but there was no answer
https://cmake.org/cmake/help/latest/command/add_custom_command.html?highlight=add_custom_command has the DEPFILE option but it only works for Ninja
Has anyone had a similar issue?
try to define your file as dependency of your add_custom_target:
cmake_minimum_required(VERSION 3.0)
project(Custom_Command_TEST)
add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/your_gen_file.txt"
COMMAND /bin/date > "${CMAKE_BINARY_DIR}/your_gen_file.txt"
COMMAND /bin/echo "RUNNING COMMAND")
add_custom_target(GenerateFile
/bin/echo "RUNNING TARGET"
DEPENDS "${CMAKE_BINARY_DIR}/your_gen_file.txt")
add_executable(${PROJECT_NAME} main.cpp)
add_dependencies(${PROJECT_NAME} GenerateFile)
will only rebuild the target GenerateFile. You could put your .txt generation step as COMMAND in add_custom_command.
output of first make:
[ 33%] Generating your_gen_file.txt
RUNNING COMMAND
RUNNING TARGET
[ 33%] Built target GenerateFile
[ 66%] Building CXX object CMakeFiles/Custom_Command_TEST.dir/main.cpp.o
[100%] Linking CXX executable Custom_Command_TEST
[100%] Built target Custom_Command_TEST
output second make:
RUNNING TARGET
[ 33%] Built target GenerateFile
[100%] Built target Custom_Command_TEST
In case you want to rebuild everything again, you'll want to make clean.
You could also check this older post

correctly set the location of imported cmake targets for an installed package

I would like to be able to import targets from an installed library but
when using:
install(TARGETS
foobar
EXPORT foobarLibTargets
LIBRARY DESTINATION lib)
cmake generates a foobarLibTargets.cmake containing an absolute path:
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION_NOCONFIG "/where/I/happened/to/build/libfoobar.so"
IMPORTED_SONAME_NOCONFIG "libfoobar.so"
)
Such that a build using the imported target from the installation will fail as the path does not exist.
Q How can I get it to use the correct relative location instead?
This would be equivalent to:
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
If I look at another project which does something similar but works it has:
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libfoobar.so"
IMPORTED_SONAME_RELEASE "libfoobar.so"
)
Here are some example files that reproduce the issue:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)
project(FOOBAR VERSION 1.2.3)
set(VERSION 1.2.3)
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
set(CMAKE_INSTALL_PREFIX "/opt/foobar" CACHE PATH "Install path prefix" FORCE)
add_library(foobar SHARED
foobar.cpp
)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_NAME "foobar")
set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
include(CPack)
# Indicate the content of the distribution pakcages
install(FILES
${CMAKE_SOURCE_DIR}/foobar.h
DESTINATION include
)
install(TARGETS
foobar
EXPORT foobarLibTargets
LIBRARY DESTINATION lib)
include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/foobar)
set(INCLUDE_INSTALL_DIR include)
set(LIBRARY_INSTALL_DIR lib)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
configure_package_config_file(foobarConfig.cmake.in
"${CMAKE_BINARY_DIR}/foobarConfig.cmake"
INSTALL_DESTINATION "${ConfigFileInstallDir}"
PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR)
write_basic_package_version_file(
"${CMAKE_BINARY_DIR}/foobarConfigVersion.cmake"
VERSION "${VERSION}"
COMPATIBILITY ExactVersion)
export(EXPORT foobarLibTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake")
install(EXPORT foobarLibTargets
FILE foobarTargets.cmake
DESTINATION lib/cmake)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/foobarConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/foobarConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake"
DESTINATION "${ConfigFileInstallDir}")
foobarConfig.cmake.in:
set(FOOBAR_VERSION #VERSION#)
#PACKAGE_INIT#
set_and_check(FOOBAR_INCLUDE_DIR "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
set_and_check(FOOBAR_LIBRARY_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
include("${CMAKE_CURRENT_LIST_DIR}/foobarLibTargets.cmake")
# workaround - correct absolute path in the above
# this shouldn't be necessary (hence this question)
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
#)
foobar.h:
void hello();
foobar.cpp:
#include <iostream>
void hello() {
std::cerr << "hello world\n";
}
useFoo.cmake (a CMakeLists.txt for an example project using the installed library):
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
set(VERSION 1.2.3)
find_package(foobar)
file(GENERATE OUTPUT foobar-gen CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
message(STATUS "FOOBAR_LIBRARY_DIR=${FOOBAR_LIBRARY_DIR}")
message(STATUS "FOOBAR_INCLUDE_DIR=${FOOBAR_INCLUDE_DIR}")
build.sh (build and use the installation package):
#!/bin/sh
rm -rf target
mkdir target
cd target
cmake .. &&
make &&
cpack -G TGZ
if [ $? -ne 0 ]; then
echo "doh!"
exit 1
fi
cd ..
rm -rf install
mkdir install
cd install
tar -xvzf ../target/foobar-1.2.3.tar.gz
cp ../useFoo.cmake CMakeLists.txt
export CMAKE_PREFIX_PATH=`pwd`/opt/foobar/lib/cmake:`pwd`/opt/foobar/lib/cmake/foobar
cmake .
if [ $? -ne 0 ]; then
echo "doh!"
exit 1
fi
cat foobar-gen
The output of cat foobar-gen is:
<TARGET_FILE:foobar>=/where/I/happened/to/build/libfoobar.so
I would like it to be:
<TARGET_FILE:foobar>=/where/I/actually/installed/libfoobar.so
Which it becomes if I uncomment the workaround.
Is there a way which avoids the workaround?
The related question - Strange issue with variables in a config-file cmake package - has similar code which both reproduces this issue and adds another one on top.
The main issue is that the two files foobarLibTargets.cmake and foobarTargets.cmake were both installed and the wrong one was picked up.
You will find below an improved project along with remarks to better organize the build system.
ChangeLog summarizing edits
2019-05-25
Create GitHub project to streamline reuse and adaptation. See https://github.com/jcfr/stackoverflow-56135785-answer
Rename project and source directory from foobar to FooBarLib, update Suggestions section accordingly
Improve build.sh
Updated suggestions (CPACK_PACKAGING_INSTALL_PREFIX should be absolute)
RPM:
Add support for building RPM package using make package
Update build.sh to display content of RPM package
Remarks
Two config files should be generated:
one for the build tree: this allow user of your project to directly build against your project and import targets
one for the install tree (which also end up being packaged)
Do not force the value of CMAKE_INSTALL_PREFIX
CPACK_PACKAGING_INSTALL_PREFIX should NOT be set to an absolute directory
For sake of consistency, use foobarTargets instead of foobarLibTargets
<projecname_uc> placeholder used below correspond to the name of the project upper-cased (ABC instead of abc)
To allow configuring your project when vendorized along other one, prefer variable with <projecname_uc>_. This means <projecname_uc>_INSTALL_LIBRARY_DIR is better than LIBRARY_INSTALL_DIR.
To allow user of the project to configure *_INSTALL_DIR variables, wrap them around if(DEFINED ...)
Consistently use variables (e.g LIBRARY_INSTALL_DIR should always be used instead of lib)
Prefer naming variable <projecname_uc>_INSTALL_*_DIR instead of <projecname_uc>_*_INSTALL_DIR, it make it easier to know the purpose of the variable when reading the code.
Since version is already associated with the project, there is no need to set VERSION variable. Instead, you can use PROJECT_VERSION or FOOBAR_VERSION
If starting a new project, prefer the most recent CMake version. CMake 3.13 instead of CMake 3.7
Introduced variable <projecname_uc>_INSTALL_CONFIG_DIR
<project_name>Targets.cmake should not be installed using install(FILES ...), it is already associated with an install rule
conditionally set CMAKE_INSTALL_RPATH, it is valid only on Linux
<project_name>Config.cmake.in:
there is no need to set FOOBAR_LIBRARY, this information is already associated with the exported foobar target
FOOBAR_LIBRARY_DIR is also not needed, this information is already associated with the exported foobar target
instead of setting FOOBAR_INCLUDE_DIR, the command target_include_directories should be used
remove setting of FOOBAR_VERSION, the generate version file already takes care of setting the version.
always specify ARCHIVE, LIBRARY and RUNTIME when declaring install rules for target. It avoid issue when switching library type. One less thing to think about.
always specify component with your install rule. It allows user of your project to selectively install part of it only development component or only runtime one, ...
initializing CMAKE_BUILD_TYPE is also important, it ensures the generated Targets file are associated with a configuration (instead of having the suffix -noconfig.cmake)
Suggested changes
Generally speaking, I recommend to have a source tree, a build tree and install tree. The files posted below assumed the following layout:
./build.sh
./FooBarLib/FooBarLibConfig.cmake.in
./FooBarLib/CMakeLists.txt
./FooBarLib/foobar.cpp
./FooBarLib/foobar.h
./FooBarLib-build
./FooBarLib-install
./useFoo/CMakeLists.txt
./useFoo-build
build.sh
#!/bin/bash
set -xeu
set -o pipefail
script_dir=$(cd $(dirname $0) || exit 1; pwd)
project_name=FooBarLib
archive_name=${project_name}
# cleanup ${project_name}-build
cd $script_dir
rm -rf ${project_name}-build
mkdir ${project_name}-build
cd ${project_name}-build
# configure, build and package ${project_name}
cmake ../${project_name}
make
make package # equivalent to running "cpack -G TGZ" and "cmake -G RPM"
# extract ${project_name} archive
cd $script_dir
rm -rf ${project_name}-install
mkdir ${project_name}-install
cd ${project_name}-install
tar -xvzf ../${project_name}-build/${archive_name}-1.2.3.tar.gz
# cleanup useFoo-build
cd $script_dir
rm -rf useFoo-build
mkdir useFoo-build
cd useFoo-build
cpack_install_prefix=/opt
# configure useFoo
cmake -D${project_name}_DIR=$script_dir/${project_name}-install${cpack_install_prefix}/lib/cmake/${project_name}/ ../useFoo
cat foobar-gen
# display content of RPM. If command "rpmbuild" is available, RPM package is expected.
if command -v rpmbuild &> /dev/null; then
rpm -qlp $script_dir/${project_name}-build/${archive_name}-1.2.3.rpm
fi
FooBarLib/CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(FooBarLib VERSION 1.2.3)
if(UNIX AND NOT APPLE)
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
endif()
#------------------------------------------------------------------------------
# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Release' as none was specified.")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
mark_as_advanced(CMAKE_BUILD_TYPE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
#------------------------------------------------------------------------------
# This variable controls the prefix used to generate the following files:
# <export_config_name>ConfigVersion.cmake
# <export_config_name>Config.cmake
# <export_config_name>Targets.cmake
# and it also used to initialize FOOBARLIB_INSTALL_CONFIG_DIR value.
set(export_config_name ${PROJECT_NAME})
#------------------------------------------------------------------------------
if(NOT DEFINED FOOBARLIB_INSTALL_INCLUDE_DIR)
set(FOOBARLIB_INSTALL_INCLUDE_DIR include)
endif()
if(NOT DEFINED FOOBARLIB_INSTALL_BIN_DIR)
set(FOOBARLIB_INSTALL_BIN_DIR bin)
endif()
if(NOT DEFINED FOOBARLIB_INSTALL_LIBRARY_DIR)
set(FOOBARLIB_INSTALL_LIBRARY_DIR lib)
endif()
if(NOT DEFINED FOOBARLIB_INSTALL_CONFIG_DIR)
set(FOOBARLIB_INSTALL_CONFIG_DIR ${FOOBARLIB_INSTALL_LIBRARY_DIR}/cmake/${export_config_name})
endif()
#------------------------------------------------------------------------------
set(headers
foobar.h
)
# Install rule for headers
install(
FILES ${headers}
DESTINATION ${FOOBARLIB_INSTALL_INCLUDE_DIR}
COMPONENT Development
)
#------------------------------------------------------------------------------
add_library(foobar SHARED
foobar.cpp
)
target_include_directories(foobar
PUBLIC
$<BUILD_INTERFACE:${FooBarLib_SOURCE_DIR}>
$<INSTALL_INTERFACE:${FOOBARLIB_INSTALL_INCLUDE_DIR}>
)
install(
TARGETS foobar
EXPORT ${export_config_name}Targets
ARCHIVE DESTINATION ${FOOBARLIB_INSTALL_LIBRARY_DIR} COMPONENT Development
LIBRARY DESTINATION ${FOOBARLIB_INSTALL_LIBRARY_DIR} COMPONENT RuntimeLibraries
RUNTIME DESTINATION ${FOOBARLIB_INSTALL_BIN_DIR} COMPONENT RuntimeLibraries
)
#------------------------------------------------------------------------------
# Configure <export_config_name>ConfigVersion.cmake common to build and install tree
include(CMakePackageConfigHelpers)
set(config_version_file ${PROJECT_BINARY_DIR}/${export_config_name}ConfigVersion.cmake)
write_basic_package_version_file(
${config_version_file}
VERSION "${FooBarLib_VERSION}"
COMPATIBILITY ExactVersion
)
#------------------------------------------------------------------------------
# Export '<export_config_name>Targets.cmake' for a build tree
export(
EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${export_config_name}Targets.cmake"
)
# Configure '<export_config_name>Config.cmake' for a build tree
set(build_config ${CMAKE_BINARY_DIR}/${export_config_name}Config.cmake)
configure_package_config_file(
${export_config_name}Config.cmake.in
${build_config}
INSTALL_DESTINATION "${PROJECT_BINARY_DIR}"
)
#------------------------------------------------------------------------------
# Export '<export_config_name>Targets.cmake' for an install tree
install(
EXPORT ${export_config_name}Targets
FILE ${export_config_name}Targets.cmake
DESTINATION ${FOOBARLIB_INSTALL_CONFIG_DIR}
)
set(install_config ${PROJECT_BINARY_DIR}/CMakeFiles/${export_config_name}Config.cmake)
configure_package_config_file(
${export_config_name}Config.cmake.in
${install_config}
INSTALL_DESTINATION ${FOOBARLIB_INSTALL_CONFIG_DIR}
)
# Install config files
install(
FILES ${config_version_file} ${install_config}
DESTINATION "${FOOBARLIB_INSTALL_CONFIG_DIR}"
)
#------------------------------------------------------------------------------
# Generate package
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
# Setting this variable also impacts the layout of TGZ.
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt")
# Setting CPACK_SOURCE_* and CPACK_GENERATOR allow to have "make package" generates
# the expected archive.
# Disable source generator enabled by default
set(CPACK_SOURCE_TBZ2 OFF CACHE BOOL "Enable to build TBZ2 source packages" FORCE)
set(CPACK_SOURCE_TGZ OFF CACHE BOOL "Enable to build TGZ source packages" FORCE)
set(CPACK_SOURCE_TZ OFF CACHE BOOL "Enable to build TZ source packages" FORCE)
# Select generators
if(UNIX AND NOT APPLE)
set(CPACK_GENERATOR "TGZ")
find_program(RPMBUILD_PATH rpmbuild)
if(RPMBUILD_PATH)
list(APPEND CPACK_GENERATOR "RPM")
endif()
elseif(APPLE)
# ...
endif()
include(CPack)
FooBarLib/FooBarLibConfig.cmake.in
#PACKAGE_INIT#
set(export_config_name "#export_config_name#")
set_and_check(${export_config_name}_TARGETS "${CMAKE_CURRENT_LIST_DIR}/${export_config_name}Targets.cmake")
include(${${export_config_name}_TARGETS})
useFoo/CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(useFoo VERSION 1.2.3)
find_package(FooBarLib REQUIRED)
file(GENERATE OUTPUT foobar-gen CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
get_target_property(foobar_INCLUDE_DIR foobar INTERFACE_INCLUDE_DIRECTORIES)
message(STATUS "foobar_INCLUDE_DIR=${foobar_INCLUDE_DIR}")
get_target_property(imported_location foobar IMPORTED_LOCATION_RELEASE)
get_filename_component(foobar_LIBRARY_DIR ${imported_location} DIRECTORY)
message(STATUS "foobar_LIBRARY_DIR=${foobar_LIBRARY_DIR}")
Output of build.sh
./build.sh
+ set -o pipefail
+++ dirname ./build.sh
++ cd .
++ pwd
+ script_dir=/tmp/stackoverflow-56135785-answer
+ project_name=FooBarLib
+ archive_name=FooBarLib
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf FooBarLib-build
+ mkdir FooBarLib-build
+ cd FooBarLib-build
+ cmake ../FooBarLib
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to 'Release' as none was specified.
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/stackoverflow-56135785-answer/FooBarLib-build
+ make
Scanning dependencies of target foobar
[ 50%] Building CXX object CMakeFiles/foobar.dir/foobar.cpp.o
[100%] Linking CXX shared library libfoobar.so
[100%] Built target foobar
+ make package
[100%] Built target foobar
Run CPack packaging tool...
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: FooBarLib
CPack: - Install project: FooBarLib
CPack: Create package
CPack: - package: /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.tar.gz generated.
CPack: Create package using RPM
CPack: Install projects
CPack: - Run preinstall target for: FooBarLib
CPack: - Install project: FooBarLib
CPack: Create package
-- CPackRPM:Debug: Using CPACK_RPM_ROOTDIR=/tmp/stackoverflow-56135785-answer/FooBarLib-build/_CPack_Packages/Linux/RPM
CPackRPM: Will use GENERATED spec file: /tmp/stackoverflow-56135785-answer/FooBarLib-build/_CPack_Packages/Linux/RPM/SPECS/foobarlib.spec
CPack: - package: /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.rpm generated.
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf FooBarLib-install
+ mkdir FooBarLib-install
+ cd FooBarLib-install
+ tar -xvzf ../FooBarLib-build/FooBarLib-1.2.3.tar.gz
opt/
opt/include/
opt/include/foobar.h
opt/lib/
opt/lib/libfoobar.so
opt/lib/cmake/
opt/lib/cmake/FooBarLib/
opt/lib/cmake/FooBarLib/FooBarLibTargets.cmake
opt/lib/cmake/FooBarLib/FooBarLibTargets-release.cmake
opt/lib/cmake/FooBarLib/FooBarLibConfigVersion.cmake
opt/lib/cmake/FooBarLib/FooBarLibConfig.cmake
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf useFoo-build
+ mkdir useFoo-build
+ cd useFoo-build
+ cpack_install_prefix=/opt
+ cmake -DFooBarLib_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib/cmake/FooBarLib/ ../useFoo
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- foobar_INCLUDE_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/include
-- foobar_LIBRARY_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/stackoverflow-56135785-answer/useFoo-build
+ cat foobar-gen
<TARGET_FILE:foobar>=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib/libfoobar.so
+ command -v rpmbuild
+ rpm -qlp /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.rpm
/opt
/opt/include
/opt/include/foobar.h
/opt/lib
/opt/lib/cmake
/opt/lib/cmake/FooBarLib
/opt/lib/cmake/FooBarLib/FooBarLibConfig.cmake
/opt/lib/cmake/FooBarLib/FooBarLibConfigVersion.cmake
/opt/lib/cmake/FooBarLib/FooBarLibTargets-release.cmake
/opt/lib/cmake/FooBarLib/FooBarLibTargets.cmake
/opt/lib/libfoobar.so
Only after instrumenting the source of cmake itself was I finally able to track this down.
The export and install commands are both capable of generating cmake files for targets.
The export command e.g.:
export(EXPORT foobarLibTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake")
creates a Targets.cmake referencing the build tree.
The install command e.g.:
install(EXPORT foobarLibTargets
FILE foobarTargets.cmake
DESTINATION lib/cmake)
creates a Targets.cmake referencing the relocatable install location.
This is essentially what #J-Christophe meant by saying that two files were installed and the wrong one was picked up.
I had wrongly assumed that the install command was only responsible for installing files and the export command was only responsible for generating them.
The documentation makes sense now
export(EXPORT [NAMESPACE ] [FILE ])
The file created by this command is specific to the build tree and
should never be installed. See the install(EXPORT) command to export
targets from an installation tree.
The workaround I had previously is no longer necesary.
For reference this was to explicitly set the correct location in the package's Config.cmake as in:
set(FOOBAR_VERSION #VERSION#)
#PACKAGE_INIT#
set_and_check(FOOBAR_INCLUDE_DIR "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
set_and_check(FOOBAR_LIBRARY_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
include("${CMAKE_CURRENT_LIST_DIR}/foobarLibTargets.cmake")
# workaround - correct absolute path in the above
# this shouldn't be necessary!
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
)
Most of the solutions here are misleading. It's by design working that way: https://github.com/Kitware/CMake/blob/f46c67de0e16293a40bbbade18aa7cee9edb02b0/Source/cmExportInstallFileGenerator.cxx#L184-L192
So if the DESTINATION in the install(EXPORT ...) statement is an absolute path, then hardcode it as absolute path in the exported package config files. Otherwise, generate the path dynamically using _IMPORT_PREFIX.

cmake add_custom_command not working

I am trying to run gperf from a cmake file.
I created a very minimal CMakeLists.txt below.
When I run it by
$ cmake .
$ make
It does not create the example.hpp file
What could be problem with the below CMakeLists.txt?
cmake_minimum_required( VERSION 2.6 )
function(gperf_generate_new source target)
add_custom_target(${target} echo "Creating ${target}")
add_custom_command(
SOURCE ${source}
TARGET ${target}
COMMAND gperf -L c++ ${source} > ${target}
OUTPUTS ${target}
DEPENDS ${source}
)
endfunction()
gperf_generate_new(command_options.new.gperf example.hpp)
Files, produced by source-files generators(like gpref) are rarely needed as standalone. Instead, these source files are usually used for creating executables or libraries inside a project.
So, standard pattern of using source-file generators in the CMake looks like:
# Call add_custom_command() with appropriate arguments for generate output file
# Note, that *gperf* will work in the build tree,
# so for file in the source tree full path should be used.
function(gperf_generate_new input output)
add_custom_command(
OUTPUT ${output}
COMMAND gperf -L c++ ${input} > ${output}
DEPENDS ${input}
COMMENT "Generate ${output}" # Just for nice message during build
)
endfunction()
# Generate *example.hpp* file ...
gperf_generate_new(${CMAKE_CURRENT_SOURCE_DIR}/command_options.new.gperf example.hpp)
# ... for use it in executable
add_executable(my_program ${CMAKE_CURRENT_BINARY_DIR}/example.hpp <other sources>)
If you want only to test whether example.hpp is generating, instead of add_executable() use
add_custom_target(my_target
ALL # Force target to be built with default build target.
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/example.hpp
)
Note, that linkage between add_custom_command and add_custom_target is expressed using same filename in their OUTPUT and DEPENDS options correspondingly. With such link order of these commands is insignificant (but both commands should be called from the same CMakeLists.txt script).