Pull in CMake Files via FetchContent


I wanted to use cmake scripts provided by Catch2 and used the following code:

if(NOT catch2_POPULATED)
    add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR})
    list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib/)

In general this works, but there is the problem that CMAKE_MODULE_PATH is not set globally. But I put the code into it’s own directory dependencies/catch2 to create a seperate scope (Where I could override Variables controlling the fetched project), therefore CMAKE_MODULE_PATH is empty in my test folder, where I need it.
Is there any way to fix this?

Your best bet is to set a CACHE variable with the location of the fetched module, and write your other CMakeLists.txt files to expect that variable. e.g.

In dependencies/catch2/CMakeLists.txt:

# instead of list(APPEND CMAKE_MODULE_PATH...)
set(CATCH2_MODULE_PATH ${catch2_SOURCE_DIR}/contrib/
  "Path to downloaded Catch2 modules"

In your test directory:


You can also mark_as_advanced(CATCH2_MODULE_PATH) if you don’t want it showing up by default in e.g. CMake GUI configuration tools. (Probably wise, since you’re going to ignore and forcibly override whatever variable the user might set in there.) Even better, I changed it to type INTERNAL so it will never show up in a GUI.

Another variant would be to use FetchContent_MakeAvailable() (it’s shorter and makes this suggestion simpler) and call it in both places (dependencies and tests). The call in dependencies will do the actual download. The call in tests will do nothing because the download has already been done, but it will still set the output variables that give the locations of the source dir (and others). Then you use the source dir location to populate the CMAKE_MODULE_PATH variable just like you were before, but now you’re doing it in the directory scope where you actually want/need it. It’s pretty short and clear:

# In the tests directory
1 Like

@ferdnyc Thanks for the suggestion! That’s a good idea

@craig.scott Nice :slight_smile: I didn’t know that this FetchContent_MakeAvailable does populate the options as well. Maybe add that note to your book for the next version? (Or did I not see it in there?). I was already disappointed that I couldn’t use MakeAvailable.

I think though, it would still be great to have a global version of CMAKE_MODULE_PATH? Just like targets are globally available it seems logical to me to make the script globally available as well. Obviously there might be reasons where you want local scope. What would you think of a new feature to allow both? (Perhaps just a second variable, or a general mechanism to have parts of a list that are global and others that are local?)

Oh, neat — so, like find_package() output variables, the module’s results are cached and re-used in subsequent calls/runs? (I assume that would mean it also avoids the download on a subsequent cmake run in an existing build dir, since the files are already present?)

Hey, turns out Modern CMake has a section on FetchContent, including macros to work around the lack of FetchContent_MakeAvailable in CMake 3.11-3.13. …Which I now see are also in the module documentation as well.

Not cached, but essentially the behavior looks similar. The first declared details for a dependency fully determine everything, so the relevant variables are computed from that. FetchContent_MakeAvailable() makes a call to FetchContent_GetProperties() internally which populates them. Actually, you could just call that in the tests directory instead of FetchContent_MakeAvailable(), now that I think about it. This has the advantage that FetchContent_GetProperties() is available from CMake 3.11, whereas FetchContent_MakeAvailable() is only available from 3.14.

And yes, once a download has been performed by FetchContent, subsequent runs should re-use that rather than re-download it again. Note that there might still be an update step performed though, depending on what sort of GIT_TAG you use (or whatever other download method you use).

1 Like