Skip to content

Commit c4b8cb0

Browse files
committed
Pass mandatory compiler arguments from CMake 4.3 to cpptools
The `CMAKE_<LANG>_COMPILER` variable or the `CC` or similar environment variable can contain command line arguments that must be passed to every invocation of the compiler. Previously, these were lost on the way from CMake to cpptools because the CMake file API did not expose them, causing non-working code navigation after cpptools called the compiler without them to determine system include paths and built-in preprocessor macro definitions. CMake 4.3 adds a new `commandFragment` member to the `toolchains. toolchains[].compiler` object to rectify that. Pass that along in configuration provider responses.
1 parent 543551a commit c4b8cb0

File tree

8 files changed

+83
-11
lines changed

8 files changed

+83
-11
lines changed

CHANGELOG.md

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

3+
## 1.22
4+
5+
Features:
6+
7+
- Pass mandatory compiler arguments from `CMAKE_<LANG>_COMPILER` to cpptools so it can properly determine system include paths and built-in preprocessor macro definitions. Requires CMake 4.3 or newer. [#4627](https://github.com/microsoft/vscode-cmake-tools/pull/4627) [@cwalther](https://github.com/cwalther)
8+
39
## 1.21
410

511
Features:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4011,7 +4011,7 @@
40114011
"tsconfig-paths": "^3.11.0",
40124012
"tslint": "^6.1.3",
40134013
"typescript": "^4.1.5",
4014-
"vscode-cmake-tools": "^1.5.0",
4014+
"vscode-cmake-tools": "^1.6.0",
40154015
"vscode-nls-dev": "^3.3.2",
40164016
"webpack": "^5.94.0",
40174017
"webpack-cli": "^4.5.0"

src/cpptools.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,22 +449,34 @@ export class CppConfigurationProvider implements cpptools.CustomConfigurationPro
449449
const targetArchFromToolchains = targetFromToolchains ? parseTargetArch(targetFromToolchains) : undefined;
450450

451451
const normalizedCompilerPath = util.platformNormalizePath(compilerPath);
452-
let compileCommandFragments = useFragments ? (fileGroup.compileCommandFragments || target.compileCommandFragments) : [];
452+
const compileCommandFragments = useFragments ? (fileGroup.compileCommandFragments || target.compileCommandFragments).slice(0) : [];
453453
const getAsFlags = (fragments?: string[]) => {
454454
if (!fragments) {
455455
return [];
456456
}
457457
return [...util.flatMap(fragments, fragment => shlex.split(fragment))];
458458
};
459-
let flags: string[] = [];
459+
const flags: string[] = [];
460460
let extraDefinitions: string[] = [];
461461
let standard: StandardVersion;
462462
let targetArch: Architecture;
463463
let intelliSenseMode: IntelliSenseMode;
464464
let defines = (fileGroup.defines || target.defines || []);
465-
if (!useFragments) {
465+
if (useFragments) {
466+
if (compilerToolchains?.commandFragment) {
467+
compileCommandFragments.unshift(compilerToolchains.commandFragment);
468+
}
469+
} else {
470+
if (compilerToolchains?.commandFragment) {
471+
// This is incorrect: shlex.split() does not do what one would
472+
// expect, it treats quotes and backslashes differently than a
473+
// shell would. But maybe it's good enough for a legacy codepath,
474+
// what are the chances that anyone has old CppTools but new
475+
// CMake Tools and new CMake?
476+
flags.push(...shlex.split(compilerToolchains.commandFragment));
477+
}
466478
// Send the intelliSenseMode and standard only for CppTools API v5 and below.
467-
flags = getAsFlags(fileGroup.compileCommandFragments || target.compileCommandFragments);
479+
flags.push(...getAsFlags(fileGroup.compileCommandFragments || target.compileCommandFragments));
468480
({ extraDefinitions, standard, targetArch } = parseCompileFlags(this.cpptoolsVersion, flags, lang));
469481
defines = defines.concat(extraDefinitions);
470482
intelliSenseMode = getIntelliSenseMode(this.cpptoolsVersion, compilerPath, targetArchFromToolchains ?? targetArch);
@@ -491,7 +503,6 @@ export class CppConfigurationProvider implements cpptools.CustomConfigurationPro
491503
}
492504
if (targetFromToolchains) {
493505
if (useFragments) {
494-
compileCommandFragments = compileCommandFragments.slice(0);
495506
compileCommandFragments.push(`--target=${targetFromToolchains}`);
496507
} else {
497508
flags.push(`--target=${targetFromToolchains}`);

src/drivers/cmakeFileApi.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export namespace Toolchains {
200200

201201
export interface Compiler {
202202
path?: string;
203+
commandFragment?: string;
203204
id?: string;
204205
version?: string;
205206
target?: string;
@@ -594,11 +595,15 @@ export async function loadToolchains(filename: string): Promise<Map<string, Code
594595

595596
return toolchains.toolchains.reduce((acc, el) => {
596597
if (el.compiler.path) {
598+
const tc: CodeModelToolchain = { path: el.compiler.path };
597599
if (el.compiler.target) {
598-
acc.set(el.language, { path: el.compiler.path, target: el.compiler.target });
599-
} else {
600-
acc.set(el.language, { path: el.compiler.path });
600+
tc.target = el.compiler.target;
601601
}
602+
if (el.compiler.commandFragment) {
603+
// available (optional) since toolchains object version 1.1 (CMake 4.3)
604+
tc.commandFragment = el.compiler.commandFragment;
605+
}
606+
acc.set(el.language, tc);
602607
}
603608
return acc;
604609
}, new Map<string, CodeModelToolchain>());

test/unit-tests/cpptools.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,13 +227,20 @@ suite('CppTools tests', () => {
227227
}]
228228
}]
229229
}],
230-
toolchains: new Map<string, codeModel.CodeModelToolchain>([['CXX', { path: 'path_from_toolchain_object' }]])
230+
toolchains: new Map<string, codeModel.CodeModelToolchain>([['CXX', {
231+
path: 'path_from_toolchain_object',
232+
commandFragment: '--fragment from -toolchain=object'
233+
}]])
231234
};
232235

233236
provider.updateConfigurationData({ cache, codeModelContent: codeModel3, activeTarget: 'target3', activeBuildTypeVariant: 'Release', folder: smokeFolder });
234237
let configurations = await provider.provideConfigurations([vscode.Uri.file(sourceFile3)]);
235238
expect(configurations.length).to.eq(1);
236239
expect(configurations[0].configuration.compilerPath).to.eq('path_from_toolchain_object');
240+
expect(configurations[0].configuration.compilerFragments).to.be.undefined;
241+
expect(configurations[0].configuration.compilerArgs?.[0]).to.eq('--fragment');
242+
expect(configurations[0].configuration.compilerArgs?.[1]).to.eq('from');
243+
expect(configurations[0].configuration.compilerArgs?.[2]).to.eq('-toolchain=object');
237244

238245
configurations = await provider.provideConfigurations([uri1]);
239246
expect(configurations.length).to.eq(1);
@@ -333,13 +340,18 @@ suite('CppTools tests', () => {
333340
}]
334341
}]
335342
}],
336-
toolchains: new Map<string, codeModel.CodeModelToolchain>([['CXX', { path: 'path_from_toolchain_object' }]])
343+
toolchains: new Map<string, codeModel.CodeModelToolchain>([['CXX', {
344+
path: 'path_from_toolchain_object',
345+
commandFragment: '--fragment "from" -toolchain=object'
346+
}]])
337347
};
338348

339349
provider.updateConfigurationData({ cache, codeModelContent: codeModel3, activeTarget: 'target3', activeBuildTypeVariant: 'Release', folder: smokeFolder });
340350
let configurations = await provider.provideConfigurations([vscode.Uri.file(sourceFile3)]);
341351
expect(configurations.length).to.eq(1);
342352
expect(configurations[0].configuration.compilerPath).to.eq('path_from_toolchain_object');
353+
expect(configurations[0].configuration.compilerFragments?.[0]).to.eq('--fragment "from" -toolchain=object');
354+
expect(configurations[0].configuration.compilerArgs).to.be.empty;
343355

344356
configurations = await provider.provideConfigurations([uri1]);
345357
expect(configurations.length).to.eq(1);

test/unit-tests/driver/driver-codemodel-tests.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CMakeExecutable, getCMakeExecutableInformation } from '@cmt/cmakeExecut
33
import { ConfigurationReader } from '@cmt/config';
44
import { ConfigureTrigger } from '@cmt/cmakeProject';
55
import { CodeModelContent } from '@cmt/drivers/codeModel';
6+
import { versionLess } from '@cmt/util';
67
import * as chai from 'chai';
78
import { expect } from 'chai';
89
import * as chaiString from 'chai-string';
@@ -283,5 +284,40 @@ export function makeCodeModelDriverTestsuite(driverName: string, driver_generato
283284
expect(sources).to.include(path.normalize(sourcefile_name).toLowerCase());
284285
}
285286
}).timeout(90000);
287+
288+
function testToolchain(withArguments: boolean) {
289+
return async function (this: Mocha.Context) {
290+
const cmakeVersion = (await getCMakeExecutableInformation(cmakePath)).version ?? { major: 0, minor: 0, patch: 0 };
291+
// toolchains was only added to the file API in CMake 3.20
292+
// also, CMAKE_<LANG>_COMPILER with arguments is only supported from CMake 3.19 on
293+
if (versionLess(cmakeVersion, '3.20')) {
294+
this.skip();
295+
}
296+
const compiler = getTestRootFilePath(`test/fakebin/cross-compile-gcc${process.platform === 'win32' ? '.exe' : ''}`);
297+
expect(compiler).to.satisfy(fs.existsSync, `${compiler} not found. Run 'yarn pretest-buildfakebin'.`);
298+
299+
const codemodel_data = await generateCodeModelForConfiguredDriver(
300+
this,
301+
[
302+
'-DCMAKE_TOOLCHAIN_FILE=toolchain-' + (withArguments ? 'with' : 'no') + '-args.cmake',
303+
'-DTEST_FULL_COMPILER_PATH=' + compiler
304+
]
305+
);
306+
expect(codemodel_data).to.be.not.null;
307+
const toolchain = codemodel_data?.toolchains?.get('CXX');
308+
expect(toolchain).to.exist;
309+
expect(toolchain?.path).to.equal(compiler);
310+
// commandFragment was only added to the file API in CMake 4.3
311+
if (!withArguments || versionLess(cmakeVersion, '4.2.20251203')) {
312+
expect(toolchain?.commandFragment).to.be.undefined;
313+
} else {
314+
expect(toolchain?.commandFragment).to.equal('--hello world --something=other');
315+
}
316+
};
317+
}
318+
319+
test('Test toolchain without compiler arguments', testToolchain(false)).timeout(90000);
320+
321+
test('Test toolchain with compiler arguments', testToolchain(true)).timeout(90000);
286322
});
287323
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
set(CMAKE_CXX_COMPILER "${TEST_FULL_COMPILER_PATH}")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
set(CMAKE_CXX_COMPILER "${TEST_FULL_COMPILER_PATH};--hello;world;--something=other")

0 commit comments

Comments
 (0)