Linking against an ExternalProject_add dependency in CMAKE - c++

I'm getting this ninja build error below while running Ninja. My CMAKE build command iscmake -G"Ninja" -DCMAKE_BUILD_TYPE=Release.
ninja: error: 'ext_deps/api/src/ext_api/build/src/lib/libapi.a', needed by 'Project', missing and no known rule to make it
Let's say my project consists of an API (downloaded via CMAKE from GitHub) and the implementation (the Project).
The layout would look like:
Project/
-- build/
-- cmake/modules
----- ExternalDep.cmake
----- FindAPI.cmake
-- CMakeLists.txt
-- src/
---- CMakeLists.txt
-- include/
Let's say that in the top-level CMakeLists.txt I do the usual business of setting build settings, CXX flags, et cetera, and then I call include(ExternalDep), which checks if the "API" library is in the user's system (if not it is downloaded via CMAKE).
In src/CMakeLists.txt I try to link against the API library using a
target_link_libraries(${PROJECT_NAME} PRIVATE ${API_LIBRARY})
The first issue I'm having is that before the "API" library can even be downloaded and built, I get the ninja build error I posted above. I'm positive the ExternalDep.cmake is included before I try to add the Project executable and link against the "API" library.
Here's a simplified version of ExternalDep.cmake:
set(EXT_DEPS_PREFIX "ext_deps")
ExternalProject_Add(ext_lib
GIT_REPOSITORY "https://github.com/fake/api.git"
GIT_TAG "master"
PREFIX "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api"
TMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-tmp"
STAMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-stamp"
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
SOURCE_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api"
BINARY_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build"
BUILD_ALWAYS true
TEST_COMMAND "")
add_dependencies(ext_projects ext_api)
set(API_LIBRARY "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build/src/lib/libapi.a")

I ran into the same issue with Ninja while it worked fine with Unix Makefiles, and I managed to get it to work with Ninja by adding a BUILD_BYPRODUCTS line to my ExternalProject_Add block. Example:
ExternalProject_Add(SDL2_PROJECT
PREFIX 3rdparty
URL https://www.libsdl.org/release/SDL2-2.0.5.tar.gz
URL_MD5 d4055424d556b4a908aa76fad63abd3c
CONFIGURE_COMMAND <SOURCE_DIR>/configure ${SDL2_configure_args} --prefix=<INSTALL_DIR> --disable-shared
INSTALL_COMMAND make install -j9
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/libSDL2.a
)
ExternalProject_Get_Property(SDL2_PROJECT INSTALL_DIR)
set(SDL2_INSTALL_DIR ${INSTALL_DIR})
add_library(SDL2_LIBRARY STATIC IMPORTED GLOBAL)
set_property(TARGET SDL2_LIBRARY PROPERTY IMPORTED_LOCATION ${SDL2_INSTALL_DIR}/lib/libSDL2.a)
add_dependencies(SDL2_LIBRARY SDL2_PROJECT)

I was able to solve this by generating Unix Makefiles instead of Ninja. I'm still not exactly sure if this was the singular issue but it was definitely one of the issues.

Related

CMake Linking Header Only Library (Working Example) for which I want explanation

I am learning how to use ExternalProject to download header only libaries and link to my executable.
My workflow is following:
I download header library Eigen using ExtenalProject:
cmake -DGET_LIBS=ON -DBUILD_SHARED_LIBS=ON -DBUILD_MY_PROJECTS=OFF -G
"Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Then I run the same CMakeLists a second time, but this time I disable ExternalProject and compile the executable that links the Eigen:
cmake -DGET_LIBS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_MY_PROJECTS=ON
-G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Question
Why I need to use both of these commands since in target_include_directories command I specify the same path as in include_directories?
In the code below I need two commands include_directories and target_include_directories.
I thought that it would be enough to use only target_include_directories, but without include_directories it wont work.
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
My full CMakeLists.txt is following:
project(superbuild LANGUAGES CXX)
cmake_minimum_required(VERSION 3.19)
########################################################################
# EIGEN
########################################################################
SET(GET_LIBS "" CACHE STRING "Set option to download dependencies")
if (GET_LIBS)
message(AUTHOR_WARNING ${GET_LIBS})
ExternalProject_Add(eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/eigen #this does nothing...
SOURCE_DIR "${CMAKE_BINARY_DIR}/install/eigen" #install in my local build dir
#SOURCE_DIR ${CMAKE_INSTALL_PREFIX}
BUILD_COMMAND "" #do not build
INSTALL_COMMAND "" #do not install
)
endif ()
###############################################################################
#EXECUTABLES
###############################################################################
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
Tl;dr never use include_directories. Ever. Learn how property visibility works in CMake instead.
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
In the code you supplied, include_directories is implicitly setting the INCLUDE_DIRECTORIES property on your my_exe target. Then the second line sets the INTERFACE_INCLUDE_DIRECTORIES property.
INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building the target. INTERFACE_INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building targets that link to this one. This doesn't make much sense for an executable
Slightly better would be to write:
target_include_directories(my_exe PRIVATE "${CMAKE_BINARY_DIR}/install/eigen/")
This will just populate INCLUDE_DIRECTORIES without touching the INTERFACE_ version. If you wanted both you could write PUBLIC instead of PRIVATE or INTERFACE. By definition, PUBLIC is just both of the others.
But this isn't a great dependency management strategy anyway... digging into the source tree guts of a project isn't scalable. It's also difficult to try different versions of Eigen without editing your build files.
I would just write:
cmake_minimum_required(VERSION 3.23)
project(example)
find_package(Eigen3 REQUIRED)
add_executable(my_exe main.cpp)
target_link_libraries(my_exe PRIVATE Eigen3::Eigen)
Use a proper package manager like vcpkg or Conan to handle downloading Eigen when it isn't available on the system.

How do I change the name of the executable that I'm installing using CPack?

I do apologize in advanced if this sounds like something trivial. I am new to using CMake and CPack.
I am currently trying to build my own compiler as a side project, and I want to test out how CPack will install my project.
Here is my CMakeLists.txt file that I have at the root of my project folder:
cmake_minimum_required(VERSION 3.15)
project(Simple-C-Compiler VERSION 0.01)
set(CMAKE_CXX_STANDARD 20)
set(COMPILER_VERSION ${PROJECT_VERSION})
add_library(include INTERFACE)
target_include_directories(include INTERFACE include/)
add_subdirectory(lib)
add_subdirectory(phases)
add_subdirectory(compiler)
add_subdirectory(tests)
target_link_libraries(compiler lexer)
target_link_libraries(tester lexer)
add_compile_options(-Wall)
install(TARGETS compiler DESTINATION bin)
set(CPACK_PACKAGE_EXECUTABLES "compiler" "Simple-C")
include(CPack)
When I try to install my compiler, by doing:
mkdir build
cd build
cmake ../
make install
I get the following output:
[ 22%] Built target lib
[ 55%] Built target lexer
[ 77%] Built target compiler
[100%] Built target tester
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/compiler
CPack installed my compiler as "compiler" not "Simple-C". I want the name of the executable being installed to be "Simple-C". How do I go about doing that in my CMakeLists.txt file?
You can change the name of a target with the following command:
set_target_properties(compiler PROPERTIES OUTPUT_NAME Simple-C)
This must be called after the add_subdirectory(compiler)
As a side note, the commands you mentionned did not invoke cpack. In order to invoke cpack, you would need to run the cpack command.
You can use the RENAME option of CMake install. See https://cmake.org/cmake/help/v3.13/command/install.html
In short
install(TARGETS compiler DESTINATION bin RENAME Simple-C)

How to build and link external CMake dependencies with CMake?

I'd like to use the following third party library:
https://github.com/Corvusoft/restbed
I'd like to create a folder structure similar to this:
- root
- CMakeLists.txt
- src
- third_party
- restbed
- src
- CMakeLists.txt
- CMakeLists.txt
I've found out that I should use the ExternalProject CMake module to achieve this but it does not seem that I can do it correctly. For some reason the external lib's (restbed's) source does not appear in my XCode project when I generate it out with the use of the root CMakeLists.txt. This root CMakeLists.txt includes in the third_party folder's CMakeLists.txt. The third_party folder's CMakeLists.txt looks like this:
include(ExternalProject)
# CMAKE_CURRENT_LIST_DIR -> third_party folder
set(RESTBED_DIR ${CMAKE_CURRENT_LIST_DIR}/restbed)
ExternalProject_Add(restbed
PREFIX ${RESTBED_DIR}
SOURCE_DIR ${RESTBED_DIR}
CMAKE_ARGS -DBUILD_SSL=NO
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
BUILD_COMMAND cmake ..
BINARY_DIR ${RESTBED_DIR}/build
INSTALL_DIR ${RESTBED_DIR}/distribution
INSTALL_COMMAND make install
)
set(RESTBED_INCLUDE_DIR ${RESTBED_DIR}/distribution/include)
set(RESTBED_LIB_DIR ${RESTBED_DIR}/distribution/lib)
add_library(librestbed IMPORTED STATIC)
add_dependencies(librestbed restbed)
After this I'd like to use target_include_dir and target_link_libraries to add librestbed as a dependency to my root project. After this when I build my XCode project nothing happens, the external lib won't get built. What am I doing wrong? I would not like to use the built-in CMake git functionality because I'd like to store my external dependencies as git submodules in the third_party lib. I'd rather not let CMake figure out if it needs to re-download a given dependency. Thanks in advance!
I think you mixed up the BUILD_COMMAND option with the CONFIGURE_COMMAND option. The cmake .. should be placed after the option CONFIGURE_COMMAND.
ExternalProject_Add(restbed
PREFIX ${RESTBED_DIR}
SOURCE_DIR ${RESTBED_DIR}
CMAKE_ARGS -DBUILD_SSL=NO
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
CONFIGURE_COMMAND cmake ..
BINARY_DIR ${RESTBED_DIR}/build
INSTALL_DIR ${RESTBED_DIR}/distribution
INSTALL_COMMAND make install
)
Per the documentation, there are defaults to each of these commands, so you likely don't have to specify the CONFIGURE_COMMAND at all. For example, for BUILD_COMMAND:
If this option is not given, the default build command will be chosen to integrate with the main build in the most appropriate way (e.g. using recursive make for Makefile generators or cmake --build if the project uses a CMake build).
Also, if you do specify the CONFIGURE_COMMAND, you likely expand the arguments you pass it to something like this:
...
CONFIGURE_COMMAND cd restbed && mkdir build && cd build && cmake ..
...

CMakeLists configuration to link two C++ projects

I have the following situation: a Project A depends on a Project B, but both are built at the same time. Project A has to include the includes of project B and it needs also to link its libraries. Up to now I've tried this way:
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/other_project other_project)
and then:
INCLUDE_DIRECTORIES(includ ${CMAKE_SOURCE_DIR}/other_project/include})
LIST(APPEND LINK_LIBS other_project)
in the CMakeLists.txt of Project A
but it doesn't seem to work, the compiler also gives me error when including the headers of Project B saying that they do not exist.
What is the right way to add dependencies in A? How should the CMakeLists.txt look like?
EDIT:
as suggested in the comments, this question was addressed in this, however I'd like to see an example of how to use it in a CMakeList.txt file.
The following is a simple example which builds zlib and then builds libxml2 which depends on the zlib we build. One thing to note, I quickly pulled this example from stuff I've done before. The libxml2 example uses make, thus will only actually build on a system which has it, e.g. Linux, Mac ...
Here is the proposed directory structure for this example ...
src/
-- CMakeLists.txt
CMake/
---- External_ZLib.cmake
---- External_libxml2.cmake
Dowloads/ ( no need to create this directory, CMake will do it for you )
build/ ( Out of source build to prevent littering source tree )
Files:
CMakeLists.txt
# Any version that supports ExternalProject should do
cmake_minimum_required( VERSION 3.1)
project(test_ext_proj)
set(test_ext_proj_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
set(CMAKE_MODULE_PATH ${test_ext_proj_CMAKE_DIR} ${CMAKE_MODULE_PATH})
set(test_ext_proj_BUILD_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install)
set(test_ext_proj_DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Downloads CACHE PATH "Directory to store downloaded tarballs.")
include(ExternalProject)
include(External_ZLib)
include(External_libxml2)
External_ZLib.cmake:
set(ZLib_version 1.2.8)
set(ZLib_url "http://zlib.net/zlib-${ZLib_version}.tar.gz")
set(ZLib_md5 "44d667c142d7cda120332623eab69f40")
ExternalProject_Add(ZLib
URL ${ZLib_url}
URL_MD5 ${ZLib_md5}
PREFIX ${vision-tpl_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
CMAKE_GENERATOR ${gen}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${test_ext_proj_BUILD_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
)
#This variable is required so other packages can find it.
set(ZLIB_ROOT ${test_ext_proj_BUILD_INSTALL_PREFIX} CACHE PATH "" FORCE)
External_libxml2.cmake:
set(libxml2_release "2.9")
set(libxml2_patch_version 0)
set(libxml2_url "ftp://xmlsoft.org/libxml2/libxml2-sources-${libxml2_release}.${libxml2_patch_version}.tar.gz")
set(libxml2_md5 "7da7af8f62e111497d5a2b61d01bd811")
#We need to state that we're dependent on ZLib so build order is correct
set(_XML2_DEPENDS ZLib)
#This build requires make, ensure we have it, or error out.
if(CMAKE_GENERATOR MATCHES ".*Makefiles")
set(MAKE_EXECUTABLE "$(MAKE)")
else()
find_program(MAKE_EXECUTABLE make)
if(NOT MAKE_EXECUTABLE)
message(FATAL_ERROR "Could not find 'make', required to build libxml2.")
endif()
endif()
ExternalProject_Add(libxml2
DEPENDS ${_XML2_DEPENDS}
URL ${libxml2_url}
URL_MD5 ${libxml2_md5}
PREFIX ${test_ext_proj_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure
--prefix=${test_ext_proj_BUILD_INSTALL_PREFIX}
--with-zlib=${ZLIB_ROOT}
BUILD_COMMAND ${MAKE_EXECUTABLE}
INSTALL_COMMAND ${MAKE_EXECUTABLE} install
)

CMake ExternalProject_Add() and FindPackage()

Is there are proper way to find a library (via FindPackage()) which was built with ExternalProject_Add()?
The problem is that CMake cannot find the library at CMake-time because the external library gets build at compile time. I know that it is possible to combine these two CMake function when building the library and the project in a superbuild but I want to use it in a normal CMake project.
In fact I would like to build VTK 6 with ExternalProject_Add and find it with FindPackage all inside my CMake project.
there is a way to do this. but it´s kind of hackish.
you basically add a custom target, that reruns cmake during build.
you will have to try this in a small test project, to decide if it works for you
find_package(Beaengine)
############################################
#
# BeaEngine
#
include(ExternalProject)
externalproject_add(BeaEngine
SOURCE_DIR ${PROJECT_SOURCE_DIR}/beaengine
SVN_REPOSITORY http://beaengine.googlecode.com/svn/trunk/
CMAKE_ARGS -DoptHAS_OPTIMIZED=TRUE -DoptHAS_SYMBOLS=FALSE -DoptBUILD_64BIT=FALSE -DoptBUILD_DLL=FALSE -DoptBUILD_LITE=FALSE
INSTALL_COMMAND ""
)
if(NOT ${Beaengine_FOUND})
#rerun cmake in initial build
#will update cmakecache/project files on first build
#so you may have to reload project after first build
add_custom_target(Rescan ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} DEPENDS BeaEngine)
else()
#Rescan becomes a dummy target after first build
#this prevents cmake from rebuilding cache/projects on subsequent builds
add_custom_target(Rescan)
endif()
add_executable(testapp testapp.cpp )
add_dependencies(testapp Rescan)
if(${Beaengine_FOUND})
target_link_libraries(testapp ${Beaengine_LIBRARY})
endif()
this seems to work well for mingw makefiles / eclipse makefile projects.
vs will request to reload all projects after first build.
You can force a build using the build_external_project function below.
It works by generating a simple helper project inside the build tree and then calling the cmake configuration and the cmake build on the helper.
Customize at will for the actual ExternalProject_add command.
Note that the trailing arguments are used to pass CMAKE_ARGS. Furthur enhancements are left as an exercise to the reader :-)
# This function is used to force a build on a dependant project at cmake configuration phase.
#
function (build_external_project target prefix url) #FOLLOWING ARGUMENTS are the CMAKE_ARGS of ExternalProject_Add
set(trigger_build_dir ${CMAKE_BINARY_DIR}/force_${target})
#mktemp dir in build tree
file(MAKE_DIRECTORY ${trigger_build_dir} ${trigger_build_dir}/build)
#generate false dependency project
set(CMAKE_LIST_CONTENT "
cmake_minimum_required(VERSION 2.8)
include(ExternalProject)
ExternalProject_add(${target}
PREFIX ${prefix}/${target}
URL ${url}
CMAKE_ARGS ${ARGN}
INSTALL_COMMAND \"\"
)
add_custom_target(trigger_${target})
add_dependencies(trigger_${target} ${target})
")
file(WRITE ${trigger_build_dir}/CMakeLists.txt "${CMAKE_LIST_CONTENT}")
execute_process(COMMAND ${CMAKE_COMMAND} ..
WORKING_DIRECTORY ${trigger_build_dir}/build
)
execute_process(COMMAND ${CMAKE_COMMAND} --build .
WORKING_DIRECTORY ${trigger_build_dir}/build
)
endfunction()
Time has passed and CMake implemented a native version allowing to reference targets from an ExternalProject_Add.
This feature is implemented in the FetchContent module. It allows downloading and immediately consuming targets defined at configure time.
It uses a scratch build dir as hinted by my previous answer, but in a more integrated API.