Cmake Externalproject_Add() and Findpackage()

Use find_package() on external project

You have misunderstood how ExternalProject is supposed to work. You cannot find_package(messages REQUIRED) because it hasn't been built yet. ExternalProject merely creates the build steps necessary to build the subproject.

You have two options:

  1. Use add_subdirectory or FetchContent in place of ExternalProject. In this case, you don't need a find_package call. This effectively adds the sub-project to the main build and imports the subproject's targets.
  2. Use two ExternalProject calls: one for messages and another for main_project, which depends on messages. If messages uses the export(EXPORT) function, you can point CMAKE_PREFIX_PATH or messages_ROOT to the build directory. Otherwise you'll need to run the install step for messages and set up an install prefix inside your build directory. Then the find_project(messages REQUIRED) call inside main_project will succeed. This will likely require re-structuring your build.

Generally speaking, ExternalProject is only useful for defining super-builds, which are chains of CMake builds that depend on one another. And super builds are only useful when you need completely different configure-time options, like different toolchains (eg. you're cross compiling, but need a code generator to run on the build machine). If that's not the case, prefer FetchContent or add_subdirectory with a git submodule.

It is best to use FetchContent with CMake 3.14+ since it adds the FetchContent_MakeAvailable macro that cuts down on boilerplate.

Docs:

https://cmake.org/cmake/help/latest/module/ExternalProject.html
https://cmake.org/cmake/help/latest/module/FetchContent.html

CMake ExternalProject_Add() and FindPackage()

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.

CMake - linking to library downloaded from ExternalProject_add()

When you're using ExternalProject_Add, you can't use find_package, since there's nothing to find when CMake runs to configure the outer project.

So, if library locations vary by platform you will need conditional logic based on your platform. (I don't know protobuf's libraries or structure here, so this is just an example, but it should get you headed in the right direction...) Something like this:

if(WIN32)
set(PROTOBUF_LIB_DIR "${MYPROJ_SOURCE_DIR}/dependencies/win"
set(prefix "")
set(suffix ".lib")
elseif(APPLE)
set(PROTOBUF_LIB_DIR "${MYPROJ_SOURCE_DIR}/dependencies/mac"
set(prefix "lib")
set(suffix ".a")
else()
set(PROTOBUF_LIB_DIR "${MYPROJ_SOURCE_DIR}/dependencies/linux"
set(prefix "lib")
set(suffix ".a")
endif()

set(PROTOBUF_LIBRARIES
"${PROTOBUF_LIB_DIR}/${prefix}protobufLib1${suffix}"
"${PROTOBUF_LIB_DIR}/${prefix}protobufLib2${suffix}")

Granted, this is less convenient than using find_package. If you can use a pre-built/pre-installed package, you should, so that you can use find_package. If you must build the other package from source code as part of your project, though, ExternalProject_Add is useful, even though it is unable to abstract away all the details for you.

Make an external project available for find_package CMake

well technically you can "modify" the png project by applying a patch.

Unfortunately FetchContent/add_subdirectory() i.e. incorporating third party as subproject, can't work with find_package().

You should replace by something like this

if(NOT TARGET ZLIB::ZLIB)
find_package(ZLIB)
endif()

i.e. create a patch an apply it on top of png

  message(CHECK_START "Fetching png")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
png
GIT_REPOSITORY "https://github.com/glennrp/libpng.git"
GIT_TAG "master"
PATCH_COMMAND git apply ".../patches/png.patch")
# here if you want to force some option(s) (must have CMP0077 to NEW)
#e.g. set(CMAKE_BUILD_SHARED OFF)
FetchContent_MakeAvailable(png)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")

CMake related "issue": https://gitlab.kitware.com/cmake/cmake/-/issues/17735

note: take a look at https://github.com/google/or-tools/blob/master/cmake/dependencies/CMakeLists.txt (sorry no png)

note2: madler zlib is unmaintained concerning CMake stuff take a look at my patch ;)

CMake find_package for another library in same project

find_package() doesn't actually look at the global targets list, it has its own targets list that only prevents other find calls from refetching

instead of creating an alias target call find_package_handle_standard_args in a Find<>.cmake module for each class

so for this case a Findcppzmq.cmake

include(FindPackageHandleStandardArgs)
find_package(ZeroMQ REQUIRED)
if(TARGET cppzmq)
find_package_handle_standard_args(cppzmq
REQUIRED_VARS cppzmq_BINARY_DIR)
endif()

and a FindZeroMQ.cmake

include(FindPackageHandleStandardArgs)
if(TARGET ZeroMQ)
find_package_handle_standard_args(ZeroMQ
REQUIRED_VARS ZeroMQ_BINARY_DIR)
endif()

alternatively if all the repos in the builder project are seperate you can instead use ExternalProject_Add so that projects will be generated in order at build time. Making an effective package.

cmake_minimum_required(VERSION 3.16)
project(ExternalBuilder)
include(ExternalProject)
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_LIST_DIR}/install)
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX})
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
ExternalProject_Add(
ZeroMQ
GIT_REPOSITORY https://github.com/zeromq/libzmq.git
GIT_TAG v4.3.2
CMAKE_ARGS -DCMAKE_MODULE_PATH:PATH=${CMAKE_INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} -DBUILD_TESTS=OFF -DBUILD_STATIC=OFF
)
ExternalProject_Add(
cppzmq
DEPENDS ZeroMQ
GIT_REPOSITORY https://github.com/zeromq/cppzmq.git
GIT_TAG v4.7.1
CMAKE_ARGS -DCMAKE_MODULE_PATH:PATH=${CMAKE_INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} -DCPPZMQ_BUILD_TESTS=OFF
)
ExternalProject_Add(
zmqProject
DEPENDS ZeroMQ
...
)

Integrate pre-compiled libraries into C++ codebase with CMake ExternalProject

There is a natural problem with ExternalProject_Add:

ExternalProject_Add executes commands only on build.

Hence, download will not happen at the configure stage of your project which makes it difficult to use find_package, because the files cannot be found during your first configure run.

Take this CMakeLists.txt:

cmake_minimum_required(VERSION 3.21)
project(untitled)

set(CMAKE_CXX_STANDARD 17)

add_executable(untitled main.cpp)

include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)

find_package(casadi HINTS ${CMAKE_BINARY_DIR}/external/casadi/src/casadi-3.5.5/casadi)

target_link_libraries(untitled casadi)

In order to use it you have to do the following:

  1. Configure your project
mkdir build
cd build
cmake ..

  1. Build (download) casadi-3.5.5
cmake --build . --target casadi-3.5.5

  1. Reconfigure your project, because now find_package will find the needed files
cmake ..

  1. Build your targets
cmake --build .

If you want a one step build, there are ways to get around this problem

  • Use FetchContent
  • Create a sub-cmake-project in a subfolder with all the ExternalProject_Add commands and execute the approriate build (download) steps manually in your own CMakeLists.txt via execute_process calls: https://stackoverflow.com/a/37554269/8088550

Here is an example for the second option, which might be better since FetchContent doesn't have the full functionality of ExternalProject.

  • main.cpp
#include <casadi/casadi.hpp>

int main()
{
casadi_printf("This works!");
return 0;
}
  • CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(untitled)

set(CMAKE_CXX_STANDARD 17)

# some default target
add_executable(untitled main.cpp)

# Configure and build external project
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/external)
execute_process(
COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/external
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external
)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/external
)

# find and link externals
find_package(casadi REQUIRED HINTS ${CMAKE_BINARY_DIR}/external/external/casadi/src/casadi-3.5.5/casadi)
target_link_libraries(untitled casadi)
  • external/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(external)

include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)

The point is to have another cmake project under external/CMakeLists.txt, which gets configured and build via execute_process calls from the main cmake project. Do note, that you can now have find_package(casadi REQUIRED ...) at configure stage, because the download will happen just before.

Using ExternalProject_Add with ITK

So I should not be building the ordered list of static libraries in ITK_LIBRARIES, this is too complex. Instead I should be using the logic from a call to find_package(ITK).

I need to change the way I build my project and switch to a SuperBuild type solution.

How to install external library from source for cmake to find it?

Mostly, developers add SDKs folder to let others use the required SDKs. It may be because they did minor/major changes inside the library and it became a custom build version and special for them. So, it means, they have to pack it inside the project since others can't find the modified version of that library. And I think, this is what you're looking for:

find_package(Library ${LIB_VERSION} EXACT REQUIRED PATHS "${LIB_DIR}")

By declaring PATHS you can have CMake look for the libraries specific directories.



Related Topics



Leave a reply



Submit