From ee6bfdc1f39d3919f810da83fcaa0b53701d7af0 Mon Sep 17 00:00:00 2001 From: Aiden Woodruff Date: Thu, 2 Jul 2026 16:58:51 -0400 Subject: [PATCH 1/3] 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 --- Source/cmGlobalGenerator.cxx | 19 +++------ Source/cmState.h | 19 +++++++++ Source/cmake.cxx | 40 ++++++++++--------- Source/cmake.h | 3 +- .../CompilerChange/RunCMakeTest.cmake | 10 +++++ .../ToolchainFromFileDeleted-step1-prep.cmake | 4 ++ .../ToolchainFromFileDeleted-step1-stdout.txt | 3 ++ .../ToolchainFromFileDeleted-step1.cmake | 2 + .../ToolchainFromFileDeleted-step2-prep.cmake | 5 +++ .../ToolchainFromFileDeleted-step2-stderr.txt | 5 +++ .../ToolchainFromFileDeleted-step2-stdout.txt | 3 ++ .../ToolchainFromFileDeleted-step2.cmake | 2 + 12 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-prep.cmake create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-stdout.txt create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1.cmake create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-prep.cmake create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stderr.txt create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stdout.txt create mode 100644 Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2.cmake diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index d7792d3096..ea0cabcb99 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -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; } } diff --git a/Source/cmState.h b/Source/cmState.h index 9ed91f5780..dc0b122983 100644 --- a/Source/cmState.h +++ b/Source/cmState.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include +#include #include #include #include @@ -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 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 DependencyProvider; bool ProcessingTopLevelIncludes = false; + std::map DeleteCacheChangeVars; + bool Reconfiguring = false; }; diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 80c648aef2..fc65e8175a 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2374,11 +2374,11 @@ struct SaveCacheEntry cmStateEnums::CacheEntryType type; }; -int cmake::HandleDeleteCacheVariables(std::string const& var) +int cmake::HandleDeleteCacheVariables( + std::map 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 delCacheVars = + this->State->GetDeleteCacheChangeVars(); + if (!delCacheVars.empty()) { + return this->HandleDeleteCacheVariables(delCacheVars); } return ret; } diff --git a/Source/cmake.h b/Source/cmake.h index 6344339e4f..99c11c4859 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -710,7 +710,8 @@ public: protected: void RunCheckForUnusedVariables(); - int HandleDeleteCacheVariables(std::string const& var); + int HandleDeleteCacheVariables( + std::map const& var); using RegisteredGeneratorsVector = std::vector>; diff --git a/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake b/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake index 105d1aef46..7c89c1d5b4 100644 --- a/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake +++ b/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake @@ -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) diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-prep.cmake b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-prep.cmake new file mode 100644 index 0000000000..a3f481cde0 --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-prep.cmake @@ -0,0 +1,4 @@ +file(WRITE + "${RunCMake_TEST_BINARY_DIR}/toolchain.txt" + "${RunCMake_BINARY_DIR}/baz.cmake\n" +) diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-stdout.txt b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-stdout.txt new file mode 100644 index 0000000000..33e2b03cc6 --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1-stdout.txt @@ -0,0 +1,3 @@ +-- Toolchain file: [^ +]*/Tests/RunCMake/CompilerChange/baz\.cmake +-- Using toolchain: baz diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1.cmake b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1.cmake new file mode 100644 index 0000000000..68a8f6a7d7 --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step1.cmake @@ -0,0 +1,2 @@ +message(STATUS "Toolchain file: ${CMAKE_TOOLCHAIN_FILE}") +message(STATUS "Using toolchain: ${toolchain_var}") diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-prep.cmake b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-prep.cmake new file mode 100644 index 0000000000..764dd4963f --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-prep.cmake @@ -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" +) diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stderr.txt b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stderr.txt new file mode 100644 index 0000000000..e65f0d973a --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stderr.txt @@ -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 diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stdout.txt b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stdout.txt new file mode 100644 index 0000000000..1b041ef37b --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2-stdout.txt @@ -0,0 +1,3 @@ +-- Toolchain file: [^ +]*/Tests/RunCMake/CompilerChange/bar\.cmake +-- Using toolchain: bar diff --git a/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2.cmake b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2.cmake new file mode 100644 index 0000000000..68a8f6a7d7 --- /dev/null +++ b/Tests/RunCMake/CompilerChange/ToolchainFromFileDeleted-step2.cmake @@ -0,0 +1,2 @@ +message(STATUS "Toolchain file: ${CMAKE_TOOLCHAIN_FILE}") +message(STATUS "Using toolchain: ${toolchain_var}") From ca0bf9848d4727bbaabee2289a3a78661c1c5dd5 Mon Sep 17 00:00:00 2001 From: Aiden Woodruff Date: Fri, 3 Jul 2026 10:00:16 -0400 Subject: [PATCH 2/3] cmake: Retain cache from presets and command line before re-configure Triggering a cache reset and re-configure by changing the compiler or CMAKE_TOOLCHAIN_FILE should be a shortcut for cmake --fresh. Losing the cache variables from the command line and especially presets can have an unexpected effect. This commit restores the preset cache variables and reprocesses cache-affecting variables from the immediate command line. Fixes: #27899 --- Source/cmake.cxx | 12 ++++++++++++ Source/cmake.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Source/cmake.cxx b/Source/cmake.cxx index fc65e8175a..9cd8734353 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2192,6 +2192,9 @@ bool cmake::SetArgsFromPreset(cmCMakePresetsConfigureArgs const& args, this->SetTraceFile(expandedPreset->TraceRedirect); } + // Store preset variables in case of cache reset. + this->InitialPresetVariables = this->UnprocessedPresetVariables; + return true; } @@ -2410,6 +2413,15 @@ int cmake::HandleDeleteCacheVariables( this->DeleteCache(this->GetHomeOutputDirectory()); // load the empty cache this->LoadCache(); +#ifndef CMAKE_BOOTSTRAP + // Restore preset cache variables. + this->UnprocessedPresetVariables = this->InitialPresetVariables; + this->ProcessPresetVariables(); +#endif + // Restore command line cache variables (from this invocation cmake only). + bool resetArgsSuccess = this->SetCacheArgs(this->cmdArgs); + assert(resetArgsSuccess); + (void)resetArgsSuccess; // restore the changed compilers for (SaveCacheEntry const& i : saved) { this->AddCacheEntry(i.key, i.value, i.help, i.type); diff --git a/Source/cmake.h b/Source/cmake.h index 99c11c4859..311c858674 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -806,6 +806,8 @@ private: UnprocessedPresetVariables; std::map> UnprocessedPresetEnvironment; + std::map> + InitialPresetVariables; #endif #if !defined(CMAKE_BOOTSTRAP) From ca3d1007f06acc2e8f3cc8ddd2542a6f5d3944df Mon Sep 17 00:00:00 2001 From: Aiden Woodruff Date: Fri, 3 Jul 2026 10:20:20 -0400 Subject: [PATCH 3/3] Tests: Simplify RunCMake.CompilerChange The test suite can be simplified now that command line arguments are restored after resetting the cache due to a compiler change. Previously the test case name had to be set in the environment and now that is not required. --- Tests/RunCMake/CompilerChange/CMakeLists.txt | 3 --- Tests/RunCMake/CompilerChange/EmptyCompiler-stderr.txt | 2 +- Tests/RunCMake/CompilerChange/RunCMakeTest.cmake | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Tests/RunCMake/CompilerChange/CMakeLists.txt b/Tests/RunCMake/CompilerChange/CMakeLists.txt index d389c9e71a..788c1cdf42 100644 --- a/Tests/RunCMake/CompilerChange/CMakeLists.txt +++ b/Tests/RunCMake/CompilerChange/CMakeLists.txt @@ -1,7 +1,4 @@ cmake_minimum_required(VERSION 3.10) -if(NOT RunCMake_TEST) - set(RunCMake_TEST "$ENV{RunCMake_TEST}") # needed when cache is deleted -endif() if(RunCMake_TEST MATCHES "^ToolchainFromFile") # CMAKE_TOOLCHAIN_FILE should be set before project. The ToolchainFromFile # cases test detecting toolchain path changes between CMake invocations and diff --git a/Tests/RunCMake/CompilerChange/EmptyCompiler-stderr.txt b/Tests/RunCMake/CompilerChange/EmptyCompiler-stderr.txt index f4bed2f533..ff17824567 100644 --- a/Tests/RunCMake/CompilerChange/EmptyCompiler-stderr.txt +++ b/Tests/RunCMake/CompilerChange/EmptyCompiler-stderr.txt @@ -10,4 +10,4 @@ CMake Error at EmptyCompiler\.cmake:2 \(enable_language\): variable "CC" or the CMake cache entry CMAKE_C_COMPILER to the full path to the compiler, or to the compiler name if it is in the PATH\. Call Stack \(most recent call first\): - CMakeLists\.txt:14 \(include\)$ + CMakeLists\.txt:11 \(include\)$ diff --git a/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake b/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake index 7c89c1d5b4..4c50b468ee 100644 --- a/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake +++ b/Tests/RunCMake/CompilerChange/RunCMakeTest.cmake @@ -27,12 +27,9 @@ configure_file(${ccIn} ${cc2} @ONLY) block() set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ChangeCompiler-build) - set(ENV{RunCMake_TEST} "FirstCompiler") run_cmake_with_options(FirstCompiler -DCMAKE_C_COMPILER=${cc1}) set(RunCMake_TEST_NO_CLEAN 1) - set(ENV{RunCMake_TEST} "SecondCompiler") run_cmake_with_options(SecondCompiler -DCMAKE_C_COMPILER=${cc2}) - set(ENV{RunCMake_TEST} "EmptyCompiler") run_cmake_with_options(EmptyCompiler -DCMAKE_C_COMPILER=) endblock() @@ -55,7 +52,6 @@ block() set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ToolchainFromFile-build) run_cmake(ToolchainFromFile-step1) set(RunCMake_TEST_NO_CLEAN 1) - set(ENV{RunCMake_TEST} "ToolchainFromFile-step2") run_cmake(ToolchainFromFile-step2) endblock() @@ -65,7 +61,6 @@ 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() @@ -76,7 +71,6 @@ block() "-DCMAKE_TOOLCHAIN_FILE=${RunCMake_BINARY_DIR}/foo.cmake" ) set(RunCMake_TEST_NO_CLEAN 1) - set(ENV{RunCMake_TEST} "ToolchainFromCmdline-step2") run_cmake_with_options(ToolchainFromCmdline-step2 "-DCMAKE_TOOLCHAIN_FILE=${RunCMake_BINARY_DIR}/bar.cmake" )