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

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.

Related

Replace LibXML2 with Yocto Built LibXML2

I have been working on this all day, but I think I finally have my base case figured out. I am using a GitHub repo DBCPPP in my application. This repo relies on LibXml2, to ensure it is available LibXml2 is included as a submodule and build with dbcppp through a series of CMakeLists.txt. I am trying to resolve an issue with my build in which the Linux kernel and the DBCPPP recipe are both attempting to install /usr/lib/libxml2.so.2.9.10. I have an open question about it here.
My potential solution is to prevent DBCPPP from creating the libxml2.so.2.9.10 file, and instead build using the libxml2.so.2.9.10 file created by Yocto.
So far my CMakeLists.txt contains:
cmake_minimum_required(VERSION 3.12)
project("libdbcppp" VERSION 0.1.0)
set(LIBXML2_INCLUDE_DIR /mnt/WorkDrive/Documents/EVCC_Application/build-fb/tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/libxml2/2.9.10-r0/libxml2-2.9.10/include)
set(LIBXML2_LIBRARIES /mnt/WorkDrive/Documents/EVCC_Application/build-fb/tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/libxml2/2.9.10-r0/image/usr/lib)
#include(FindPkgConfig)
#include(FindLibXml2 REQUIRED)
find_package(LibXml2 REQUIRED)
option(build_tests "Build tests" ON)
option(build_examples "Build examples" ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
add_definitions("/bigobj")
endif()
include_directories("include")
include_directories("third-party/libxmlmm/libxmlmm")
include_directories("/mnt/WorkDrive/Documents/EVCC_Application/build-fb/tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/boost")
include_directories("/mnt/WorkDrive/Documents/EVCC_Application/build-fb/tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/libxml2/2.9.10-r0/libxml2-2.9.10/include")
include_directories("third-party/cxxopts/include")
file(GLOB libxmlmm_header
"third-party/libxmlmm/libxmlmm/*.h"
)
file(GLOB libxmlmm_src
"third-party/libxmlmm/libxmlmm/*.cpp"
)
add_library(libxmlmm SHARED "")
target_link_libraries(libxmlmm LibXml2::LibXml2)
target_sources("libxmlmm"
PRIVATE ${libxmlmm_header}
PRIVATE ${libxmlmm_src}
)
install(TARGETS "libxmlmm" EXPORT ${PROJECT_NAME}Targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(
DIRECTORY "libxmlmm"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libxmlmm
FILES_MATCHING PATTERN "*.h")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
CONFIGURE_FILE(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
IMMEDIATE #ONLY
)
ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
)
Edit: I am updating my question with my latest CMakeLists.txt. I am integrating this with a Yocto project. This appears to work within Ubuntu but when I added it to the Yocto build I get the error.
error: /mnt/WorkDrive/Documents/MAIN_Application/build-fb/tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/libxml2/2.9.10-r0/image/usr/lib: read: Is a directory
| collect2: error: ld returned 1 exit status
| ninja: build stopped: subcommand failed.
| WARNING: exit code 1 from a shell command.

cmake move build files in a subdirectory after configuring and building [duplicate]

I'm using cmake to compile a C++ project, and I want cmake to generate all the output files(metafiles like Makefile used to create binaries) in the build folder. I've checked all the answers in How do I make cmake output into a 'bin' dir?, none of them worked for me(suprisingly!). Files are generated in the root folder instead of in the build folder, what's wrong here? I guess I must have missed something.
Code Structure
➜ cmake-test tree .
.
├── CMakeLists.txt
└── hello.cpp
0 directories, 2 files
CMakeLists.txt
# Specify the minimum version for CMake
cmake_minimum_required(VERSION 3.11)
# Project's name
project(hello)
# Set the output folder where your program will be created
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# The following folder will be included
include_directories("${PROJECT_SOURCE_DIR}")
add_executable(hello ${PROJECT_SOURCE_DIR}/hello.cpp)
Build Commands and Outputs
➜ cmake-test cmake .
-- The C compiler identification is GNU 8.2.0
-- The CXX compiler identification is GNU 8.2.0
-- 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
-- Configuring done
-- Generating done
-- Build files have been written to: /home/searene/CLionProjects/cmake-test
➜ cmake-test ls
bin CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt hello.cpp Makefile
cmake version
➜ cmake-test cmake --version
cmake version 3.11.4
CMake suite maintained and supported by Kitware (kitware.com/cmake).
OS
Linux
The usual way to do this, rather than changing variables to set the path, is simply to create the output directory, change to it, and run cmake from there. So instead of cmake . you usually have cmake .. or similar.
I understand the initial impulse to say "But I expect my build system to write output somewhere else." But CMake is not usually used in the way you were initially expecting, and other people who run your CMake build won't expect what you were expecting, so it's probably best to just use the built-in, default behavior, which is to put the output wherever cmake was run.
Put another way: You are fighting against the tool. Don't do that.
Disclaimer: I recommend going with #john-zwinck's answer.
By default, cmake uses the current working directory as build directory and whatever path you provide as source directory. So the normal way of achieving your goal is
create the build directory (mkdir build)
go there (cd build)
call cmake with the source dir as argument (cmake path/to/source)
BUT there is another way, as far as I know not documented in the cmake docs and only kept for compatibility reasons or internal usage, that some people are using. The -B and -H flags
cmake -Hpath/to/source -Bpath/to/build
or even from the source dir
cmake . -Bbuild
Important: no space after -B.
CMake 3.19.1 (not sure how about older ones) has following option (from docs):
cmake [<options>] -S <path-to-source> -B <path-to-build>
Uses as the build tree and as the
source tree. The specified paths may be absolute or relative to the
current working directory. The source tree must contain a
CMakeLists.txt file. The build tree will be created automatically if
it does not already exist. For example:
cmake -S src -B build

CMake with MinGW-w64 does not install built DLL, but it's being built fine

NB: The files referenced here are all given below the horizontal ruler a bit down.
This is the MWE derived from a project where I ran into this. CMake version used is 3.12.2 on Ubuntu 16.04 (16.04.6 to be precise).
The goal is to create a CMakeLists.txt which can be re-targeted to build a Windows DLL using the MinGW-w64 toolchain (apt-get install mingw-w64 on Ubuntu/Debian).
When using no explicit toolchain file (i.e. without -DCMAKE_TOOLCHAIN_FILE=...), all works as expected and the lib${PRJNAME}.so gets installed as desired. However, once I use the toolchain file given below, I only get the resulting import lib ${PRJNAME}Lib.dll.a but not the corresponding .dll file installed.
If I invoke the Bash script as follows:
./build.sh 2>&1 |grep '^-- Install'
the output is:
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-native/install-target/lib/libtest.so
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll.a
As you can see the native build installs the actual shared library, but the one targeting Windows only installs the import lib, not the DLL. What I'd expect to see is something like this:
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-native/install-target/lib/libtest.so
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll.a
What is it I am doing wrong here? It would seem that the install() function is invoked properly:
install(
TARGETS ${PRJNAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT library
)
Clearly ARCHIVE DESTINATION takes effect as that's where the import lib ends up. But why is the built .dll completely ignored here?
Side-note: I am aware of GNUInstallDirs, but that fell totally apart once I started cross-compiling for Windows. So I am setting the desired paths "manually" before invoking install().
build.sh (should be executable)
The script will first wipe the folders build-native and build-windows, if present, and then create those again. Then it will invoke cmake from these folders respectively, targeting using the system (native) toolchain and the MinGW-w64 toolchain respectively. Last but not least it will invoke the installation from these folders respectively.
So if you place this into an empty folder along with the other files this should not meddle with your data elsewhere in any way.
#/usr/bin/env bash
for i in native windows; do
D=build-$i
test -d $D && rm -rf $D
mkdir $D
[[ "$i" == "windows" ]] && TCFILE=mingw64-64bit.cmake
( set -x; cd $D && cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=. ${TCFILE+-DCMAKE_TOOLCHAIN_FILE=$TCFILE} -DCMAKE_VERBOSE_MAKEFILE=ON )
( set -x; cd $D && cmake --build . --target install )
done
test.cpp
#ifdef _WIN32
# if defined(test_EXPORTS)
# define TEST_API __declspec(dllexport)
# else
# define TEST_API __declspec(dllimport)
# endif
#else
# define TEST_API
#endif
TEST_API void SomeFunction()
{
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
set(PRJNAME test)
set(TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/install-target")
project(${PRJNAME})
add_library(${PRJNAME} SHARED test.cpp)
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(DLL_PREFIX)
set(DLL_POSTFIX Lib)
else()
set(DLL_PREFIX lib)
set(DLL_POSTFIX)
endif()
set_target_properties(
${PRJNAME}
PROPERTIES
PREFIX "${DLL_PREFIX}"
IMPORT_PREFIX "${DLL_PREFIX}"
DEBUG_POSTFIX "${DLL_POSTFIX}"
RELEASE_POSTFIX "${DLL_POSTFIX}"
CXX_STANDARD 11
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE 1
)
set(CMAKE_INSTALL_PREFIX ${TARGET_DIR})
set(CMAKE_INSTALL_LIBDIR lib)
set(CMAKE_INSTALL_INCLUDEDIR include)
install(
TARGETS ${PRJNAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT library
)
mingw64-64bit.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc-posix)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++-posix)
set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
install(
TARGETS ${PRJNAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT library
)
According to the install command documentation the DLL file is considered a runtime object. I tested the example on Ubuntu 14.04.

Force CMake to use absolute include path

I have a project whose directory layout looks like:
- src/ #Contains main source code
- ext/ #Contains external libraries and headers from GitHub
- CMakeLists.txt
The problem is that no matter what I do, CMake always seems to pass ext/ to the compiler as a relative path, like this:
/usr/bin/c++ -I../ext mysrc.cpp
I've tried doing both:
include_directories("${PROJECT_SOURCE_DIR}/ext")
include_directories("/home/user/project/ext")
But it doesn't seem to matter. The directory is always passed to -I as ../ext.
Why does this matter? At the end of my build I invoke gcov -r <source file> which tells gcov to generate coverage reports from my source file and any relative paths found within. As a result, gcov is going into ext/ and generating reports for tons of stuff I don't care about and it's taking up a lot of time. If CMake would instead pass in -I/home/user/project/ext then gcov -r would ignore everything in ext/.
As far as I can tell from:
https://cmake.org/cmake/help/v3.13/command/include_directories.html ... this isn't possible, but maybe I'm just missing something?
Edit: This appears to be a problem with specifically the ninja generator. When using the Unix Makefiles generator, everything is passed via absolute paths.
https://gitlab.kitware.com/cmake/cmake/issues/18666
Edit2:
user#antimony:~/cmake_test$ ls
CMakeLists.txt ext src
user#antimony:~/cmake_test$ cat CMakeLists.txt
project(Hello)
add_subdirectory(src)
user#antimony:~/cmake_test$ cat src/CMakeLists.txt
include_directories(
.
${PROJECT_SOURCE_DIR}/ext
)
add_executable(hello_world hello.cpp)
user#antimony:~/cmake_test$ cat src/hello.cpp
#include <useless.h>
int main()
{
hello h;
return 0;
}
user#antimony:~/cmake_test$ cat ext/useless.h
struct hello {
int x;
};
user#antimony:~/cmake_test$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake --version
cmake version 3.13.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
user#antimony:~/cmake_test$ mkdir build && cd build
user#antimony:~/cmake_test/build$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake .. -G Ninja
-- The C compiler identification is GNU 7.3.0
-- The CXX compiler identification is GNU 7.3.0
...
-- Build files have been written to: /home/user/cmake_test/build
user#antimony:~/cmake_test/build$ ninja -v
[1/2] /usr/bin/c++ -I../src/. -I../ext -MD -MT src/CMakeFiles/hello_world.dir/hello.o -MF src/CMakeFiles/hello_world.dir/hello.o.d -o src/CMakeFiles/hello_world.dir/hello.o -c ../src/hello.cpp
[2/2] : && /usr/bin/c++ -rdynamic src/CMakeFiles/hello_world.dir/hello.o -o src/hello_world && :
user#antimony:~/cmake_test/build$ cat build.ninja
# CMAKE generated file: DO NOT EDIT!
# Generated by "Ninja" Generator, CMake Version 3.13
# This file contains all the build statements describing the
# compilation DAG.
...
#############################################
# Order-only phony target for hello_world
build cmake_object_order_depends_target_hello_world: phony || src/CMakeFiles/hello_world.dir
build src/CMakeFiles/hello_world.dir/hello.o: CXX_COMPILER__hello_world ../src/hello.cpp || cmake_object_order_depends_target_hello_world
DEP_FILE = src/CMakeFiles/hello_world.dir/hello.o.d
INCLUDES = -I../src/. -I../ext
OBJECT_DIR = src/CMakeFiles/hello_world.dir
OBJECT_FILE_DIR = src/CMakeFiles/hello_world.dir
TARGET_COMPILE_PDB = src/CMakeFiles/hello_world.dir/
TARGET_PDB = src/hello_world.pdb
# =============================================================================
# Link build statements for EXECUTABLE target hello_world
The example shows what may be considered an in-source build. That is when the build directory is the same or a sub-directory of the src folder (not that there is a hard definition or anything, but this does trigger the ninja issue of using relative paths on the command line). Try mkdir ~/cmake_build && cd ~/cmake_build && cmake ~/cmake_test then it should use absolute paths for everything.
Either way there really isn't a specific way to force one or the other. In general cmake generators will use absolute paths for everything that ends up used on the command line. There seems to be issues with Ninja that prevent the generator from using absolute paths for in-source builds (https://github.com/ninja-build/ninja/issues/1251).

How to auto generate pkgconfig files from cmake targets

I would like to generate pkgconfig files in cmake from the targets. I started by writing something like this:
function(auto_pkgconfig TARGET)
get_target_property(INCLUDE_DIRS ${TARGET} INTERFACE_INCLUDE_DIRECTORIES)
string(REPLACE "$<BUILD_INTERFACE:" "$<0:" INCLUDE_DIRS "${INCLUDE_DIRS}")
string(REPLACE "$<INSTALL_INTERFACE:" "$<1:" INCLUDE_DIRS "${INCLUDE_DIRS}")
string(REPLACE "$<INSTALL_PREFIX>" "${CMAKE_INSTALL_PREFIX}" INCLUDE_DIRS "${INCLUDE_DIRS}")
file(GENERATE OUTPUT ${TARGET}.pc CONTENT "
Name: ${TARGET}
Cflags: -I$<JOIN:${INCLUDE_DIRS}, -I>
Libs: -L${CMAKE_INSTALL_PREFIX}/lib -l${TARGET}
")
install(FILES ${TARGET}.pc DESTINATION lib/pkgconfig)
endfunction()
This is a simplified version but it basically reads the INTERFACE_INCLUDE_DIRECTORIES properties and processes the INSTALL_INTERFACE of the generator expressions.
This works well as long as the include directories are set before calling auto_pkgconfig, like this:
add_library(foo foo.cpp)
target_include_directories(foo PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
${OTHER_INCLUDE_DIRS}
)
auto_pkgconfig(foo)
However, sometimes properties are set after the call to auto_pkgconfig, like this:
add_library(foo foo.cpp)
auto_pkgconfig(foo)
target_include_directories(foo PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
${OTHER_INCLUDE_DIRS}
)
However, this won't properly read the include directories anymore. I would like auto_pkgconfig to run after all the target properties are set. I could use generator expressions for this, by changing auto_pkgconfig to this:
function(auto_pkgconfig TARGET)
file(GENERATE OUTPUT ${TARGET}.pc CONTENT "
Name: ${TARGET}
Cflags: -I$<JOIN:$<TARGET_PROPERTY:${TARGET},INTERFACE_INCLUDE_DIRECTORIES>, -I>
Libs: -L$<TARGET_FILE_DIR:${TARGET}> -l${TARGET}
")
install(FILES ${TARGET}.pc DESTINATION lib/pkgconfig)
endfunction()
However, this will read the BUILD_INTERFACE instead of the INSTALL_INTERFACE. So is there another way to read target properties after they have been set?
According to the CMake documentation, the contents of INSTALL_INTERFACE are only available when calling install(EXPORT). Unless they extend CMake, it will be best to do something else to generate your PkgConfig files. Ideally you would have enough control over your install layout to make this easy.
However, this doesn't mean you can't do what you ask; it's just "Tony the Pony" levels of evil. I actually hesitated to post this. Please don't take this as a recommendation.
The idea is to use install(EXPORT) to have CMake generate the appropriate scripts. Then generate a dummy CMake project that uses the file(GENERATE OUTPUT ...) code you gave above; the dummy project will see the exported, ie. INSTALL_INTERFACE properties.
I initially tried to use install(CODE [[ ... ]]) to do this, but it also sees the $<BUILD_INTERFACE:...> view. I've asked about this on the CMake Discourse.
cmake_minimum_required(VERSION 3.16)
project(example)
# Dummy library for demo
add_library(example SHARED example.cpp)
target_compile_definitions(example
PUBLIC $<BUILD_INTERFACE:BUILD>
$<INSTALL_INTERFACE:INSTALL>)
target_include_directories(example
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>)
# Here be dragons...
function(auto_pc TARGET)
file(CONFIGURE OUTPUT "pc.${TARGET}/CMakeLists.txt"
CONTENT [[
cmake_minimum_required(VERSION 3.16)
project(pc_#TARGET#)
find_package(pc_#TARGET# REQUIRED CONFIG)
file(GENERATE OUTPUT #TARGET#.pc
CONTENT [=[
Name: #TARGET#
Cflags: -I$<JOIN:$<TARGET_PROPERTY:INTERFACE_INCLUDE_DIRECTORIES>, -I> -D$<JOIN:$<TARGET_PROPERTY:INTERFACE_COMPILE_DEFINITIONS>, -D>
Libs: -L$<TARGET_FILE_DIR:#TARGET#> -l#TARGET#
]=] TARGET "#TARGET#")
]] #ONLY NEWLINE_STYLE LF)
install(TARGETS ${TARGET} EXPORT pc_${TARGET})
install(EXPORT pc_${TARGET} DESTINATION "_auto_pc" FILE pc_${TARGET}-config.cmake)
file(CONFIGURE OUTPUT "pc.${TARGET}/post-install.cmake"
CONTENT [[
file(REAL_PATH "${CMAKE_INSTALL_PREFIX}" prefix)
set(proj "#CMAKE_CURRENT_BINARY_DIR#/pc.#TARGET#")
execute_process(COMMAND "#CMAKE_COMMAND#" "-Dpc_#TARGET#_DIR=${prefix}/_auto_pc" -S "${proj}" -B "${proj}/build")
file(COPY "${proj}/build/#TARGET#.pc" DESTINATION "${prefix}")
]] #ONLY NEWLINE_STYLE LF)
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/pc.${TARGET}/post-install.cmake")
endfunction()
auto_pc(example)
# Clean up install path
install(CODE [[ file(REMOVE_RECURSE "${CMAKE_INSTALL_PREFIX}/_auto_pc") ]])
This results in the following:
alex#Alex-Desktop:~/test$ cmake -S . -B build
...
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
alex#Alex-Desktop:~/test$ cmake --build build/
...
alex#Alex-Desktop:~/test$ cmake --install build --prefix install
-- Install configuration: ""
-- Installing: /home/alex/test/install/lib/libexample.so
-- Installing: /home/alex/test/install/_auto_pc/pc_example-config.cmake
-- Installing: /home/alex/test/install/_auto_pc/pc_example-config-noconfig.cmake
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build/pc.example/build
alex#Alex-Desktop:~/test$ ls install/
example.pc lib
alex#Alex-Desktop:~/test$ cat install/example.pc
Name: example
Cflags: -I/home/alex/test/install/include -DINSTALL
Libs: -L/home/alex/test/install/lib -lexample
This should make you sad. It makes me sad.
edit: off topic, since here, the pc files are generated manually
pkgconfig template files
motivation:
CMakeLists.txt should be the single source of truth (name, version)
pkgconfig files are about 10 times smaller than cmake files (cmake to pkgconfig is a lossy transformation)
template file: my_package.pc.in
prefix="#CMAKE_INSTALL_PREFIX#"
exec_prefix="${prefix}"
libdir="${prefix}/lib"
includedir="${prefix}/include"
Name: #PROJECT_NAME#
Description: #CMAKE_PROJECT_DESCRIPTION#
Version: #PROJECT_VERSION#
Cflags: -I${includedir}
Libs: -L${libdir} -l#target1#
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(my_library VERSION 1.1.2 LANGUAGES C
DESCRIPTION "example library")
add_library(my_library src/my_library.c)
# generate pc file for pkg-config
set(target1 my_library)
configure_file(my_package.pc.in
lib/pkgconfig/my_package.pc #ONLY)
based on: CMake generate pkg-config .pc
related: exporting targets to cmake files