How to prevent FetchContent to patch source packages again?

I have this code and it works fine to fetch a boost archive and apply needed patches. I also builds fine with my cmake workflow preset.

# ---- Add Boost source tar archive via FetchContent ----
include(FetchContent)
set(BOOST_PATCH_DIR "${CMAKE_CURRENT_SOURCE_DIR}/patches/${BOOST_VERSION}")
set(BOOST_PATCHES "${BOOST_PATCH_DIR}/TheBigBoost.patch")

FetchContent_Declare(
    Boost
    URL ${BOOST_URL}
    URL_HASH SHA256=${BOOST_URL_SHA256}
    DOWNLOAD_EXTRACT_TIMESTAMP ON
    PATCH_COMMAND git apply -p0 ${BOOST_PATCHES}
    SYSTEM
    ${EXCLUDE_FROM_ALL}
)

FetchContent_MakeAvailable(Boost)
set(BOOST_SOURCE ${Boost_SOURCE_DIR})
message(STATUS "Boost_SOURCE_DIR=${Boost_SOURCE_DIR}")

# TODO(CK): should be a function instead use of BOOST_SOURCE and BOOST_VERSION
include(cmake/CheckBoostVersion.cmake)
message(STATUS "Boost found: ${BOOST_VERSION} ${BOOST_SOURCE}")

# ...

But if I run cmake -L . in binary directory, or if I touch my CMakeLists.txt and run ninja, this happens:

root@06bf603b8cf4:/src/build/Release# cmake -L .
-- CXXFLAGS=-stdlib=libc++ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0
'clang++' '-print-file-name=libc++.modules.json'
-- CMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/x86_64-linux-gnu/libc++.modules.json
-- CMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/x86_64-linux-gnu/libc++.modules.json
CMake Warning (uninitialized) at CMakeLists.txt:184 (if):
  uninitialized variable 'CPM_SOURCE_CACHE'


error: patch failed: build/Release/_deps/boost-src/tools/cmake/include/BoostInstall.cmake:337
error: build/Release/_deps/boost-src/tools/cmake/include/BoostInstall.cmake: patch does not apply
error: patch failed: build/Release/_deps/boost-src/tools/cmake/include/BoostRoot.cmake:123
error: build/Release/_deps/boost-src/tools/cmake/include/BoostRoot.cmake: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/serialization/CMakeLists.txt:6
error: build/Release/_deps/boost-src/libs/serialization/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/any/CMakeLists.txt:2
error: build/Release/_deps/boost-src/libs/any/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/conversion/CMakeLists.txt:4
error: build/Release/_deps/boost-src/libs/conversion/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/dll/CMakeLists.txt:5
error: build/Release/_deps/boost-src/libs/dll/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/lexical_cast/CMakeLists.txt:3
error: build/Release/_deps/boost-src/libs/lexical_cast/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/pfr/CMakeLists.txt:4
error: build/Release/_deps/boost-src/libs/pfr/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/type_index/CMakeLists.txt:4
error: build/Release/_deps/boost-src/libs/type_index/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/lexical_cast/include/boost/lexical_cast/detail/lcast_precision.hpp:10
error: build/Release/_deps/boost-src/libs/lexical_cast/include/boost/lexical_cast/detail/lcast_precision.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/lexical_cast/include/boost/lexical_cast/detail/lcast_unsigned_converters.hpp:20
error: build/Release/_deps/boost-src/libs/lexical_cast/include/boost/lexical_cast/detail/lcast_unsigned_converters.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/pfr/include/boost/pfr/detail/io.hpp:51
error: build/Release/_deps/boost-src/libs/pfr/include/boost/pfr/detail/io.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/CMakeLists.txt:3
error: build/Release/_deps/boost-src/libs/stacktrace/CMakeLists.txt: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/test/test.cpp:276
error: build/Release/_deps/boost-src/libs/stacktrace/test/test.cpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/libbacktrace_impls.hpp:35
error: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/libbacktrace_impls.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/addr_base.hpp:15
error: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/addr_base.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/frame_unwind.ipp:50
error: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/frame_unwind.ipp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/addr2line_impls.hpp:164
error: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/addr2line_impls.hpp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/frame_msvc.ipp:402
error: build/Release/_deps/boost-src/libs/stacktrace/include/boost/stacktrace/detail/frame_msvc.ipp: patch does not apply
error: patch failed: build/Release/_deps/boost-src/libs/stacktrace/modules/boost_stacktrace_dump.cppm:15
error: build/Release/_deps/boost-src/libs/stacktrace/modules/boost_stacktrace_dump.cppm: patch does not apply
CMake Error at build/Release/CMakeFiles/fc-tmp/boost/patch.cmake:7 (execute_process):
  execute_process last command failed
Call Stack (most recent call first):
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:1766 (include)
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:1729 (__FetchContent_doStepDirect)
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:1620 (__FetchContent_populateDirect)
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:2160:EVAL:2 (__FetchContent_doPopulation)
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:2160 (cmake_language)
  /root/.local/share/pipx/venvs/cmake/lib/python3.12/site-packages/cmake/data/share/cmake-4.4/Modules/FetchContent.cmake:2401 (__FetchContent_Populate)
  CMakeLists.txt:223 (FetchContent_MakeAvailable)


-- Configuring incomplete, errors occurred!
-- Cache values
BOOST_BEAST_BUILD_EXAMPLES:BOOL=OFF
BOOST_BEAST_BUILD_FUZZERS:BOOL=OFF
BOOST_BEAST_BUILD_TESTS:BOOL=OFF
BOOST_BEAST_WARNINGS_AS_ERRORS:BOOL=OFF
BOOST_BUILD_ALL:BOOL=ON
BOOST_BUILD_TESTING:BOOL=TRUE
BOOST_CONTAINER_HEADER_ONLY:BOOL=TRUE
BOOST_CONTEXT_ABI:STRING=sysv
BOOST_CONTEXT_ARCHITECTURE:STRING=x86_64
BOOST_CONTEXT_ASM_SUFFIX:STRING=.S
BOOST_CONTEXT_ASSEMBLER:STRING=gas
BOOST_CONTEXT_BINARY_FORMAT:STRING=elf
BOOST_CONTEXT_IMPLEMENTATION:STRING=fcontext
BOOST_FILESYSTEM_DISABLE_ARC4RANDOM:BOOL=OFF
BOOST_FILESYSTEM_DISABLE_BCRYPT:BOOL=OFF
BOOST_FILESYSTEM_DISABLE_COPY_FILE_RANGE:BOOL=OFF
BOOST_FILESYSTEM_DISABLE_GETRANDOM:BOOL=OFF
BOOST_FILESYSTEM_DISABLE_SENDFILE:BOOL=OFF
BOOST_FILESYSTEM_DISABLE_STATX:BOOL=OFF
BOOST_FILESYSTEM_EMSCRIPTEN_USE_WASI:BOOL=OFF
BOOST_FILESYSTEM_NO_DEPRECATED:BOOL=OFF
BOOST_HEAP_BUILD_TESTS:BOOL=OFF
BOOST_HEAP_USE_FILE_SET:BOOL=OFF
BOOST_LOCKFREE_BUILD_TESTS:BOOL=OFF
BOOST_LOCKFREE_TESTS_STRESSTEST:BOOL=OFF
BOOST_LOCKFREE_USE_FILE_SET:BOOL=OFF
BOOST_MATH_BUILD_WITH_LEGACY_FUNCTIONS:BOOL=OFF
BOOST_MSM_BUILD_MRDOCS:BOOL=OFF
BOOST_MSM_TEST_NLOHMANN_JSON:BOOL=OFF
BOOST_OPENMETHOD_BUILD_EXAMPLES:BOOL=OFF
BOOST_OPENMETHOD_BUILD_TESTS:BOOL=OFF
BOOST_OPENMETHOD_MRDOCS_BUILD:BOOL=OFF
BOOST_OPENMETHOD_WARNINGS_AS_ERRORS:BOOL=OFF
BOOST_PROCESS_USE_STD_FS:BOOL=TRUE
BOOST_RANDOM_ENABLE_RANDOM_DEVICE:BOOL=ON
BOOST_REGEX_STANDALONE:BOOL=OFF
BOOST_SRC_DIR:STRING=/src/build/Release/_deps/boost-src/libs/beast/../..
BOOST_STACKTRACE_ENABLE_ADDR2LINE:BOOL=ON
BOOST_STACKTRACE_ENABLE_BACKTRACE:BOOL=OFF
BOOST_STACKTRACE_ENABLE_BASIC:BOOL=ON
BOOST_STACKTRACE_ENABLE_FROM_EXCEPTION:BOOL=ON
BOOST_STACKTRACE_ENABLE_NOOP:BOOL=ON
BOOST_STACKTRACE_ENABLE_WINDBG:BOOL=OFF
BOOST_STACKTRACE_ENABLE_WINDBG_CACHED:BOOL=OFF
BOOST_TEST_HEADERS_ONLY:BOOL=TRUE
BOOST_THREAD_THREADAPI:STRING=pthread
BOOST_URL:STRING=https://github.com/boostorg/boost/releases/download/boost-1.92.0/boost-1.92.0-cmake.7z
BOOST_URL_BUILD_EXAMPLES:BOOL=OFF
BOOST_URL_BUILD_FUZZERS:BOOL=OFF
BOOST_URL_BUILD_TESTS:BOOL=OFF
BOOST_URL_DISABLE_THREADS:BOOL=OFF
BOOST_URL_MRDOCS_BUILD:BOOL=OFF
BOOST_URL_SHA256:STRING=158852d45a7ce23de167a3dc2e57b04490829b233f06e835d5c81a65547ef68b
BOOST_URL_WARNINGS_AS_ERRORS:BOOL=OFF
BOOST_USE_MODULES:BOOL=ON
BOOST_USE_WINAPI_VERSION:STRING=
BOOST_UUID_LINK_LIBATOMIC:BOOL=ON
BUILD_SHARED_LIBS:BOOL=TRUE
BUILD_TESTING:BOOL=OFF
BoostPrettyPrinters_GDB:FILEPATH=BoostPrettyPrinters_GDB-NOTFOUND
Boost_NOWIDE_WERROR:BOOL=ON
CCACHE_BIN:FILEPATH=/usr/bin/ccache
CMAKE_BUILD_TYPE:STRING=Release
CMAKE_CXX_COMPILER_LAUNCHER:FILEPATH=/usr/bin/ccache
CMAKE_CXX_EXTENSIONS:BOOL=FALSE
CMAKE_CXX_STANDARD_REQUIRED:BOOL=TRUE
CMAKE_CXX_STDLIB_MODULES_JSON:FILEPATH=/usr/lib/x86_64-linux-gnu/libc++.modules.json
CMAKE_C_COMPILER_LAUNCHER:FILEPATH=/usr/bin/ccache
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE
CMAKE_INSTALL_PREFIX:PATH=/src/stagedir
CMAKE_PREFIX_PATH:STRING=/src/stagedir
CMAKE_SKIP_INSTALL_RULES:BOOL=FALSE
FETCHCONTENT_BASE_DIR:PATH=/src/build/Release/_deps
FETCHCONTENT_FULLY_DISCONNECTED:BOOL=OFF
FETCHCONTENT_QUIET:BOOL=ON
FETCHCONTENT_SOURCE_DIR_BOOST:PATH=
FETCHCONTENT_UPDATES_DISCONNECTED:BOOL=OFF
FETCHCONTENT_UPDATES_DISCONNECTED_BOOST:BOOL=OFF
root@06bf603b8cf4:/src/build/Release# 

@craig.scott Do you have any idea how to prevent this feature?

Or is this an issue?

One way I found to prevent this is:

message(STATUS "Boost found: ${BOOST_VERSION} ${BOOST_SOURCE}")
# TODO(CK): one possible workaround to not call the PATCH_COMMAND while each RERUN_CMAKE
set(FETCHCONTENT_SOURCE_DIR_BOOST
    "${Boost_SOURCE_DIR}"
    CACHE PATH
    "Boost patched source DIR"
    FORCE
)

The behavior in your original post is not what I’d expect. I suggest you try to reduce it down to an absolutely minimal reproducer. Try it with different dependencies as well. In the process of doing that, I suspect you might find something about your project that is causing, or at least related to, the observed unexpected behavior.

It seems a site effect of import std; usage?

First try is this, but it should be a smaller MRE, I think:

No, it is independent of building CXX_MODULES.

But when the patches touches files/project used by the top-level project, then it happens!

here is my MRE: GitHub - ClausKlein/FetchContentTest · GitHub

Claus, would you include the CMakeUserPresets.json file in your reproducer? It is referenced by the GNUmakefile and README.md but seems to be missing.

It is included under cmake subdir and should be creates as a symbolic link from gmake:

iMac:FetchContentTest clausklein$ gmake -n
ln -fs cmake/CMakeUserPresets.json CMakeUserPresets.json
cmake --preset release --fresh --log-level=VERBOSE
touch .init
ninja -C build/release build/release/compile_commands.json
if [ "X" != "Xbuild/release/compile_commands.json" ] ; then \
	ln -fs build/release/compile_commands.json compile_commands.json; \
fi
cmake --workflow --preset release
iMac:FetchContentTest clausklein$ 

It happens also without preset and makefile:

iMac:FetchContentTest clausklein$ CC=gcc-16 CXX=g++-16 cmake -S . -B build -G Ninja -D CMAKE_CXX_STANDARD=23 -D CMAKE_BUILD_TYPE=Release -D CMAKE_PREFIX_PATH="${HOME}/.local" --log-level=VERBOSE --fresh
-- The CXX compiler identification is GNU 16.2.0
-- The C compiler identification is GNU 16.2.0
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - no
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - no
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/bin/g++-16 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Checking whether C compiler has -isysroot
-- Checking whether C compiler has -isysroot - no
-- Checking whether C compiler supports OSX deployment target flag
-- Checking whether C compiler supports OSX deployment target flag - no
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/local/bin/gcc-16 - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Executing download step for boost-cmake
-- verifying file...
       file='/Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-tmp/v1.92.0-rc3.tar.gz'
-- File already exists and hash match (skip download):
  file='/Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-tmp/v1.92.0-rc3.tar.gz'
  SHA256='bbcd4de95670755b39f874ae7efe307beef19c6b53c9736c037ebd794e10bab0'
-- extracting...
     src='/Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-tmp/v1.92.0-rc3.tar.gz'
     dst='/Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-src'
-- extracting... [tar xf]
-- extracting... [analysis]
-- extracting... [rename]
-- extracting... [clean up]
-- extracting... done
-- Executing update step for boost-cmake
-- Executing patch step for boost-cmake
'patch' '--backup' '--forward' '-p1' '-i' '/Users/clausklein/cmake/FetchContentTest/mypatch.patch'
patching file 'examples/regex.cpp'
CMake Warning at build/_deps/boost-cmake-src/cmake/prelude.cmake:36 (message):
  This CMake file has to be included before first project() command call!
Call Stack (most recent call first):
  build/_deps/boost-cmake-src/examples/CMakeLists.txt:3 (include)


-- Configuring done (3.2s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/clausklein/cmake/FetchContentTest/build
iMac:FetchContentTest clausklein$ ninja -C build/ 
ninja: Entering directory `build/'
[0/1] Re-running CMake...
patching file 'examples/regex.cpp'
Ignoring previously applied (or reversed) patch.
1 out of 1 hunks ignored--saving rejects to 'examples/regex.cpp.rej'
CMake Error at build/CMakeFiles/fc-tmp/boost-cmake/patch.cmake:7 (execute_process):
  execute_process last command failed
Call Stack (most recent call first):
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:1766 (include)
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:1729 (__FetchContent_doStepDirect)
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:1620 (__FetchContent_populateDirect)
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:2160:EVAL:2 (__FetchContent_doPopulation)
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:2160 (cmake_language)
  /Users/clausklein/.local/share/cmake-4.4/Modules/FetchContent.cmake:2401 (__FetchContent_Populate)
  CMakeLists.txt:42 (FetchContent_MakeAvailable)


-- Configuring incomplete, errors occurred!
FAILED: [code=1] build.ninja /Users/clausklein/cmake/FetchContentTest/build/cmake_install.cmake /Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-build/cmake_install.cmake /Users/clausklein/cmake/FetchContentTest/build/CTestTestfile.cmake /Users/clausklein/cmake/FetchContentTest/build/_deps/boost-cmake-build/CTestTestfile.cmake 
/Users/clausklein/.local/bin/cmake --regenerate-during-build -S/Users/clausklein/cmake/FetchContentTest -B/Users/clausklein/cmake/FetchContentTest/build
ninja: error: rebuilding 'build.ninja': subcommand failed
iMac:FetchContentTest clausklein$ 

I am able to reproduce by running the following steps:

  1. cmake -B build --log-level=VERBOSE --fresh
  2. cmake --build build
  3. touch CMakeLists.txt
  4. cmake --build build.

The --log-level=VERBOSE is required. This seems like a legitimate bug?

Yes, I can confirm, it is a site effect of cmake -L build --log-level=VERBOSE

Claus, I opened a CMake issue here: https://gitlab.kitware.com/cmake/cmake/-/work_items/28055

Thanks!
It fails caused by the stamp file is NOT newer than patch file after it was called with --log-level set the first time:

function(__FetchContent_doStepDirect)
  set(noValueOptions )
  set(singleValueOptions
    SCRIPT_FILE
    STAMP_FILE
  )
  set(multiValueOptions
    DEPENDS
  )
  cmake_parse_arguments(PARSE_ARGV 0 arg
    "${noValueOptions}" "${singleValueOptions}" "${multiValueOptions}"
  )

  if(NOT EXISTS ${arg_STAMP_FILE})
    set(do_step YES)
  else()
    set(do_step NO)
    foreach(dep_file IN LISTS arg_DEPENDS arg_SCRIPT_FILE)
      if(NOT EXISTS "${arg_STAMP_FILE}" OR
        NOT EXISTS "${dep_file}" OR
        NOT "${arg_STAMP_FILE}" IS_NEWER_THAN "${dep_file}")
        set(do_step YES)
        break()
      endif()
    endforeach()
  endif()

  if(do_step)
    include(${arg_SCRIPT_FILE})
    file(TOUCH "${arg_STAMP_FILE}")
  endif()
endfunction()

After locking into the ExternalProject and FetchContent code, I decided to use CMAKE_MESSAGE_LOG_LEVEL instead to use --log-level command line option.

Then the log-level is consistent in each cmake preset workflow step and everything works fine.