Skip to content

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

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kadircet this is what I was talking about with unsafe reference capturing. The solution (while keeping lambda compatibility for unit-tests) would be to make the function copyable but pass the Mangler by the shared_ptr into the very first instance.

PathRef CommandPath) {
Mangler(Command, CommandPath);
});
return std::move(MDB);
}

DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
: Base(Base) {
if (Base)
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/GlobalCompilationDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ class OverlayCDB : public DelegatingCDB {
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) */
Expand Down
7 changes: 7 additions & 0 deletions clang-tools-extra/clangd/ProjectModules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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;
};

Expand Down
38 changes: 23 additions & 15 deletions clang-tools-extra/clangd/ScanningProjectModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class ModuleDependencyScanner {
};

/// 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.
Expand All @@ -57,7 +58,7 @@ class ModuleDependencyScanner {
/// 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.
Expand All @@ -69,7 +70,9 @@ class ModuleDependencyScanner {

/// 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;
Expand All @@ -87,7 +90,8 @@ class ModuleDependencyScanner {
};

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;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}
Expand All @@ -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 {};

Expand All @@ -177,20 +180,25 @@ class ScanningAllProjectModules : public ProjectModules {
~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
/// ModuleDependencyScanner for detail.
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(
Expand Down
44 changes: 40 additions & 4 deletions clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down
Loading