-
Notifications
You must be signed in to change notification settings - Fork 15.3k
Add --project-root to clangd #155905
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
base: main
Are you sure you want to change the base?
Add --project-root to clangd #155905
Conversation
|
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 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. |
|
@llvm/pr-subscribers-clangd @llvm/pr-subscribers-clang-tools-extra Author: Dominicentek (Dominicentek) ChangesAdds an ability to change the current working directory for fallback commands. Full diff: https://github.com/llvm/llvm-project/pull/155905.diff 8 Files Affected:
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..51230b4506b1a 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -208,6 +208,7 @@ ClangdServer::Options::operator TUScheduler::Options() const {
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
+ Opts.FallbackProjectRoot = FallbackProjectRoot;
return Opts;
}
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4a1eae188f7eb..2c56d6f7e6d6c 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -152,6 +152,10 @@ class ClangdServer {
/// FIXME: If not set, should use the current working directory.
std::optional<std::string> WorkspaceRoot;
+ /// If set, fallback command uses this path as its current working directory
+ /// instead of the file's parent path.
+ std::optional<std::string> FallbackProjectRoot;
+
/// The resource directory is used to find internal headers, overriding
/// defaults and -resource-dir compiler flag).
/// If std::nullopt, ClangdServer calls
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index c6afd0bc07cbd..b73697d4ee7e5 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -55,7 +55,7 @@ void actOnAllParentDirectories(PathRef FileName,
} // namespace
tooling::CompileCommand
-GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
+GlobalCompilationDatabase::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
std::vector<std::string> Argv = {"clang"};
// Clang treats .h files as C by default and files without extension as linker
// input, resulting in unhelpful diagnostics.
@@ -64,7 +64,7 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
if (FileExtension.empty() || FileExtension == ".h")
Argv.push_back("-xobjective-c++-header");
Argv.push_back(std::string(File));
- tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
+ tooling::CompileCommand Cmd(ProjectRoot ? *ProjectRoot : llvm::sys::path::parent_path(File),
llvm::sys::path::filename(File), std::move(Argv),
/*Output=*/"");
Cmd.Heuristic = "clangd fallback";
@@ -797,8 +797,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
return Cmd;
}
-tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
- auto Cmd = DelegatingCDB::getFallbackCommand(File);
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
+ auto Cmd = DelegatingCDB::getFallbackCommand(File, ProjectRoot);
std::lock_guard<std::mutex> Lock(Mutex);
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
FallbackFlags.end());
@@ -877,10 +877,10 @@ DelegatingCDB::getProjectModules(PathRef File) const {
return Base->getProjectModules(File);
}
-tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
+tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
if (!Base)
- return GlobalCompilationDatabase::getFallbackCommand(File);
- return Base->getFallbackCommand(File);
+ return GlobalCompilationDatabase::getFallbackCommand(File, ProjectRoot);
+ return Base->getFallbackCommand(File, ProjectRoot);
}
bool DelegatingCDB::blockUntilIdle(Deadline D) const {
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 1d636d73664be..5d1b5cb632154 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -55,7 +55,7 @@ class GlobalCompilationDatabase {
/// Makes a guess at how to build a file.
/// The default implementation just runs clang on the file.
/// Clangd should treat the results as unreliable.
- virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
+ virtual tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const;
/// If the CDB does any asynchronous work, wait for it to complete.
/// For use in tests.
@@ -86,7 +86,7 @@ class DelegatingCDB : public GlobalCompilationDatabase {
std::unique_ptr<ProjectModules>
getProjectModules(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
bool blockUntilIdle(Deadline D) const override;
@@ -200,7 +200,7 @@ class OverlayCDB : public DelegatingCDB {
std::optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
/// Sets or clears the compilation command for a particular file.
/// Returns true if the command was changed (including insertion and removal),
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 035e5e63d8fbb..3dc53767e0ea4 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -723,6 +723,7 @@ class ASTWorker {
const GlobalCompilationDatabase &CDB;
/// Callback invoked when preamble or main file AST is built.
ParsingCallbacks &Callbacks;
+ std::optional<std::string> FallbackProjectRoot;
Semaphore &Barrier;
/// Whether the 'onMainAST' callback ran for the current FileInputs.
@@ -840,13 +841,14 @@ ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
: IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
+ FallbackProjectRoot(Opts.FallbackProjectRoot),
Barrier(Barrier), Done(false), Status(FileName, Callbacks),
PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
// Set a fallback command because compile command can be accessed before
// `Inputs` is initialized. Other fields are only used after initialization
// from client inputs.
- FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
+ FileInputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
}
ASTWorker::~ASTWorker() {
@@ -888,7 +890,7 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
if (Cmd)
Inputs.CompileCommand = std::move(*Cmd);
else
- Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
+ Inputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
bool InputsAreTheSame =
std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h
index d0da20310a8b2..581a639646527 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -236,6 +236,10 @@ class TUScheduler {
/// Typically to inject per-file configuration.
/// If the path is empty, context sholud be "generic".
std::function<Context(PathRef)> ContextProvider;
+
+ /// If set, fallback command uses this path as its current working directory
+ /// instead of the file's parent path.
+ std::optional<std::string> FallbackProjectRoot;
};
TUScheduler(const GlobalCompilationDatabase &CDB, const Options &Opts,
diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index df8d075e80596..8d49b82d2ca53 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -187,7 +187,7 @@ class Checker {
Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Cmd.Directory,
printArgv(Cmd.CommandLine));
} else {
- Cmd = CDB->getFallbackCommand(File);
+ Cmd = CDB->getFallbackCommand(File, Opts.FallbackProjectRoot);
log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
printArgv(Cmd.CommandLine));
}
@@ -502,7 +502,7 @@ bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
config::DiagnosticCallback Diag) const override {
config::Fragment F;
// If we're timing clang-tidy checks, implicitly disabling the slow ones
- // is counterproductive!
+ // is counterproductive!
if (CheckTidyTime.getNumOccurrences())
F.Diagnostics.ClangTidy.FastCheckFilter.emplace("None");
return {std::move(F).compile(Diag)};
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index f287439f10cab..75d71c5a78f45 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -499,6 +499,14 @@ opt<bool> EnableConfig{
init(true),
};
+opt<Path> ProjectRoot{
+ "project-root",
+ cat(Misc),
+ desc("Path to use as the current working directory for fallback commands."),
+ init(""),
+ ValueOptional,
+};
+
opt<bool> UseDirtyHeaders{"use-dirty-headers", cat(Misc),
desc("Use files open in the editor when parsing "
"headers instead of reading from the disk"),
@@ -906,6 +914,8 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
}
if (!ResourceDir.empty())
Opts.ResourceDir = ResourceDir;
+ if (!ProjectRoot.empty())
+ Opts.FallbackProjectRoot = ProjectRoot;
Opts.BuildDynamicSymbolIndex = true;
#if CLANGD_ENABLE_REMOTE
if (RemoteIndexAddress.empty() != ProjectRoot.empty()) {
|
|
Hi @Dominicentek! Thanks for submitting this PR. The topic of this PR actually touches on a set of improvements I've been wanting to make to clangd for a long time, called "strong workspace mode". Some background on this proposed mode can be found in clangd/clangd#907 (with credit to @sam-mccall for the name and initial description). Basically, "strong workspace mode" would be a mode of operation of clangd where an instance of clangd is more strongly coupled to a particular workspace/project, and clangd's default behaviours are adjusted to reflect this. The specific behaviours that I have in mind are:
In addition, there is some discussion in clangd/clangd#1341 that suggests to me that it might make sense for "strong workspace mode" to be something the user opts into at the client level (for example, in vscode, using a setting provided by the I'd be curious to hear your opinion on the following:
|
|
Hi, thanks for reaching out. I made this pull request as my editor's (Zed) built-in clangd client doesn't set the project root directory as the working directory for fallback commands. VSCode does actually have this behavior. Since I was relying on fallback commands rather than compile_commands.json and couldn't really find a way to make clangd behave like this with Zed, I made it so you can change the working directory for fallback commands via the As for the "strong workspace mode", I am quite fond of this idea. Though I'd like to see this being done at the server level (with a CLI flag), since both VSCode and Zed let you customize CLI arguments for clangd, but Zed doesn't really have any clangd-specific settings, as opposed to VSCode. |
Do you know how VSCode does this? |
|
No, I'm not entirely sure. |
I tried to reproduce the described behaviour in VSCode, and I haven't been able to. Here's what I tried specifically:
The clangd logs are showing: indicating that it's using the source file's containing directory ( Could you elaborate on what VSCode behaviour you were observing? |
|
Ah I apologize, I was misremembering. I used |
|
Thanks for the clarification. I have a couple of follow-up questions about using clangd with Zed, with a view to understanding what sort of constraints we have on how to configure this behaviour:
|
|
I'm seeing the client include I take it this is the same directory you were envisioning passing to
Ok, thanks for checking. So given all that, what do you think about the following approach:
|
|
Yep, sounds great! |
Ok, cool. Would you like to update this PR to implement this revised approach? I'd be happy to review and (if no one else objects) approve it. |
|
Alright, I've made the changes. |
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
🐧 Linux x64 Test ResultsThe build failed before running any tests. Click on a failure below to see the details. tools/clang/tools/extra/clangd/unittests/CMakeFiles/ClangdTests.dir/TestFS.cpp.otools/clang/tools/extra/clangd/unittests/CMakeFiles/ClangdTests.dir/GlobalCompilationDatabaseTests.cpp.otools/clang/tools/extra/clangd/unittests/CMakeFiles/ClangdTests.dir/ClangdTests.cpp.otools/clang/tools/extra/clangd/unittests/CMakeFiles/ClangdTests.dir/TUSchedulerTests.cpp.oIf these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the |
HighCommander4
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't had a chance to take a very detailed look, but some high-level feedback:
- We shouldn't assume the current working directory (
current_path) will be the workspace root, even if that happens to be the case today. Instead, let's propagate therootUri(orrootPath) that the client sent in theinitializerequest to the place where we need it. We already propagate it intoOpts.WorkspaceRoot, we can plumb it further from there intoGlobalCompilationDatabase. - Rather than changing the
GlobalCompilationDatabase::getFallbackCommandsignature, let's provide the flag to theGlobalCompilationDatabasevia its constructor (and derived classes' constructors).- In fact, since we need to tell it both the bool and the path, maybe we can group them into an
optional<string>, which would contain the path in strong workspace mode, and be empty in the default mode.
- In fact, since we need to tell it both the bool and the path, maybe we can group them into an
- We should add some sort of test coverage. We can add a unit test similar to
GlobalCompilationDatabaseTest.FallbackCommandwhich makes assertions aboutCmd.Directory.
Adds an ability to change the current working directory for fallback commands.