Home > OS >  How do I build a parameterized third-party library in cmake?
How do I build a parameterized third-party library in cmake?

Time:02-06

I have a project in which I have a third party library checked out as a git submodule. The third party library is just source code with no build system. In addition, The third party library must configured on a per-executable basis by way of compiler definitions and selectively compiling only the parts of the library that I need. I use this library in a lot of different repositories, so I want to make a reusable component to generate an instantiation of the library for any particular executable.

The way I've currently attempted this is by creating a generate_thirdparty.cmake. This file looks something like this:

function(generate_thirdparty parameters)
    # several calls to add_library for the different components of this third party library
    # the generated libraries depend on the parameters argument
    # the parameters configure compile definitions and which source files are compiled
endfunction()

Then in my project CMakeLists.txt I have something like:

cmake_minimum_required(VERSION 3.8)
project(test C)

include(generate_thirdparty.cmake)
set(parameters <some parameters here>)
generate_thirdparty(${parameters})

add_executable(my_exe main.c)
target_link_libraries(my_exe <library names from generate_thirdparty>)

It seems like what I have works, but I'm confused on how you're supposed to do this. I've read through other posts and seen people suggest using find_package or ExternalProject_add. Given a third party repository that contains source code and no build system, which you have no control over, how do you create a reusable way to build that library, especially in the case that the library must be parameterized any given executable?

EDIT: I would like to have the flexibility to have multiple instantiations of the library in the same project.

CodePudding user response:

Let's sum up:

  1. The third-party library does not provide its own build.
  2. You need many instantiations of the library within a single build.
  3. You use these instantiations across multiple different repositories.

I think you're pretty much taking the right approach. Let's call the third-party library libFoo for brevity. Here's what I think you should do...

  1. Create a wrapper repository for libFoo that contains a FindFoo.cmake file and the actual foo repository submodule next to it. This is to avoid the contents of FindFoo.cmake from being independently versioned across your various projects.
  2. Include the wrapper as your submodule in dependent projects, say in the directory third_party/foo_wrapper
  3. In those dependent projects, write:
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party/foo_wrapper")
find_package(Foo REQUIRED)

generate_foo(config1 ...)
generate_foo(config2 ...)

add_executable(app1 src/app1/main.cpp)
target_link_libraries(app1 PRIVATE foo::config1)

add_executable(app2 src/app2/main.cpp)
target_link_libraries(app2 PRIVATE foo::config2)
  1. The contents of FindFoo.cmake will simply be:
cmake_minimum_required(VERSION 3.18)

function(generate_foo target)
   # Use CMAKE_CURRENT_FUNCTION_LIST_DIR to reference foo's source files
   # for example:

   set(sources "src/src1.cpp" "src/src2.cpp" ...)
   list(TRANSFORM sources PREPEND "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/foo/")

   add_library(${target} ${sources})
   add_library(foo::${target} ALIAS ${target})

   # Do other things with ARGN
endfunction()

# Define a version for this dependency   script combo.
set(Foo_VERSION 0.1.0)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo VERSION_VAR Foo_VERSION
                                  HANDLE_COMPONENTS)

The CMAKE_CURRENT_FUNCTION_LIST_DIR is the absolute path to the file containing the function being called. Remember that this is the root of your wrapper repository, so the actual sources for libFoo will be in the adjacent foo directory. list(TRANSFORM) lets us write relative paths in the sources list that get converted to absolute paths for the sake of add_library (passing a relative path to add_library would be relative to the caller's source directory).

I also create an ALIAS target so that callers of generate_foo can link to the alias. This is important because CMake considers names that contain :: to be targets when a library is expected. This very helpfully turns typos from unintended linker flags into configure-time "target not found" errors.

Then we define the function like normal and call the find_package_handle_standard_args to handle find_package arguments like REQUIRED, COMPONENTS (even just to check that none were incorrectly specified), and VERSION. See the docs for it, here: https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html

  •  Tags:  
  • Related