CMake: Linking to an imported library with dependencies fails - cmake

I have a subdirectory with a CMakeLists.txt which should compile a library using make and export the result as an imported library to the parent directory:
set(static_lib ${CMAKE_CURRENT_BINARY_DIR}/lib/mylib.a)
add_custom_command(
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT ${static_lib}
COMMAND make
COMMAND make install PREFIX=${CMAKE_CURRENT_BINARY_DIR}
)
add_custom_target(compile_mylib DEPENDS ${static_lib})
add_library(mylib STATIC IMPORTED)
set_property(TARGET mylib PROPERTY IMPORTED_LOCATION ${static_lib})
add_dependencies(mylib compile_mylib)
The CMakeLists.txt in the parent directory looks like this:
add_subdirectory(deps/mylib)
add_executable(mybin source.c)
target_link_libraries(mybin mylib)
On OSX this works just fine - but if I compile the same on Ubuntu it seems to ignore the subdirectory's CMakeLists and complains:
/usr/bin/ld.bfd.real: cannot find -lmylib
I'm using Clang for compilation.

The solution is to add GLOBAL to your add_library call so that it is visible to the parent CMakeLists.txt

you can try this:
find_library(MYLIB mylib.a ${CMAKE_CURRENT_BINARY_DIR}/lib)
then set your link libraries:
target_link_libraries(mybin ${MYLIB})
should be work.

Related

Get cmake to find a custom config file

A cmake project I want to build depends on SDL2, and it comes with the following instruction:
Create a helper called /usr/local/lib/cmake/SDL2/sdl2-config.cmake (because SDL2 doesn't have an SDL2::SDL2 alias) containing:
if(NOT TARGET SDL2::SDL2
AND EXISTS "/usr/lib/x86_64-linux-gnu/cmake/SDL2/sdl2-config.cmake")
include(/usr/lib/x86_64-linux-gnu/cmake/SDL2/sdl2-config.cmake)
add_library(SDL2 SHARED IMPORTED)
set_target_properties(SDL2
PROPERTIES
IMPORTED_LOCATION "${libdir}/libSDL2.so"
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIRS}"
)
add_library(SDL2::SDL2 ALIAS SDL2)
endif()
The problem is that this file isn't found by my cmake (I put a message(...) at the beginning to verify this). If I change the dependency in my CMakeLists.txt from SDL2::SDL2 to just SDL2, it works fine and reads from /usr/lib/x86_64-linux-gnu/cmake/SDL2/sdl2-config.cmake. But this shouldn't be necessary.
How can I get cmake to find this custom file, or how do I find out where to put it?

Cannot specify compile options for imported target "..."

I want to provide the users of my library with two targets: one that specifies the include path etc., and one that carries useful extra compile options. However, for the extra target some of my users are getting the error
Cannot specify compile options for imported target "myproject::extra"
so it seems on older CMake versions.
I tested with CMake 3.9.2. The test project, including CI is on GitHub, with failing build here.
(How) can my approach be rendered robust for all CMake versions?
The project's main CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(myproject)
add_library(myproject INTERFACE)
set(MYPROJECT_VERSION "1.0.0")
target_include_directories(myproject INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION include)
install(TARGETS myproject EXPORT myproject-targets)
install(EXPORT myproject-targets FILE myprojectTargets.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" VERSION ${MYPROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/myprojectConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
The project's myprojectConfig.cmake:
include(CMakeFindDependencyMacro)
if(NOT TARGET myproject)
include("${CMAKE_CURRENT_LIST_DIR}/myprojectTargets.cmake")
endif()
if(NOT TARGET myproject::extra)
add_library(myproject::extra INTERFACE IMPORTED)
if(MSVC)
target_compile_options(myproject::extra INTERFACE /W4)
else()
target_compile_options(myproject::extra INTERFACE -Wall)
endif()
endif()
The user's project CMakeLists.txt could then look as follows:
cmake_minimum_required(VERSION 3.0)
project(myexec)
find_package(myproject REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PRIVATE myproject myproject::extra)
List of functions applicable for IMPORTED and INTERFACE targets changes as CMake evolves.
Most of such functions affects only on specific target properties. So, instead of calling a function, you may set the property directly. This will work in any CMake version:
# Works only in new CMake versions
target_compile_options(myproject::extra INTERFACE /W4)
# Equivalent which works in any CMake version
set_property(TARGET myproject::extra PROPERTY INTERFACE_COMPILE_OPTIONS /W4)

Adding compiler "#define" flags in sub_directory CMakeLists.txt

My directory structure of project is build with multi CMakeLists.txts.
root
CMakeLists.txt
Src
main.c
CMSIS_lib
calculate.c
calculate.h
CMakeLists.txt
cmake
toolchain.cmake
build
In my CMSIS_lib I build separately my dependency source files calculate.c
and calculate.h with CMSIS_lib/CMakeList.txt:
set(util_source_files
calculate.c
calculate.h
)
add_library(util ${util_source_files})
target_include_directories(util calculate.h)
In my root CMakeLists.txt:
cmake_minimum_required(VERSION 3.4)
project(main_projct)
set(TOOLCHAIN_PREFIX /opt/gcc-arm-none-eabi)
set(CMAKE_TOOLCHAIN_FILE cmake/toolchain.cmake)
add_subdirectory(CMSIS_lib)
add_executable(main_projct main.c)
target_link_libraries(main_projct util)
Problem is that I must tell my compiler to add a #define GUCCI in my calculate.h (In MakeFile I know there is flag to tell a header define with -DGUCCI). I would like to add this flag to my compiler in my CMSIS_lib/CMakeList.txt, because when the first CMSIS_lib/CMakeList.txt is done building, he will skip everything under #ifndef GUCCI in my calculate.h, and when added in root CMakeLists.txt with target_link_libraries() I will not have all defines configuration correctly.
I am using cross-compiler and in my toolchain.cmake I use to define compiler flags with command SET_TARGET_PROPERTIES(${TARGET} PROPERTIES COMPILE_DEFINITIONS GUCCI}"), but this is to late because this only is seen by my root CMakeLists.txt and not by my sub director CMakeLists.txt.
Your CMSIS_lib/CMakeLists.txt should look like this:
set(util_source_files
calculate.c
calculate.h
)
add_library(util ${util_source_files})
target_include_directories(util ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(util PUBLIC GUCCI)
Note target_compile_definitions line, with PUBLIC parameter: it instructs cmake to use -DGUCCI compiler option while compiling util, and all targets that are linked against util.
Also note change in target_include_directories. You placed header file as parameter, but you should place directory instead.

custom target as a target library in cmake

I have a custom target that is in fact an externally generated library that I want to integrate in my build.
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/liblib2.a
COMMAND make -f ${CMAKE_CURRENT_SOURCE_DIR}/makefile liblib2.a)
add_custom_target(lib2
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/liblib2.a)
How can I tell cmake that this target is in fact a library, where it can be found and where are the headers ?
To be clear : I don't want the upper CMakeList using this library having to manually specify include folders and the library location folder It must be done automatically (from the target properties).
On a standard cmake library I would just have to add the INTERFACE_INCLUDE_DIRECTORIES property in the library CMakeLists to make cmake link my app with the relevant -I and -L gcc parameters :
set_target_properties(lib1
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR})
But in the case of a custom target I don't know how to to it.
Any clue ?
Thanks for your help.
Thanks to zaufi it works!
For others who may be interested in embedded externally build target inside cmake here is what I did :
cmake_minimum_required(VERSION 2.8)
SET(LIB_FILE ${CMAKE_CURRENT_SOURCE_DIR}/bin/liblib2.a)
SET(LIB_HEADER_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/include)
# how to build the result of the library
add_custom_command(OUTPUT ${LIB_FILE}
COMMAND make
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
# create a target out of the library compilation result
add_custom_target(lib2_target DEPENDS ${LIB_FILE})
# create an library target out of the library compilation result
add_library(lib2 STATIC IMPORTED GLOBAL)
add_dependencies(lib2 lib2_target)
# specify where the library is and where to find the headers
set_target_properties(lib2
PROPERTIES
IMPORTED_LOCATION ${LIB_FILE}
INTERFACE_INCLUDE_DIRECTORIES ${LIB_HEADER_FOLDER})
Now in a CMakeLists.txt I can do somthing like
add_subdirectory(${ROOT_DIR}/lib1 bin/lib1)
add_subdirectory(${ROOT_DIR}/lib2 bin/lib2)
add_executable(app app.c )
target_link_libraries(app lib1 lib2)
No need to specify where the .a and the .h are.
You can use add_library() and tell that it actually imported. Then, using set_target_properties() you can set required INTERFACE_XXX properties for it. After that, you can use it as an ordinal target like every other built by your project.
Thank you for posting the solution. I have wrapped your snippet in a function:
function(add_external_library)
set(options)
set(oneValueArgs TARGET WORKING_DIRECTORY OUTPUT COMMENT)
set(multiValueArgs COMMAND INCLUDE_DIRS)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" ${multiValueArgs}" ${ARGN})
# Specify how to build the result of the library
add_custom_command(OUTPUT "${ARGS_OUTPUT}"
COMMAND ${ARGS_COMMAND}
WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}"
COMMENT "${ARGS_COMMENT}")
# Create a target out of the library compilation result
add_custom_target(${ARGS_TARGET}_target DEPENDS ${ARGS_OUTPUT})
# Create an library target out of the library compilation result
add_library(${ARGS_TARGET} STATIC IMPORTED GLOBAL)
add_dependencies(${ARGS_TARGET} ${ARGS_TARGET}_target)
# Specify where the library is and where to find the headers
set_target_properties(${ARGS_TARGET}
PROPERTIES
IMPORTED_LOCATION "${ARGS_OUTPUT}"
INTERFACE_INCLUDE_DIRECTORIES "${ARGS_INCLUDE_DIRS}")
endfunction()
# Example
add_external_library(TARGET YourLib
COMMAND /bin/bash compile_your_lib.sh
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT "output/yourlib.a"
INCLUDE_DIRS "include/a" "include/b"
COMMENT "Building YourLib")
add_executable(YourExe)
target_link_libraries(YourExe YourLib)

Finding directories in CMake

I'm trying to build a static library and use it for a compiling a
fortran file. If I do:
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
add_library(mylib STATIC ${lib_src}/mylib.for)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
add_executable(bin/out ${PROJECT_SOURCE_DIR}/src/program.f)
target_link_libraries(bin/out mylib)
then it all works (note the library is built into the binary directory
root, but the fortran is compiled into a subdirectory); but, if I do
set(archives ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
add_library(mylib STATIC ${lib_src}/mylib.for)
find_library(mylib NAMES mylib PATHS ${archives})
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
add_executable(bin/out ${PROJECT_SOURCE_DIR}/src/program.f)
target_link_libraries(bin/out mylib)
I get an error when I run cmake:
CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
Please set them or make sure they are set and tested correctly in the CMake files:
mylib linked by target "bin/out" in directory /home/chris/project
If I leave out the final 2 lines, then the archive file does get
written to the lib subdirectory, as libmylib.a as expected. If I do
set(archives ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
add_library(mylib STATIC ${lib_src}/mylib.for)
find_library(mylib NAMES mylib PATHS ${archives})
include_directories(${archives})
set(libs ${libs} ${mylib})
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
add_executable(bin/out ${PROJECT_SOURCE_DIR}/src/program.f)
target_link_libraries(bin/out {LIBS})
then the cmake command runs fine, but running make then generates
compile errors (I know the set command and target_link_libraries
variables are different cases - one of the things I don't understand
is why this only fails when make is run, instead of cmake; if the
variables are the same case, then I get the same error as above).
So, how can I get CMake to recognise my ${PROJECT_BINARY_DIR}/lib
folder that is created during the CMake run? Can someone point out my
(probably obvious) mistake?!
You should not use find_library on one of your target, remove the line:
find_library(mylib NAMES mylib PATHS ${archives})
CMake already knowns about the mylib library as it's one of its target and calling find_library shadows the mylib variable.
You can keep you target_link_libraries call the same, as arguments can be either path to libraries or targets.r