Saturday, July 9, 2022

[SOLVED] Include and access multiple .so files and headers in one project using cmake

Issue

I have a simple foobar project which has a directory layout as follows:

.
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    ├── Foo
    │   ├── CMakeLists.txt
    │   ├── foo.cpp
    │   └── foo.h
    └── Bar
        ├── CMakeLists.txt
        ├── bar.cpp
        └── bar.h

Where Foo is standalone but Bar depends on Foo and should be able to #include <Foo/foo.h>.

My top level CMakeLists.txt file is:

# Specify cmake version
cmake_minimum_required(VERSION 3.10)

# Set the project name
project(foobar VERSION 0.1.0)

# Specify the C++ standard
set(CMAKE_CXX_COMPILER /usr/bin/g++)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")

# Set location for .so files
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")

# Set location for executables
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)

# Add libraries
add_subdirectory(src)

# Install library
install(TARGETS Foo Bar LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

my src level is:

add_subdirectory(Foo)
add_subdirectory(Bar)

my Foo level is:

add_library(Foo SHARED foo.cpp)

and finally my Bar level is:

add_library(Bar SHARED bar.cpp)
add_dependencies(Bar Foo)
target_link_libraries(Bar Foo)

Invoking cmake goes OK, but upon make I get the following:

Scanning dependencies of target Foo
[ 25%] Building CXX object src/Foo/CMakeFiles/Foo.dir/foo.cpp.o
[ 50%] Linking CXX shared library libFoo.so
[ 50%] Built target Foo
Scanning dependencies of target Bar
[ 75%] Building CXX object src/Bar/CMakeFiles/Bar.dir/bar.cpp.o
In file included from /home/drjrm3/code/cmake_app/src/Bar/bar.cpp:1:
/home/drjrm3/code/cmake_app/src/Bar/bar.h:3:10: fatal error: Foo/foo.h: No such file or directory
    3 | #include <Foo/foo.h>
      |          ^~~~~~~~~~~
compilation terminated.
make[2]: *** [src/Bar/CMakeFiles/Bar.dir/build.make:63: src/Bar/CMakeFiles/Bar.dir/bar.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:160: src/Bar/CMakeFiles/Bar.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

which shows me that I am not properly making Foo visible to Bar in the way that I'm hoping to.

What needs to change so that these can each be built into isolated shared object libraries but Bar depends upon Foo and can <Foo/foo.h> can be visible from within that subcomponent?

Note that my choices for making these shared objects is because this is just a minimal example of a bigger project where I have multiple components and multiple apps but wanted to make a minimal example for this question. In fact I have this example up on Github is anyone is interested.


Solution

The key concept here is property visibility. There are two types in CMake:

  1. PRIVATE: the property affects the target being built.
  2. INTERFACE: the property affects targets that link to this one directly, or transitively through target_link_libraries(... INTERFACE ...).

The PUBLIC visibility is simply a shorthand for both.


Therefore. the build for Bar should look like so:

add_library(Bar SHARED bar.cpp)
target_link_libraries(Bar PUBLIC Foo)

We use PUBLIC visibility here because Bar's headers include Foo's headers. That is, libraries linking to Bar need to know about Foo as well.

Now we build Foo like so:

add_library(Foo SHARED foo.cpp)
target_include_directories(Foo PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>")

The main bit of magic here is the $<BUILD_INTERFACE:...> generator expression. This prevents the include directory (which is an absolute path to somewhere on your build machine) from being exported when you eventually use install(EXPORT) to create a find_package-compatible CMake package for these libraries.

Otherwise, we're just relying on the fact that ${CMAKE_CURRENT_SOURCE_DIR}/.. will be propagated from Foo to Bar (and anything that links to Bar) because this path is in the INTERFACE of Foo.



Answered By - Alex Reinking
Answer Checked By - Pedro (WPSolving Volunteer)