Skip to content

[clangd] Add BuiltinHeaders flag to Config file #129459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 9, 2025

Conversation

kammce
Copy link
Contributor

@kammce kammce commented Mar 3, 2025

Resolves clangd/clangd#2074

Problem

If clangd is used on a project that uses a GCC cross compiler, the system headers for GCC are removed and the clangd system headers are used instead. clangd's system headers are built for the host machine. This difference between the host and cross compile target system headers can lead to clangd reporting errors.

In my case, I'm using the ARM GNU toolchain arm-none-eabi-gcc on an M1 Mac.
The issue comes down to including cstddef which includes stddef.h which gets resolved to clang's version of stddef.h which includes the __config file from clang. The thread API if/else macro chain will emit an #error "No Thread API" because the thread API macros are not defined.

A workaround is to define the necessary macros in my clangd configuration file, to silence the errors coming from the mac system headers. This kind of fix seems brittle as a change to the macro names could occur which would break me down the line.

Solution

Add a flag named BuiltinHeaders to the CompileFlag collection. The option can have one of two values QueryDriver and Clangd. The default is Clangd which uses the builtin headers for Clangd. QueryDriver will use the system header from the compiler matched via the query driver.

Usage

CompileFlags:
  BuiltinHeaders: QueryDriver

Usage:

```
CompileFlags:
  BuiltinHeaders: QueryDriver
```

The two supported options are `QueryDriver` and `Clangd`. This Controls
whether Clangd should include its own built-in headers (like stddef.h),
or use only QueryDriver's built-in headers. Use the option value
'Clangd' (default) to use Clangd's headers, and use 'QueryDriver'
to indicate QueryDriver's headers.
Copy link

github-actions bot commented Mar 3, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Mar 3, 2025

@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clangd

Author: Khalil Estell (kammce)

Changes

Usage:

CompileFlags:
  BuiltinHeaders: QueryDriver

The two supported options are QueryDriver and Clangd. This Controls whether Clangd should include its own built-in headers (like stddef.h), or use only QueryDriver's built-in headers. Use the option value 'Clangd' (default) to use Clangd's headers, and use 'QueryDriver' to indicate QueryDriver's headers.


Full diff: https://github.com/llvm/llvm-project/pull/129459.diff

6 Files Affected:

  • (modified) clang-tools-extra/clangd/Config.h (+2)
  • (modified) clang-tools-extra/clangd/ConfigCompile.cpp (+12)
  • (modified) clang-tools-extra/clangd/ConfigFragment.h (+6)
  • (modified) clang-tools-extra/clangd/ConfigYAML.cpp (+4)
  • (modified) clang-tools-extra/clangd/SystemIncludeExtractor.cpp (+24-15)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5-2)
diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h
index 586d031d58481..33718231d9b55 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -59,6 +59,7 @@ struct Config {
     std::optional<std::string> FixedCDBPath;
   };
 
+  enum class BuiltinHeaderPolicy { Clangd, QueryDriver };
   /// Controls how the compile command for the current file is determined.
   struct {
     /// Edits to apply to the compile command, in sequence.
@@ -66,6 +67,7 @@ struct Config {
         Edits;
     /// Where to search for compilation databases for this file's flags.
     CDBSearchSpec CDBSearch = {CDBSearchSpec::Ancestors, std::nullopt};
+    BuiltinHeaderPolicy BuiltinHeaders = BuiltinHeaderPolicy::Clangd;
   } CompileFlags;
 
   enum class BackgroundPolicy { Build, Skip };
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index aa2561e081047..31530c206acd7 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -290,6 +290,18 @@ struct FragmentCompiler {
       });
     }
 
+    if (F.BuiltinHeaders) {
+      if (auto Val =
+              compileEnum<Config::BuiltinHeaderPolicy>("BuiltinHeaders",
+                                                       *F.BuiltinHeaders)
+                  .map("Clangd", Config::BuiltinHeaderPolicy::Clangd)
+                  .map("QueryDriver", Config::BuiltinHeaderPolicy::QueryDriver)
+                  .value())
+        Out.Apply.push_back([Val](const Params &, Config &C) {
+          C.CompileFlags.BuiltinHeaders = *Val;
+        });
+    }
+
     if (F.CompilationDatabase) {
       std::optional<Config::CDBSearchSpec> Spec;
       if (**F.CompilationDatabase == "Ancestors") {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h
index 9535b20253b13..c6b89cde51039 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -170,6 +170,12 @@ struct Fragment {
     /// - Ancestors: search all parent directories (the default)
     /// - std::nullopt: do not use a compilation database, just default flags.
     std::optional<Located<std::string>> CompilationDatabase;
+
+    /// Controls whether Clangd should include its own built-in headers (like
+    /// stddef.h), or use only the QueryDriver's built-in headers. Use the
+    /// option value 'Clangd' (default) to indicate Clangd's headers, and use
+    /// 'QueryDriver' to indicate QueryDriver's headers.
+    std::optional<Located<std::string>> BuiltinHeaders;
   };
   CompileFlagsBlock CompileFlags;
 
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp
index 95cc5c1f9f1cf..a46d45df0d319 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -104,6 +104,10 @@ class Parser {
       if (auto Values = scalarValues(N))
         F.Remove = std::move(*Values);
     });
+    Dict.handle("BuiltinHeaders", [&](Node &N) {
+      if (auto BuiltinHeaders = scalarValue(N, "BuiltinHeaders"))
+        F.BuiltinHeaders = *BuiltinHeaders;
+    });
     Dict.handle("CompilationDatabase", [&](Node &N) {
       F.CompilationDatabase = scalarValue(N, "CompilationDatabase");
     });
diff --git a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
index c1c2e9fab9664..e9f4814738afb 100644
--- a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
+++ b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
@@ -30,6 +30,7 @@
 // in the paths that are explicitly included by the user.
 
 #include "CompileCommands.h"
+#include "Config.h"
 #include "GlobalCompilationDatabase.h"
 #include "support/Logger.h"
 #include "support/Threading.h"
@@ -401,22 +402,30 @@ extractSystemIncludesAndTarget(const DriverArgs &InputArgs,
   if (!Info)
     return std::nullopt;
 
-  // The built-in headers are tightly coupled to parser builtins.
-  // (These are clang's "resource dir", GCC's GCC_INCLUDE_DIR.)
-  // We should keep using clangd's versions, so exclude the queried builtins.
-  // They're not specially marked in the -v output, but we can get the path
-  // with `$DRIVER -print-file-name=include`.
-  if (auto BuiltinHeaders =
-          run({Driver, "-print-file-name=include"}, /*OutputIsStderr=*/false)) {
-    auto Path = llvm::StringRef(*BuiltinHeaders).trim();
-    if (!Path.empty() && llvm::sys::path::is_absolute(Path)) {
-      auto Size = Info->SystemIncludes.size();
-      llvm::erase(Info->SystemIncludes, Path);
-      vlog("System includes extractor: builtin headers {0} {1}", Path,
-           (Info->SystemIncludes.size() != Size)
-               ? "excluded"
-               : "not found in driver's response");
+  switch (Config::current().CompileFlags.BuiltinHeaders) {
+  case Config::BuiltinHeaderPolicy::Clangd: {
+    // The built-in headers are tightly coupled to parser builtins.
+    // (These are clang's "resource dir", GCC's GCC_INCLUDE_DIR.)
+    // We should keep using clangd's versions, so exclude the queried
+    // builtins. They're not specially marked in the -v output, but we can
+    // get the path with `$DRIVER -print-file-name=include`.
+    if (auto BuiltinHeaders = run({Driver, "-print-file-name=include"},
+                                  /*OutputIsStderr=*/false)) {
+      auto Path = llvm::StringRef(*BuiltinHeaders).trim();
+      if (!Path.empty() && llvm::sys::path::is_absolute(Path)) {
+        auto Size = Info->SystemIncludes.size();
+        llvm::erase(Info->SystemIncludes, Path);
+        vlog("System includes extractor: builtin headers {0} {1}", Path,
+             (Info->SystemIncludes.size() != Size)
+                 ? "excluded"
+                 : "not found in driver's response");
+      }
     }
+    break;
+  }
+  case Config::BuiltinHeaderPolicy::QueryDriver:
+    vlog("System includes extractor: builtin headers from query driver");
+    break;
   }
 
   log("System includes extractor: successfully executed {0}\n\tgot includes: "
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 71edb704b49d6..2d942c8f856d1 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -58,6 +58,9 @@ Semantic Highlighting
 Compile flags
 ^^^^^^^^^^^^^
 
+- Added `BuiltinHeaders` which controls if system includes are extracted from
+  Clangd or solely from the QueryDriver.
+
 Hover
 ^^^^^
 
@@ -112,7 +115,7 @@ Changes in existing checks
   <clang-tidy/checks/bugprone/unchecked-optional-access>` fixing false
   positives from smart pointer accessors repeated in checking ``has_value``
   and accessing ``value``. The option `IgnoreSmartPointerDereference` should
-  no longer be needed and will be removed. Also fixing false positive from 
+  no longer be needed and will be removed. Also fixing false positive from
   const reference accessors to objects containing optional member.
 
 - Improved :doc:`bugprone-unsafe-functions
@@ -131,7 +134,7 @@ Changes in existing checks
 
 - Improved :doc:`performance/unnecessary-value-param
   <clang-tidy/checks/performance/unnecessary-value-param>` check performance by
-  tolerating fix-it breaking compilation when functions is used as pointers 
+  tolerating fix-it breaking compilation when functions is used as pointers
   to avoid matching usage of functions within the current compilation unit.
 
 - Improved :doc:`performance-move-const-arg

@kadircet
Copy link
Member

kadircet commented Mar 3, 2025

I probably won't have the bandwidth to pay attention here, but I'd like to mention (as it's also said in the comment in code); clangd is only guaranteed to work with builtin headers coming from the same revision of clang (i.e. it might even break with builtin headers from a different clang version, let alone using another compiler).

It might be worthwhile to mention what you're trying to solve/improve with this change, so that reviewers can understand "why/if" this change is needed (sorry if that discussion already happened in another channel that isn't visible here, but this is the main context for the change, hence it should be mentioned here as well).

@HighCommander4 HighCommander4 self-requested a review March 3, 2025 23:34
@HighCommander4
Copy link
Collaborator

Added myself as a reviewer. I will look at the implementation over the coming days as time permits, but I wanted to address the big-picture questions now:

I'd like to mention (as it's also said in the comment in code); clangd is only guaranteed to work with builtin headers coming from the same revision of clang (i.e. it might even break with builtin headers from a different clang version, let alone using another compiler).

The BuiltinHeaders: QueryDriver behaviour was clangd's default (in situations where --query-driver was used) up to and including clangd 17. We know it works well in many cases, because many users were using --query-driver successfully in those versions.

The default behaviour was changed to the BuiltinHeaders: Clangd behaviour from clangd 18 onwards, in https://reviews.llvm.org/D156044.

Prior to that change, we've had users for whom the BuiltinHeaders: Clangd behaviour worked better and wanted to switch to it (examples here, here, here, here), and after that change, we've had users for whom the BuiltinHeaders: QueryDriver behaviour worked better and want to switch back to it (examples here, here, here, here).

This is what makes me believe that a configuration toggle is appropriate here; we can't make everyone happy with one setting.

@kammce
Copy link
Contributor Author

kammce commented Mar 4, 2025

Thank you @HighCommander4 for the added context!

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this patch looks great! I just have some minor wording nits and it should be good to go.

Regarding testing: in principle it would be nice to have an automated test exercising this config option, but unfortunately --query-driver behaviour is tricky to exercise in a test. The one test we have for it is, IMO, sufficiently hard to read or extend that I don't think it's worth doing for this change, which is very simple at the implementation level (applying a boolean config option to gate an existing piece of code). @kadircet please feel free to yell at me if you disagree 😆

@kadircet
Copy link
Member

kadircet commented Mar 7, 2025

we've had users for whom the BuiltinHeaders: QueryDriver behaviour worked better and want to switch back to it (examples here, here, here, here).

I'd argue that the fix isn't as simple as using builtin headers from a different toolchain for those users. Because clang itself (and I'd be surprised if gcc didn't) makes lots of assumptions about what compiler builtins are available, what predefined macros will be set etc. when parsing these builtin headers. So my worry is, by giving "some support" we'll actually create the impression that this should "work" for the users and create more confusion, which will turn into more complexity/maintenance burden in the project.

e.g. right now people are getting file not found errors for such headers, going forward we'll just have different errors like declarations not being found, constants having unexpected values etc. Because gcc/clang will rely on different builtins being available.

but, i won't block the change from going in. in the end, i don't know too much about gcc intrinsics, and maybe things will just work. i am happy to wait and see the outcome, but if we start getting more requests, i think before digging a deeper hole here we should really consider if we should.

@kadircet please feel free to yell at me if you disagree 😆

I completely agree about tests. It was solely meant as an "integration" test to make sure interaction works end to end. If we want to test more, I think we should do some restructring. At least move the output we're trying to "emulate" into a separate file so that it's clear what we're parsing.

@HighCommander4
Copy link
Collaborator

we've had users for whom the BuiltinHeaders: QueryDriver behaviour worked better and want to switch back to it (examples here, here, here, here).

I'd argue that the fix isn't as simple as using builtin headers from a different toolchain for those users. Because clang itself (and I'd be surprised if gcc didn't) makes lots of assumptions about what compiler builtins are available, what predefined macros will be set etc. when parsing these builtin headers. So my worry is, by giving "some support" we'll actually create the impression that this should "work" for the users and create more confusion, which will turn into more complexity/maintenance burden in the project.

e.g. right now people are getting file not found errors for such headers, going forward we'll just have different errors like declarations not being found, constants having unexpected values etc. Because gcc/clang will rely on different builtins being available.

but, i won't block the change from going in. in the end, i don't know too much about gcc intrinsics, and maybe things will just work. i am happy to wait and see the outcome, but if we start getting more requests, i think before digging a deeper hole here we should really consider if we should.

Thanks for the added thoughts. One thing we can do to help mitigate these concerns is add a disclaimer to the documentation of BuiltinHeaders: QueryDriver, that it's a "here be dragons" kind of option.

(The patch is of course keeping BuiltinHeaders: Clangd as the default, and I think that's the right choice for exactly the reasons you outline.)

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@HighCommander4 HighCommander4 merged commit 85d60a4 into llvm:main Mar 9, 2025
11 of 12 checks passed
Copy link

github-actions bot commented Mar 9, 2025

@kammce Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add option to include/exclude builtin headers when using --query-driver
4 participants