Skip to content
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
22 changes: 12 additions & 10 deletions clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,10 @@ class CachingProjectModules : public ProjectModules {
/// Collect the directly and indirectly required module names for \param
/// ModuleName in topological order. The \param ModuleName is guaranteed to
/// be the last element in \param ModuleNames.
llvm::SmallVector<StringRef> getAllRequiredModules(PathRef RequiredSource,
CachingProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<llvm::StringRef> ModuleNames;
llvm::SmallVector<std::string> getAllRequiredModules(PathRef RequiredSource,
CachingProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<std::string> ModuleNames;
llvm::StringSet<> ModuleNamesSet;

auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void {
Expand All @@ -444,7 +444,7 @@ llvm::SmallVector<StringRef> getAllRequiredModules(PathRef RequiredSource,
if (ModuleNamesSet.insert(RequiredModuleName).second)
Visitor(RequiredModuleName, Visitor);

ModuleNames.push_back(ModuleName);
ModuleNames.push_back(ModuleName.str());
};
VisitDeps(ModuleName, VisitDeps);

Expand Down Expand Up @@ -494,28 +494,30 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
// Get Required modules in topological order.
auto ReqModuleNames = getAllRequiredModules(RequiredSource, MDB, ModuleName);
for (llvm::StringRef ReqModuleName : ReqModuleNames) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
if (BuiltModuleFiles.isModuleUnitBuilt(ReqModuleName))
continue;

if (auto Cached = Cache.getModule(ReqModuleName)) {
if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing module {0} from {1}", ModuleName,
log("Reusing module {0} from {1}", ReqModuleName,
Cached->getModuleFilePath());
BuiltModuleFiles.addModuleFile(std::move(Cached));
continue;
}
Cache.remove(ReqModuleName);
}

std::string ReqFileName =
MDB.getSourceForModuleName(ReqModuleName, RequiredSource);
llvm::Expected<ModuleFile> MF = buildModuleFile(
ModuleName, ModuleUnitFileName, getCDB(), TFS, BuiltModuleFiles);
ReqModuleName, ReqFileName, getCDB(), TFS, BuiltModuleFiles);
if (llvm::Error Err = MF.takeError())
return Err;

log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath());
log("Built module {0} to {1}", ReqModuleName, MF->getModuleFilePath());
auto BuiltModuleFile = std::make_shared<const ModuleFile>(std::move(*MF));
Cache.add(ModuleName, BuiltModuleFile);
Cache.add(ReqModuleName, BuiltModuleFile);
BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile));
}

Expand Down
96 changes: 96 additions & 0 deletions clang-tools-extra/clangd/test/module_dependencies.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# A smoke test to check that a simple dependency chain for modules can work.
#
# FIXME: This fails on the Windows ARM64 build server. Not entirely sure why as it has been tested on
# an ARM64 Windows VM and appears to work there.
# UNSUPPORTED: host=aarch64-pc-windows-msvc
#
# RUN: rm -fr %t
# RUN: mkdir -p %t
# RUN: split-file %s %t
#
# RUN: sed -e "s|DIR|%/t|g" %t/compile_commands.json.tmpl > %t/compile_commands.json.tmp
# RUN: sed -e "s|CLANG_CC|%clang|g" %t/compile_commands.json.tmp > %t/compile_commands.json
# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc.tmp
Comment on lines +11 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

Using sed for paths like this isn't portable, and I'm seeing failures on some internal windows bots because of it. The UNSUPPORTED here doesn't seem sufficient.

Notably, since on windows paths will include backslashes, a number of paths will be interpreted as having escape sequences, so we end up with either an error from the sed command saying we can't use that escape sequence there, or some kind of garbage in the replacement text.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@bogner in case @fleeting-xx doesn't respond in time, would you like to fix these failures in windows by updating the UNSUPPORTED clause properly? I hope we can get this in 20.x. Thanks in ahead.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@fleeting-xx I suspect the right medium to long term solution here would be to convert these tests to unit tests, as @ChuanqiXu9 suggested. This would avoid having to mess around with the config file using sed since all of that set up could be done programatically.

Thanks @zmodem for the short term fix!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I missed this. I'll see about getting these moved to unit tests.

#
# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..."
# (with the extra slash in the front), so we add it here.
# RUN: sed -E -e 's|"file://([A-Z]):/|"file:///\1:/|g' %/t/definition.jsonrpc.tmp > %/t/definition.jsonrpc
#
# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \
# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc

#--- A-frag.cppm
export module A:frag;
export void printA() {}

#--- A.cppm
export module A;
export import :frag;

#--- Use.cpp
import A;
void foo() {
print
}

#--- compile_commands.json.tmpl
[
{
"directory": "DIR",
"command": "CLANG_CC -fprebuilt-module-path=DIR -std=c++20 -o DIR/main.cpp.o -c DIR/Use.cpp",
"file": "DIR/Use.cpp"
},
{
"directory": "DIR",
"command": "CLANG_CC -std=c++20 DIR/A.cppm --precompile -o DIR/A.pcm",
"file": "DIR/A.cppm"
},
{
"directory": "DIR",
"command": "CLANG_CC -std=c++20 DIR/A-frag.cppm --precompile -o DIR/A-frag.pcm",
"file": "DIR/A-frag.cppm"
}
]

#--- definition.jsonrpc.tmpl
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"processId": 123,
"rootPath": "clangd",
"capabilities": {
"textDocument": {
"completion": {
"completionItem": {
"snippetSupport": true
}
}
}
},
"trace": "off"
}
}
---
{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": "file://DIR/Use.cpp",
"languageId": "cpp",
"version": 1,
"text": "import A;\nvoid foo() {\n print\n}\n"
}
}
}

# CHECK: "message"{{.*}}printA{{.*}}(fix available)

---
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}}
---
{"jsonrpc":"2.0","id":2,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}