ComicSansMS
ComicSansMS

Reputation: 54589

CMake find_package dependency on subproject

I have the following directory layout:

main_folder
 + static_lib1
 + executable

Everything works fine if I manually build 'static_lib1' and then 'executable'. But when running the CMakeLists from the main folder, I get an error because find_package is unable to find the library files from 'static_lib1' which have not yet been built.

How can I resolve this while keeping the CMakeLists files separate (i.e. without including the static_lib's CMakeLists from the executable's CMakeLists)?

Upvotes: 8

Views: 4052

Answers (3)

visard
visard

Reputation: 38

I guess I will leave this answer for posterity since only recently I have searched for a solution to this problem and found out that...

Since CMake 3.24 it is possible!

It is possible to override subsequent calls to find_package() with FetchContent_Declare() flag OVERRIDE_FIND_PACKAGE.

Your

add_subdirectory("path/to/static_lib1")

call has to be replaced in main_folder/CMakeLists.txt with:

include(FetchContent)
FetchContent_Declare(
    static_lib1
    SOURCE_DIR "path/to/static_lib1"
    OVERRIDE_FIND_PACKAGE
)

Any calls to find_package(static_lib1) will call FetchContent_MakeAvailable() for you, virtually making it identical to add_subdirectory() call.

You can read more about OVERRIDE_FIND_PACKAGE in CMake documentation.

Upvotes: 2

ComicSansMS
ComicSansMS

Reputation: 54589

Switch from a file-based approach to a target-based approach for handling the dependency from executable to static_lib1.

The original problem occurred because executable called find_package for locating static_lib1, which then attempted to fill a variable like STATIC_LIB1_LIBRARY with the paths to the library files by calling find_library. executable then consumes the content of that variable in a target_link_libraries(executable ${STATIC_LIB1_LIBRARY}) call. The problem here is, since those library files only get generated as part of the build, that call to find_library will not be able to find anything.

Building executable needs to support two scenarios here:

  1. Building standalone, where a pre-compiled version of static_lib1 is located somewhere on the disc.
  2. Building from main_folder, where both executable and static_lib1 are part of the same build.

The approach from the question supports scenario 1, but not scenario 2.

Instead of using using a variable to communicate a dependency between the two builds, use a target. The CMakeLists.txt for static_lib1 likely creates a library target like add_library(static_lib1 [...]). In executable we now simply do target_link_libraries(executable PUBLIC static_lib1). This is sufficient to support scenario 2.

To also allow for scenario 1 at the same time, we look at the call to find_package(static_lib1) in the CMakeLists.txt for executable. Instead of providing a variable like before, this call now needs to provide a target static_lib1 for consumption.

So we adapt the find script for static_lib1 to the following behavior:

  • If a target static_lib1 already exists, there's nothing to be done and the find script can just return (this is scenario 2).

  • Otherwise, we call find_library to locate the library file on disc (as before in the original approach) and then create a new imported target: add_library(static_lib1 STATIC IMPORTED). We then configure all relevant properties of the static library to that target. For instance, to add the location of the library file, we could do

    set_target_properties(static_lib1 PROPERTIES
      IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
      IMPORTED_LOCATION ${STATIC_LIB1_LIBRARY}
    )
    

    To support multi-config generators like MSVC, instead of setting IMPORTED_LOCATION and IMPORTED_LINK_INTERFACE_LANGUAGES, you will want to set the configuration specific properties like IMPORTED_LOCATION_DEBUG and IMPORTED_LOCATION_RELEASE instead. Since this can get quite tedious to do manually, you can have CMake generate this information (and a bunch of other convenient stuff) for you in a package script. The find mechanism for package scripts works slightly different under the hood, but the code in the CMakeLists.txt for executable will look just the same, a simple call to find_package(static_lib1). The main difference is that this call will then not dispatch to a hand-written find script, but to a package script that was automatically generated by CMake as part of the build process of static_lib1.

Upvotes: 0

arrowd
arrowd

Reputation: 34401

In executable's CMakeLists.txt you can check if you are building stand-alone or as part of project:

if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )
  # stand-alone build
  find_package(static_lib1)
else()
  include_directories(../static_lib1)
  link_directories(../static_lib1)
  ...
  target_link_libraries(executable static_lib1)
endif()

Upvotes: 3

Related Questions