Disclaimers
Apologies for CMake Mortals
Before I dig into the issue below, I want to apologize up front to anyone not deep into CMake features. There are a lot of arcane features at play here, which I do not like, but they are the options we have to choose from. Feel free to ask questions and I’ll do my best to provide references to CMake upstream docs and interpretations of them.
To be clear, I don’t use all these features, and I’ve been teaching myself about them quite a bit while working on the Problem Statement here.
CMake Upstream Needs Work
I think there are upstream CMake issues to sort out in all this, especially with respect to how one declares a dependency in CMakeLists.txt
. I can start conversations in those contexts, especially the CMake Discourse, but I don’t think the Beman Project will want to wait around for those concerns to play out.
Problem Statement
To start, Beman projects should be fully packageable, full stop. If Beman projects are not able to ship to Debian, Ubuntu, Arch Linux, Conan, Vcpkg, etc., it’s dramatically undermines a key goals of the whole endeavor – to get usage experience for these APIs before they are standardized.
Right now, the Beman Exemplar and possibly other Beman libraries are not packageable, but even more simply, they are not designed to consume their dependencies as packages. Instead, they are hardcoded to fetch dependencies, especially GoogleTest, from github.com and build them from source. This isn’t wrong, especially to get started, but it is limiting and causes problems.
For instance:
- Users may want to use an existing copy of
GoogleTest
because:- Other dependencies require that version
- They need to patch
GoogleTest
to work in their environment - They have the full
GoogleTest
source available as a package (this is a thing at least on Ubuntu)
- Users may not want their build systems performing off-machine I/O because:
- They are concerned about supply chain attacks and/or need to produce supply chain documentation such as Software Bills of Materials
- They are concerned about reproducibility of builds
- They’re coding on an airplane or train or otherwise have a bad ISP
- Build pessimization
- Speed - downloading and building GoogleTest from source is slower than linking against a prebuilt one
- Reliability - a prebuilt GoogleTest has fewer moving parts – you’re not running through all the GoogleTest
CMakeLists.txt
in addition to the ones in the repo you’re building
What to do?
We have some options, unfortunately. There isn’t a clear consensus in the wider CMake world
Option 1: New option per library (per dependency?) such as -DBEMAN_EXEMPLAR_FETCH_GOOGLETEST
This would be an option()
in CMakeLists.txt
per library. It would be used in an if statement controlling whether to call FetchContent
or find_package
for GoogleTest
Option 2: Always use FetchContent
This PR uses this approach. It calls only FetchContent
in CMakeLists.txt
and uses FETCHCONTENT_TRY_FIND_PACKAGE_MODE
to redirect to find_package
.
However, it seems blocked unless we work with upstream (GoogleTest and/or CMake) to fix name collisions inside the GoogleTest project on Windows when using FETCHCONTENT_TRY_FIND_PACKAGE_MODE
.
Option 3: Always use find_package
This PR uses this approach. It attempts to not break existing build-from-source use cases by leveraging standard CMake mechanisms to intercept find_package(GTest)
and redirect to equivalent dependency fetching using FetchContent
Users and CI could/would set this as needed -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./cmake/use-fetch-content.cmake
when they want to use FetchContent
, git
, etc. to build GoogleTest from source.
Use of a lockfile.json
is demonstrated in the PR, though we could hardcode FetchContent calls in use-fetch-content.cmake
if that’s preferred
Option 4: Develop a Better API
I’m mostly throwing this in for completeness. It’s a great design, but innovating in build tooling is pretty tangential to the end goals of the Beman project. Probably if you like this option, it’s because none of the above are really interesting you to the point that you don’t care about helping CMake broadly decide how things should work with current options.
xkcd: Standards, but at $dayjob, we actually don’t do any of the above. What we do looks a lot like Option 3 involving an abstract dependency provider API, but we use the target names instead of the find_package
package names. Also we can do them in a batch command:
require_targets(
GTest::main
Beman::Optional
)
# ... later ...
target_link_libraries(some-application-test PRIVATE
GTest::main
Beman::Optional
)
If we’re really excited about this approach, I could implement a new pass at this as an open source CMake module in a week or two. It would interoperate fine with find_package
and FetchContent
because target names are first-class citizens in CMake.