Use library with static dependencies in CMAKE

Viewed 99

Is there a way in CMake to use a library for multiple targets with different static dependencies? To explain it better, consider this minimal example: I want two executables: The first should print "YES", the second one should print "NO". For this I use the libarary "printsth", which prints "something". The string it prints comes from the header file the "user" (either printyes or printno) supplies. This altogether looks like this:

├── apps
│   ├── printno
│   │   ├── CMakeLists.txt
│   │   │       add_executable(printno main.cpp)
│   │   │       target_link_libraries(printno PRIVATE printsth)
│   │   │
│   │   ├── main.cpp
│   │   │       #include "printsth/printsth.h"
│   │   │       
│   │   │       int main() {
│   │   │         printsth();
│   │   │         return 0;
│   │   │       }
│   │   │
│   │   └── print_usr.h
│   │           #define USR_STRING  "NO"
│   │
│   └── printyes
│   │   ├── CMakeLists.txt
│   │   │       add_executable(printyes main.cpp)
│   │   │       target_link_libraries(printyes PRIVATE printsth)
│   │   │
│   │   ├── main.cpp
│   │   │       #include "printsth/printsth.h"
│   │   │       
│   │   │       int main() {
│   │   │         printsth();
│   │   │         return 0;
│   │   │       }
│   │   │
│   │   └── print_usr.h
│   │           #define USR_STRING  "YES"
│   │
├── extern
│   └── printsh
│       ├── include
│       │   └── printsh
│       │       └── printsh.h
│       │               void printsth();
│       │
│       ├── src
│       │    ├── CMakeLists.txt
│       │    │       add_library(printsth printsth.cpp)
│       │    │       target_include_directories(printsth PUBLIC ../include)
│       │    │
│       │    └── printsh.cpp
│       │            #include "printsth/printsth.h"
│       │            #include "print_usr.h"
│       │            #include <iostream>
│       │            
│       │            void printsth() {
│       │              std::cout << USR_STRING << std::endl;
│       │            }
│       │     
│       └── CMakeLists.txt
│               cmake_minimum_required(VERSION 3.11...3.16)
│               
│               project(printsh
│                   VERSION 0.1
│                   DESCRIPTION "Print something"
│                   LANGUAGES CXX)
│               
│               add_subdirectory(src)
│        
└── CMakeLists.txt
        cmake_minimum_required(VERSION 3.11...3.16)

        project(printexamples
            VERSION 0.1
            DESCRIPTION "Print examples"
            LANGUAGES CXX)

        add_subdirectory(apps/printyes)
        add_subdirectory(apps/printno)
        add_subdirectory(extern/printsth)

When building I obviously get the error

fatal error: print_usr.h: No such file or directory

So is there any change I can tell CMake to use apps/printno as include directory when building printsh lib for printno and to use apps/printyes as include directory when building for printyes?

I know this example does not make much sense and it is easy to get rid of the header dependencies (e.g. passing the custom string as an argument to printsth()) and everything works just fine. So this is just an example to demonstrate a "real-world" problem, where I can't easily get rid of the dependencies.

1 Answers

In CMake sense a library target denotes the library which is compiled once but can be linked into several targets(executable or other libraries).

Because you cannot compile both macro definitions into the single library, you need to create a different library target for every definitions set. That is, in your case you need to create two libraries.

For repetitive sequence of commands CMake provides macros and functions and call them several times with different parameters.

You may also create a "parameterized" CMakeLists.txt, and "call" it (via add_subdirectory) several times:

extern/printsh/src/CMakeLists.txt:

# Requires 'printsth_target' and 'printish_usr_dir' variables to be set.
# The first variable denotes name of the target created,
# the second variable denotes include directory with 'print_usr.h' header.
if (NOT printsth_target)
  message(FATAL_ERROR "printsth_target variable is required but is not set.")
endif()
if (NOT printish_usr_dir)
  message(FATAL_ERROR "printish_usr_dir variable is required but is not set.")
endif()

# Create a library target with user-provided name
add_library(${printsth_target} printsth.cpp)
target_include_directories(${printsth_target} PUBLIC ../include)
# Add user-provided include directory
target_include_directories(${printsth_target} PRIVATE ${printish_usr_dir})

With such script the printsth library can be instantiated in applications' CMakeLists.txt.

apps/printno/CMakeLists.txt:

add_executable(printno main.cpp)
# Instantiate 'printsth' library with needed parameters.
set(printsth_target "printsh_no")
set(printish_usr_dir ${CMAKE_CURRENT_SOURCE_DIR})
# Need to specify the second argument - build directory, where the library will be built.
add_subdirectory(../../extern/printsh printsh_no_dir)
# No we can link with a library. Use a name, specified for 'printsth_target' variable.
target_link_libraries(printno PRIVATE printsh_no)

Top-level CMakeLists.txt should be modified for not instantiate the library.

CMakeLists.txt:

# <...>

add_subdirectory(apps/printyes)
add_subdirectory(apps/printno)
# Uncommenting the following line causes FATAL_ERROR triggered.
#add_subdirectory(extern/printsth)
Related