Skip to content

Conversation

@cwalther
Copy link

@cwalther cwalther commented Dec 3, 2025

This changes visible behavior

The following changes are proposed:

  • Pass mandatory compiler arguments from CMake 4.3 to cpptools

The purpose of this change

Fix IntelliSense in projects with compiler arguments in CMAKE_<LANG>_COMPILER by passing those arguments, newly exposed in the file API by CMake 4.3, to cpptools so it can properly determine system include paths and built-in preprocessor macro definitions.

Other Notes/Information

Problem

In CMake, the CC or similar environment variables as well as the CMAKE_<LANG>_COMPILER variables set by toolchain files can, in addition to the compiler path, contain mandatory command line arguments that must be passed to every invocation of the compiler. Previously, these arguments were not exposed in the File API, therefore CMake Tools as an IntelliSense configuration provider had no way to pass them on to cpptools, therefore cpptools would call the compiler without them when trying to determine system include paths and built-in preprocessor macro definitions, potentially resulting in non-working code navigation.

For example, for building for embedded systems with different CPU architectures we use toolchain files with lines like

set(CMAKE_CXX_COMPILER "${INDEL_TOOLCHAIN_LOCATION}/bin/clang++${CMAKE_HOST_EXECUTABLE_SUFFIX};--config;aarch64-indel-elf.cfg;-mcpu=cortex-a72")

where the referenced config file contains arguments like --target, --sysroot, -stdlib++-isystem that influence include paths and definitions. Currently, Visual Studio Code cpptools calls the compiler without these arguments when trying to determine the include paths and definitions, and gets back wrong values related to the host platform instead of the embedded system.

Steps to Reproduce

Making a generic minimal reproducing example is a bit tricky because CMake expects working compilers (including sysroots). Here is something that works for me on an x86_64 Linux system with Clang 18 installed (Ubuntu 20.04, clang-18 package from official archive.ubuntu.com/security.ubuntu.com):

CMakeLists.txt:

cmake_minimum_required(VERSION 3.24)
project("compilerargs" C)
add_executable("test" main.c)

toolchain.cmake:

set(CMAKE_C_COMPILER "/usr/bin/clang-18;-target;i386-linux-gnu")

CMakePresets.json:

{
	"version": 6,
	"configurePresets": [
		{
			"name": "Test",
			"generator": "Ninja",
			"binaryDir": "${sourceDir}/output",
			"toolchainFile": "toolchain.cmake"
		}
	]
}

main.c:

#include <something.h>

int main() {
	return hello;
}

/usr/include/i386-linux-gnu/something.h:

int hello = 42;

Steps to reproduce

  1. Put the first four files above in a folder anywhere and the last in the named location.
  2. Open the folder in Visual Studio Code.
  3. Click Build in the status bar and select preset Test when prompted.
  4. The build completes successfully and you can run ./output/test; echo $? to get the expected result 42. file output/test confirms that it was compiled for i386.
  5. Open main.c.

Actual result

  • The first line #include <something.h> has red squiggly underlines. Ctrl-clicking on it does nothing.
  • Hovering on hello shows no tooltip.
  • For reasons I don’t understand that may be related to remnants from previous experiments done on my system, ctrl-clicking on hello does work and takes me to its definition in …/i386-linux-gnu/something.h. I would expect it to fail as well.
  • CMake: Log Diagnostics and C/C++: Log Diagnostics give no indication that the required -target i386-linux-gnu arguments have made it from CMake to cpptools, in particular the C/C++ diagnostics show the wrong system include: /usr/include/x86_64-linux-gnu for main.c.

Expected result

  • No red squiggly underlines.
  • Hovering on hello shows a tooltip “int hello”.
  • Ctrl-clicking on either <something.h> or hello opens …/i386-linux-gnu/something.h.
  • CMake: Log Diagnostics shows "compilerFragments": ["-target i386-linux-gnu"] for main.c.
  • C/C++: Log Diagnostics shows the correct system include: /usr/include/i386-linux-gnu for main.c.

Proposed Solution

CMake has just merged an addition to the file API to expose these arguments, to be released in CMake 4.3. I therefore propose the attached commits to retrieve them from the file API and pass them on to cpptools.

Only the last two commits are part of this change, the rest are #4620, which is required to actually be able to test with CMake 4.3 (so don’t use the combined diff for review, but the individual commits). But if there are objections to that, I will of course rework it. This change does not strictly depend on #4620, some of the added tests will just be skipped without it.

An update to vscode-cmake-tools-api is required, submitted in microsoft/vscode-cmake-tools-api#15, so there is a dependency version bump in package.json. CONTRIBUTING.md tells me to ping “our team” in that case, but I don’t know who that is (maybe @gcampbell-msft?).

Open Questions

  • I am not sure what to do about the use of shlex.split(), see comments in cpptools.ts:471. Possible options:

    • Leave it as it is and declare the (rare, as far as I can tell) case where it causes problems unsupported.
    • Add a second shlex-split function that does what I would expect and what is needed here.
    • Change the behavior of shlex.split to be what I would expect and what is needed here. However I fear that would break other things, because presumably the current behavior, which is explicitly tested for in test/unit-tests/shlex.test.td, was introduced for a reason in support parsing substring and also substrings with single quote and space #1663.

    Any preferences? Any comment from @elahehrashedi who wrote that behavior?

Otherwise tests that have `cmake_minimum_required(VERSION 3.0.0)` or similar
fail when run with the current CMake 4.2.0, whose lowest supported version
is 3.5.0.

I have not individually checked whether the test cases rely on any policies
that changed between 3.0.0 and 3.5.0, but the tests still pass.
Server Mode was removed in favor of the File API in CMake 3.20, so skip the
respective tests when it is not supported. Whether server mode is supported
was tracked already, but only with a lower version bound. Availability of
server mode is explicitly named in the `-E capabilities` response, so
additionally take that into account, effectively adding an upper bound.
Skip configure-time compiler checks when passing a fake sysroot to the
compiler, they would fail because it is unable to link an executable that
way.

I am not sure under what circumstances this has ever worked.
Make the tests that rely on executables in test/fakebin/ check whether they
exist, and if not give a helpful error message, instead of failing in
unclear ways when they find things in the system $PATH instead.

Add a command `yarn pretest-buildfakebin` that documents how to build these
executables, which was otherwise only documented in
.github/workflows/ci-main*.yml and .vscode/settings.json.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant