Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Features:
- Add support for the FASTBuild generator (CMake 4.2+). [#4690](https://github.com/microsoft/vscode-cmake-tools/pull/4690)

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
Expand Down
41 changes: 37 additions & 4 deletions src/kits/kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,27 @@ export async function getCompilerVersion(vendor: CompilerVendorEnum, binPath: st
};
}

/**
* Detects the compiler vendor from the compiler binary path.
* @param compilerPath Path to the compiler binary
* @returns The detected vendor or undefined if not detected
*/
function detectVendorFromBinaryPath(compilerPath: string): CompilerVendorEnum | undefined {
const binBasename = path.basename(compilerPath, '.exe').toLowerCase();
// Check for clang-cl first (before clang) to avoid false matches
if (binBasename === 'clang-cl' || binBasename.startsWith('clang-cl-')) {
return 'ClangCl';
}
if (binBasename === 'clang' || binBasename.startsWith('clang-')) {
return 'Clang';
}
if (binBasename === 'gcc' || binBasename.startsWith('gcc-') ||
binBasename.endsWith('-gcc') || /-gcc-\d/.test(binBasename)) {
return 'GCC';
}
return undefined;
}

export async function getKitDetect(kit: Kit): Promise<KitDetect> {
const c_bin = kit?.compilers?.C;
/* Special handling of visualStudio */
Expand All @@ -256,9 +277,12 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
if (!vs) {
return kit;
}
// Determine if the compiler is clang-cl based on binary name using helper function
const detectedVendor = c_bin ? detectVendorFromBinaryPath(c_bin) : undefined;
const clangVendor: CompilerVendorEnum = detectedVendor === 'ClangCl' ? 'ClangCl' : 'Clang';
let version: CompilerVersion | null = null;
if (c_bin) {
version = await getCompilerVersion('Clang', c_bin);
version = await getCompilerVersion(clangVendor, c_bin);
}
let targetArch = kit.preferredGenerator?.platform ?? kit.visualStudioArchitecture ?? 'i686';
if (targetArch === 'win32') {
Expand All @@ -268,7 +292,7 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
let versionCompiler = vs.installationVersion;
let vendor: CompilerVendorEnum;
if (version !== null) {
vendor = 'Clang';
vendor = clangVendor;
versionCompiler = version.version;
} else {
vendor = `MSVC`;
Expand All @@ -288,6 +312,10 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
} else if (kit.name.startsWith('Clang-cl')) {
vendor = 'ClangCl';
}
// Fallback: detect vendor from compiler binary path if name pattern doesn't match
if (vendor === undefined && c_bin) {
vendor = detectVendorFromBinaryPath(c_bin);
}
if (vendor === undefined) {
return kit;
}
Expand All @@ -297,7 +325,11 @@ export async function getKitDetect(kit: Kit): Promise<KitDetect> {
version = await getCompilerVersion(vendor, c_bin);
}
if (!version) {
return kit;
// Return at least the vendor information even when version detection fails
return {
...kit,
vendor
};
}
return {
vendor,
Expand Down Expand Up @@ -950,7 +982,8 @@ async function scanDirForClangForMSVCKits(dir: PathWithTrust, vsInstalls: VSInst
return null;
}

const version = dir.isTrusted ? await getCompilerVersion('Clang', binPath) : null;
const clangVendor: CompilerVendorEnum = isClangMsvcCli ? 'ClangCl' : 'Clang';
const version = dir.isTrusted ? await getCompilerVersion(clangVendor, binPath) : null;
if (dir.isTrusted && version === null) {
return null;
}
Expand Down
179 changes: 175 additions & 4 deletions test/unit-tests/kit-scan.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-expressions */
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import chaiAsPromised = require('chai-as-promised');
import * as path from 'path';

chai.use(chaiAsPromised);
Expand Down Expand Up @@ -240,6 +240,139 @@ suite('Kits scan test', () => {
}).timeout(10000);
});

suite('getKitDetect vendor detection from binary path', () => {
test('Detect ClangCl vendor from clang-cl binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: 'C:/path/to/clang-cl.exe',
CXX: 'C:/path/to/clang-cl.exe'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
// Name doesn't match known prefixes, so vendor is detected from binary path
expect(detect.vendor).to.eq('ClangCl');
});

test('Detect Clang vendor from clang binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/usr/bin/clang',
CXX: '/usr/bin/clang++'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('Clang');
});

test('Detect GCC vendor from gcc binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/usr/bin/gcc',
CXX: '/usr/bin/g++'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('GCC');
});

test('Detect GCC vendor from versioned gcc binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/usr/bin/gcc-11',
CXX: '/usr/bin/g++-11'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('GCC');
});

test('Detect Clang vendor from versioned clang binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/usr/bin/clang-14',
CXX: '/usr/bin/clang++-14'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('Clang');
});

test('Detect ClangCl vendor from versioned clang-cl binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: 'C:/LLVM/bin/clang-cl-14.exe',
CXX: 'C:/LLVM/bin/clang-cl-14.exe'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('ClangCl');
});

test('Detect GCC vendor from cross-compiler gcc binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/opt/toolchain/arm-linux-gnueabihf-gcc',
CXX: '/opt/toolchain/arm-linux-gnueabihf-g++'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('GCC');
});

test('Detect GCC vendor from versioned cross-compiler gcc binary path', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/opt/toolchain/arm-linux-gnueabihf-gcc-12',
CXX: '/opt/toolchain/arm-linux-gnueabihf-g++-12'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('GCC');
});

test('Do not falsely detect GCC from unrelated binary with gcc in name', async () => {
const testKit: kit.Kit = {
name: 'Custom Kit Name',
compilers: {
C: '/usr/bin/not-gcc-related',
CXX: '/usr/bin/not-gcc-related'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.be.undefined;
});

test('Return original kit when vendor cannot be detected', async () => {
const testKit: kit.Kit = {
name: 'Unknown Kit',
compilers: {
C: '/usr/bin/unknown-compiler',
CXX: '/usr/bin/unknown-compiler++'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.be.undefined;
});
});

suite('VS Generator mapping', () => {
test('returns correct generator for VS 2022', () => {
expect(kit.vsGeneratorForVersion('17')).to.eq('Visual Studio 17 2022');
Expand Down Expand Up @@ -371,7 +504,6 @@ suite('Kits scan test', () => {
compilers: { C: 'gcc' },
isTrusted: true
};

expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.false;
});

Expand All @@ -382,7 +514,6 @@ suite('Kits scan test', () => {
keep: true,
isTrusted: true
};

expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.true;
});

Expand All @@ -392,8 +523,48 @@ suite('Kits scan test', () => {
toolchainFile: 'toolchain.cmake',
isTrusted: true
};

expect(shouldKeepUserKitAfterScan(existingKit, new Set<string>(), true)).to.be.true;
});
});

suite('getKitDetect vendor detection from kit name', () => {
test('Detect GCC vendor from kit name starting with GCC', async () => {
const testKit: kit.Kit = {
name: 'GCC 12.2.0 x86_64-linux-gnu',
compilers: {
C: '/usr/bin/gcc-12',
CXX: '/usr/bin/g++-12'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('GCC');
});

test('Detect Clang vendor from kit name starting with Clang', async () => {
const testKit: kit.Kit = {
name: 'Clang 14.0.0 x86_64-pc-linux-gnu',
compilers: {
C: '/usr/bin/clang-14',
CXX: '/usr/bin/clang++-14'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('Clang');
});

test('Detect ClangCl vendor from kit name starting with Clang-cl', async () => {
const testKit: kit.Kit = {
name: 'Clang-cl 14.0.0 (MSVC CLI)',
compilers: {
C: 'C:/LLVM/bin/clang-cl.exe',
CXX: 'C:/LLVM/bin/clang-cl.exe'
},
isTrusted: false
};
const detect = await kit.getKitDetect(testKit);
expect(detect.vendor).to.eq('ClangCl');
});
});
});
Loading