[clangd] Support .clangd command line modifications for C++ modules (#122606)

Tunnels `Manger` object into the `ScanningAllProjectModules` so it can
be used to perform necessary command-line modifications (which also adds
`--resources` path previously added there explicitly). This allows using
the experimental C++ modules support with gcc.

This was discussed in the issue with @ChuanqiXu9 and @kadircet

Closes #112635
This commit is contained in:
Petr Polezhaev 2025-01-22 12:27:28 +03:00 committed by GitHub
parent 56592a8108
commit 2b0e2255d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 83 additions and 19 deletions

View File

@ -830,6 +830,16 @@ bool OverlayCDB::setCompileCommand(PathRef File,
return true;
}
std::unique_ptr<ProjectModules>
OverlayCDB::getProjectModules(PathRef File) const {
auto MDB = DelegatingCDB::getProjectModules(File);
MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
PathRef CommandPath) {
Mangler(Command, CommandPath);
});
return std::move(MDB);
}
DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
: Base(Base) {
if (Base)

View File

@ -209,6 +209,9 @@ public:
setCompileCommand(PathRef File,
std::optional<tooling::CompileCommand> CompilationCommand);
std::unique_ptr<ProjectModules>
getProjectModules(PathRef File) const override;
private:
mutable std::mutex Mutex;
llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */

View File

@ -9,8 +9,10 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROJECTMODULES_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROJECTMODULES_H
#include "support/Function.h"
#include "support/Path.h"
#include "support/ThreadsafeFS.h"
#include "clang/Tooling/CompilationDatabase.h"
#include <memory>
@ -36,11 +38,16 @@ namespace clangd {
/// `<primary-module-name>[:partition-name]`. So module names covers partitions.
class ProjectModules {
public:
using CommandMangler =
llvm::unique_function<void(tooling::CompileCommand &, PathRef) const>;
virtual std::vector<std::string> getRequiredModules(PathRef File) = 0;
virtual PathRef
getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSrcFile = PathRef()) = 0;
virtual void setCommandMangler(CommandMangler Mangler) {}
virtual ~ProjectModules() = default;
};

View File

@ -48,7 +48,8 @@ public:
};
/// Scanning the single file specified by \param FilePath.
std::optional<ModuleDependencyInfo> scan(PathRef FilePath);
std::optional<ModuleDependencyInfo>
scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler);
/// Scanning every source file in the current project to get the
/// <module-name> to <module-unit-source> map.
@ -57,7 +58,7 @@ public:
/// a global module dependency scanner to monitor every file. Or we
/// can simply require the build systems (or even the end users)
/// to provide the map.
void globalScan();
void globalScan(const ProjectModules::CommandMangler &Mangler);
/// Get the source file from the module name. Note that the language
/// guarantees all the module names are unique in a valid program.
@ -69,7 +70,9 @@ public:
/// Return the direct required modules. Indirect required modules are not
/// included.
std::vector<std::string> getRequiredModules(PathRef File);
std::vector<std::string>
getRequiredModules(PathRef File,
const ProjectModules::CommandMangler &Mangler);
private:
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
@ -87,7 +90,8 @@ private:
};
std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
ModuleDependencyScanner::scan(PathRef FilePath) {
ModuleDependencyScanner::scan(PathRef FilePath,
const ProjectModules::CommandMangler &Mangler) {
auto Candidates = CDB->getCompileCommands(FilePath);
if (Candidates.empty())
return std::nullopt;
@ -97,10 +101,8 @@ ModuleDependencyScanner::scan(PathRef FilePath) {
// DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
tooling::CompileCommand Cmd = std::move(Candidates.front());
static int StaticForMainAddr; // Just an address in this process.
Cmd.CommandLine.push_back("-resource-dir=" +
CompilerInvocation::GetResourcesPath(
"clangd", (void *)&StaticForMainAddr));
if (Mangler)
Mangler(Cmd, FilePath);
using namespace clang::tooling::dependencies;
@ -130,9 +132,10 @@ ModuleDependencyScanner::scan(PathRef FilePath) {
return Result;
}
void ModuleDependencyScanner::globalScan() {
void ModuleDependencyScanner::globalScan(
const ProjectModules::CommandMangler &Mangler) {
for (auto &File : CDB->getAllFiles())
scan(File);
scan(File, Mangler);
GlobalScanned = true;
}
@ -150,9 +153,9 @@ PathRef ModuleDependencyScanner::getSourceForModuleName(
return {};
}
std::vector<std::string>
ModuleDependencyScanner::getRequiredModules(PathRef File) {
auto ScanningResult = scan(File);
std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
PathRef File, const ProjectModules::CommandMangler &Mangler) {
auto ScanningResult = scan(File, Mangler);
if (!ScanningResult)
return {};
@ -177,7 +180,11 @@ public:
~ScanningAllProjectModules() override = default;
std::vector<std::string> getRequiredModules(PathRef File) override {
return Scanner.getRequiredModules(File);
return Scanner.getRequiredModules(File, Mangler);
}
void setCommandMangler(CommandMangler Mangler) override {
this->Mangler = std::move(Mangler);
}
/// RequiredSourceFile is not used intentionally. See the comments of
@ -185,12 +192,13 @@ public:
PathRef
getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSourceFile = PathRef()) override {
Scanner.globalScan();
Scanner.globalScan(Mangler);
return Scanner.getSourceForModuleName(ModuleName);
}
private:
ModuleDependencyScanner Scanner;
CommandMangler Mangler;
};
std::unique_ptr<ProjectModules> scanningProjectModules(

View File

@ -11,13 +11,14 @@
/// code mode.
#ifndef _WIN32
#include "ModulesBuilder.h"
#include "ScanningProjectModules.h"
#include "Annotations.h"
#include "CodeComplete.h"
#include "Compiler.h"
#include "ModulesBuilder.h"
#include "ScanningProjectModules.h"
#include "TestTU.h"
#include "support/ThreadsafeFS.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
@ -191,6 +192,41 @@ export module M;
EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
}
TEST_F(PrerequisiteModulesTests, ModuleWithArgumentPatch) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.ExtraClangFlags.push_back("-invalid-unknown-flag");
CDB.addFile("Dep.cppm", R"cpp(
export module Dep;
)cpp");
CDB.addFile("M.cppm", R"cpp(
export module M;
import Dep;
)cpp");
// An invalid flag will break the module compilation and the
// getRequiredModules would return an empty array
auto ProjectModules = CDB.getProjectModules(getFullPath("M.cppm"));
EXPECT_TRUE(
ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
// Set the mangler to filter out the invalid flag
ProjectModules->setCommandMangler(
[](tooling::CompileCommand &Command, PathRef) {
auto const It =
std::find(Command.CommandLine.begin(), Command.CommandLine.end(),
"-invalid-unknown-flag");
Command.CommandLine.erase(It);
});
// And now it returns a non-empty list of required modules since the
// compilation succeeded
EXPECT_FALSE(
ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
}
TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
@ -435,7 +471,7 @@ void func() {
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);
auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
Preamble.get(), Use, {});
EXPECT_FALSE(Result.Completions.empty());
@ -474,7 +510,7 @@ void func() {
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);
auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(),
*Preamble.get(), Use, MarkupKind::PlainText);
EXPECT_FALSE(Result.signatures.empty());