What is the correct way to write a find module?

I can’t seem to find any information on how to do this.

Here’s a concrete example I have in mind. I would like to build {fmt}, from source, in my project, with some specific flags (specifically with -fPIC). I’m currently doing this with ExternalProject_Add (although this means fmt::fmt doesn’t end up being an imported target for… reasons?).

One of my dependencies also depends on {fmt}, and itself does a find_package(fmt REQUIRED). I would like to ensure that my entire build consistently uses the same fmt::fmt target, so I need this call to find_package(fmt) to pick up my built-from-source version of {fmt}.

Which I guess means that I have to provide a Findfmt.cmake for everyone to find consistently. What is the Correct Way :tm: to write Findfmt.cmake to handle this case?

fmt is built with CMake, so you don’t need to (and shouldn’t) write a find module for it.

Why are you adding it with ExternalProject instead of FetchContent?

I’m building {fmt} from source.

But one of my other dependencies is calling find_package(fmt), so that needs to find… something, which needs to be the same thing that I’m building from source. There needs to be some Findfmt.cmake that gets found, and it’s not the one that ships with {fmt} - because I’m building {fmt} from source.

For your situation, I’d recommend using FetchContent and use CMake 3.24 or later. The FetchContent_Declare() function gained an OVERRIDE_FIND_PACKAGE option in CMake 3.24, enabling the workflow you describe. For example:

include(FetchContent)
FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG a0b8a92e3d1532361c2f7feb63babc5c18d00ef2 # 10.0.0
    OVERRIDE_FIND_PACKAGE
)
FetchContent_MakeAvailable(fmt)

# somewhere later in your project...
find_package(fmt REQUIRED)

The above call to find_package() gets redirected to a fmt-config.cmake that FetchContent creates for you internally. This pattern works as long as the dependency you’re pulling in follows the recommended (and increasingly expected) pattern of providing the same <namespace>::<target> target names whether it is built-from-source or brought in via find_package(). I think fmt satisfies that constraint, but I’ll have to leave you to confirm it.

2 Likes

I would use this SYSTEM and version tag to be explicit and prevent warnings from fmt headers:

include(FetchContent)
FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.0.0
    OVERRIDE_FIND_PACKAGE
    SYSTEM
)
FetchContent_MakeAvailable(fmt)

# somewhere later in your project...
find_package(fmt 10.0 REQUIRED)

Thanks Craig, that appears to do the trick!

The suggestion to use SYSTEM didn’t work though. I get a cmake error inside of FetchContent.cmake:

FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 4e39e1308550d6c30e1ef1cf9153f6032ed59a4b
    SYSTEM
    OVERRIDE_FIND_PACKAGE
)

failed with (this is with Cmake 3.25.1):

CMake Error at .../Modules/FetchContent.cmake:1616 (message):
  Build step for fmt failed: 1
Call Stack (most recent call first):
  .../Modules/FetchContent.cmake:1756:EVAL:2 (__FetchContent_directPopulate)
  .../Modules/FetchContent.cmake:1756 (cmake_language)
  .../Modules/FetchContent.cmake:1970 (FetchContent_Populate)
  cmake/deps/Findfmt.cmake:24 (FetchContent_MakeAvailable)
  CMakeLists.txt:74 (find_package)

Without SYSTEM, works just fine.

This is the wrong URL!

Doesn’t matter, that’s not the real URL I’m using in the code anyway. The cmake error is real.

pip install cmake ninja helps to go to current stable CMake v3.26.3 version

include(FetchContent)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.0.0
    SYSTEM
    OVERRIDE_FIND_PACKAGE
)
FetchContent_MakeAvailable(fmt)
bash-3.2$ ninja
[0/1] Re-running CMake...
-- Version: 10.0.0
-- Build type: Debug
-- Performing Test HAS_NULLPTR_WARNING
-- Performing Test HAS_NULLPTR_WARNING - Success
CMake Warning (dev) at cmake-build-cxx-modules-sandbox-x86_64-Debug/install/_deps/fmt-src/CMakeLists.txt:379 (install):
  CMake's C++ module support is experimental.  It is meant only for
  experimentation and feedback to CMake developers.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Configuring done (5.8s)
CMake Warning (dev):
  C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
  experimental.  It is meant only for compiler developers to try.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Generating done (0.1s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/cxx20/cxx-modules-sandbox/cmake-build-cxx-modules-sandbox-x86_64-Debug/install
[3/3] Linking CXX static library lib/libfmtd.a
bash-3.2$ 
``

So… yes it was a CMake bug in 3.25 that was fixed in 3.26?

@craig.scott may this true?

Most likely it was fixed by MR 7977, which was included in CMake 3.25.2.

No, it works for me. I assume you’re objecting to the use of git@... instead of https://..., but both should work. There are cases where you may want to use one over the other, but both are valid.

1 Like

Using OVERRIDE_FIND_PACKAGE is definitely the way to go if you are not bound to lower versions of cmake!

For <3.24 you can make the fmt config available to other deps by adding the fmt_BINARY_DIR to the prefix path and setting FMT_INSTALL=ON before MakeAvailable.