Skip to content

Commit 6dcc8be

Browse files
committed
Enhance generator mismatch detection and clean-up logic
- Added a new method to clean prior configurations if the generator changes.. - Removed deprecated functions related to Visual Studio generator mapping. - Introduced unit tests to verify generator mismatch detection and kit change logic.
1 parent 536a24a commit 6dcc8be

5 files changed

Lines changed: 247 additions & 75 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# What's New?
22

3+
## 1.23.51
4+
5+
Bug Fixes:
6+
- 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)
7+
38
## 1.23
49

510
Features:

src/drivers/cmakeDriver.ts

Lines changed: 25 additions & 10 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, getVsKitPreferredGenerator } from '@cmt/kits/kit';
19+
import { CMakeGenerator, effectiveKitEnvironment, Kit, kitChangeNeedsClean, KitDetect, getKitDetect, getVSKitEnvironment } from '@cmt/kits/kit';
2020
import * as logging from '@cmt/logging';
2121
import paths from '@cmt/paths';
2222
import { fs } from '@cmt/pr';
@@ -32,7 +32,7 @@ import * as codeModel from '@cmt/drivers/codeModel';
3232
import { Environment, EnvironmentUtils } from '@cmt/environmentVariables';
3333
import { CMakeTask, CMakeTaskProvider, CustomBuildTaskTerminal } from '@cmt/cmakeTaskProvider';
3434
import { getValue } from '@cmt/presets/preset';
35-
import { CacheEntry } from '@cmt/cache';
35+
import { CacheEntry, CMakeCache } from '@cmt/cache';
3636
import { CMakeBuildRunner } from '@cmt/cmakeBuildRunner';
3737
import { DebuggerInformation } from '@cmt/debug/cmakeDebugger/debuggerConfigureDriver';
3838
import { onBuildSettingsChange, onTestSettingsChange, onPackageSettingsChange } from '@cmt/ui/util';
@@ -622,6 +622,26 @@ export abstract class CMakeDriver implements vscode.Disposable {
622622
}
623623
}
624624

625+
/**
626+
* 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.
628+
*/
629+
protected async _cleanIfGeneratorChanged(newGeneratorName: string | undefined): Promise<void> {
630+
if (!newGeneratorName) {
631+
return;
632+
}
633+
const cachePath = this.cachePath;
634+
if (!await fs.exists(cachePath)) {
635+
return;
636+
}
637+
const cache = await CMakeCache.fromPath(cachePath);
638+
const cachedGenerator = cache.get('CMAKE_GENERATOR');
639+
if (cachedGenerator && cachedGenerator.value !== newGeneratorName) {
640+
log.info(localize('generator.changed', 'Generator changed from {0} to {1}; cleaning prior configuration', cachedGenerator.value, newGeneratorName));
641+
await this._cleanPriorConfiguration();
642+
}
643+
}
644+
625645
/**
626646
* Remove the entire build directory.
627647
*/
@@ -802,6 +822,7 @@ export abstract class CMakeDriver implements vscode.Disposable {
802822
if (needsCleanIfKitChange && (newBinaryDir === oldBinaryDir)) {
803823
await this._cleanPriorConfiguration();
804824
}
825+
await this._cleanIfGeneratorChanged(this._generator?.name);
805826
});
806827
}
807828

@@ -812,14 +833,8 @@ export abstract class CMakeDriver implements vscode.Disposable {
812833
this._kitEnvironmentVariables = await effectiveKitEnvironment(kit, this.expansionOptions);
813834

814835
// Place a kit preferred generator at the front of the list
815-
// For VS kits that don't have preferredGenerator (e.g., scanned before a VS version was added),
816-
// try to derive it from the VS installation.
817-
let kitPreferredGenerator = kit.preferredGenerator;
818-
if (!kitPreferredGenerator && kit.visualStudio) {
819-
kitPreferredGenerator = await getVsKitPreferredGenerator(kit);
820-
}
821-
if (kitPreferredGenerator) {
822-
preferredGenerators.unshift(kitPreferredGenerator);
836+
if (kit.preferredGenerator) {
837+
preferredGenerators.unshift(kit.preferredGenerator);
823838
}
824839

825840
// If no preferred generator is defined by the current kit or the user settings,

src/kits/kit.ts

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -800,60 +800,6 @@ 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 version
805-
* @param version The major version of Visual Studio (e.g., '17' for VS 2022, '18' for VS 2026)
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-
// For VS kits, use the hostArch as the target platform (x64 -> x64)
848-
const targetArch = hostArch;
849-
850-
return {
851-
name: generatorName,
852-
platform: generatorPlatformFromVSArch[targetArch] as string || targetArch,
853-
toolset: majorVersion < 15 ? undefined : host
854-
};
855-
}
856-
857803
/**
858804
* Try to get a VSKit from a VS installation and architecture
859805
* @param inst A VS installation from vswhere
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* eslint-disable no-unused-expressions */
2+
import { expect } from 'chai';
3+
import { CMakeCache, CacheEntry } from '@cmt/cache';
4+
5+
suite('Generator mismatch detection', () => {
6+
// Test the core logic that _cleanIfGeneratorChanged relies on:
7+
// reading CMAKE_GENERATOR from cache and comparing with new generator
8+
9+
test('Detects generator mismatch when cache has Ninja but VS generator selected', () => {
10+
const cacheContent = [
11+
'# This is the CMakeCache file.',
12+
'CMAKE_GENERATOR:INTERNAL=Ninja',
13+
''
14+
].join('\n');
15+
const entries = CMakeCache.parseCache(cacheContent);
16+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
17+
expect(cachedGenerator).to.not.be.undefined;
18+
expect(cachedGenerator.value).to.eq('Ninja');
19+
// Simulating the comparison in _cleanIfGeneratorChanged
20+
const newGenerator = 'Visual Studio 18 2026';
21+
expect(cachedGenerator.value).to.not.eq(newGenerator);
22+
});
23+
24+
test('No mismatch when cache generator matches selected generator', () => {
25+
const cacheContent = [
26+
'CMAKE_GENERATOR:INTERNAL=Ninja',
27+
''
28+
].join('\n');
29+
const entries = CMakeCache.parseCache(cacheContent);
30+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
31+
expect(cachedGenerator).to.not.be.undefined;
32+
expect(cachedGenerator.value).to.eq('Ninja');
33+
});
34+
35+
test('No mismatch detection when cache is empty', () => {
36+
const cacheContent = '';
37+
const entries = CMakeCache.parseCache(cacheContent);
38+
const cachedGenerator = entries.get('CMAKE_GENERATOR');
39+
expect(cachedGenerator).to.be.undefined;
40+
// _cleanIfGeneratorChanged would return early (no cached generator)
41+
});
42+
43+
test('No mismatch detection when CMAKE_GENERATOR not in cache', () => {
44+
const cacheContent = [
45+
'CMAKE_BUILD_TYPE:STRING=Debug',
46+
'CMAKE_INSTALL_PREFIX:PATH=/usr/local',
47+
''
48+
].join('\n');
49+
const entries = CMakeCache.parseCache(cacheContent);
50+
const cachedGenerator = entries.get('CMAKE_GENERATOR');
51+
expect(cachedGenerator).to.be.undefined;
52+
});
53+
54+
test('Detects mismatch with Visual Studio generators of different versions', () => {
55+
const cacheContent = [
56+
'CMAKE_GENERATOR:INTERNAL=Visual Studio 17 2022',
57+
''
58+
].join('\n');
59+
const entries = CMakeCache.parseCache(cacheContent);
60+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
61+
expect(cachedGenerator).to.not.be.undefined;
62+
const newGenerator = 'Visual Studio 18 2026';
63+
expect(cachedGenerator.value).to.not.eq(newGenerator);
64+
});
65+
66+
test('No mismatch when both are Visual Studio 18 2026', () => {
67+
const cacheContent = [
68+
'CMAKE_GENERATOR:INTERNAL=Visual Studio 18 2026',
69+
''
70+
].join('\n');
71+
const entries = CMakeCache.parseCache(cacheContent);
72+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
73+
expect(cachedGenerator).to.not.be.undefined;
74+
expect(cachedGenerator.value).to.eq('Visual Studio 18 2026');
75+
});
76+
77+
test('Detects mismatch switching from VS generator to Ninja', () => {
78+
const cacheContent = [
79+
'CMAKE_GENERATOR:INTERNAL=Visual Studio 18 2026',
80+
''
81+
].join('\n');
82+
const entries = CMakeCache.parseCache(cacheContent);
83+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
84+
expect(cachedGenerator).to.not.be.undefined;
85+
const newGenerator = 'Ninja';
86+
expect(cachedGenerator.value).to.not.eq(newGenerator);
87+
});
88+
89+
test('Handles Unix Makefiles generator in cache', () => {
90+
const cacheContent = [
91+
'CMAKE_GENERATOR:INTERNAL=Unix Makefiles',
92+
''
93+
].join('\n');
94+
const entries = CMakeCache.parseCache(cacheContent);
95+
const cachedGenerator = entries.get('CMAKE_GENERATOR') as CacheEntry;
96+
expect(cachedGenerator).to.not.be.undefined;
97+
expect(cachedGenerator.value).to.eq('Unix Makefiles');
98+
const newGenerator = 'Ninja';
99+
expect(cachedGenerator.value).to.not.eq(newGenerator);
100+
});
101+
});

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

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -240,25 +240,130 @@ 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');
243+
suite('Kit change detection for generator regression (#4890)', () => {
244+
// These tests verify kitChangeNeedsClean correctly detects generator-related
245+
// kit changes, which is the safety net for the regression fixed in #4890.
246+
247+
test('Returns false for null old kit (first kit selection)', () => {
248+
const newKit: kit.Kit = {
249+
name: 'Visual Studio Community 2026 Release - amd64',
250+
visualStudio: 'VisualStudio.18.0',
251+
visualStudioArchitecture: 'x64',
252+
isTrusted: true,
253+
preferredGenerator: {
254+
name: 'Visual Studio 18 2026',
255+
platform: 'x64',
256+
toolset: 'host=x64'
257+
}
258+
};
259+
expect(kit.kitChangeNeedsClean(newKit, null)).to.be.false;
246260
});
247261

248-
test('returns correct generator for VS 2026', () => {
249-
expect(kit.vsGeneratorForVersion('18')).to.eq('Visual Studio 18 2026');
262+
test('Returns true when preferredGenerator changes from undefined to VS generator', () => {
263+
const oldKit: kit.Kit = {
264+
name: 'Visual Studio Community 2026 Release - amd64',
265+
visualStudio: 'VisualStudio.18.0',
266+
visualStudioArchitecture: 'x64',
267+
isTrusted: true
268+
// preferredGenerator is undefined — simulates kit scanned before VS 2026 mapping
269+
};
270+
const newKit: kit.Kit = {
271+
name: 'Visual Studio Community 2026 Release - amd64',
272+
visualStudio: 'VisualStudio.18.0',
273+
visualStudioArchitecture: 'x64',
274+
isTrusted: true,
275+
preferredGenerator: {
276+
name: 'Visual Studio 18 2026',
277+
platform: 'x64',
278+
toolset: 'host=x64'
279+
}
280+
};
281+
expect(kit.kitChangeNeedsClean(newKit, oldKit)).to.be.true;
282+
});
283+
284+
test('Returns false when kits have identical preferredGenerator', () => {
285+
const generator = {
286+
name: 'Visual Studio 18 2026',
287+
platform: 'x64',
288+
toolset: 'host=x64'
289+
};
290+
const oldKit: kit.Kit = {
291+
name: 'Visual Studio Community 2026 Release - amd64',
292+
visualStudio: 'VisualStudio.18.0',
293+
visualStudioArchitecture: 'x64',
294+
isTrusted: true,
295+
preferredGenerator: { ...generator }
296+
};
297+
const newKit: kit.Kit = {
298+
name: 'Visual Studio Community 2026 Release - amd64',
299+
visualStudio: 'VisualStudio.18.0',
300+
visualStudioArchitecture: 'x64',
301+
isTrusted: true,
302+
preferredGenerator: { ...generator }
303+
};
304+
expect(kit.kitChangeNeedsClean(newKit, oldKit)).to.be.false;
250305
});
251306

252-
test('returns correct generator for VS 2019', () => {
253-
expect(kit.vsGeneratorForVersion('16')).to.eq('Visual Studio 16 2019');
307+
test('Returns true when switching from VS generator to no generator', () => {
308+
const oldKit: kit.Kit = {
309+
name: 'Visual Studio Community 2026 Release - amd64',
310+
visualStudio: 'VisualStudio.18.0',
311+
visualStudioArchitecture: 'x64',
312+
isTrusted: true,
313+
preferredGenerator: {
314+
name: 'Visual Studio 18 2026',
315+
platform: 'x64',
316+
toolset: 'host=x64'
317+
}
318+
};
319+
const newKit: kit.Kit = {
320+
name: 'GCC 12.0.0',
321+
isTrusted: true
322+
// No preferredGenerator — typical GCC kit uses Ninja default
323+
};
324+
expect(kit.kitChangeNeedsClean(newKit, oldKit)).to.be.true;
254325
});
255326

256-
test('returns undefined for unknown version', () => {
257-
expect(kit.vsGeneratorForVersion('99')).to.be.undefined;
327+
test('Returns true when generator name changes between VS versions', () => {
328+
const oldKit: kit.Kit = {
329+
name: 'Visual Studio Community 2022 Release - amd64',
330+
visualStudio: 'VisualStudio.17.0',
331+
visualStudioArchitecture: 'x64',
332+
isTrusted: true,
333+
preferredGenerator: {
334+
name: 'Visual Studio 17 2022',
335+
platform: 'x64',
336+
toolset: 'host=x64'
337+
}
338+
};
339+
const newKit: kit.Kit = {
340+
name: 'Visual Studio Community 2026 Release - amd64',
341+
visualStudio: 'VisualStudio.18.0',
342+
visualStudioArchitecture: 'x64',
343+
isTrusted: true,
344+
preferredGenerator: {
345+
name: 'Visual Studio 18 2026',
346+
platform: 'x64',
347+
toolset: 'host=x64'
348+
}
349+
};
350+
expect(kit.kitChangeNeedsClean(newKit, oldKit)).to.be.true;
258351
});
259352

260-
test('returns correct generator for legacy VS120COMNTOOLS', () => {
261-
expect(kit.vsGeneratorForVersion('VS120COMNTOOLS')).to.eq('Visual Studio 12 2013');
353+
test('Returns false when both kits have no preferredGenerator', () => {
354+
const oldKit: kit.Kit = {
355+
name: 'Visual Studio Community 2026 Release - amd64',
356+
visualStudio: 'VisualStudio.18.0',
357+
visualStudioArchitecture: 'x64',
358+
isTrusted: true
359+
};
360+
const newKit: kit.Kit = {
361+
name: 'Visual Studio Community 2026 Release - amd64',
362+
visualStudio: 'VisualStudio.18.0',
363+
visualStudioArchitecture: 'x64',
364+
isTrusted: true
365+
};
366+
expect(kit.kitChangeNeedsClean(newKit, oldKit)).to.be.false;
262367
});
263368
});
264369

0 commit comments

Comments
 (0)