Is it good or bad practice to set the CMAKE_TOOLCHAIN_FILE from within CMakeLists.txt?

…as opposed to passing it on the CMake command line or in CMakePresets.json?

I moved from a computer which had VCPKG_ROOT set to one that did not (although I have vcpkg on here). My CMAKE_TOOLCHAIN_FILE from my CMakePresets.json was no longer valid since I had it defined as "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake".

I found a suggestion on the vcpkg Github discussions to use vcpkg from a submodule so that a local path could be used and to set CMAKE_TOOLCHAIN_FILE in the CMakeLists.txt:

From experimentation I think it can work, but it has to appear before the project directive (I think that’s what made the difference), or else it doesn’t work. However I’m wondering if this suggestion squares with CMake good practice. I’m trying to balance “batteries included / works out of the box” with not over-constraining how my projects can be built.

In general, a project shouldn’t be setting CMAKE_TOOLCHAIN_FILE itself. That is normally something that the user should be in control of. They would normally set it either on the cmake command line or with a CMake preset. There are occasionally some scenarios where the project setting it can be justified, but those are typically restricted to tightly controlled company projects where the project must be built in a very specific way. Even then, presets are usually a better choice for such cases.

Posting to closed issue because similar question, but requesting clarification.

It seems the CMAKE_TOOLCHAIN_FILE is somewhat special. Given a scenario that arguably falls under the “restricted to tightly controlled projects”, consider:

cmake_minimum_required(VERSION 4.0)

# Stamp out a fake toolchain file (just for this example).
file(WRITE tc.cmake [[
message("Loading TC: ${CMAKE_CURRENT_LIST_FILE}")
]])

set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/tc.cmake")

option(TC OFF)

message(STATUS "TC entry: ${CMAKE_TOOLCHAIN_FILE}")
if(TC)
  # Try to wipe out all residue...
  unset(CMAKE_TOOLCHAIN_FILE)
  unset(CMAKE_TOOLCHAIN_FILE CACHE)

  set(CMAKE_TOOLCHAIN_FILE "something_that_does_not_exist.cmake")
  set(CMAKE_TOOLCHAIN_FILE "something_that_does_not_exist.cmake" CACHE FILEPATH "" FORCE)  # Try everything
  message(STATUS "TC set: ${CMAKE_TOOLCHAIN_FILE}")
endif()

# Will instigate toolchain processing...
project(tc)

So, first configuration, cmake ., processes the tc.cmake toolchain file. Splendid.
We can:

$ grep TOOLCHAIN CMakeCache.txt 
CMAKE_TOOLCHAIN_FILE:FILEPATH=<redacted>/tc.cmake

Now try to reconfigure based on option: cmake -DTC=ON .
The output will show cmake is still processing the original tc.cmake file.
But:

$ grep TOOLCHAIN CMakeCache.txt 
CMAKE_TOOLCHAIN_FILE:FILEPATH=something_that_does_not_exist.cmake

So, although we’ve managed to update the cache, none of the generated build files actually refer to it. This can be seen by grepping the Makefiles or Ninja build files.

So is it that once CMAKE_TOOLCHAIN_FILE is configured, then that’s it written in stone (well, unless the CMakeCache.txt is deleted)?

I don’t recall the details of what gets recorded where, but once you set a toolchain file for a build directory, you cannot change the toolchain file without clearing the build directory (or at least doing a cmake --fresh ... run to discard previously cached information).

1 Like