Search code examples
c++cmakedynamic-linkingexternal-projectcasadi

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


I want to integrate CasADi into a CMake-based C++ codebase as an ExternalProject. For this purpose, I would like to use pre-compiled libraries because building from source is not recommended. So far, I have only managed to write the following:

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)

and I noticed that all the binaries are correctly downloaded in the specified folder. However, I do not know how to link my targets to CasADi, nor how to find the package.


Solution

  • 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


    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.