Project modularization using object libraries

Hello Everyone, I hope you’re all doing well!

While i have some experience developing, i’m still trying to get familiar with CMake and trying to come up with a practical layout for a kinda-large library i’m working on. It’s intended to be an open-source (not yet public though) library, ideally built statically, but allowing the end users to build dynamically as well.

The project will consist on:

  • A main library (that should build either static or shared)
  • Bindings for python and other languages
  • Probably a native app that will use such library

The library is currently structured as follows:

/CMakeLists.txt
/include/main.hpp
/src/main.cpp
/basics/CMakeLists.txt
/basics/libA/CMakeLists.txt
/basics/libA/include/basics/libA
/basics/libA/src
/basics/libB/CMakeLists.txt
/basics/libB/include/basics/libB
/basics/libB/src

/examples/CMakeLists.txt
/examples/ex01.cpp

the Main library depends on libA which in turn depends on libB (both built as OBJECT libraries).

Code looks somewhat like this:

/basic/libA/CMakeLists.txt

add_library(A OBJECT)
target_include_directories(A include/)
target_sources(A PRIVATE src/a.cpp)

/basic/libB/CMakeLists.txt

add_library(B OBJECT)
target_include_directories(B include/)
target_sources(B PRIVATE src/b.cpp)
target_link_libraries(B PUBLIC A)

/basics/CMakeLists.txt

add_subdirectory(libA)
add_subdirectory(libB)

/CMakeLists.txt

add_library(main)
target_include_directories(main include/)
target_sources(main PRIVATE src/main.cpp)
target_link_libraries(main 
    PUBLIC A
    PUBLIC B)

/examples/CMakeLists.txt

add_executable(ex01 ex01.cpp)
target_link_libraries(ex01 main)

So far I was mostly working on writing logic, and unit tests. Since I’m linking the tests directly with object libs, everything was working smoothly. Now, when I decided to build an actual example using the library, things didn’t work unless I linked the example directly with ALL object libraries as well.

When I build my example and link against my Main library, it fails to link because of unresolved symbols in libB.

Inspecting the static lib with nm confirms this, showing an U next to the symbols for which the linker complains.

Now, i’ve done some reading and I see that object libraries don’t propagate transitively, only the usage requirements from them do (at least when using only target_link_libraries).

Is there any way for OBJECT libraries to expose their build requirements and dependencies as well? I’ve read about using the TARGET_OBJECTS generator inside the add_library directive, but my understanding was that it only propagated the objects, and not the usage/interface requirements.

Is it possible (and convenient) to have this degree of modularization?
Are object libraries intended to be used this way?

Sorry for the long post, and my english, and thank you very much in advance for taking the time to read!

You’ve chosen a tricky situation to get familiar with CMake. There is one detail you should be aware of in this case: for shared libraries on Windows, you’ll need each OBJECT library to also privately specify the export symbols for dependent OBJECT libraries because they’ll end up in the same library and need to know that they come from “the same library” rather than “from outside”.

The non-inheritibility of the objects themselves through usage requirements (to avoid adding the same object multiple times into a library and triggering duplicate symbols) was an important design decision when we added it. It is odd that A’s objects are not available in main, but maybe you’re hitting the export symbol issue mentioned above?

1 Like

Thank you for the response @ben.boeckel !

While the goal is to be compatible across windows/linux & MAC, I’m hitting this issue with CLANG in macos, and I’m on purpose no setting any CXX_VISIBILITY variable to hidden.

So, if I use the OBJECT library approach on windows i’ll need to create export macros for each object library?

If OBJECT libraries don’t support doing this? what is the best way to accomplish this level of modularization? I’ve been looking at some popular large c++ repos and see that most people just throw huge large of files on a single CMakeLists.txt, although most of them also rely on relatively old versions of CMake. Is this usually the preffered approach when building a library that consists of multiple components?

Would you mind checking if this can be an acceptable workaround? It basically required that I link all object libraries to the main one, and not only the one i’m using directly. I’m guessing this should work properly if i had say a third obj-lib c on which both a and b depended, right?

Once again, thank you very much!

My general advice regarding libraries is to only use object libraries if you have no other choice. While it may be a more familiar paradigm for some people coming from a more traditional Makefile background where you manage object files directly, a regular static library will very often be the better choice and be much simpler to work with.

2 Likes

Thank you very much @craig.scott.

Would you mind sharing what the shortcoming would be when using the approach above?

I’ve seen some articles where people build many small static libs (one for each module) and then use ar/libtool to combine them into a single shippable archive. Is this the recommended practice? Would this work if the end-user has passed -DBUILD_SHARED_LIBS?

Thanks!

You can use add_library(libname STATIC) to force a target to be a static library regardless of BUILD_SHARED_LIBS’ value.

@ben.boeckel Thanks!. I know about that, but if the end user wants to build a shared library I assume i would need a specific command to package all the static libraries inside the final shared one.

I think I read some hacks for doing this as well. But My main concern is whether the strategy previously discussed is if this a valid approach for structuring a project consisting of many modules, which will be used (in different combinations) to build both a library and an application (using different subsets of the aforementioned modules).

To give you an idea of the modules I have:

  • An Expected template class that is used by almost every other module
  • A simple event loop
  • An http client that works on top of such loop
  • A logger that is used by almost every other module as well
  • Business-logic specific components that depends on all of the modules mentioned below

Thank you once again folks!