Background
I am trying to build a custom software inside Yocto build. Software is built by CMake.
The following is my recipe - customsoftware.bb:
SRCBRANCH = "master"
SRCREV = "master"
MY_SRC = "OMITTED"
SRC_URI = "${MY_SRC};branch=${SRCBRANCH}"
# libraries dependencies
DEPENDS += "boost"
S = "${WORKDIR}/git"
B = "${WORKDIR}/build"
PARALLEL_MAKE ?= "-j 1"
inherit cmake
# My CMake Options
EXTRA_OECMAKE+=" -DSOME_OPTION=ON"
# I want unix makefiles instead on ninja build
OECMAKE_GENERATOR="Unix Makefiles"
The following is a watered down version of my cmake project - CMakeLists.txt
Please note: I have omitted irrelevant parts for brevity
cmake_minimum_required(VERSION 3.0.0)
#---------------------------------------------------------------------------------------
# set default build to release
#---------------------------------------------------------------------------------------
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif()
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/dist/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/dist/bin)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(FATAL_ERROR "
${BoldRed}Error:${ColourReset} In-source builds are not allowed. You should create separate directory for build files.
${Magenta}CMAKE_BINARY_DIR${ColourReset}(${CMAKE_SOURCE_DIR}) must be different from ${Magenta}CMAKE_SOURCE_DIR${ColourReset}(${CMAKE_BINARY_DIR})
")
endif ()
project(myapp)
find_package(Boost REQUIRED COMPONENTS thread)
add_executable(${PROJECT_NAME}
${HEADERS}
${SOURCES}
)
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
#Copy entire contents of dist/ to /opt/myapp
install(DIRECTORY ${CMAKE_BINARY_DIR}/dist/
DESTINATION /opt/myapp
)
I have appended my recipe to image.
Issue
When I ran du -h tmp/work/.../<customsoftware>/build/dist/bin binary size is 80MB. Also, after deploying to target system binary size is 80MB.
UPDATE
As suggested in comments, binaries at tmp/work/.../<customsoftware>/image are not stripped. However, binaries at tmp/work/.../<customsoftware>/packages-split are stripped.
If I ran make in source of app - not through recipe and outside yocto - binary size is 1.7MB
Question
If I remember correctly, OE build will strip debug symbols from resulting binaries.
Why does my binary still get deployed with debug symbols? What have I missed?
How can I make sure only stripped - Release type - binary is deployed?
Explicitly specifying package types and inheriting pkgconfig solved my issue. Updated recipe:
SRCBRANCH = "master"
SRCREV = "master"
MY_SRC = "OMITTED"
SRC_URI = "${MY_SRC};branch=${SRCBRANCH}"
DEPENDS += "boost"
S = "${WORKDIR}/git"
PARALLEL_MAKE ?= "-j 1"
inherit pkgconfig cmake
EXTRA_OECMAKE+=" -DSOME_OPTION"
OECMAKE_GENERATOR="Unix Makefiles"
# Specify package types
PACKAGES = "${PN}-dbg ${PN}"
FILES_${PN}-dbg += "\
<INSTALL_PATH>/.debug \
"
FILES_${PN} += "\
<INSTALL_PATH>/* \
"
Can you please try to see if this simple Hello World is stripped or not in your environment?
recipe:
DESCRIPTION = "Simple helloworld cmake"
LICENSE = "MIT"
SECTION = "examples"
LIC_FILES_CHKSUM =
"file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://CMakeLists.txt \
file://helloworld.c"
S = "${WORKDIR}"
inherit cmake
EXTRA_OECMAKE = ""
CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.10)
project(helloworld)
add_executable(helloworld helloworld.c)
install(TARGETS helloworld RUNTIME DESTINATION bin)
helloworld.c:
#include <stdio.h>
int main() {
printf("Hello World Makefile from CMake!\n");
return(0);
}
if you don't mind about debug version
you can do
OECMAKE_C_FLAGS_RELEASE += "-s"
OECMAKE_CXX_FLAGS_RELEASE += "-s"
It will strip your binaries
Related
I've been struggling to fix an intermediate linking error that I get when using CMake to build my nvcc project. I've been upgrading a previous project to utilize CUDA and was able to successfully call functions from that library from host code. When I try to call functions of that library from device code, I get the intermediate linking error. I annotated all of the functions with __device__ and __host__ descriptors.
As a side note, this is a ROS project, so I'm using some of the catkin CMake functions.
This is a snippet from the ParticleFilter code that calls the host and device functions:
#include <cuda.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <curand_kernel.h>
#include <iostream>
#include <davinci_kinematics_cuda/davinci_fwd_kinematics.cuh>
__host__
ParticleFilter::ParticleFilter(const unsigned int numParticles, const std::vector<double> &initialJointState, const unsigned int threads,
const unsigned int blocks) {
/* random other work here */
// This works fine (compiles and runs), it is calling host code from the other file
kinematics = davinci_kinematics_cuda::Forward();
std::cout << kinematics.fwd_kin_solve(initialJointState.data()).translation() << std::endl;
}
__global__
void printParticlesKernel(double *particles, const unsigned int numParticles, const unsigned int dimensions, const size_t pitch) {
int locationStart = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
// This fails, will not link
davinci_kinematics_cuda::Forward kinematics = davinci_kinematics_cuda::Forward();
for (int n = locationStart; n < numParticles; n += stride) {
double *particle = (double*) ((char*) particles + n * pitch);
/* random other work here */
// this fails, will not link
auto translation = kinematics.fwd_kin_solve(particle).translation();
printf("%f %f %f\n", translation[0], translation[1], translation[2]);
}
}
And this is from the kinematics file:
#include <cuda.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
namespace davinci_kinematics_cuda {
// use member fncs to compute and multiply successive transforms
__host__ __device__
Forward::Forward() {
/* random initialization here */
}
__host__ __device__
Eigen::Affine3d Forward::fwd_kin_solve(const double *q_vec, const unsigned int desired_joint) {
/* other work here */
}
}
This is the relevant CMake parts for the ParticleFilter file.
cmake_minimum_required(VERSION 2.8.10)
project(tool_tracking LANGUAGES CUDA CXX)
# https://stackoverflow.com/questions/25748039/add-cuda-to-ros-package
find_package(CUDA REQUIRED)
# set CUDA_NVCC_FLAGS as you would do with CXX/C FLAGS
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CURAND_FLAGS} -fPIC")
set(CUDA_SEPARABLE_COMPILATION ON)
find_package(catkin REQUIRED COMPONENTS
message_generation
roscpp
std_msgs
sensor_msgs
geometry_msgs
cwru_opencv_common
tool_model
cwru_davinci_control
cwru_davinci_kinematics
xform_utils
tf
tool_segmentation
)
catkin_package(
INCLUDE_DIRS
include
LIBRARIES
tool_tracking_particle
CATKIN_DEPENDS
message_runtime
std_msgs
sensor_msgs
geometry_msgs
cwru_opencv_common
tool_model
cwru_davinci_control
cwru_davinci_kinematics
xform_utils
tf
)
include_directories(SYSTEM ${OpenCV_INCLUDE_DIRS})
include_directories(include ${catkin_INCLUDE_DIRS} tool_model_lib )
cuda_add_executable(test_particlefilter src/ParticleFilter.cu src/Particle.cu)
target_link_libraries(test_particlefilter tool_tracking_particle ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CUDA_LIBRARIES})
This is the error from CMake:
/usr/bin/cmake -H/home/ethan/catkin_ws/src/cwru_davinci_tool_tracking/tool_tracking -B/home/ethan/catkin_ws/build/tool_tracking --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/ethan/catkin_ws/build/tool_tracking/CMakeFiles /home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/progress.marks
/usr/bin/make -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/ethan/catkin_ws/build/tool_tracking'
/usr/bin/make -f CMakeFiles/test_particlefilter.dir/build.make CMakeFiles/test_particlefilter.dir/depend
make[2]: Entering directory '/home/ethan/catkin_ws/build/tool_tracking'
[ 20%] Building NVCC intermediate link file CMakeFiles/test_particlefilter.dir/test_particlefilter_intermediate_link.o
/usr/local/cuda-11.0/bin/nvcc -lcudadevrt -m64 -ccbin /usr/bin/cc -dlink /home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/test_particlefilter.dir/src/./test_particlefilter_generated_ParticleFilter.cu.o /home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/test_particlefilter.dir/src/./test_particlefilter_generated_Particle.cu.o -o /home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/test_particlefilter.dir/./test_particlefilter_intermediate_link.o -Xcompiler -fPIC
nvlink error : Undefined reference to '_ZN23davinci_kinematics_cuda7ForwardC1Ev' in '/home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/test_particlefilter.dir/src/./test_particlefilter_generated_ParticleFilter.cu.o'
nvlink error : Undefined reference to '_ZN23davinci_kinematics_cuda7Forward13fwd_kin_solveEPKdj' in '/home/ethan/catkin_ws/build/tool_tracking/CMakeFiles/test_particlefilter.dir/src/./test_particlefilter_generated_ParticleFilter.cu.o'
CMakeFiles/test_particlefilter.dir/build.make:1468: recipe for target 'CMakeFiles/test_particlefilter.dir/test_particlefilter_intermediate_link.o' failed
make[2]: Leaving directory '/home/ethan/catkin_ws/build/tool_tracking'
make[2]: *** [CMakeFiles/test_particlefilter.dir/test_particlefilter_intermediate_link.o] Error 255
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/test_particlefilter.dir/all' failed
make[1]: Leaving directory '/home/ethan/catkin_ws/build/tool_tracking'
make[1]: *** [CMakeFiles/test_particlefilter.dir/all] Error 2
Makefile:140: recipe for target 'all' failed
make: *** [all] Error 2
How do I fix the error with the undefined reference? Seems like a linking error, but I am not familiar enough with the compilation / linking process to troubleshoot any further. If I need to post the CMake from the kinematics file I can as well.
Here's the key issue and the part that will be most helpful to other readers of this question. Catkin configures CMake to build shared libraries by default but CUDA separable compilation and nvlink only work with static libraries. You need to set your CUDA libraries (in your case, those in cwru_davinci_kinematics) to be static, always. You can do that by adding the STATIC keyword to the add_library call, as in:
add_library(my_cuda_lib STATIC source1.cu ...)
If you "link" to a shared library with CUDA in CMake, it will just ignore it. This is actually the documented behavior of nvcc. See here: https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/#libraries
The device linker has the ability to read the static host library formats (.a on Linux and Mac OS X, .lib on Windows). It ignores any dynamic (.so or .dll) libraries.
Another major lesson here is that setting CMake to an ancient version is bound to cause problems. While reproducing your issue, I was forced to build OpenCV 3 from source (it's not in Ubuntu 20.04 LTS) and there is no good way to override the search path for a particular package prior to version 3.12, which introduced CMP0074.
Upgrade your minimum CMake version. Ideally you would upgrade to the newest version available to you in your software repositories and set your files' minimums to that. There is zero benefit to being compatible with CMake versions earlier than ~3.5, and I would argue that extends up to 3.16 (the version in Ubuntu 20.04 LTS). Since you're using CUDA, 3.18 is most appropriate. Even worse, many of your projects set a minimum below 2.8.12; compatibility with this version will very soon be removed by CMake.
Here are the exact changes I made to get it to build on Ubuntu 20.04 LTS. I used the following build script, placed in and executed from the ROS workspace:
#!/usr/bin/bash
source /opt/ros/noetic/setup.bash
export CUDACXX=/usr/local/cuda/bin/nvcc
export OpenCV_ROOT=$(readlink -f opencv-install)
[ -f "$CUDACXX" ] || { echo "Invalid CUDACXX: $CUDACXX"; exit; }
[ -d "$OpenCV_ROOT" ] || { echo "Invalid OpenCV_ROOT: $OpenCV_ROOT"; exit; }
rm -rf build devel
catkin build tool_tracking --cmake-args \
-Wno-dev \
-DCMAKE_POLICY_DEFAULT_CMP0074=NEW \
-DCMAKE_CUDA_ARCHITECTURES=75
The directory opencv-install was created by building my own OpenCV 3 (because Ubuntu 20.04 only has v4). The steps for that were:
$ git clone -b 3.4.14 git#github.com:opencv/opencv.git
$ git clone -b 3.4.14 git#github.com:opencv/opencv_contrib.git
$ cmake -G Ninja -S opencv -B opencv-build/ -DOPENCV_EXTRA_MODULES_PATH=$(readlink -f opencv_contrib)/modules -DBUILD_opencv_cnn_3dobj=OFF -DBUILD_opencv_face=OFF -DBUILD_opencv_hdf=OFF -DBUILD_opencv_hfs=OFF -DBUILD_opencv_julia=OFF -DBUILD_opencv_matlab=OFF -DBUILD_opencv_ovis=OFF -DBUILD_opencv_reg=OFF -DBUILD_opencv_sfm=OFF -DBUILD_opencv_text=OFF -DBUILD_opencv_wechat_qrcode=OFF -DBUILD_opencv_ximgproc=OFF
$ cmake --build opencv-build
$ cmake --install opencv-build --prefix opencv-install
This disables the extra modules that have significant/irrelevant dependencies.
The script sets the environment variable OpenCV_ROOT to direct CMake to this locally installed version of OpenCV. Because the minimum version of CMake specified in the file is so low, I must also set CMAKE_POLICY_DEFAULT_CMP0074=NEW so that OpenCV_ROOT will be honored.
Here are the changes I made to your CMake code:
src/cwru_davinci_kinematics/CMakeLists.txt
--- a/src/cwru_davinci_kinematics/CMakeLists.txt
+++ b/src/cwru_davinci_kinematics/CMakeLists.txt
## -1,4 +1,4 ##
-cmake_minimum_required(VERSION 2.8.10)
+cmake_minimum_required(VERSION 3.18)
project(cwru_davinci_kinematics)
#This is needed as part of the migration to ros jade and later
## -26,18 +26,16 ## find_package(catkin REQUIRED COMPONENTS roscpp roslib roslint tf tf2 tf2_eigen)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=gnu++0x")
-# https://stackoverflow.com/questions/25748039/add-cuda-to-ros-package
-find_package(CUDA)
-message(STATUS "CUDA_FOUND=${CUDA_FOUND}")
-if(CUDA_FOUND)
- message(STATUS "Found CUDA, setting nvcc compilation flags")
-
- # set CUDA_NVCC_FLAGS as you would do with CXX/C FLAGS
- set(CUDA_NVCC_FLAGS CACHE STRING "nvcc flags" FORCE)
- set(CUDA_VERBOSE_BUILD ON CACHE BOOL "nvcc verbose" FORCE)
+include(CheckLanguage)
+check_language(CUDA)
+if (CMAKE_CUDA_COMPILER)
+ enable_language(CUDA)
+
# fPIC fixes some linker issues with nvcc code / objects
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CURAND_FLAGS} -fPIC")
- set(CUDA_SEPARABLE_COMPILATION ON)
+ set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -fPIC")
+ set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
+
+ find_package(CUDAToolkit REQUIRED)
endif()
include_directories(
## -48,7 +46,7 ## include_directories(
${YAML_CPP_INCLUDE_DIRS}
)
-if (CUDA_FOUND)
+if (CMAKE_CUDA_COMPILER)
catkin_package(
DEPENDS ${Eigen3_DEP}
LIBRARIES
## -82,14 +80,17 ## target_link_libraries(davinci_kinematics
davinci_kinematic_definitions
)
-if (CUDA_FOUND)
- cuda_add_library(davinci_kinematics_cuda src/davinci_fwd_kinematics.cu)
- cuda_add_library(davinci_kinematics_definitions_cuda src/davinci_kinematic_definitions.cu)
-
- target_link_libraries(davinci_kinematics_cuda
- ${catkin_LIBRARIES}
- davinci_kinematics_definitions_cuda
- )
+if (CMAKE_CUDA_COMPILER)
+ add_library(davinci_kinematics_cuda STATIC src/davinci_fwd_kinematics.cu)
+ add_library(davinci_kinematics_definitions_cuda STATIC src/davinci_kinematic_definitions.cu)
+
+ target_link_libraries(
+ davinci_kinematics_cuda
+ PRIVATE
+ CUDA::curand
+ ${catkin_LIBRARIES}
+ davinci_kinematics_definitions_cuda
+ )
endif()
# Examples
The important lines here are:
add_library(davinci_kinematics_cuda STATIC src/davinci_fwd_kinematics.cu)
add_library(davinci_kinematics_definitions_cuda STATIC src/davinci_kinematic_definitions.cu)
I also modernized the CMake code here, because the built-in CUDA language support has considerably advanced.
src/cwru_davinci_tool_tracking/tool_tracking/CMakeLists.txt
--- a/src/cwru_davinci_tool_tracking/tool_tracking/CMakeLists.txt
+++ b/src/cwru_davinci_tool_tracking/tool_tracking/CMakeLists.txt
## -1,18 +1,11 ##
-cmake_minimum_required(VERSION 2.8.10)
-project(tool_tracking LANGUAGES CUDA CXX)
+cmake_minimum_required(VERSION 3.18)
+project(tool_tracking LANGUAGES C CXX CUDA)
-# https://stackoverflow.com/questions/25748039/add-cuda-to-ros-package
-find_package(CUDA REQUIRED)
+set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -fPIC")
+set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
-# set CUDA_NVCC_FLAGS as you would do with CXX/C FLAGS
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CURAND_FLAGS} -fPIC")
-set(CUDA_SEPARABLE_COMPILATION ON)
+find_package(OpenCV 3 REQUIRED)
-#find_package(catkin_simple REQUIRED)
-## Find catkin macros and libraries
-## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
-## is used, also find other catkin packages
-find_package(OpenCV REQUIRED)
find_package(catkin REQUIRED COMPONENTS
message_generation
roscpp
## -28,11 +21,12 ## find_package(catkin REQUIRED COMPONENTS
tool_segmentation
)
+find_package(CUDAToolkit REQUIRED)
catkin_package(
INCLUDE_DIRS
include
- LIBRARIES
+ LIBRARIES
tool_tracking_particle
CATKIN_DEPENDS
message_runtime
## -47,13 +41,7 ## catkin_package(
tf
)
-include_directories(SYSTEM ${OpenCV_INCLUDE_DIRS})
-include_directories(include ${catkin_INCLUDE_DIRS} tool_model_lib )
-
-#cuda_add_library(tool_tracking_particle src/ParticleFilter.cu src/Particle.cu)
-#add_executable(particle src/tracking_particle.cpp)
-#target_link_libraries(particle tool_tracking_particle ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} davinci_kinematics_cuda
-# davinci_kinematics_definitions_cuda)
-
-cuda_add_executable(test_particlefilter src/ParticleFilter.cu src/Particle.cu)
-target_link_libraries(test_particlefilter tool_tracking_particle ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CUDA_LIBRARIES})
+add_executable(test_particlefilter src/ParticleFilter.cu src/Particle.cu)
+target_include_directories(test_particlefilter SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS})
+target_include_directories(test_particlefilter PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_link_libraries(test_particlefilter PRIVATE ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} CUDA::curand)
I also modernized the CMake code here, because the built-in CUDA language support has considerably advanced.
Miscellaneous changes
I bumped the minimum CMake version from 2.8.x to 3.0.2 in all other places to suppress warnings. I also added a version number 3 to all find_package(OpenCV ...) calls that didn't have it.
Boost no longer has a python3 package; it's just python now. I made the following change to src/vision_opencv/cv_bridge/CMakeLists.txt:
--- a/src/vision_opencv/cv_bridge/CMakeLists.txt
+++ b/src/vision_opencv/cv_bridge/CMakeLists.txt
## -1,18 +1,15 ##
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.0.2)
project(cv_bridge)
find_package(catkin REQUIRED COMPONENTS rosconsole sensor_msgs)
if(NOT ANDROID)
find_package(PythonLibs)
- if(PYTHONLIBS_VERSION_STRING VERSION_LESS 3)
- find_package(Boost REQUIRED python)
- else()
- find_package(Boost REQUIRED python3)
- endif()
+ find_package(Boost REQUIRED python)
else()
-find_package(Boost REQUIRED)
+ find_package(Boost REQUIRED)
endif()
+
find_package(OpenCV 3 REQUIRED
COMPONENTS
opencv_core
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.
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.
We can use a cmake config file to import targets.
For example given machinary including foobarConfig.cmake.in
set(FOOBAR_VERSION #VERSION#)
#PACKAGE_INIT#
set_and_check(FOOBAR_INCLUDE_DIR "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
set_and_check(FOOBAR_STATIC_LIBRARY #PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.a")
include("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
message(STATUS "foobar version: ${FOOBAR_VERSION}")
message(STATUS "foobar include location: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location: ${FOOBAR_LIBRARY_DIR}")
for an exported target foobar
We can do:
find_package(foobar)
add_executable(usesfoo
usesfoo.cpp)
target_link_libraries(usesfoo
${FOOBAR_LIBRARY})
target_include_directories(usesfoo PUBLIC
${FOOBAR_INCLUDE_DIR})
and it normally just works.
However, I have a strage case where variables set in the Config.cmake are not available after find_package.
For example given:
find_package(foobar REQUIRED)
if (foobar_FOUND)
message(STATUS "found foobar")
endif()
message(STATUS "foobar include location2: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location2: ${FOOBAR_LIBRARY_DIR}")
The output is:
foobar include location: /test-import/opt/foobar/include
foobar library location: /test-import/opt/foobar/lib
found foobar
foobar include location2:
foobar library location2:
What could be going on here?
How can I:
Find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
I got very confused trying to debug this and started to question how Config packages are supposed to work.
Should I be using properties of imported targets instead of variables?
What scope does find_package run in? I thought it was like an include() rather than an add_subdirectory() - which introduces its own scope.
How can these variables become unset?
What is find_package doing under the hood?
See also correctly set the location of imported cmake targets for an installed package.
That question contains code to reproduce that problem which is similar to the code for this problem.
Complete set of files to reproduce the problem:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)
set(VERSION 1.3.3)
project(FoobarLib VERSION "${VERSION}" LANGUAGES CXX)
SET(CMAKE_INSTALL_PREFIX "/opt/foo")
set(INSTALL_LIB_DIR lib)
add_library(foobar SHARED
foobar.cpp
)
# Create the distribution package(s)
set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_NAME "foobar")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
set(LIBRARY_INSTALL_DIR lib)
set(INCLUDE_INSTALL_DIR include)
INSTALL(TARGETS foobar
EXPORT FoobarLibTargets
LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${LIBRARY_INSTALL_DIR}
INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR})
include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/FoobarLib)
set(INCLUDE_INSTALL_DIR include CACHE PATH "install path for include files")
set(LIBRARY_INSTALL_DIR lib CACHE PATH "install path for libraries")
configure_package_config_file(FoobarLibConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
INSTALL_DESTINATION "${ConfigFileInstallDir}"
PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
VERSION "${VERSION}"
COMPATIBILITY SameMajorVersion)
EXPORT(EXPORT FoobarLibTargets
FILE FoobarLibTargets.cmake)
INSTALL(FILES
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibTargets.cmake"
DESTINATION "${ConfigFileInstallDir}")
include(CPack)
FoobarLibConfig.cmake.in:
set(FoobarLib_VERSION #VERSION#)
#PACKAGE_INIT#
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
check_required_components(FoobarLib)
run.sh:
#!/bin/sh
SRC=`pwd`
mkdir -p ./target/debug && \
cd ./target/debug &&
cmake -DCMAKE_BUILD_TYPE=Debug ../../ &&
make &&
cpack -G TGZ
cd ../..
rm -rf foo
mkdir foo
TGZ=`pwd`/target/debug/foobar-1.3.3.tar.gz
cd foo
tar -xvzf $TGZ
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
export CMAKE_PREFIX_PATH=`pwd`/opt/foo/lib/cmake:`pwd`/opt/foo/lib/cmake/
cmake . && make VERBOSE=1
echo pwd=`pwd`
# critical - check the location of the target is relative to the installation
grep $WSDIR/opt/foo/lib/libfoobar.so foobar-loc
if [ $? -ne 0 ]; then
echo "FAIL: location of imported target 'foobar' is incorect" >&2
cat foobar-loc >&2
exit 1
fi
Here is the generated Config.cmake as requested by #havogt I don't think it helps as it is the standard generated code:
# CMake configuration file for the FoobarLib package
# Use with the find_package command in config-mode to find information about
# the FoobarLib package.
#
set(FoobarLib_VERSION 1.3.3)
####### Expanded from #PACKAGE_INIT# by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was FoobarLibConfig.cmake.in ########
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
####################################################################################
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "${PACKAGE_PREFIX_DIR}/lib")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so")
check_required_components(FoobarLib)
'package'_FOUND is set by the implementation of find_package() not by the Config.cmake that it loads. Adding check_required_components() is good practice for other reasons (picking up that someone thinks the package is componentised when it isn't) but is not relevant to this issue.
Oops. This is embarrassing. I'd moved the generation code into a shell script and forgot to escape the variables!
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
The question is still useful for providing source for the related question though.
To answer my own questions:
How can I find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
https://en.wikipedia.org/wiki/Rubber_duck_debugging
Reduce the problem to a minimum reproducible example (preferably before posting on stack overflow)
Avoid (or at least take extra care) generating code from shell scripts
Reduce stress and get more sleep
check_required_components(Foobar) should be called at the end in the case. The docs.
check_required_components() should be called at the end
of the FooConfig.cmake file. This macro checks whether all requested,
non-optional components have been found, and if this is not the case,
sets the Foo_FOUND variable to FALSE, so that the package is
considered to be not found. It does that by testing the
Foo__FOUND variables for all requested required components.
This macro should be called even if the package doesn’t provide any
components to make sure users are not specifying components
erroneously. When using the NO_CHECK_REQUIRED_COMPONENTS_MACRO option,
this macro is not generated into the FooConfig.cmake file.
I want to extract a static library from a ZIP-file and link against it.
Having the following setting:
add_library(COMSDK_LIB STATIC IMPORTED GLOBAL)
set_property(TARGET COMSDK_LIB PROPERTY IMPORTED_LOCATION "/tmp/lib/libRTSClientSdk.a")
And the imported library being used in another CMakeLists.txt:
target_link_libraries(mylib COMSDK_LIB)
Would it be possible that the imported library is generated by another add_custom_command or add_custom_target?
I tried the following, but it did NOT work:
add_custom_command(
OUTPUT "/tmp/lib/libRTSClientSdk.a"
COMMAND unzip -x client_sdk.zip -o /tmp
DEPENDS client_sdk.zip
)
The given error message was:
$ ninja
ninja: error: '/tmp/lib/libRTSClientSdk.a', needed by 'mylib.dll', missing and no known rule to make it
The problem is that Your custom command is being executed during make step, and it's expecting extracted dependency earlier --- during cmake execution. So, basically, You need to get that static library from SDK before You're using it in CMake rules or during linkage itself.
Two of possible solutions are listed in CMake mailing list.
Solution #1: (executed in CMake side)
Re-phrasing one of the code snippets, downloading and unzipping is being done during CMake execution:
set(CLIENTSDK_ZIP "cliendsdk.zip")
set(CLIENTSDK_URL "http://example.com/${CLIENTSDK_ZIP}")
set(CLIENTSDK_LIB "libRTSClientSdk.a")
set(CLIENTSDK_OUTPUT_DIR "/tmp/sdk/dir")
# Basically just downloading zip file:
message(STATUS "Downloading ${CLIENTSDK_URL}")
execute_process(COMMAND wget ${CLIENTSDK_URL}
WORKING_DIRECTORY ${CLIENTSDK_OUTPUT_DIR}
RESULT_VARIABLE errno
ERROR_VARIABLE err)
if (NOT ${errno} EQUAL 0)
message(ERROR "Failed downloading ${CLIENTSDK_URL}. Code: ${err}")
endif()
# Extracting downloaded zip file:
message(STATUS "Extracting ${CLIENTSDK_ZIP})
execute_process(COMMAND unzip ${CLIENTSDK_ZIP}
WORKING_DIRECTORY ${CLIENTSDK_OUTPUT_DIR}
RESULT_VARIABLE errno
ERROR_VARIABLE err)
if (NOT ${errno} EQUAL 0)
message(ERROR "Failed extracting ${CLIENTSDK_ZIP}. Code: ${err}")
endif()
# Importing into CMake scope one library from extracted zip:
add_library(specific_sdk_lib STATIC IMPORTED)
set_target_properties(specific_sdk_lib
PROPERTIES IMPORTED_LOCATION ${CLIENTSDK_OUTPUT_DIR}/lib/${CLIENTSDK_LIB})
# Adding rule for linking to specific static library from extracted zip:
target_link_libraries(mylib specific_sdk_lib)
Solution #2 (executed in make side):
# We must include ExternalProject CMake module first!
include("ExternalProject")
set(CLIENTSDK_URL "http://example.com/clientsdk.zip")
set(CLIENTSDK_LIB "libRTSClientSdk.a")
set(CLIENTSDK_PREFIX "3rd_party")
set(CLIENTSDK_EXTRACTED_DIR
"${CMAKE_CURRENT_BINARY_DIR}/${CLIENTSDK_PREFIX}/src/DownloadClientSDK/")
ExternalProject_Add("DownloadClientSDK"
PREFIX ${CLIENTSDK_PREFIX}
URL "${CLIENTSDK_URL}"
# Suppress ExternalProject configure/build/install targets:
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
add_library(COMSDK_LIB STATIC IMPORTED)
set_target_properties(COMSDK_LIB PROPERTIES IMPORTED_LOCATION
${CLIENTSDK_EXTRACTED_DIR}/${CLIENTSDK_LIB})
# Require all that download/unzip mumbojumbo only for COMSDK_LIB:
add_dependencies(COMSDK_LIB DownloadClientSDK)
target_link_libraries(my_library COMSDK_LIB)
Basically, CMake and it's ExternalProject module takes care (generates proper make targets) of recognizing archive format, unzipping it, and if needed - configuring, building and installing.