Search code examples
c++cmakeabseil

What is the idiomatic way to export a 3rd party static library dependency in CMake?


I have the following reduced CMake code for using Abseil in a library: (minimal repository to reproduce)

cmake_minimum_required(VERSION 3.20)

project(MyProject)

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")

set(ABSL_PROPAGATE_CXX_STD ON)
find_package(absl REQUIRED)

add_library(MyStaticLibTarget STATIC MyStaticLib.cpp)
target_link_libraries(MyStaticLibTarget absl::strings)

install(TARGETS MyStaticLibTarget EXPORT MyProjectTargets)
install(TARGETS strings EXPORT MyProjectTargets)

if(EXPORT_MYSTATICLIB)
  export(TARGETS MyStaticLibTarget FILE lib/cmake/MyProject/MyProjectTargets.cmake)
endif()

# Setup for downstream clients of MyProject?
install(EXPORT MyProjectTargets DESTINATION lib/cmake/MyProject COMPONENT cmake-exports)

However, I run into an error as shown if I try to use the export code path, when running CMake with:

cmake -G Ninja -S . -B build -DABSL_ENABLE_INSTALL=ON -DEXPORT_MYSTATICLIB=ON

# CMake Error in CMakeLists.txt:
# export called with target "MyStaticLibTarget" which requires target
#   "strings" that is not in any export set.

I don't understand the error message; it seems to me that strings is already part of MyProjectTargets (which I think is an export set?), so there shouldn't be any error.

That said, based on the error message, I tried adding an export line for strings, and transitively fixed all the errors, and I ended up with

export(TARGETS
  strings
  strings_internal
  type_traits
  base
  config
  throw_delegate
  dynamic_annotations
  log_severity
  base_internal
  raw_logging_internal
  atomic_hook
  core_headers
  spinlock_wait
  endian
  bits
  int128
  memory
  errno_saver
  meta
  APPEND FILE lib/cmake/MyProject/MyProjectTargets.cmake)

That does fix all errors! In practice, this would be very painful to do by hand for a larger library. It also seems like the "wrong" solution, because I am naming lots of internal details of Abseil by hand.

  1. Is manually adding the targets as above the "right" solution?
  2. Is there a better way to achieve the same result as above?

I have the following constraints:

  • I need to export MyStaticLibTarget so it can be used by downstream clients.
  • I'm pretty sure the dependency (in target_link_libraries) needs to be public, as in the actual case (this is a minimal example), Abseil headers will be included in the library's public headers.

Solution

  • TLDR: Follow the Conan docs, especially the cmake_find_package docs and skim the cheatsheet.

    Following @Tsyvarev's advice in the comments, I used a different setup and made this work, you can see the final result in conan branch.

    The steps are as follows:

    1. Use Conan to download and install Abseil, using a project-local conanfile.txt. Now, "install" is a scary word (I did not want to modify global state), but Conan essentially just caches sources and build artifacts, which means that you can have multiple versions of the same libraries in different directories, and different projects can use these different versions, and it should "just work". (At least, that's my basic understanding so far.)
      [requires]         # Which 3rd party conan packages we are using
      abseil/20210324.2
      
      [generators]       # How to create build system files that capture the dependency information
      cmake_find_package
      cmake_paths
      
      With the above configuration, Conan can be run:
      conan install . --install-folder build/conan
      # Bunch of CMake files will be available under build/conan
      
    2. Point CMake to Conan-generated files by adding a line to CMakeLists.txt.
      include("${CMAKE_BINARY_DIR}/conan/conan_paths.cmake")
      
    3. Configure normally using CMake.
      cmake -G Ninja -S . -B build -DEXPORT_MYSTATICLIB=ON
      
      (The ABSL_ENABLE_INSTALL option is handled by Conan and is no longer needed.)