custom target as a target library in cmake - 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)

Related

How does cmake set the file name of the dynamic library to be built?

I want to add a LuaJIT wrapper to libgit2 so that it can be used in neovim.
The cmake configuration is as follows:
cmake_minimum_required(VERSION 3.22.2)
project("git2-neovim")
message(STATUS "cmake binary directory: ${CMAKE_BINARY_DIR}")
# Compile commands are output to "compile_commands.json", so that tools such as "ccls" can provide assistance.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Set gcc compile options.
set(CMAKE_C_FLAGS_DEBUG "$ENV{CFLAGS} -Wall -g3 -ggdb")
set(CMAKE_C_FLAGS_RELEASE "$ENV{CFLAGS} -O3 -Wall")
set(
SRC_FILES
src/libgit2.c
)
add_library(${PROJECT_NAME} SHARED ${SRC_FILES})
find_package(PkgConfig REQUIRED)
if (PKG_CONFIG_FOUND)
pkg_check_modules(LIBGIT2 REQUIRED libgit2)
include_directories(${LIBGIT2_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${LIBGIT2_LIBRARIES})
endif(PKG_CONFIG_FOUND)
This generates a "libgit2-neovim.so" file, but I want to generate a "libgit2.so" file.
Since I only use it in neovim, there is no conflict with the real libgit2.
environmental information:
operating system: Archlinux
cmake version: 3.25.1
============================== replenish ============================
When LuaJIT is looking for a shared library, it will only automatically add the file of the shared library according to the operating system, and will not add a prefix. The example is as follows:
require("demo")
The lua code above loads the "demo.so" file but not the "libdemo.so" file.
Therefore, I want to set in cmake, compile in any operating system, the output shared library must have "lib" prefix.
How does cmake set the file name of the dynamic library to be built?
The output filename is controlled by target properties https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#library-output-artifacts , and the default is composed of https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LIBRARY_PREFIX.html followed by library name followed by https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LIBRARY_SUFFIX.html .
I want to generate a "libgit2.so" file
So name your library git2 not git2-neovim.
add_library(git2
or set LIBRARY_OUTPUT_NAME target property of the target.

CMake: Can I wrap an ExternalProject in some object that I can just link to my target?

I'm including this library as an external project. Based on the documentation, with some small tweaks, I have this:
# LIEF dependency
# ===========================
set(LIEF_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/LIEF")
set(LIEF_INSTALL_DIR "${LIEF_PREFIX}")
set(LIEF_INCLUDE_DIRS "${LIEF_PREFIX}/include")
# LIEF static library
set(LIEF_LIBFILE
"${LIEF_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}LIEF${CMAKE_STATIC_LIBRARY_SUFFIX}")
# URL of the LIEF repo (Can be your fork)
set(LIEF_GIT_URL "https://github.com/lief-project/LIEF.git")
# LIEF's version to be used (can be 'master')
set(LIEF_VERSION 0.11.5)
# LIEF compilation config
set(LIEF_CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DLIEF_DOC=off
-DLIEF_PYTHON_API=off
-DLIEF_EXAMPLES=off
-DLIEF_OAT=off
-DLIEF_DEX=off
-DLIEF_VDEX=off
-DLIEF_ART=off
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
)
# Specify MSVCRT on MSVC
if(MSVC)
list(APPEND ${LIEF_CMAKE_ARGS} -DLIEF_USE_CRT_RELEASE=MT)
list(APPEND ${LIEF_CMAKE_ARGS} -DLIEF_USE_CRT_DEBUG=MTd)
endif()
# External project
ExternalProject_Add(LIEF_extproj
PREFIX "${LIEF_PREFIX}"
GIT_REPOSITORY ${LIEF_GIT_URL}
GIT_TAG ${LIEF_VERSION}
INSTALL_DIR ${LIEF_INSTALL_DIR}
CMAKE_ARGS ${LIEF_CMAKE_ARGS}
BUILD_BYPRODUCTS ${LIEF_LIBFILE}
UPDATE_COMMAND ""
)
However, the original docs simply included the directories and linked separately. Can I somehow wrap these into a single target, where if I link to that target I get everything from that library?
EDIT:
My current attempt at setting up an imported target is this:
add_library(LIEF_depimpl STATIC IMPORTED)
set_target_properties(LIEF_depimpl PROPERTIES
IMPORTED_LOCATION ${LIEF_LIBFILE}
INTERFACE_INCLUDE_DIRECTORIES ${LIEF_INCLUDE_DIRS}
)
add_dependencies(LIEF_depimpl LIEF_extproj)
When I use target_link_libraries() to link LIEF against my project, CMake generates successfully, but then I get an error in the generated makefile.
add_executable(testapp lief-test.cpp)
...
# Link the executable with LIEF
target_link_libraries(testapp PUBLIC ${LIEF_depimpl})
The syntax for target_link_libraries() isn't what I thought it was. The dependency variable should not be expanded, like this:
target_link_libraries(testapp PUBLIC LIEF_depimpl)
In addition, CMake will throw an error if it can't find the include directories for an external project, so you should create that folder in your CMake file like so:
set(LIEF_INCLUDE_DIRS "${LIEF_PREFIX}/include")
file(MAKE_DIRECTORY ${LIEF_INCLUDE_DIRS})

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)

CMake target dependency to compile protobuf files

I want to build a c++ static lib out of some protobuf definitions with cmake/make.
I made a custom COMMAND to compile the protobuf to c++, and I set it as a PRE_BUILD dependency to my static lib.
project(mylib)
set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}/proto_definitions")
file(GLOB PROTO_FILES "${PROTO_PATH}/*.proto")
foreach(PROTO_FILE in ${PROTO_FILES})
string(REGEX REPLACE "[.]proto$" ".pb.cc" OUTPUT_SOURCE ${PROTO_FILE})
list(APPEND OUTPUT_SOURCES ${OUTPUT_SOURCE})
endforeach()
add_custom_command(TARGET ${PROJECT_NAME}
PRE_BUILD
COMMAND protoc --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/compiled_proto ${PROTO_FILES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "some comment")
add_library(${PROJECT_NAME} STATIC ${OUTPUT_SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
I get the following error when running cmake:
CMake Error: cannot determine link language for target "mylib"
Regardless of this error, the makefiles are generated, but when I make mylib, it does not trigger any proto compilation
The approach more native to CMake would be to add custom commands with the OUTPUT signature to generate the .cc files, and then use them as sources for the library normally. That way, they CMake will know what they are and how to produce them:
project(mylib)
set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}/proto_definitions")
file(GLOB PROTO_FILES "${PROTO_PATH}/*.proto")
foreach(PROTO_FILE in ${PROTO_FILES})
string(REGEX REPLACE "[.]proto$" ".pb.cc" OUTPUT_SOURCE ${PROTO_FILE})
list(APPEND OUTPUT_SOURCES ${OUTPUT_SOURCE})
endforeach()
add_custom_command(OUTPUT ${OUTPUT_SOURCES}
COMMAND protoc --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/compiled_proto ${PROTO_FILES}
DEPENDS ${PROTO_FILES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "some comment")
add_library(${PROJECT_NAME} STATIC ${OUTPUT_SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
Done this way, there will be one command which reads all the .proto files and produces all the .cc files — which means that if any of the .proto file changes, all the .cc files will be re-generated. I am not familiar with Protobuffers so I cannot know whether that's sane or not. If they are independent, it would be better to introduce one add_custom_command for each output file.
Also, given the arguments you're passing to protocc, you might have to modify the paths in OUTPUT_SOURCES to correctly point to the generated files.
Also note that CMake comes with a FindProtobuf module which defines a protobuf_generate_cpp() command, so you might want to use that instead of hand-coding the Protobuf support.
Comment for the answer above: There is no in in cmake foreach, which spend me some time to solve it.
After studying his answer(very thanks!), I summarized a method that works for me, which has the following characteristics:
Proto dir and Output dir can be different
Generate grpc_output
Create a custom_command for each proto
My project strcuture is:
hwpb/ (a library for super project)
cpp/
hwpb/ (store *.pb.cc, *.pb.h)
CMakeLists.txt
proto/ (store protos)
go/ (and other languages, not mentioned here)
And this is my CMakeListx.txt:
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../proto")
set(OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/hwpb")
file(GLOB PROTO_FILES "${PROTO_DIR}/*.proto")
foreach(PROTO_FILE ${PROTO_FILES})
get_filename_component (FILENAME ${PROTO_FILE} NAME_WLE)
set(PROTO_SRC "${OUTPUT_DIR}/${FILENAME}.pb.cc")
set(PROTO_HDR "${OUTPUT_DIR}/${FILENAME}.pb.h")
set(GRPC_SRC "${OUTPUT_DIR}/${FILENAME}.grpc.pb.cc")
set(GRPC_HDR "${OUTPUT_DIR}/${FILENAME}.grpc.pb.h")
add_custom_command(
OUTPUT "${PROTO_SRC}" "${PROTO_HDR}" "${GRPC_SRC}" "${GRPC_HDR}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --plugin=protoc-gen-grpc=${_GRPC_CPP_PLUGIN_EXECUTABLE}
--cpp_out="${OUTPUT_DIR}" --grpc_out="${OUTPUT_DIR}"
-I"${PROTO_DIR}" "${PROTO_FILE}"
DEPENDS ${PROTO_FILE}
)
list(APPEND OUTPUT_SOURCES ${PROTO_SRC} ${GRPC_HDR})
endforeach()
add_library(hwpb ${OUTPUT_SOURCES})
target_link_libraries(hwpb ${_GRPC_GRPCPP_UNSECURE} ${_PROTOBUF_LIBPROTOBUF})
target_include_directories(hwpb PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

Library depend on a header file

Let's assume I have a project with a series of libraries. I also need to generate a header file that will be used by all of these projects. So I created a CMake file, like this:
project(main)
add_subdirectory(sub_1)
add_subdirectory(sub_2)
# ...
add_subdirectory(sub_n)
add_custom_command(
OUTPUT CustomHeader.h
COMMENT "Generating custom header for all the libraries"
COMMAND ...)
add_library(${PROJECT_NAME} STATIC ${OBJECT_LIST})
The problem is, that I don't know how to tell CMake to run my custom command (that generates this CustomHeader.h) before it would try to build the libraries in the subfolders.
I tried add_custom_target(TARGET MyPrebuild PRE_BUILD ...) but I'm running on Linux, and this option only works on Windows platform according to the documentation.
add_dependencies only work between targets, and not a target and a single file.
I could, in theory, add the header to be among the source files of the individual libraries (in the sub_1, .., sub_n folders) but it feels wrong, as the header is not required to be part of those libraries.
So I just have no idea how I can make a library depend on an include file, that is not part of it.
Any tips how I can overcome this problem?
For make header file (re)built before a library in subdirectory is compiled, you may create target, which builds the file, and make the library dependent from the target:
# *CMakeLists.txt*
# ...
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h ...)
add_custom_target(generate_custom_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
# *sub/CMakeLists.txt*
# ...
add_library(libA ...)
add_dependencies(libA generate_custom_header)
Instead of using add_dependencies, you may create header-only library which "implements" you header and link with it:
# *CMakeLists.txt*
# ...
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h ...)
add_custom_target(generate_custom_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
add_library(libCustom INTERFACE) # Header only library
add_dependencies(libCustom generate_custom_header) # which depends on generated header
# You may even assign include directories for the header-only library
target_include_directories(libCustom INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
# *sub/CMakeLists.txt*
# ...
add_library(libA ...)
target_link_libraries(libA libCustom) # Common linking with a header-only library.
Note, that INTERFACE library is a "fake" - it is never created by itself. Instead, all "features" of INTERFACE library are just propagated to its users.
I would suggest to add another library target that will both keep track of the generated headers and will help to properly configure other libraries to know where to find them (i.e. target_include_directories).
cmake_minimum_required(VERSION 3.0)
project(testable)
set(CustomHeaderInPath ${CMAKE_CURRENT_SOURCE_DIR}/CustomHeader.example)
set(CustomHeaderPath ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
add_custom_command(
OUTPUT ${CustomHeaderPath}
COMMAND cp ${CustomHeaderInPath} ${CustomHeaderPath}
COMMENT "Generated file"
DEPENDS ${CustomHeaderInPath})
add_library(CustomHeaderLibrary ${CustomHeaderPath})
target_include_directories(CustomHeaderLibrary PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(CustomHeaderLibrary PROPERTIES LINKER_LANGUAGE C)
add_library(LibA a.c)
target_link_libraries(LibA CustomHeaderLibrary)
add_library(LibB b.c)
target_link_libraries(LibB CustomHeaderLibrary)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} PUBLIC LibA LibB)
Note that I had to explicitly set the LINKER_LANGUAGE of the new target as cmake won't be able to deduce it properly if no c or cpp files are added to the library.