diff --git a/CHANGELOG.md b/CHANGELOG.md index fa059eb03..64f34e12c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ Bug Fixes: - Fix kit detection returning "unknown vendor" when using clang-cl compiler. [#4638](https://github.com/microsoft/vscode-cmake-tools/issues/4638) - Update testing framework to fix bugs when running tests of CMake Tools without a reliable internet connection. [#4891](https://github.com/microsoft/vscode-cmake-tools/pull/4891) [@cwalther](https://github.com/cwalther) +## 1.23.52 + +Bug Fixes: +- Fix regression where Visual Studio kits with an existing Ninja-based build cache would fail due to a generator mismatch. Ninja is now preferred again when available, stale VS kits derive the correct generator at runtime as a fallback, and the build directory is auto-cleaned on generator mismatches. [#4890](https://github.com/microsoft/vscode-cmake-tools/issues/4890) + ## 1.23 Features: diff --git a/docs/kits.md b/docs/kits.md index ee3012847..3f8f01a27 100644 --- a/docs/kits.md +++ b/docs/kits.md @@ -6,11 +6,12 @@ A _kit_ defines project-agnostic and configuration-agnostic info about how to bu - A Visual Studio installation: building for Visual Studio involves more than just finding the necessary compiler executable. Visual C++ requires certain environment variables to be set to tell it how to find and link to the Visual C++ toolchain headers and libraries. - A toolchain file: The low-level way to instruct CMake how to compile and link for a target. CMake Tools handles toolchain files using kits. -Kits are mostly CMake-generator-agnostic (a CMake generator writes the input files for the native build system). Visual Studio kits have a preferred generator that will be used as a fallback to ensure a matching MSBuild and .sln generator are used for the Visual C++ compiler. +Kits are mostly CMake-generator-agnostic (a CMake generator writes the input files for the native build system). The exception is Visual Studio kits: when you [scan for kits](#scan-for-kits), CMake Tools looks up the VS version and sets `preferredGenerator` on the kit to the matching CMake generator (e.g., `"Visual Studio 18 2026"` for VS 2026). If a kit was scanned before CMake Tools added support for that VS version, the extension derives the correct VS generator at runtime as a last-resort fallback — it is tried only after other generators like Ninja. If the version can't be determined, the kit falls through to default generators. > **Note:** -> * If you use the [Ninja](https://ninja-build.org/) build system, don't worry about Visual Studio CMake Generators. CMake Tools will prefer Ninja if it is present, unless configured otherwise. +> * If you use the [Ninja](https://ninja-build.org/) build system, don't worry about Visual Studio CMake Generators. CMake Tools will prefer Ninja if it is present, unless configured otherwise or the kit has a `preferredGenerator` set at scan time. To explicitly use a specific generator, set `cmake.generator` in your settings. > * If you change the active kit while a project is configured, the project configuration will be re-generated with the chosen kit. +> * When the selected generator doesn't match what's already in an existing `CMakeCache.txt`, CMake Tools cleans the prior configuration instead of letting CMake error out. > * Using a kit is recommended but optional. If you don't use a kit, CMake will perform its own automatic detection. ## How kits are found and defined @@ -52,7 +53,7 @@ Update [user-local kits](#user-local-kits) by running **Scan for Kits** from the - CMake tools includes `vswhere.exe`, which it uses to find Visual Studio instances installed on the system. -- For each of `x86`, `amd64`, `x86_amd64`, `x86_arm`, `x86_arm64`, `amd64_x86`, `amd64_arm`, and `amd64_arm64`, CMake Tools checks for installed Visual C++ environments. A kit is generated for each existing MSVC toolchain that is found. +- For each of `x86`, `amd64`, `x86_amd64`, `x86_arm`, `x86_arm64`, `amd64_x86`, `amd64_arm`, and `amd64_arm64`, CMake Tools checks for installed Visual C++ environments. A kit is generated for each existing MSVC toolchain that is found. For known VS versions (2019, 2022, 2026, etc.), the kit gets a `preferredGenerator` pointing at the right CMake generator, like `"Visual Studio 18 2026"`. If the VS version wasn't recognized at scan time, CMake Tools derives the correct generator at runtime as a last-resort fallback. Re-running **Scan for Kits** will set `preferredGenerator` permanently. **3. Save results to the user-local kits file** @@ -147,7 +148,15 @@ The following additional options may be specified: `preferredGenerator` -> The CMake generator that should be used with this kit if not the default. CMake Tools will still search in `cmake.preferredGenerators` from `settings.json`, but will fall back to this option if no generator from the user settings is available +> The CMake generator to use with this kit if not the default. For Visual Studio kits, this is set during [kit scanning](#scan-for-kits) based on the VS version. When picking a generator, CMake Tools checks these in order: +> +> 1. `cmake.generator` from your settings — if set, this wins outright; nothing below is consulted. +> 2. The kit's `preferredGenerator` (set at scan time for VS kits). +> 3. `cmake.preferredGenerators` from your settings, in order. +> 4. `Ninja`, then `Unix Makefiles` — only consulted when neither #2 nor #3 produced any candidate (i.e. the kit has no `preferredGenerator` and `cmake.preferredGenerators` is empty). +> 5. For VS kits that have no `preferredGenerator` of their own: the VS generator derived at runtime from the kit's VS version — pushed to the end of the candidate list, so it's tried after the Ninja/Unix Makefiles fallback when both apply. +> +> If a VS kit was scanned before the VS version mapping existed, CMake Tools derives the correct generator at runtime (#5) — but only after Ninja and Unix Makefiles. To make the VS generator the unconditional choice, either re-run **Scan for Kits** so the kit gets its own `preferredGenerator`, or set `cmake.generator` in your settings. `cmakeSettings` diff --git a/src/drivers/cmakeDriver.ts b/src/drivers/cmakeDriver.ts index cd8f6f933..865cf916d 100644 --- a/src/drivers/cmakeDriver.ts +++ b/src/drivers/cmakeDriver.ts @@ -32,7 +32,7 @@ import * as codeModel from '@cmt/drivers/codeModel'; import { Environment, EnvironmentUtils } from '@cmt/environmentVariables'; import { CMakeTask, CMakeTaskProvider, CustomBuildTaskTerminal } from '@cmt/cmakeTaskProvider'; import { getValue } from '@cmt/presets/preset'; -import { CacheEntry } from '@cmt/cache'; +import { CacheEntry, CMakeCache } from '@cmt/cache'; import { CMakeBuildRunner } from '@cmt/cmakeBuildRunner'; import { DebuggerInformation } from '@cmt/debug/cmakeDebugger/debuggerConfigureDriver'; import { onBuildSettingsChange, onTestSettingsChange, onPackageSettingsChange } from '@cmt/ui/util'; @@ -148,6 +148,18 @@ export interface InstallPath { * * This class defines the basis for what a driver must implement to work. */ +/** + * Compare a new generator name against a cached CMAKE_GENERATOR value. + * Returns true when both are defined and differ (i.e. a mismatch that needs cleaning). + * Pure function — no I/O — exported for direct unit testing. + */ +export function generatorMismatch(newGeneratorName: string | undefined, cachedGeneratorValue: string | undefined): boolean { + if (!newGeneratorName || !cachedGeneratorValue) { + return false; + } + return cachedGeneratorValue !== newGeneratorName; +} + export abstract class CMakeDriver implements vscode.Disposable { /** * Do the configuration process for the current project. @@ -622,6 +634,26 @@ export abstract class CMakeDriver implements vscode.Disposable { } } + /** + * Check if the generator to be used differs from what is cached in CMakeCache.txt. + */ + protected async _hasGeneratorChanged(newGeneratorName: string | undefined): Promise { + if (!newGeneratorName) { + return false; + } + const cachePath = this.cachePath; + if (!await fs.exists(cachePath)) { + return false; + } + const cache = await CMakeCache.fromPath(cachePath); + const cachedGenerator = cache.get('CMAKE_GENERATOR'); + if (generatorMismatch(newGeneratorName, cachedGenerator?.value)) { + log.info(localize('generator.changed', 'Generator changed from {0} to {1}; cleaning prior configuration', cachedGenerator!.value, newGeneratorName)); + return true; + } + return false; + } + /** * Remove the entire build directory. */ @@ -799,7 +831,8 @@ export abstract class CMakeDriver implements vscode.Disposable { await this._refreshExpansions(); const scope = this.workspaceFolder ? vscode.Uri.file(this.workspaceFolder) : undefined; const newBinaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope), this.expansionOptions)); - if (needsCleanIfKitChange && (newBinaryDir === oldBinaryDir)) { + const generatorChanged = await this._hasGeneratorChanged(this._generator?.name); + if ((needsCleanIfKitChange || generatorChanged) && (newBinaryDir === oldBinaryDir)) { await this._cleanPriorConfiguration(); } }); @@ -811,15 +844,9 @@ export abstract class CMakeDriver implements vscode.Disposable { log.debug(localize('cmakedriver.kit.set.to', 'CMakeDriver Kit set to {0}', kit.name)); this._kitEnvironmentVariables = await effectiveKitEnvironment(kit, this.expansionOptions); - // Place a kit preferred generator at the front of the list - // For VS kits that don't have preferredGenerator (e.g., scanned before a VS version was added), - // try to derive it from the VS installation. - let kitPreferredGenerator = kit.preferredGenerator; - if (!kitPreferredGenerator && kit.visualStudio) { - kitPreferredGenerator = await getVsKitPreferredGenerator(kit); - } - if (kitPreferredGenerator) { - preferredGenerators.unshift(kitPreferredGenerator); + // Place a kit preferred generator at the front of the list. + if (kit.preferredGenerator) { + preferredGenerators.unshift(kit.preferredGenerator); } // If no preferred generator is defined by the current kit or the user settings, @@ -833,6 +860,16 @@ export abstract class CMakeDriver implements vscode.Disposable { preferredGenerators.push({ name: "Unix Makefiles" }); } + // For VS kits that don't have preferredGenerator (e.g., scanned before + // a VS version was added), derive the VS generator as a last-resort + // fallback so it is tried only after Ninja / Unix Makefiles. + if (!kit.preferredGenerator && kit.visualStudio) { + const derived = await getVsKitPreferredGenerator(kit); + if (derived) { + preferredGenerators.push(derived); + } + } + // Use the "best generator" logic only if the user did not define a particular // generator to be used via the `cmake.generator` setting. if (this.config.generator) { @@ -1606,6 +1643,20 @@ export abstract class CMakeDriver implements vscode.Disposable { return { exitCode: -2, resultType: ConfigureResultType.Other }; } + // Safety net for the kits/variants path: if a setting change + // (cmake.generator / cmake.preferredGenerators) reloaded the driver + // without going through setKit(), the cached CMAKE_GENERATOR may + // differ from this._generator. Clean the prior configuration here + // so CMake doesn't fail with a "generator changed" error. + // Presets handle this in setConfigurePreset via configurePresetChangeNeedsClean. + if (!this.useCMakePresets + && !showCommandOnly + && !shouldUseCachedConfiguration + && trigger !== ConfigureTrigger.configureWithCache + && await this._hasGeneratorChanged(this._generator?.name)) { + await this._cleanPriorConfiguration(); + } + let expanded_flags: string[]; let defaultPresetName: string | undefined; if (this.useCMakePresets) { diff --git a/src/kits/kit.ts b/src/kits/kit.ts index 9e0b49dd7..1e8451b68 100644 --- a/src/kits/kit.ts +++ b/src/kits/kit.ts @@ -833,8 +833,8 @@ const VsGenerators: { [key: string]: string } = { }; /** - * Get the CMake generator name for a given Visual Studio version - * @param version The major version of Visual Studio (e.g., '17' for VS 2022, '18' for VS 2026) + * Get the CMake generator name for a given Visual Studio major version. + * @param version The major version string (e.g., '17', '18') * @returns The CMake generator name, or undefined if the version is not recognized */ export function vsGeneratorForVersion(version: string): string | undefined { @@ -876,12 +876,10 @@ export async function getVsKitPreferredGenerator(kit: Kit): Promise