Converting exemplar to a CMake INTERFACE-type library

I have a commit up to eliminate the static/shared library that beman.exemplar creates as a default:

Although my draft commit doesn’t currently do so, the idea would be to preserve the ability to create the static/shared library by configuring cookiecutter, while making that configuration no longer the default.

Many if not most Beman libraries are heavily template-based and users don’t inherently need to consume linker artifacts in order to use them.

I wanted to ask our CMake experts what they think about this idea and my draft of how exemplar would look as an INTERFACE library.

1 Like

Takeaways on this topic from today’s weekly sync:

  • We should adopt what beman.optional does currently and declare the named header set in the top-level CMakeLists.txt while populating it in include/beman/exemplar/CMakeLists.txt
  • It’s okay to unconditionally move the lines find_package(beman-install-library REQUIRED) and beman_install_library(beman.exemplar) from src/beman/exemplar/CMakeLists.txt into the top-level CMakeLists.txt regardless of whether or not we’re building an INTERFACE vs a STATIC/SHARED library
  • The location of the beman.exemplar-config.cmake.in file (in the draft, tentatively moved from src/beman/exemplar into the top level directory) requires more discussion

@vito.gamberini, what are your thoughts on the last bullet item above?

1 Like

What if we want support CXX_MODULES too?

see Linker error with CXX_MODULES - Development - CMake Discourse

and Prepare CXX_MODULE usage by ClausKlein · Pull Request #286 · bemanproject/exemplar · GitHub

I think my question belongs here. With the recent Refresh to latest beman.exemplar template merge I noticed that there is a library generated. In my case libbeman.timed_lock_alg.a. I assume it should be possible to do sudo cmake --install build/gcc-release without generating it since the actual library is header only - or is that static library used for anything?

The static library is cruft— I’d recommend changing it to an INTERFACE library:

  • Change add_library(beman.timed_lock_alg) to add_library(beman.timed_lock_alg INTERFACE)
  • Delete add_subdirectory(src/beman/timed_lock_alg)
  • Delete the src directory entirely
  • Remove the now-superflous shared library CI tests by deleting all the instances of "Debug.Dynamic" from .github/workflows/ci_tests.yml
1 Like

Great! Seems to have worked:

/opt/beman
├── include
│   └── beman
│       └── timed_lock_alg
│           ├── mutex.hpp
│           └── timed_lock_alg.hpp
└── lib64
    └── cmake
        └── beman.timed_lock_alg
            ├── beman.timed_lock_alg-config.cmake
            ├── beman.timed_lock_alg-config-version.cmake
            ├── beman.timed_lock_alg-targets.cmake
            └── cxx-modules
                └── cxx-modules-beman.timed_lock_alg-targets.cmake

Perhaps I need to do some changes to the README.md though. This isn’t correct anymore, is it?

You will then need to add beman::timed_lock_alg to the link libraries of any libraries or executables that include beman.timed_lock_alg headers.

target_link_libraries(yourlib PUBLIC beman::timed_lock_alg)

1 Like

To my understanding, even INTERFACE libraries need to be added to a target’s dependencies using target_link_libraries; this is simply a poorly named CMake statement which expresses a dependency relation regardless of the actual involvement of a linker, and the effect of which is to make sure that the include directory parameters to the compilation are wired up correctly. Maybe we should rephrase the English words “to the link libraries,” though.

Yeah, I think I understand why other libs and binaries need to be dependant beman::timed_lock_alg but by making the dependency PUBLIC this also affects libraries dependant on these other libraries, which results in that they get the include path to beman’s include directory added - even if they don’t include any beman headers if I’m not misunderstanding how it works. I’m no good with CMake so I can’t say for sure but I think that dependency is mainly used for linking:

libX → depends on → libY → PUBLIC depends on timed_lock_alg
makes
libX → PUBLIC dependant on timed_lock_alg

I could be totally off here…

I think you’re right, but that it’s working as intended. Two situations:

  • If libY has a header file that includes timed_lock_alg, then it should mark its dependency on timed_lock_alg as PUBLIC, because otherwise libY will fail to transitively include timed_lock_alg’s headers when it includes libX’s header

  • If libY uses timed_lock_alg in its implementation but not in its headers, then it should mark its dependency on timed_lock_alg as PRIVATE, since libX doesn’t need to see timed_lock_alg’s headers to link against libY in that case

Yeah, you are right of course! I didn’t think that through :slight_smile: