Skip to content

Commit d36ebc9

Browse files
committed
improve Visual Studio generator handling and add tests for version mapping
- Modify kit scanning logic to derive preferred generators for Visual Studio kits lacking a predefined generator. - Introduce utility functions for mapping Visual Studio versions to CMake generators. - Add unit tests to verify correct generator mapping for various Visual Studio versions.
1 parent d1f2d64 commit d36ebc9

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Bug Fixes:
88
## 1.23.51
99

1010
Bug Fixes:
11-
- Fix regression where VS kits with existing Ninja cache would fail due to generator mismatch after update. [#4890](https://github.com/microsoft/vscode-cmake-tools/issues/4890)
11+
- Fix regression where Visual Studio kits with an existing Ninja-based build cache would fail after updating CMake Tools, due to a generator mismatch. Ninja is now preferred again when available. For stale VS kits missing `preferredGenerator`, the VS generator is derived at runtime as a last-resort fallback. A safety net auto-cleans the build directory when a generator mismatch with `CMakeCache.txt` is detected. [#4890](https://github.com/microsoft/vscode-cmake-tools/issues/4890)
1212

1313
## 1.23
1414

docs/kits.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ A _kit_ defines project-agnostic and configuration-agnostic info about how to bu
66
- 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.
77
- 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.
88

9-
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). This only happens at scan time. If a kit was scanned before CMake Tools knew about that VS version, the kit won't have a `preferredGenerator` and will fall through to whatever default generator is available (usually Ninja).
9+
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.
1010

1111
> **Note:**
12-
> * 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. A Visual Studio kit without `preferredGenerator` will just use Ninja.
12+
> * 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.
1313
> * If you change the active kit while a project is configured, the project configuration will be re-generated with the chosen kit.
1414
> * 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.
1515
> * Using a kit is recommended but optional. If you don't use a kit, CMake will perform its own automatic detection.
@@ -53,7 +53,7 @@ Update [user-local kits](#user-local-kits) by running **Scan for Kits** from the
5353

5454
- CMake tools includes `vswhere.exe`, which it uses to find Visual Studio instances installed on the system.
5555

56-
- 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 CMake Tools doesn't recognize the VS version yet, `preferredGenerator` is left unset and the kit will use Ninja or whatever default is available.
56+
- 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.
5757

5858
**3. Save results to the user-local kits file**
5959

@@ -154,8 +154,9 @@ The following additional options may be specified:
154154
> 2. `cmake.preferredGenerators` from your settings
155155
> 3. The kit's `preferredGenerator` (set at scan time for VS kits)
156156
> 4. Fallback: Ninja, then Unix Makefiles
157+
> 5. For VS kits without `preferredGenerator`: the derived VS generator (last-resort fallback)
157158
>
158-
> If your kit doesn't have a `preferredGenerator` (maybe it was scanned before CMake Tools added support for that VS version), it will just use the fallback. Re-running **Scan for Kits** will pick up the correct generator.
159+
> If a VS kit was scanned before the VS version mapping existed, CMake Tools derives the correct generator at runtime but only as a last resort — Ninja and Unix Makefiles are tried first. To use the VS generator by default, re-run **Scan for Kits** or set `cmake.generator` in your settings.
159160
160161
`cmakeSettings`
161162

src/drivers/cmakeDriver.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { CMakeOutputConsumer } from '@cmt/diagnostics/cmake';
1616
import { RawDiagnosticParser } from '@cmt/diagnostics/util';
1717
import { ProgressMessage } from '@cmt/drivers/drivers';
1818
import * as expand from '@cmt/expand';
19-
import { CMakeGenerator, effectiveKitEnvironment, Kit, kitChangeNeedsClean, KitDetect, getKitDetect, getVSKitEnvironment } from '@cmt/kits/kit';
19+
import { CMakeGenerator, effectiveKitEnvironment, Kit, kitChangeNeedsClean, KitDetect, getKitDetect, getVSKitEnvironment, getVsKitPreferredGenerator } from '@cmt/kits/kit';
2020
import * as logging from '@cmt/logging';
2121
import paths from '@cmt/paths';
2222
import { fs } from '@cmt/pr';
@@ -624,22 +624,22 @@ export abstract class CMakeDriver implements vscode.Disposable {
624624

625625
/**
626626
* Check if the generator to be used differs from what is cached in CMakeCache.txt.
627-
* If so, auto-clean the prior configuration to prevent CMake errors.
628627
*/
629-
protected async _cleanIfGeneratorChanged(newGeneratorName: string | undefined): Promise<void> {
628+
protected async _hasGeneratorChanged(newGeneratorName: string | undefined): Promise<boolean> {
630629
if (!newGeneratorName) {
631-
return;
630+
return false;
632631
}
633632
const cachePath = this.cachePath;
634633
if (!await fs.exists(cachePath)) {
635-
return;
634+
return false;
636635
}
637636
const cache = await CMakeCache.fromPath(cachePath);
638637
const cachedGenerator = cache.get('CMAKE_GENERATOR');
639638
if (cachedGenerator && cachedGenerator.value !== newGeneratorName) {
640639
log.info(localize('generator.changed', 'Generator changed from {0} to {1}; cleaning prior configuration', cachedGenerator.value, newGeneratorName));
641-
await this._cleanPriorConfiguration();
640+
return true;
642641
}
642+
return false;
643643
}
644644

645645
/**
@@ -819,10 +819,10 @@ export abstract class CMakeDriver implements vscode.Disposable {
819819
await this._refreshExpansions();
820820
const scope = this.workspaceFolder ? vscode.Uri.file(this.workspaceFolder) : undefined;
821821
const newBinaryDir = util.lightNormalizePath(await expand.expandString(this.config.buildDirectory(this.isMultiProject, scope), this.expansionOptions));
822-
if (needsCleanIfKitChange && (newBinaryDir === oldBinaryDir)) {
822+
const generatorChanged = await this._hasGeneratorChanged(this._generator?.name);
823+
if ((needsCleanIfKitChange || generatorChanged) && (newBinaryDir === oldBinaryDir)) {
823824
await this._cleanPriorConfiguration();
824825
}
825-
await this._cleanIfGeneratorChanged(this._generator?.name);
826826
});
827827
}
828828

@@ -832,7 +832,7 @@ export abstract class CMakeDriver implements vscode.Disposable {
832832
log.debug(localize('cmakedriver.kit.set.to', 'CMakeDriver Kit set to {0}', kit.name));
833833
this._kitEnvironmentVariables = await effectiveKitEnvironment(kit, this.expansionOptions);
834834

835-
// Place a kit preferred generator at the front of the list
835+
// Place a kit preferred generator at the front of the list.
836836
if (kit.preferredGenerator) {
837837
preferredGenerators.unshift(kit.preferredGenerator);
838838
}
@@ -848,6 +848,16 @@ export abstract class CMakeDriver implements vscode.Disposable {
848848
preferredGenerators.push({ name: "Unix Makefiles" });
849849
}
850850

851+
// For VS kits that don't have preferredGenerator (e.g., scanned before
852+
// a VS version was added), derive the VS generator as a last-resort
853+
// fallback so it is tried only after Ninja / Unix Makefiles.
854+
if (!kit.preferredGenerator && kit.visualStudio) {
855+
const derived = await getVsKitPreferredGenerator(kit);
856+
if (derived) {
857+
preferredGenerators.push(derived);
858+
}
859+
}
860+
851861
// Use the "best generator" logic only if the user did not define a particular
852862
// generator to be used via the `cmake.generator` setting.
853863
if (this.config.generator) {

src/kits/kit.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,58 @@ const VsGenerators: { [key: string]: string } = {
800800
18: 'Visual Studio 18 2026'
801801
};
802802

803+
/**
804+
* Get the CMake generator name for a given Visual Studio major version.
805+
* @param version The major version string (e.g., '17', '18')
806+
* @returns The CMake generator name, or undefined if the version is not recognized
807+
*/
808+
export function vsGeneratorForVersion(version: string): string | undefined {
809+
return VsGenerators[version];
810+
}
811+
812+
/**
813+
* Get the preferred CMake generator for a Visual Studio kit.
814+
* This is useful for kits that were scanned before a VS version was added to the VsGenerators mapping.
815+
* @param kit The Visual Studio kit
816+
* @returns The preferred generator, or undefined if the kit is not a VS kit or the VS version is not recognized
817+
*/
818+
export async function getVsKitPreferredGenerator(kit: Kit): Promise<CMakeGenerator | undefined> {
819+
if (!kit.visualStudio || !kit.visualStudioArchitecture) {
820+
return undefined;
821+
}
822+
823+
// If the kit already has a preferredGenerator, return it
824+
if (kit.preferredGenerator) {
825+
return kit.preferredGenerator;
826+
}
827+
828+
// Try to derive the preferredGenerator from the VS installation
829+
const vsInstall = await getVSInstallForKit(kit);
830+
if (!vsInstall) {
831+
return undefined;
832+
}
833+
834+
const version = /^(\d+)+./.exec(vsInstall.installationVersion);
835+
if (!version) {
836+
return undefined;
837+
}
838+
839+
const generatorName = VsGenerators[version[1]];
840+
if (!generatorName) {
841+
return undefined;
842+
}
843+
844+
const majorVersion = parseInt(vsInstall.installationVersion);
845+
const hostArch = kit.visualStudioArchitecture;
846+
const host: string = hostArch.toLowerCase().replace(/ /g, "").startsWith("host=") ? hostArch : "host=" + hostArch;
847+
848+
return {
849+
name: generatorName,
850+
platform: generatorPlatformFromVSArch[hostArch] as string || hostArch,
851+
toolset: majorVersion < 15 ? undefined : host
852+
};
853+
}
854+
803855
/**
804856
* Try to get a VSKit from a VS installation and architecture
805857
* @param inst A VS installation from vswhere

test/unit-tests/kit-scan.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,28 @@ suite('Kits scan test', () => {
240240
}).timeout(10000);
241241
});
242242

243+
suite('VS Generator mapping', () => {
244+
test('returns correct generator for VS 2022', () => {
245+
expect(kit.vsGeneratorForVersion('17')).to.eq('Visual Studio 17 2022');
246+
});
247+
248+
test('returns correct generator for VS 2026', () => {
249+
expect(kit.vsGeneratorForVersion('18')).to.eq('Visual Studio 18 2026');
250+
});
251+
252+
test('returns correct generator for VS 2019', () => {
253+
expect(kit.vsGeneratorForVersion('16')).to.eq('Visual Studio 16 2019');
254+
});
255+
256+
test('returns undefined for unknown version', () => {
257+
expect(kit.vsGeneratorForVersion('99')).to.be.undefined;
258+
});
259+
260+
test('returns correct generator for legacy VS120COMNTOOLS', () => {
261+
expect(kit.vsGeneratorForVersion('VS120COMNTOOLS')).to.eq('Visual Studio 12 2013');
262+
});
263+
});
264+
243265
suite('Kit change detection for generator regression (#4890)', () => {
244266
// These tests verify kitChangeNeedsClean correctly detects generator-related
245267
// kit changes, which is the safety net for the regression fixed in #4890.

0 commit comments

Comments
 (0)