CMake - linked static library's header files not found?

Viewed 999

My app is not able to find header files from a separately-built library I am trying to use.
Here is the folder structure of my project:

app/
  app.h
  app.cpp
  CMakeLists.txt
  build/
    ...

lib/
  lib.h
  lib.cpp
  CMakeLists.txt
  build/
    libtest_lib.a
    ...

The contents of app/CMakeLists.txt:

project(test_app)

add_executable(test_app app.cpp)

target_link_libraries(test_app ${PROJECT_SOURCE_DIR}/../lib/build/libtest_lib.a)

The contents of lib/CMakeLists.txt:

project(test_lib)

add_library(test_lib lib.cpp)

target_include_directories(test_lib PUBLIC ${PROJECT_SOURCE_DIR})

The error occurs when I try to #include "lib.h" from app.cpp.

I have tried adding an "Imported library" as well and setting the location property, but still encountered the same issue.

I know that in these kinds of projects a top-level CMakeLists is used, but I am trying to have these 2 components be built separately.

2 Answers

I know that in these kinds of projects a top-level CMakeLists is used, but I am trying to have these 2 components be built separately.

If you want to build them truly separately, you'll have to go through some boilerplate. I assume you're in a situation where this is worth the pain: maybe you have to use two different toolchains. Otherwise, creating a build at the top level with just two add_subdirectory calls for lib and then app with the same following lists files will work (and you can even remove the packaging stuff).

Basically, in CMake you should always work with targets. Never link directly to a library file unless there isn't any other way. When you use targets, as I do below, it will even set up the include paths for you correctly. This is because CMake propagates the PUBLIC/INTERFACE properties (like those set by target_include_directories) to linkees.

CMake cannot magically determine include paths from a fully compiled static library it didn't even know it built. That is the crux of the issue here. You need to use targets.

Anyway, the boilerplate is just to declare which targets you want to export and then actually go ahead and export them. The GNUInstallDirs module sets up sane defaults for any system. This particular feature requires CMake 3.14+, but I put 3.19 because that's what I tested it on (you should never declare an untested minimum version in CMake, or anywhere else really).

Here's the CMakeLists.txt for the library:

cmake_minimum_required(VERSION 3.19)
project(test_lib)

add_library(test_lib lib.cpp)
add_library(test_lib::test_lib ALIAS test_lib)

target_include_directories(test_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)

## Packaging rules to generate CMake exports

include(GNUInstallDirs)

install(TARGETS test_lib 
        EXPORT test_lib_targets
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")

export(EXPORT test_lib_targets
       NAMESPACE test_lib::
       FILE "${CMAKE_INSTALL_LIBDIR}/cmake/test_lib/test_lib-config.cmake")

In the app's CMakeLists.txt:

cmake_minimum_required(VERSION 3.19)
project(test_app)

find_package(test_lib REQUIRED)

add_executable(test_app app.cpp)
target_link_libraries(test_app PRIVATE test_lib::test_lib)

At the command line, you would build them separately like so:

$ cmake -S lib -B lib/build -DCMAKE_BUILD_TYPE=Release
$ cmake --build lib/build
$ cmake -S app -B app/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/lib/build
$ cmake --build app/build

If you want to go further and support installing your CMake targets scripts (rather than just exporting them from the build directory), look at the install(EXPORT) command: https://cmake.org/cmake/help/latest/command/install.html#export

For cmake to do what you intend, the target_link_libraries must be provided with a cmake target, and not some generated file name:

target_link_libraries(test_app PRIVATE test_lib)

You get more functionality by doing less work - how cool is that, right? That's how cmake was meant to be used. This implies that cmake must know about both targets, i.e. it must know about the library and the application, both. This implies a top-level CMakeLists.txt, with two add_subdirectory statements that include both the application and the library folders:

The contents of ./CMakeLists.txt:

add_subdirectory(lib)
add_subdirectory(app)

Now things will work the way you wish.

Related