diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp @@ -6,6 +6,9 @@ // //===----------------------------------------------------------------------===// +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/DiagnosticSerialization.h" #include "clang/Basic/TargetInfo.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" @@ -15,7 +18,9 @@ #include "clang/Parse/Parser.h" #include "clang/Sema/Lookup.h" #include "clang/Serialization/ASTReader.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Threading.h" @@ -25,6 +30,7 @@ #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "lldb/Core/ModuleList.h" +#include "lldb/Core/Progress.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Symbol/CompileUnit.h" @@ -61,6 +67,9 @@ void EndSourceFile() override; private: + bool HandleModuleRemark(const clang::Diagnostic &info); + void SetCurrentModuleProgress(llvm::StringRef module_name); + typedef std::pair IDAndDiagnostic; std::vector m_diagnostics; @@ -72,6 +81,9 @@ /// Output string filled by m_os. Will be reused for different diagnostics. std::string m_output; Log *m_log; + /// A Progress with explicitly managed lifetime. + std::unique_ptr m_current_progress_up; + std::vector m_module_build_stack; }; /// The private implementation of our ClangModulesDeclVendor. Contains all the @@ -140,6 +152,9 @@ void StoringDiagnosticConsumer::HandleDiagnostic( clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &info) { + if (HandleModuleRemark(info)) + return; + // Print the diagnostic to m_output. m_output.clear(); m_diag_printer->HandleDiagnostic(DiagLevel, info); @@ -170,9 +185,54 @@ } void StoringDiagnosticConsumer::EndSourceFile() { + m_current_progress_up = nullptr; m_diag_printer->EndSourceFile(); } +bool StoringDiagnosticConsumer::HandleModuleRemark( + const clang::Diagnostic &info) { + Log *log = GetLog(LLDBLog::Expressions); + switch (info.getID()) { + case clang::diag::remark_module_build: { + const auto &module_name = info.getArgStdStr(0); + SetCurrentModuleProgress(module_name); + m_module_build_stack.push_back(module_name); + + const auto &module_path = info.getArgStdStr(1); + LLDB_LOG(log, "Building Clang module {0} as {1}", module_name, module_path); + return true; + } + case clang::diag::remark_module_build_done: { + // The current module is done. + m_module_build_stack.pop_back(); + if (m_module_build_stack.empty()) { + m_current_progress_up = nullptr; + } else { + // Update the progress to re-show the module that was currently being + // built from the time the now completed module was originally began. + const auto &resumed_module_name = m_module_build_stack.back(); + SetCurrentModuleProgress(resumed_module_name); + } + + const auto &module_name = info.getArgStdStr(0); + LLDB_LOG(log, "Finished building Clang module {0}", module_name); + return true; + } + default: + return false; + } +} + +void StoringDiagnosticConsumer::SetCurrentModuleProgress( + llvm::StringRef module_name) { + // Ensure the ordering of: + // 1. Completing the existing progress event. + // 2. Beginining a new progress event. + m_current_progress_up = nullptr; + m_current_progress_up = std::make_unique( + llvm::formatv("Currently building module {0}", module_name)); +} + ClangModulesDeclVendor::ClangModulesDeclVendor() : ClangDeclVendor(eClangModuleDeclVendor) {} @@ -610,7 +670,8 @@ arch.GetTriple().str(), "-fmodules-validate-system-headers", "-Werror=non-modular-include-in-framework-module", - "-Xclang=-fincremental-extensions"}; + "-Xclang=-fincremental-extensions", + "-Rmodule-build"}; target.GetPlatform()->AddClangModuleCompilationOptions( &target, compiler_invocation_arguments); @@ -648,16 +709,18 @@ } } - llvm::IntrusiveRefCntPtr diagnostics_engine = - clang::CompilerInstance::createDiagnostics(new clang::DiagnosticOptions, - new StoringDiagnosticConsumer); - std::vector compiler_invocation_argument_cstrs; compiler_invocation_argument_cstrs.reserve( compiler_invocation_arguments.size()); for (const std::string &arg : compiler_invocation_arguments) compiler_invocation_argument_cstrs.push_back(arg.c_str()); + auto diag_options_up = + clang::CreateAndPopulateDiagOpts(compiler_invocation_argument_cstrs); + llvm::IntrusiveRefCntPtr diagnostics_engine = + clang::CompilerInstance::createDiagnostics(diag_options_up.release(), + new StoringDiagnosticConsumer); + Log *log = GetLog(LLDBLog::Expressions); LLDB_LOG(log, "ClangModulesDeclVendor's compiler flags {0:$[ ]}", llvm::make_range(compiler_invocation_arguments.begin(), diff --git a/lldb/test/API/functionalities/progress_reporting/clang_modules/Makefile b/lldb/test/API/functionalities/progress_reporting/clang_modules/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/progress_reporting/clang_modules/Makefile @@ -0,0 +1,4 @@ +OBJC_SOURCES := main.m +CFLAGS_EXTRAS = -fmodules -I$(BUILDDIR) + +include Makefile.rules diff --git a/lldb/test/API/functionalities/progress_reporting/clang_modules/MyModule.h b/lldb/test/API/functionalities/progress_reporting/clang_modules/MyModule.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/progress_reporting/clang_modules/MyModule.h @@ -0,0 +1 @@ +extern int doesNotActuallyExist; diff --git a/lldb/test/API/functionalities/progress_reporting/clang_modules/TestClangModuleBuildProgress.py b/lldb/test/API/functionalities/progress_reporting/clang_modules/TestClangModuleBuildProgress.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/progress_reporting/clang_modules/TestClangModuleBuildProgress.py @@ -0,0 +1,46 @@ +""" +Test clang module build progress events. +""" +import os +import shutil + +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestCase(TestBase): + @skipUnlessDarwin + def test_clang_module_build_progress_report(self): + """Test receipt of progress events for clang module builds""" + self.build() + + # Ensure an empty module cache. + mod_cache = self.getBuildArtifact("new-modules") + if os.path.isdir(mod_cache): + shutil.rmtree(mod_cache) + self.runCmd(f"settings set symbols.clang-modules-cache-path '{mod_cache}'") + + # TODO: The need for this seems like a bug. + self.runCmd( + f"settings set target.clang-module-search-paths '{self.getSourceDir()}'" + ) + + lldbutil.run_to_name_breakpoint(self, "main") + + # Just before triggering module builds, start listening for progress + # events. Listening any earlier would result in a queue filled with + # other unrelated progress events. + broadcaster = self.dbg.GetBroadcaster() + listener = lldbutil.start_listening_from( + broadcaster, lldb.SBDebugger.eBroadcastBitProgress + ) + + # Trigger module builds. + self.expect("expression @import MyModule") + + event = lldbutil.fetch_next_event(self, listener, broadcaster) + payload = lldb.SBDebugger.GetProgressFromEvent(event) + message = payload[0] + self.assertEqual(message, "Currently building module MyModule") diff --git a/lldb/test/API/functionalities/progress_reporting/clang_modules/main.m b/lldb/test/API/functionalities/progress_reporting/clang_modules/main.m new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/progress_reporting/clang_modules/main.m @@ -0,0 +1 @@ +int main() {} diff --git a/lldb/test/API/functionalities/progress_reporting/clang_modules/module.modulemap b/lldb/test/API/functionalities/progress_reporting/clang_modules/module.modulemap new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/progress_reporting/clang_modules/module.modulemap @@ -0,0 +1,4 @@ +module MyModule { + header "MyModule.h" + export * +}