And everything works fine. However, if I simply change GIT_TAG to be 10.1.0, my build breaks in very strange ways (I get issues with several other targets that don’t even depend on {fmt}…).
After some debugging, it turns out that the issue is that I have a cmake function in my repo named add_module_library(). At some point, {fmt} also added such a cmake function as well: 75f3b1c09 (that version is slightly broken, was fixed two commits later). This apparently overrides my add_module_library, which does something different, breaking my build.
Is there a way to fix this on my end (outside of simply renaming my add_module_library to like add_barrys_module_library), or do I have to patch {fmt} to rename its function to fmt_add_module_library (or both)?
I’d suggest you to open a ticket (or, even better, a PR) for fmt to use prefixed version, AND to use a prefix in your project.
Basically pulling dependencies via FetchContent always risks some name clashes. Using prefixes consistently help avoiding most of them.
@craig.scott It seem to me, that FetchContent bring all functions into current scope:
function(add_module_library name)
message(FATAL_ERROR "should not be used!")
endfunction()
set(FMT_VERSION 10.1.1)
include(FetchContent)
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG ${FMT_VERSION}
#XXX OVERRIDE_FIND_PACKAGE
EXCLUDE_FROM_ALL
SYSTEM
)
FetchContent_MakeAvailable(fmt)
# somewhere later in your project...
#XXX find_package(fmt CONFIG ${FMT_VERSION} REQUIRED)
add_module_library(fmt)
which result in:
bash-5.2$ cmake -B build -S . -G Ninja --debug-find-pkg=fmt
Running with debug output on for the 'find' commands for package(s) fmt.
-- The CXX compiler identification is Clang 17.0.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/opt/llvm/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Version: 10.1.0
-- Build type:
-- Performing Test HAS_NULLPTR_WARNING
-- Performing Test HAS_NULLPTR_WARNING - Success
CMake Error at build/_deps/fmt-src/CMakeLists.txt:50 (add_library):
add_library cannot create target "fmt" because another target with the same
name already exists. The existing target is a static library created in
source directory "/Users/clausklein/cmake/example/build/_deps/fmt-src".
See documentation for policy CMP0002 for more details.
Call Stack (most recent call first):
CMakeLists.txt:79 (add_module_library)
-- target Greeter is NO INTERFACE_LIBRARY
-- Configuring incomplete, errors occurred!
bash-5.2$
Adding the CONFIG keyword isn’t the solution to this problem. All it is effectively doing in this case is preventing the user’s Findfmt.cmake module from being used. Based on the description, that find module seems to be providing fmt by pulling it in with FetchContent, which is valid, although not normally how I’d recommend doing things. Ideally, find_package() calls should be pretty minimal. In general, I’d only add the CONFIG keyword if I knew of scenarios where both a config package and a Find module existed for a dependency, and I specifically wanted to avoid the Find module because it didn’t work for my project. I don’t think that’s quite the case here.
In CMake, all functions and macros are global. When you use FetchContent, you’re absorbing a whole project from another dependency into your build, so any functions or macros that dependency defines will naturally affect the main build too. There’s nothing special about FetchContent here, you’d have the same problem with git submodules, for example. The main reason we encounter it more with FetchContent rather than with installed packages is that installed packages typically only create the functions and macros that are part of their public API. Since you aren’t adding the whole build of the dependency, only just the result of it, there’s much less pollution of the global namespace.
With CMake’s current functionality, this is my recommendation also. Good practice would be to prefix any command names you care about with your project name, e.g. myproj_do_something(). You only need to do this for functions that may be defined before a dependency is brought in and used afterwards. Another way to avoid such problems is to simply find/load all your dependencies first before your main project does anything, then define your project-specific functions and macros after that. Of course, that may break the dependencies’ commands if you override some of theirs and you use them (directly or indirectly) in your project.