Skip to content

Commit 83262f9

Browse files
Copilotsnehara99
andauthored
Fix kit detection returning 'unknown vendor' when using clang-cl (#4687)
* Initial plan * Fix clang-cl vendor detection returning 'unknown vendor' Co-authored-by: snehara99 <[email protected]> * Extract vendor detection to helper function for consistency Co-authored-by: snehara99 <[email protected]> * Fix issue link in CHANGELOG from #4685 to #4638 Co-authored-by: snehara99 <[email protected]> * Merge main to resolve conflicts Agent-Logs-Url: https://github.com/microsoft/vscode-cmake-tools/sessions/7c8bcb9a-2743-414a-90a9-cb756eba74a9 Co-authored-by: snehara99 <[email protected]> * fixed kit rename breaking existing selections regression risks and merge conflicts * fixed chai use * fixed lint errors --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: snehara99 <[email protected]> Co-authored-by: Sneha Ramachandran <[email protected]>
1 parent 43d72b5 commit 83262f9

3 files changed

Lines changed: 213 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Features:
66
- Add support for the FASTBuild generator (CMake 4.2+). [#4690](https://github.com/microsoft/vscode-cmake-tools/pull/4690)
77

88
Bug Fixes:
9+
- Fix kit detection returning "unknown vendor" when using clang-cl compiler. [#4638](https://github.com/microsoft/vscode-cmake-tools/issues/4638)
910
- 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)
1011

1112
## 1.23

src/kits/kit.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,27 @@ export async function getCompilerVersion(vendor: CompilerVendorEnum, binPath: st
248248
};
249249
}
250250

251+
/**
252+
* Detects the compiler vendor from the compiler binary path.
253+
* @param compilerPath Path to the compiler binary
254+
* @returns The detected vendor or undefined if not detected
255+
*/
256+
function detectVendorFromBinaryPath(compilerPath: string): CompilerVendorEnum | undefined {
257+
const binBasename = path.basename(compilerPath, '.exe').toLowerCase();
258+
// Check for clang-cl first (before clang) to avoid false matches
259+
if (binBasename === 'clang-cl' || binBasename.startsWith('clang-cl-')) {
260+
return 'ClangCl';
261+
}
262+
if (binBasename === 'clang' || binBasename.startsWith('clang-')) {
263+
return 'Clang';
264+
}
265+
if (binBasename === 'gcc' || binBasename.startsWith('gcc-') ||
266+
binBasename.endsWith('-gcc') || /-gcc-\d/.test(binBasename)) {
267+
return 'GCC';
268+
}
269+
return undefined;
270+
}
271+
251272
export async function getKitDetect(kit: Kit): Promise<KitDetect> {
252273
const c_bin = kit?.compilers?.C;
253274
/* Special handling of visualStudio */
@@ -256,9 +277,12 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
256277
if (!vs) {
257278
return kit;
258279
}
280+
// Determine if the compiler is clang-cl based on binary name using helper function
281+
const detectedVendor = c_bin ? detectVendorFromBinaryPath(c_bin) : undefined;
282+
const clangVendor: CompilerVendorEnum = detectedVendor === 'ClangCl' ? 'ClangCl' : 'Clang';
259283
let version: CompilerVersion | null = null;
260284
if (c_bin) {
261-
version = await getCompilerVersion('Clang', c_bin);
285+
version = await getCompilerVersion(clangVendor, c_bin);
262286
}
263287
let targetArch = kit.preferredGenerator?.platform ?? kit.visualStudioArchitecture ?? 'i686';
264288
if (targetArch === 'win32') {
@@ -268,7 +292,7 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
268292
let versionCompiler = vs.installationVersion;
269293
let vendor: CompilerVendorEnum;
270294
if (version !== null) {
271-
vendor = 'Clang';
295+
vendor = clangVendor;
272296
versionCompiler = version.version;
273297
} else {
274298
vendor = `MSVC`;
@@ -288,6 +312,10 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
288312
} else if (kit.name.startsWith('Clang-cl')) {
289313
vendor = 'ClangCl';
290314
}
315+
// Fallback: detect vendor from compiler binary path if name pattern doesn't match
316+
if (vendor === undefined && c_bin) {
317+
vendor = detectVendorFromBinaryPath(c_bin);
318+
}
291319
if (vendor === undefined) {
292320
return kit;
293321
}
@@ -297,7 +325,11 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
297325
version = await getCompilerVersion(vendor, c_bin);
298326
}
299327
if (!version) {
300-
return kit;
328+
// Return at least the vendor information even when version detection fails
329+
return {
330+
...kit,
331+
vendor
332+
};
301333
}
302334
return {
303335
vendor,
@@ -950,7 +982,8 @@ async function scanDirForClangForMSVCKits(dir: PathWithTrust, vsInstalls: VSInst
950982
return null;
951983
}
952984

953-
const version = dir.isTrusted ? await getCompilerVersion('Clang', binPath) : null;
985+
const clangVendor: CompilerVendorEnum = isClangMsvcCli ? 'ClangCl' : 'Clang';
986+
const version = dir.isTrusted ? await getCompilerVersion(clangVendor, binPath) : null;
954987
if (dir.isTrusted && version === null) {
955988
return null;
956989
}

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

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-unused-expressions */
22
import * as chai from 'chai';
3-
import * as chaiAsPromised from 'chai-as-promised';
3+
import chaiAsPromised = require('chai-as-promised');
44
import * as path from 'path';
55

66
chai.use(chaiAsPromised);
@@ -240,6 +240,139 @@ suite('Kits scan test', () => {
240240
}).timeout(10000);
241241
});
242242

243+
suite('getKitDetect vendor detection from binary path', () => {
244+
test('Detect ClangCl vendor from clang-cl binary path', async () => {
245+
const testKit: kit.Kit = {
246+
name: 'Custom Kit Name',
247+
compilers: {
248+
C: 'C:/path/to/clang-cl.exe',
249+
CXX: 'C:/path/to/clang-cl.exe'
250+
},
251+
isTrusted: false
252+
};
253+
const detect = await kit.getKitDetect(testKit);
254+
// Name doesn't match known prefixes, so vendor is detected from binary path
255+
expect(detect.vendor).to.eq('ClangCl');
256+
});
257+
258+
test('Detect Clang vendor from clang binary path', async () => {
259+
const testKit: kit.Kit = {
260+
name: 'Custom Kit Name',
261+
compilers: {
262+
C: '/usr/bin/clang',
263+
CXX: '/usr/bin/clang++'
264+
},
265+
isTrusted: false
266+
};
267+
const detect = await kit.getKitDetect(testKit);
268+
expect(detect.vendor).to.eq('Clang');
269+
});
270+
271+
test('Detect GCC vendor from gcc binary path', async () => {
272+
const testKit: kit.Kit = {
273+
name: 'Custom Kit Name',
274+
compilers: {
275+
C: '/usr/bin/gcc',
276+
CXX: '/usr/bin/g++'
277+
},
278+
isTrusted: false
279+
};
280+
const detect = await kit.getKitDetect(testKit);
281+
expect(detect.vendor).to.eq('GCC');
282+
});
283+
284+
test('Detect GCC vendor from versioned gcc binary path', async () => {
285+
const testKit: kit.Kit = {
286+
name: 'Custom Kit Name',
287+
compilers: {
288+
C: '/usr/bin/gcc-11',
289+
CXX: '/usr/bin/g++-11'
290+
},
291+
isTrusted: false
292+
};
293+
const detect = await kit.getKitDetect(testKit);
294+
expect(detect.vendor).to.eq('GCC');
295+
});
296+
297+
test('Detect Clang vendor from versioned clang binary path', async () => {
298+
const testKit: kit.Kit = {
299+
name: 'Custom Kit Name',
300+
compilers: {
301+
C: '/usr/bin/clang-14',
302+
CXX: '/usr/bin/clang++-14'
303+
},
304+
isTrusted: false
305+
};
306+
const detect = await kit.getKitDetect(testKit);
307+
expect(detect.vendor).to.eq('Clang');
308+
});
309+
310+
test('Detect ClangCl vendor from versioned clang-cl binary path', async () => {
311+
const testKit: kit.Kit = {
312+
name: 'Custom Kit Name',
313+
compilers: {
314+
C: 'C:/LLVM/bin/clang-cl-14.exe',
315+
CXX: 'C:/LLVM/bin/clang-cl-14.exe'
316+
},
317+
isTrusted: false
318+
};
319+
const detect = await kit.getKitDetect(testKit);
320+
expect(detect.vendor).to.eq('ClangCl');
321+
});
322+
323+
test('Detect GCC vendor from cross-compiler gcc binary path', async () => {
324+
const testKit: kit.Kit = {
325+
name: 'Custom Kit Name',
326+
compilers: {
327+
C: '/opt/toolchain/arm-linux-gnueabihf-gcc',
328+
CXX: '/opt/toolchain/arm-linux-gnueabihf-g++'
329+
},
330+
isTrusted: false
331+
};
332+
const detect = await kit.getKitDetect(testKit);
333+
expect(detect.vendor).to.eq('GCC');
334+
});
335+
336+
test('Detect GCC vendor from versioned cross-compiler gcc binary path', async () => {
337+
const testKit: kit.Kit = {
338+
name: 'Custom Kit Name',
339+
compilers: {
340+
C: '/opt/toolchain/arm-linux-gnueabihf-gcc-12',
341+
CXX: '/opt/toolchain/arm-linux-gnueabihf-g++-12'
342+
},
343+
isTrusted: false
344+
};
345+
const detect = await kit.getKitDetect(testKit);
346+
expect(detect.vendor).to.eq('GCC');
347+
});
348+
349+
test('Do not falsely detect GCC from unrelated binary with gcc in name', async () => {
350+
const testKit: kit.Kit = {
351+
name: 'Custom Kit Name',
352+
compilers: {
353+
C: '/usr/bin/not-gcc-related',
354+
CXX: '/usr/bin/not-gcc-related'
355+
},
356+
isTrusted: false
357+
};
358+
const detect = await kit.getKitDetect(testKit);
359+
expect(detect.vendor).to.be.undefined;
360+
});
361+
362+
test('Return original kit when vendor cannot be detected', async () => {
363+
const testKit: kit.Kit = {
364+
name: 'Unknown Kit',
365+
compilers: {
366+
C: '/usr/bin/unknown-compiler',
367+
CXX: '/usr/bin/unknown-compiler++'
368+
},
369+
isTrusted: false
370+
};
371+
const detect = await kit.getKitDetect(testKit);
372+
expect(detect.vendor).to.be.undefined;
373+
});
374+
});
375+
243376
suite('VS Generator mapping', () => {
244377
test('returns correct generator for VS 2022', () => {
245378
expect(kit.vsGeneratorForVersion('17')).to.eq('Visual Studio 17 2022');
@@ -371,7 +504,6 @@ suite('Kits scan test', () => {
371504
compilers: { C: 'gcc' },
372505
isTrusted: true
373506
};
374-
375507
expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.false;
376508
});
377509

@@ -382,7 +514,6 @@ suite('Kits scan test', () => {
382514
keep: true,
383515
isTrusted: true
384516
};
385-
386517
expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.true;
387518
});
388519

@@ -392,8 +523,48 @@ suite('Kits scan test', () => {
392523
toolchainFile: 'toolchain.cmake',
393524
isTrusted: true
394525
};
395-
396526
expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.true;
397527
});
398528
});
529+
530+
suite('getKitDetect vendor detection from kit name', () => {
531+
test('Detect GCC vendor from kit name starting with GCC', async () => {
532+
const testKit: kit.Kit = {
533+
name: 'GCC 12.2.0 x86_64-linux-gnu',
534+
compilers: {
535+
C: '/usr/bin/gcc-12',
536+
CXX: '/usr/bin/g++-12'
537+
},
538+
isTrusted: false
539+
};
540+
const detect = await kit.getKitDetect(testKit);
541+
expect(detect.vendor).to.eq('GCC');
542+
});
543+
544+
test('Detect Clang vendor from kit name starting with Clang', async () => {
545+
const testKit: kit.Kit = {
546+
name: 'Clang 14.0.0 x86_64-pc-linux-gnu',
547+
compilers: {
548+
C: '/usr/bin/clang-14',
549+
CXX: '/usr/bin/clang++-14'
550+
},
551+
isTrusted: false
552+
};
553+
const detect = await kit.getKitDetect(testKit);
554+
expect(detect.vendor).to.eq('Clang');
555+
});
556+
557+
test('Detect ClangCl vendor from kit name starting with Clang-cl', async () => {
558+
const testKit: kit.Kit = {
559+
name: 'Clang-cl 14.0.0 (MSVC CLI)',
560+
compilers: {
561+
C: 'C:/LLVM/bin/clang-cl.exe',
562+
CXX: 'C:/LLVM/bin/clang-cl.exe'
563+
},
564+
isTrusted: false
565+
};
566+
const detect = await kit.getKitDetect(testKit);
567+
expect(detect.vendor).to.eq('ClangCl');
568+
});
569+
});
399570
});

0 commit comments

Comments
 (0)