[clangd] Add a (currently hidden) --strong-workspace-mode flag (#172160)
Its current effect is to use the `rootUri` provided by the client as the working directory for fallback commands. Future effects will include other behaviors appropriate for cases where clangd is being run in the context of editing a single workspace, such as using the `compile_commands.json` file in the workspace root for all opened files. The flag is hidden until other behaviors are implemented and they constitute a cohesive "mode" for users. Original PR in #155905, which was reverted due to a UBSan failure that is fixed in this version.
This commit is contained in:
parent
7db97696a2
commit
4a1b6966ac
7 changed files with 92 additions and 16 deletions
|
|
@ -554,6 +554,8 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
|
|||
if (const auto &Dir = Params.initializationOptions.compilationDatabasePath)
|
||||
CDBOpts.CompileCommandsDir = Dir;
|
||||
CDBOpts.ContextProvider = Opts.ContextProvider;
|
||||
if (Opts.StrongWorkspaceMode)
|
||||
CDBOpts.applyFallbackWorkingDirectory(Opts.WorkspaceRoot);
|
||||
BaseCDB =
|
||||
std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,11 @@ public:
|
|||
/// FIXME: If not set, should use the current working directory.
|
||||
std::optional<std::string> WorkspaceRoot;
|
||||
|
||||
/// Sets an alternate mode of operation. Current effects are:
|
||||
/// - Using the current working directory as the working directory for
|
||||
/// fallback commands
|
||||
bool StrongWorkspaceMode = false;
|
||||
|
||||
/// The resource directory is used to find internal headers, overriding
|
||||
/// defaults and -resource-dir compiler flag).
|
||||
/// If std::nullopt, ClangdServer calls
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ 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(FallbackWorkingDirectory
|
||||
? *FallbackWorkingDirectory
|
||||
: llvm::sys::path::parent_path(File),
|
||||
llvm::sys::path::filename(File), std::move(Argv),
|
||||
/*Output=*/"");
|
||||
Cmd.Heuristic = "clangd fallback";
|
||||
|
|
@ -349,7 +351,8 @@ bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
|
|||
|
||||
DirectoryBasedGlobalCompilationDatabase::
|
||||
DirectoryBasedGlobalCompilationDatabase(const Options &Opts)
|
||||
: Opts(Opts), Broadcaster(std::make_unique<BroadcastThread>(*this)) {
|
||||
: GlobalCompilationDatabase(Opts.FallbackWorkingDirectory), Opts(Opts),
|
||||
Broadcaster(std::make_unique<BroadcastThread>(*this)) {
|
||||
if (!this->Opts.ContextProvider)
|
||||
this->Opts.ContextProvider = [](llvm::StringRef) {
|
||||
return Context::current().clone();
|
||||
|
|
@ -460,6 +463,21 @@ DirectoryBasedGlobalCompilationDatabase::lookupCDB(
|
|||
return Result;
|
||||
}
|
||||
|
||||
void DirectoryBasedGlobalCompilationDatabase::Options::
|
||||
applyFallbackWorkingDirectory(
|
||||
std::optional<std::string> FallbackWorkingDirectory) {
|
||||
if (FallbackWorkingDirectory)
|
||||
this->FallbackWorkingDirectory = *FallbackWorkingDirectory;
|
||||
else {
|
||||
// Clangd is running in strong workspace mode but the client didn't
|
||||
// specify a workspace path in the `initialize` request.
|
||||
// Fallback to current working directory.
|
||||
SmallString<256> CWD;
|
||||
llvm::sys::fs::current_path(CWD);
|
||||
this->FallbackWorkingDirectory = std::string(CWD);
|
||||
}
|
||||
}
|
||||
|
||||
// The broadcast thread announces files with new compile commands to the world.
|
||||
// Primarily this is used to enqueue them for background indexing.
|
||||
//
|
||||
|
|
@ -759,9 +777,10 @@ DirectoryBasedGlobalCompilationDatabase::getProjectModules(PathRef File) const {
|
|||
|
||||
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
|
||||
std::vector<std::string> FallbackFlags,
|
||||
CommandMangler Mangler)
|
||||
: DelegatingCDB(Base), Mangler(std::move(Mangler)),
|
||||
FallbackFlags(std::move(FallbackFlags)) {}
|
||||
CommandMangler Mangler,
|
||||
std::optional<std::string> FallbackWorkingDirectory)
|
||||
: DelegatingCDB(Base, FallbackWorkingDirectory),
|
||||
Mangler(std::move(Mangler)), FallbackFlags(std::move(FallbackFlags)) {}
|
||||
|
||||
std::optional<tooling::CompileCommand>
|
||||
OverlayCDB::getCompileCommand(PathRef File) const {
|
||||
|
|
@ -844,16 +863,20 @@ OverlayCDB::getProjectModules(PathRef File) const {
|
|||
return MDB;
|
||||
}
|
||||
|
||||
DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
|
||||
: Base(Base) {
|
||||
DelegatingCDB::DelegatingCDB(
|
||||
const GlobalCompilationDatabase *Base,
|
||||
std::optional<std::string> FallbackWorkingDirectory)
|
||||
: GlobalCompilationDatabase(FallbackWorkingDirectory), Base(Base) {
|
||||
if (Base)
|
||||
BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
|
||||
OnCommandChanged.broadcast(Changes);
|
||||
});
|
||||
}
|
||||
|
||||
DelegatingCDB::DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base)
|
||||
: DelegatingCDB(Base.get()) {
|
||||
DelegatingCDB::DelegatingCDB(
|
||||
std::unique_ptr<GlobalCompilationDatabase> Base,
|
||||
std::optional<std::string> FallbackWorkingDirectory)
|
||||
: DelegatingCDB(Base.get(), FallbackWorkingDirectory) {
|
||||
BaseOwner = std::move(Base);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ struct ProjectInfo {
|
|||
/// Provides compilation arguments used for parsing C and C++ files.
|
||||
class GlobalCompilationDatabase {
|
||||
public:
|
||||
GlobalCompilationDatabase(
|
||||
std::optional<std::string> FallbackWorkingDirectory = std::nullopt)
|
||||
: FallbackWorkingDirectory(FallbackWorkingDirectory) {}
|
||||
virtual ~GlobalCompilationDatabase() = default;
|
||||
|
||||
/// If there are any known-good commands for building this file, returns one.
|
||||
|
|
@ -69,14 +72,19 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
std::optional<std::string> FallbackWorkingDirectory;
|
||||
mutable CommandChanged OnCommandChanged;
|
||||
};
|
||||
|
||||
// Helper class for implementing GlobalCompilationDatabases that wrap others.
|
||||
class DelegatingCDB : public GlobalCompilationDatabase {
|
||||
public:
|
||||
DelegatingCDB(const GlobalCompilationDatabase *Base);
|
||||
DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base);
|
||||
DelegatingCDB(
|
||||
const GlobalCompilationDatabase *Base,
|
||||
std::optional<std::string> FallbackWorkingDirectory = std::nullopt);
|
||||
DelegatingCDB(
|
||||
std::unique_ptr<GlobalCompilationDatabase> Base,
|
||||
std::optional<std::string> FallbackWorkingDirectory = std::nullopt);
|
||||
|
||||
std::optional<tooling::CompileCommand>
|
||||
getCompileCommand(PathRef File) const override;
|
||||
|
|
@ -117,6 +125,12 @@ public:
|
|||
// Only look for a compilation database in this one fixed directory.
|
||||
// FIXME: fold this into config/context mechanism.
|
||||
std::optional<Path> CompileCommandsDir;
|
||||
// Working directory for fallback commands
|
||||
// If unset, parent directory of file should be used
|
||||
std::optional<std::string> FallbackWorkingDirectory;
|
||||
|
||||
void applyFallbackWorkingDirectory(
|
||||
std::optional<std::string> FallbackWorkingDirectory);
|
||||
};
|
||||
|
||||
DirectoryBasedGlobalCompilationDatabase(const Options &Opts);
|
||||
|
|
@ -194,9 +208,11 @@ public:
|
|||
// Base may be null, in which case no entries are inherited.
|
||||
// FallbackFlags are added to the fallback compile command.
|
||||
// Adjuster is applied to all commands, fallback or not.
|
||||
OverlayCDB(const GlobalCompilationDatabase *Base,
|
||||
std::vector<std::string> FallbackFlags = {},
|
||||
CommandMangler Mangler = nullptr);
|
||||
OverlayCDB(
|
||||
const GlobalCompilationDatabase *Base,
|
||||
std::vector<std::string> FallbackFlags = {},
|
||||
CommandMangler Mangler = nullptr,
|
||||
std::optional<std::string> FallbackWorkingDirectory = std::nullopt);
|
||||
|
||||
std::optional<tooling::CompileCommand>
|
||||
getCompileCommand(PathRef File) const override;
|
||||
|
|
|
|||
|
|
@ -169,6 +169,8 @@ public:
|
|||
bool buildCommand(const ThreadsafeFS &TFS) {
|
||||
log("Loading compilation database...");
|
||||
DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
|
||||
if (Opts.StrongWorkspaceMode)
|
||||
CDBOpts.applyFallbackWorkingDirectory(Opts.WorkspaceRoot);
|
||||
CDBOpts.CompileCommandsDir =
|
||||
Config::current().CompileFlags.CDBSearch.FixedCDBPath;
|
||||
BaseCDB =
|
||||
|
|
@ -178,8 +180,10 @@ public:
|
|||
getSystemIncludeExtractor(llvm::ArrayRef(Opts.QueryDriverGlobs));
|
||||
if (Opts.ResourceDir)
|
||||
Mangler.ResourceDir = *Opts.ResourceDir;
|
||||
|
||||
CDB = std::make_unique<OverlayCDB>(
|
||||
BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
|
||||
BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler),
|
||||
CDBOpts.FallbackWorkingDirectory);
|
||||
|
||||
if (auto TrueCmd = CDB->getCompileCommand(File)) {
|
||||
Cmd = std::move(*TrueCmd);
|
||||
|
|
@ -502,7 +506,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)};
|
||||
|
|
|
|||
|
|
@ -500,6 +500,17 @@ opt<bool> EnableConfig{
|
|||
init(true),
|
||||
};
|
||||
|
||||
opt<bool> StrongWorkspaceMode{
|
||||
"strong-workspace-mode",
|
||||
cat(Features),
|
||||
desc("An alternate mode of operation for clangd, where the clangd instance "
|
||||
"is used to edit a single workspace.\n"
|
||||
"When enabled, fallback commands use the workspace directory as their "
|
||||
"working directory instead of the parent folder."),
|
||||
init(false),
|
||||
Hidden,
|
||||
};
|
||||
|
||||
opt<bool> UseDirtyHeaders{"use-dirty-headers", cat(Misc),
|
||||
desc("Use files open in the editor when parsing "
|
||||
"headers instead of reading from the disk"),
|
||||
|
|
@ -907,6 +918,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
|
|||
}
|
||||
if (!ResourceDir.empty())
|
||||
Opts.ResourceDir = ResourceDir;
|
||||
Opts.StrongWorkspaceMode = StrongWorkspaceMode;
|
||||
Opts.BuildDynamicSymbolIndex = true;
|
||||
#if CLANGD_ENABLE_REMOTE
|
||||
if (RemoteIndexAddress.empty() != ProjectRoot.empty()) {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,20 @@ TEST(GlobalCompilationDatabaseTest, FallbackCommand) {
|
|||
testPath("foo/bar")));
|
||||
}
|
||||
|
||||
TEST(GlobalCompilationDatabaseTest, FallbackWorkingDirectory) {
|
||||
MockFS TFS;
|
||||
DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
|
||||
CDBOpts.applyFallbackWorkingDirectory(testPath("foo"));
|
||||
EXPECT_EQ(CDBOpts.FallbackWorkingDirectory, testPath("foo"));
|
||||
|
||||
DirectoryBasedGlobalCompilationDatabase DB(CDBOpts);
|
||||
auto Cmd = DB.getFallbackCommand(testPath("foo/src/bar.cc"));
|
||||
EXPECT_EQ(Cmd.Directory, testPath("foo"));
|
||||
EXPECT_THAT(Cmd.CommandLine,
|
||||
ElementsAre("clang", testPath("foo/src/bar.cc")));
|
||||
EXPECT_EQ(Cmd.Output, "");
|
||||
}
|
||||
|
||||
static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) {
|
||||
return tooling::CompileCommand(
|
||||
testRoot(), File, {"clang", std::string(Arg), std::string(File)}, "");
|
||||
|
|
|
|||
Loading…
Reference in a new issue