CMAKE_TOOLCHAIN_FILE: Improve recovery from changed compiler or toolchain file

Replace __CMAKE_DELETE_CACHE_CHANGE_VARS_ with a set of changed
variables in cmState to avoid interference from scripts and simplify
handling of changed variables.

Errors during configure step will block re-configure when the toolchain
file has changed. Downstream errors can be caused by failing in
EnableLanguage without an error, behavior introduced in 7a989ed58a
(CMAKE_TOOLCHAIN_FILE: Detect path changes and reset cache, 2026-06-24).
Errors directly related to a changed toolchain file should be fixed by
using the new one.

Trigger a fatal error when the toolchain file change is detected to
avoid wasteful configure that will be reset anyway, then re-configure
only for that specific case. Avoid re-configuring multiple times.

Fixes: #27867
This commit is contained in:
Aiden Woodruff
2026-07-03 09:25:33 -04:00
parent 1fde7f4599
commit ee6bfdc1f3
12 changed files with 81 additions and 34 deletions
+5 -14
View File
@@ -295,17 +295,8 @@ void cmGlobalGenerator::ResolveLanguageCompiler(std::string const& lang,
}
cmCMakePath foundPath = path;
if (foundPath.Normal() != cachedPath.Normal()) {
cmValue cvars = this->GetCMakeInstance()->GetState()->GetGlobalProperty(
"__CMAKE_DELETE_CACHE_CHANGE_VARS_");
if (cvars) {
changeVars += *cvars;
changeVars += ";";
}
changeVars += langComp;
changeVars += ";";
changeVars += *cname;
this->GetCMakeInstance()->GetState()->SetGlobalProperty(
"__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars);
this->GetCMakeInstance()->GetState()->AddDeleteCacheChangeVar(langComp,
*cname);
}
}
}
@@ -716,12 +707,12 @@ void cmGlobalGenerator::EnableLanguage(
cmValue storedToolchainFile =
mf->GetDefinition("_CMAKE_SYSTEM_TOOLCHAIN_FILE");
if (toolchainFile && toolchainFile != storedToolchainFile) {
mf->GetState()->AppendGlobalProperty(
"__CMAKE_DELETE_CACHE_CHANGE_VARS_",
"CMAKE_TOOLCHAIN_FILE;" + *toolchainFile);
mf->GetState()->AddDeleteCacheChangeVar("CMAKE_TOOLCHAIN_FILE",
*toolchainFile);
for (std::string const& lang : cur_languages) {
this->LanguagesInProgress.erase(lang);
}
cmSystemTools::SetFatalErrorOccurred();
return;
}
}
+19
View File
@@ -5,6 +5,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
@@ -263,6 +264,22 @@ public:
}
bool InTopLevelIncludes() const { return this->ProcessingTopLevelIncludes; }
void ClearDeleteCacheChangeVars() { this->DeleteCacheChangeVars.clear(); }
void AddDeleteCacheChangeVar(std::string var, std::string value)
{
this->DeleteCacheChangeVars[var] = value;
}
std::map<std::string, std::string> GetDeleteCacheChangeVars() const
{
return this->DeleteCacheChangeVars;
}
void SetReconfiguring(bool reconfiguring)
{
this->Reconfiguring = reconfiguring;
}
bool IsReconfiguring() const { return this->Reconfiguring; }
private:
friend class cmake;
cmStateSnapshot Reset(cmStateSnapshot const& diagnosticState);
@@ -342,4 +359,6 @@ private:
TryCompile IsTryCompile = TryCompile::No;
cm::optional<cmDependencyProvider> DependencyProvider;
bool ProcessingTopLevelIncludes = false;
std::map<std::string, std::string> DeleteCacheChangeVars;
bool Reconfiguring = false;
};
+21 -19
View File
@@ -2374,11 +2374,11 @@ struct SaveCacheEntry
cmStateEnums::CacheEntryType type;
};
int cmake::HandleDeleteCacheVariables(std::string const& var)
int cmake::HandleDeleteCacheVariables(
std::map<std::string, std::string> const& vars)
{
cmList argsSplit{ var, cmList::EmptyElements::Yes };
// erase the property to avoid infinite recursion
this->State->SetGlobalProperty("__CMAKE_DELETE_CACHE_CHANGE_VARS_", "");
// erase the set to avoid infinite recursion
this->State->ClearDeleteCacheChangeVars();
if (this->GetIsInTryCompile()) {
return 0;
}
@@ -2388,18 +2388,11 @@ int cmake::HandleDeleteCacheVariables(std::string const& var)
<< "You have changed variables that require your cache to be deleted.\n"
"Configure will be re-run and you may have to reset some variables.\n"
"The following variables have changed:\n";
for (auto i = argsSplit.begin(); i != argsSplit.end(); ++i) {
for (auto const& var : vars) {
SaveCacheEntry save;
save.key = *i;
warning << *i << "= ";
i++;
if (i != argsSplit.end()) {
save.value = *i;
warning << *i << '\n';
} else {
warning << '\n';
i -= 1;
}
save.key = var.first;
save.value = var.second;
warning << save.key << "= " << save.value << '\n';
cmValue existingValue = this->State->GetCacheEntryValue(save.key);
if (existingValue) {
save.type = this->State->GetCacheEntryType(save.key);
@@ -2425,6 +2418,15 @@ int cmake::HandleDeleteCacheVariables(std::string const& var)
// avoid reconfigure if there were errors
if (!cmSystemTools::GetErrorOccurredFlag()) {
// re-run configure
this->State->SetReconfiguring(true);
return this->Configure();
}
// Toolchain changes trigger a fatal error, but reconfiguring with the new
// toolchain should fix them.
if (vars.count("CMAKE_TOOLCHAIN_FILE") && !this->State->IsReconfiguring()) {
cmSystemTools::ResetErrorOccurredFlag();
this->State->SetReconfiguring(true);
return this->Configure();
}
return 0;
@@ -2523,10 +2525,10 @@ int cmake::Configure()
cmStateEnums::INTERNAL);
int ret = this->ActualConfigure();
cmValue delCacheVars =
this->State->GetGlobalProperty("__CMAKE_DELETE_CACHE_CHANGE_VARS_");
if (delCacheVars && !delCacheVars->empty()) {
return this->HandleDeleteCacheVariables(*delCacheVars);
std::map<std::string, std::string> delCacheVars =
this->State->GetDeleteCacheChangeVars();
if (!delCacheVars.empty()) {
return this->HandleDeleteCacheVariables(delCacheVars);
}
return ret;
}
+2 -1
View File
@@ -710,7 +710,8 @@ public:
protected:
void RunCheckForUnusedVariables();
int HandleDeleteCacheVariables(std::string const& var);
int HandleDeleteCacheVariables(
std::map<std::string, std::string> const& var);
using RegisteredGeneratorsVector =
std::vector<std::unique_ptr<cmGlobalGeneratorFactory>>;
@@ -59,6 +59,16 @@ block()
run_cmake(ToolchainFromFile-step2)
endblock()
# New toolchain path comes from file but old toolchain has been deleted
file(WRITE "${RunCMake_BINARY_DIR}/baz.cmake" "set(toolchain_var baz)\n")
block()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ToolchainFromFileDeleted-build)
run_cmake(ToolchainFromFileDeleted-step1)
set(RunCMake_TEST_NO_CLEAN 1)
set(ENV{RunCMake_TEST} "ToolchainFromFileDeleted-step2")
run_cmake(ToolchainFromFileDeleted-step2)
endblock()
# New toolchain path comes from command line
block()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ToolchainFromCmdline-build)
@@ -0,0 +1,4 @@
file(WRITE
"${RunCMake_TEST_BINARY_DIR}/toolchain.txt"
"${RunCMake_BINARY_DIR}/baz.cmake\n"
)
@@ -0,0 +1,3 @@
-- Toolchain file: [^
]*/Tests/RunCMake/CompilerChange/baz\.cmake
-- Using toolchain: baz
@@ -0,0 +1,2 @@
message(STATUS "Toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
message(STATUS "Using toolchain: ${toolchain_var}")
@@ -0,0 +1,5 @@
file(REMOVE "${RunCMake_BINARY_DIR}/baz.cmake")
file(WRITE
"${RunCMake_TEST_BINARY_DIR}/toolchain.txt"
"${RunCMake_BINARY_DIR}/bar.cmake\n"
)
@@ -0,0 +1,5 @@
You have changed variables that require your cache to be deleted.
Configure will be re-run and you may have to reset some variables.
The following variables have changed:
CMAKE_TOOLCHAIN_FILE= [^
]*/Tests/RunCMake/CompilerChange/bar\.cmake
@@ -0,0 +1,3 @@
-- Toolchain file: [^
]*/Tests/RunCMake/CompilerChange/bar\.cmake
-- Using toolchain: bar
@@ -0,0 +1,2 @@
message(STATUS "Toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
message(STATUS "Using toolchain: ${toolchain_var}")