https://github.com/anutosh491 created https://github.com/llvm/llvm-project/pull/226022
## Motivation Clang’s -v output is informational, but several paths currently write it directly to `llvm::errs()`. That is a reasonable default for standalone command-line tools, but applications embedding Clang may need to route verbose output somewhere else .. for example, to an application-owned stream, log buffer, notebook output channel, or browser console. The immediate motivation is xeus-cpp, the C++ kernel for JupyterLite(try [here](https://notebook.link/@quantstack/xeus-cpp)). Jupyterlite is a Jupyter lab distribution that runs entirely in the browser. Its flow is: ``` xeus-cpp → CppInterOp → clang::Interpreter → Clang Driver/Frontend ``` The webassembly build for llvm libs and `ClangInterpreter` used by this kernel is maintained in [emscripten-forge](https://github.com/emscripten-forge/recipes). Jupyter frontends render stderr as red error output, so Clang’s initialization information—such as the version, generated -cc1 command, and header search paths—looks like a compiler failure. Something like this <img width="1363" height="507" alt="image" src="https://github.com/user-attachments/assets/146250c2-dee5-4c59-b7ba-5fc811e33611" /> To fix this the build carries a downstream [patch](https://github.com/emscripten-forge/recipes/blob/main/recipes/recipes_emscripten/llvm/patches/fix_initialization_logs.patch) that changes those writes from `llvm::errs()` to `llvm::outs()`. Hardcoding stdout downstream solves the presentation problem, but it replaces one fixed policy with another and requires us to maintain the patch across LLVM releases. The same issue also arises in [WasmBolt](https://github.com/anutosh491/WasmBolt) that I've been developing recently (read [RFC](https://discourse.llvm.org/t/rfc-embeddable-llvm-tool-drivers-for-long-lived-hosts/91754) & try [here](https://anutosh21.github.io/WasmBolt/)). WasmBolt embeds Clang through `clang::tooling::ToolInvocation` and a custom `ToolAction`, running the compiler inside a long-lived browser process. Such applications need to direct informational output to UI-owned streams without changing where actual compiler diagnostics are reported. ## Existing precedent `CompilerInstance` already provides: ```cpp setVerboseOutputStream(llvm::raw_ostream &) ``` However, not all frontend verbosity respects it. Header-search output, for example, still writes directly to `llvm::errs()`. The Driver and higher-level Interpreter and Tooling entry points also do not currently expose an equivalent configuration path. This patch extends the existing `CompilerInstance` model instead of introducing a separate logging abstraction. ## Changes This patch: - adds a configurable verbose output stream to `clang::driver::Driver`. - makes header-search verbosity respect `CompilerInstance`’s existing verbose stream. - lets `IncrementalCompilerBuilder` propagate a caller-selected stream through both the Driver and frontend. - lets `ToolInvocation` and `ClangTool` propagate the stream through the Driver and `FrontendActionFactory`. - makes the selected stream available to custom `ToolAction` implementations. For example, a notebook kernel can select stdout: ```cpp clang::IncrementalCompilerBuilder Builder; Builder.SetVerboseOutputStream(llvm::outs()); ``` A Tooling-based embedder can similarly use: ```cpp clang::tooling::ToolInvocation Invocation(...); Invocation.setVerboseOutputStream(llvm::outs()); ``` The stream is a general `llvm::raw_ostream`, so callers may also use a file, string buffer, application-specific stream, or `llvm::nulls()`. ## Compatibility - The default remains `llvm::errs()` everywhere. Therefore no standalone behaviour for clang/clang-repl etc changes - Compiler diagnostics are not redirected by this API. They continue to use Clang’s diagnostics engine and its configured diagnostic consumer. This change only affects informational verbose output. This framing makes the important distinction clear: stderr remains a compatible CLI default, while embedded clients gain control over presentation and routing. >From aaf383d46d24d89678dd887be5d593cfaa1baf41 Mon Sep 17 00:00:00 2001 From: anutosh491 <[email protected]> Date: Thu, 24 Sep 2026 10:09:42 +0530 Subject: [PATCH] [clang] Make verbose output streams configurable --- clang/include/clang/Driver/Driver.h | 13 ++++ clang/include/clang/Interpreter/Interpreter.h | 8 +++ clang/include/clang/Lex/HeaderSearch.h | 4 +- clang/include/clang/Tooling/Tooling.h | 27 ++++++++ clang/lib/Driver/Compilation.cpp | 2 +- clang/lib/Driver/Driver.cpp | 20 +++--- clang/lib/Driver/ToolChains/Darwin.cpp | 8 +-- clang/lib/Driver/ToolChains/ZOS.cpp | 2 +- clang/lib/Frontend/CompilerInstance.cpp | 3 +- clang/lib/Interpreter/Interpreter.cpp | 10 ++- clang/lib/Lex/InitHeaderSearch.cpp | 43 +++++++----- clang/lib/Tooling/Tooling.cpp | 16 ++++- .../IncrementalCompilerBuilderTest.cpp | 23 +++++++ clang/unittests/Tooling/ToolingTest.cpp | 66 +++++++++++++++++++ 14 files changed, 204 insertions(+), 41 deletions(-) diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h index e653d8e3a2dbe6..0d492b49b8ed3d 100644 --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -95,6 +95,10 @@ class Driver { IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS; + /// Stream used for informational output produced by verbose driver modes. + /// Diagnostics continue to use the diagnostics engine. + raw_ostream *VerboseOutputStream; + enum DriverMode { GCCMode, GXXMode, @@ -413,6 +417,15 @@ class Driver { void setCheckInputsExist(bool Value) { CheckInputsExist = Value; } + /// Replace the stream used for informational verbose output. The caller + /// retains ownership and must keep the stream alive for this Driver. + void setVerboseOutputStream(raw_ostream &Value) { + VerboseOutputStream = &Value; + } + + /// Return the stream used for informational verbose output. + raw_ostream &getVerboseOutputStream() const { return *VerboseOutputStream; } + bool getProbePrecompiled() const { return ProbePrecompiled; } void setProbePrecompiled(bool Value) { ProbePrecompiled = Value; } diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h index c2622b23d5d9ce..3235b9de44da52 100644 --- a/clang/include/clang/Interpreter/Interpreter.h +++ b/clang/include/clang/Interpreter/Interpreter.h @@ -59,6 +59,13 @@ class IncrementalCompilerBuilder { void SetTargetTriple(std::string TT) { TargetTriple = TT; } + /// Set the stream used for informational verbose output. The caller must + /// ensure that the stream outlives compiler instances created by this + /// builder. Diagnostics are not redirected. + void SetVerboseOutputStream(llvm::raw_ostream &OS) { + VerboseOutputStream = &OS; + } + // General C++ llvm::Expected<std::unique_ptr<CompilerInstance>> CreateCpp(); @@ -84,6 +91,7 @@ class IncrementalCompilerBuilder { std::vector<const char *> UserArgs; std::optional<std::string> TargetTriple; + llvm::raw_ostream *VerboseOutputStream = nullptr; llvm::StringRef OffloadArch; llvm::StringRef CudaSDKPath; diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h index adaa126024c3ca..09dfa39088682b 100644 --- a/clang/include/clang/Lex/HeaderSearch.h +++ b/clang/include/clang/Lex/HeaderSearch.h @@ -37,6 +37,7 @@ namespace llvm { +class raw_ostream; class Triple; } // namespace llvm @@ -1061,7 +1062,8 @@ class HeaderSearch { void ApplyHeaderSearchOptions(HeaderSearch &HS, const HeaderSearchOptions &HSOpts, const LangOptions &Lang, - const llvm::Triple &triple); + const llvm::Triple &triple, + llvm::raw_ostream *VerboseOutput = nullptr); void normalizeModuleCachePath(FileManager &FileMgr, StringRef Path, SmallVectorImpl<char> &NormalizedPath); diff --git a/clang/include/clang/Tooling/Tooling.h b/clang/include/clang/Tooling/Tooling.h index 99093944954962..f2bdeb0420a110 100644 --- a/clang/include/clang/Tooling/Tooling.h +++ b/clang/include/clang/Tooling/Tooling.h @@ -79,15 +79,28 @@ getCC1Arguments(DiagnosticsEngine *Diagnostics, /// If your tool is based on FrontendAction, you should be deriving from /// FrontendActionFactory instead. class ToolAction { + llvm::raw_ostream *VerboseOutputStream = nullptr; + public: virtual ~ToolAction(); + /// Set the stream used for informational verbose output. The caller retains + /// ownership and must keep the stream alive while this action runs. + void setVerboseOutputStream(llvm::raw_ostream &OS) { + VerboseOutputStream = &OS; + } + /// Perform an action for an invocation. virtual bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps, DiagnosticConsumer *DiagConsumer) = 0; + +protected: + llvm::raw_ostream *getVerboseOutputStream() const { + return VerboseOutputStream; + } }; /// Interface to generate clang::FrontendActions. @@ -289,6 +302,12 @@ class ToolInvocation { this->DiagOpts = DiagOpts; } + /// Set the stream used for informational verbose output. The caller retains + /// ownership and must keep the stream alive while this invocation runs. + void setVerboseOutputStream(llvm::raw_ostream &OS) { + VerboseOutputStream = &OS; + } + /// Run the clang invocation. /// /// \returns True if there were no errors during execution. @@ -307,6 +326,7 @@ class ToolInvocation { std::shared_ptr<PCHContainerOperations> PCHContainerOps; DiagnosticConsumer *DiagConsumer = nullptr; DiagnosticOptions *DiagOpts = nullptr; + llvm::raw_ostream *VerboseOutputStream = nullptr; }; /// Utility to run a FrontendAction over a set of files. @@ -345,6 +365,12 @@ class ClangTool { this->DiagConsumer = DiagConsumer; } + /// Set the stream used for informational verbose output. The caller retains + /// ownership and must keep the stream alive while this tool runs. + void setVerboseOutputStream(llvm::raw_ostream &OS) { + VerboseOutputStream = &OS; + } + /// Map a virtual file to be used while running the tool. /// /// \param FilePath The path at which the content will be mapped. @@ -400,6 +426,7 @@ class ClangTool { ArgumentsAdjuster ArgsAdjuster; DiagnosticConsumer *DiagConsumer = nullptr; + llvm::raw_ostream *VerboseOutputStream = nullptr; bool PrintErrorMessage = true; }; diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp index c81c4445a29f95..54e6d7767942c2 100644 --- a/clang/lib/Driver/Compilation.cpp +++ b/clang/lib/Driver/Compilation.cpp @@ -168,7 +168,7 @@ int Compilation::ExecuteCommand(const Command &C, bool LogOnly) const { if ((getDriver().CCPrintOptions || getArgs().hasArg(options::OPT_v)) && !getDriver().CCGenDiagnostics) { - raw_ostream *OS = &llvm::errs(); + raw_ostream *OS = &getDriver().getVerboseOutputStream(); std::unique_ptr<llvm::raw_fd_ostream> OwnedStream; // Follow gcc implementation of CC_PRINT_OPTIONS; we could also cache the diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 7a742e404bf5ca..29700f7f3631b2 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -200,8 +200,8 @@ std::string CUIDOptions::getCUID(StringRef InputFile, Driver::Driver(StringRef DriverExecutable, StringRef TargetTriple, DiagnosticsEngine &Diags, std::string Title, IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) - : Diags(Diags), VFS(std::move(VFS)), Mode(GCCMode), - SaveTemps(SaveTempsNone), BitcodeEmbed(EmbedNone), + : Diags(Diags), VFS(std::move(VFS)), VerboseOutputStream(&llvm::errs()), + Mode(GCCMode), SaveTemps(SaveTempsNone), BitcodeEmbed(EmbedNone), Offload(OffloadHostDevice), CXX20HeaderType(HeaderMode_None), ModulesModeCXX20(false), DriverExecutable(DriverExecutable), SysRoot(DEFAULT_SYSROOT), DriverTitle(Title), CCCPrintBindings(false), @@ -2432,7 +2432,7 @@ int Driver::ExecuteCompilation( SmallVectorImpl<std::pair<int, const Command *>> &FailingCommands) { if (C.getArgs().hasArg(options::OPT_fdriver_only)) { if (C.getArgs().hasArg(options::OPT_v)) - C.getJobs().Print(llvm::errs(), "\n", true); + C.getJobs().Print(getVerboseOutputStream(), "\n", true); C.ExecuteJobs(C.getJobs(), FailingCommands, /*LogOnly=*/true); @@ -2445,7 +2445,7 @@ int Driver::ExecuteCompilation( // Just print if -### was present. if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) { - C.getJobs().Print(llvm::errs(), "\n", true); + C.getJobs().Print(getVerboseOutputStream(), "\n", true); return Diags.hasErrorOccurred() ? 1 : 0; } @@ -2709,23 +2709,23 @@ bool Driver::HandleImmediateArgs(Compilation &C) { C.getArgs().hasArg(options::OPT_print_supported_cpus) || C.getArgs().hasArg(options::OPT_print_supported_extensions) || C.getArgs().hasArg(options::OPT_print_enabled_extensions)) { - PrintVersion(C, llvm::errs()); + PrintVersion(C, getVerboseOutputStream()); SuppressMissingInputWarning = true; } if (C.getArgs().hasArg(options::OPT_v)) { if (!SystemConfigDir.empty()) - llvm::errs() << "System configuration file directory: " - << SystemConfigDir << "\n"; + getVerboseOutputStream() + << "System configuration file directory: " << SystemConfigDir << "\n"; if (!UserConfigDir.empty()) - llvm::errs() << "User configuration file directory: " - << UserConfigDir << "\n"; + getVerboseOutputStream() + << "User configuration file directory: " << UserConfigDir << "\n"; } const ToolChain &TC = C.getDefaultToolChain(); if (C.getArgs().hasArg(options::OPT_v)) - TC.printVerboseInfo(llvm::errs()); + TC.printVerboseInfo(getVerboseOutputStream()); if (C.getArgs().hasArg(options::OPT_print_resource_dir)) { llvm::outs() << ResourceDir << '\n'; diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp index 0fb94d8712e69f..d376992cc0e1be 100644 --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -3078,8 +3078,8 @@ void AppleMachO::AddClangCXXStdlibIncludeArgs( addSystemInclude(DriverArgs, CC1Args, InstallBin); return; } else if (DriverArgs.hasArg(options::OPT_v)) { - llvm::errs() << "ignoring nonexistent directory \"" << InstallBin - << "\"\n"; + getDriver().getVerboseOutputStream() + << "ignoring nonexistent directory \"" << InstallBin << "\"\n"; } // Otherwise, check for (2) @@ -3089,8 +3089,8 @@ void AppleMachO::AddClangCXXStdlibIncludeArgs( addSystemInclude(DriverArgs, CC1Args, SysrootUsr); return; } else if (DriverArgs.hasArg(options::OPT_v)) { - llvm::errs() << "ignoring nonexistent directory \"" << SysrootUsr - << "\"\n"; + getDriver().getVerboseOutputStream() + << "ignoring nonexistent directory \"" << SysrootUsr << "\"\n"; } // Otherwise, don't add any path. diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp index 4c6df90e419b98..01cdb138691169 100644 --- a/clang/lib/Driver/ToolChains/ZOS.cpp +++ b/clang/lib/Driver/ToolChains/ZOS.cpp @@ -331,7 +331,7 @@ void ZOS::TryAddIncludeFromPath(llvm::SmallString<128> Path, llvm::opt::ArgStringList &CC1Args) const { if (!getVFS().exists(Path)) { if (DriverArgs.hasArg(options::OPT_v)) - WithColor::warning(errs(), "Clang") + WithColor::warning(getDriver().getVerboseOutputStream(), "Clang") << "ignoring nonexistent directory \"" << Path << "\"\n"; if (!DriverArgs.hasArg(options::OPT__HASH_HASH_HASH)) return; diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 87abcd38c1a927..bf599ef9a11c25 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -504,7 +504,8 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) { HeaderSearchTriple = &PP->getAuxTargetInfo()->getTriple(); ApplyHeaderSearchOptions(PP->getHeaderSearchInfo(), getHeaderSearchOpts(), - PP->getLangOpts(), *HeaderSearchTriple); + PP->getLangOpts(), *HeaderSearchTriple, + &getVerboseOutputStream()); PP->setPreprocessedOutput(getPreprocessorOutputOpts().ShowCPP); diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp index 512b28be5a544e..543a6a03e81e6d 100644 --- a/clang/lib/Interpreter/Interpreter.cpp +++ b/clang/lib/Interpreter/Interpreter.cpp @@ -269,6 +269,9 @@ IncrementalCompilerBuilder::create(std::string TT, driver::Driver Driver(/*MainBinaryName=*/ClangArgv[0], TT, Diags); Driver.setCheckInputsExist(false); // the input comes from mem buffers + llvm::raw_ostream &VerboseOS = + VerboseOutputStream ? *VerboseOutputStream : llvm::errs(); + Driver.setVerboseOutputStream(VerboseOS); llvm::ArrayRef<const char *> RF = llvm::ArrayRef(ClangArgv); std::unique_ptr<driver::Compilation> Compilation(Driver.BuildCompilation(RF)); @@ -277,13 +280,16 @@ IncrementalCompilerBuilder::create(std::string TT, return std::move(Err); if (Compilation->getArgs().hasArg(options::OPT_v)) - Compilation->getJobs().Print(llvm::errs(), "\n", /*Quote=*/false); + Compilation->getJobs().Print(VerboseOS, "\n", /*Quote=*/false); auto ErrOrCC1Args = GetCC1Arguments(&Diags, Compilation.get()); if (auto Err = ErrOrCC1Args.takeError()) return std::move(Err); - return CreateCI(**ErrOrCC1Args); + auto CI = CreateCI(**ErrOrCC1Args); + if (CI) + (*CI)->setVerboseOutputStream(VerboseOS); + return CI; } llvm::Expected<std::unique_ptr<CompilerInstance>> diff --git a/clang/lib/Lex/InitHeaderSearch.cpp b/clang/lib/Lex/InitHeaderSearch.cpp index e894086b66e76d..5dba00b330ce6b 100644 --- a/clang/lib/Lex/InitHeaderSearch.cpp +++ b/clang/lib/Lex/InitHeaderSearch.cpp @@ -47,13 +47,16 @@ class InitHeaderSearch { std::vector<DirectoryLookupInfo> IncludePath; std::vector<std::pair<std::string, bool> > SystemHeaderPrefixes; HeaderSearch &Headers; + llvm::raw_ostream &VerboseOutput; bool Verbose; std::string IncludeSysroot; bool HasSysroot; public: - InitHeaderSearch(HeaderSearch &HS, bool verbose, StringRef sysroot) - : Headers(HS), Verbose(verbose), IncludeSysroot(std::string(sysroot)), + InitHeaderSearch(HeaderSearch &HS, bool verbose, StringRef sysroot, + llvm::raw_ostream &VerboseOutput) + : Headers(HS), VerboseOutput(VerboseOutput), Verbose(verbose), + IncludeSysroot(std::string(sysroot)), HasSysroot(!(sysroot.empty() || sysroot == "/")) {} /// Add the specified path to the specified group list, prefixing the sysroot @@ -165,8 +168,8 @@ bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group, } if (Verbose) - llvm::errs() << "ignoring nonexistent directory \"" - << MappedPathStr << "\"\n"; + VerboseOutput << "ignoring nonexistent directory \"" << MappedPathStr + << "\"\n"; return false; } @@ -282,7 +285,8 @@ void InitHeaderSearch::AddDefaultIncludePaths( /// remove the later (dead) ones. Returns the number of non-system headers /// removed, which is used to update NumAngled. static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList, - unsigned First, bool Verbose) { + unsigned First, bool Verbose, + llvm::raw_ostream &VerboseOutput) { llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenDirs; llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenFrameworkDirs; llvm::SmallPtrSet<const HeaderMap *, 8> SeenHeaderMaps; @@ -347,11 +351,11 @@ static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList, } if (Verbose) { - llvm::errs() << "ignoring duplicate directory \"" - << CurEntry.getName() << "\"\n"; + VerboseOutput << "ignoring duplicate directory \"" << CurEntry.getName() + << "\"\n"; if (DirToRemove != i) - llvm::errs() << " as it is a non-system directory that duplicates " - << "a system directory\n"; + VerboseOutput << " as it is a non-system directory that duplicates " + << "a system directory\n"; } if (DirToRemove != i) ++NonSystemRemoved; @@ -397,14 +401,14 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) { SearchList.push_back(Include); // Deduplicate and remember index. - RemoveDuplicates(SearchList, 0, Verbose); + RemoveDuplicates(SearchList, 0, Verbose, VerboseOutput); unsigned NumQuoted = SearchList.size(); for (auto &Include : IncludePath) if (Include.Group == Angled) SearchList.push_back(Include); - RemoveDuplicates(SearchList, NumQuoted, Verbose); + RemoveDuplicates(SearchList, NumQuoted, Verbose, VerboseOutput); unsigned NumAngled = SearchList.size(); for (auto &Include : IncludePath) @@ -423,7 +427,8 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) { // Remove duplicates across both the Angled and System directories. GCC does // this and failing to remove duplicates across these two groups breaks // #include_next. - unsigned NonSystemRemoved = RemoveDuplicates(SearchList, NumQuoted, Verbose); + unsigned NonSystemRemoved = + RemoveDuplicates(SearchList, NumQuoted, Verbose, VerboseOutput); NumAngled -= NonSystemRemoved; Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled, @@ -433,10 +438,10 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) { // If verbose, print the list of directories that will be searched. if (Verbose) { - llvm::errs() << "#include \"...\" search starts here:\n"; + VerboseOutput << "#include \"...\" search starts here:\n"; for (unsigned i = 0, e = SearchList.size(); i != e; ++i) { if (i == NumQuoted) - llvm::errs() << "#include <...> search starts here:\n"; + VerboseOutput << "#include <...> search starts here:\n"; StringRef Name = SearchList[i].Lookup.getName(); const char *Suffix; if (SearchList[i].Lookup.isNormalDir()) @@ -447,17 +452,19 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) { assert(SearchList[i].Lookup.isHeaderMap() && "Unknown DirectoryLookup"); Suffix = " (headermap)"; } - llvm::errs() << " " << Name << Suffix << "\n"; + VerboseOutput << " " << Name << Suffix << "\n"; } - llvm::errs() << "End of search list.\n"; + VerboseOutput << "End of search list.\n"; } } void clang::ApplyHeaderSearchOptions(HeaderSearch &HS, const HeaderSearchOptions &HSOpts, const LangOptions &Lang, - const llvm::Triple &Triple) { - InitHeaderSearch Init(HS, HSOpts.Verbose, HSOpts.Sysroot); + const llvm::Triple &Triple, + llvm::raw_ostream *VerboseOutput) { + InitHeaderSearch Init(HS, HSOpts.Verbose, HSOpts.Sysroot, + VerboseOutput ? *VerboseOutput : llvm::errs()); // Add the user defined entries. for (unsigned i = 0, e = HSOpts.UserEntries.size(); i != e; ++i) { diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp index 71307d1fe73076..a9eeec8cf550b2 100644 --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -372,6 +372,9 @@ bool ToolInvocation::run() { for (const std::string &Str : CommandLine) Argv.push_back(Str.c_str()); const char *const BinaryName = Argv[0]; + llvm::raw_ostream &VerboseOS = + VerboseOutputStream ? *VerboseOutputStream : llvm::errs(); + Action->setVerboseOutputStream(VerboseOS); // Parse diagnostic options from the driver command-line only if none were // explicitly set. @@ -405,6 +408,7 @@ bool ToolInvocation::run() { const std::unique_ptr<driver::Driver> Driver( newDriver(&*Diagnostics, BinaryName, Files->getVirtualFileSystemPtr())); + Driver->setVerboseOutputStream(VerboseOS); // The "input file not found" diagnostics from the driver are useful. // The driver is only aware of the VFS working directory, but some clients // change this at the FileManager level instead. @@ -431,9 +435,11 @@ bool ToolInvocation::runInvocation( std::shared_ptr<PCHContainerOperations> PCHContainerOps) { // Show the invocation, with -v. if (Invocation->getHeaderSearchOpts().Verbose) { - llvm::errs() << "clang Invocation:\n"; - Compilation->getJobs().Print(llvm::errs(), "\n", true); - llvm::errs() << "\n"; + llvm::raw_ostream &OS = + VerboseOutputStream ? *VerboseOutputStream : llvm::errs(); + OS << "clang Invocation:\n"; + Compilation->getJobs().Print(OS, "\n", true); + OS << "\n"; } return Action->runInvocation(std::move(Invocation), Files, @@ -446,6 +452,8 @@ bool FrontendActionFactory::runInvocation( DiagnosticConsumer *DiagConsumer) { // Create a compiler instance to handle the actual work. CompilerInstance Compiler(std::move(Invocation), std::move(PCHContainerOps)); + if (llvm::raw_ostream *OS = getVerboseOutputStream()) + Compiler.setVerboseOutputStream(*OS); Compiler.setVirtualFileSystem(Files->getVirtualFileSystemPtr()); Compiler.setFileManager(Files); Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); @@ -627,6 +635,8 @@ int ClangTool::run(ToolAction *Action) { ToolInvocation Invocation(std::move(CommandLine), Action, Files.get(), PCHContainerOps); Invocation.setDiagnosticConsumer(DiagConsumer); + if (VerboseOutputStream) + Invocation.setVerboseOutputStream(*VerboseOutputStream); if (!Invocation.run()) { // FIXME: Diagnostics should be used instead. diff --git a/clang/unittests/Interpreter/IncrementalCompilerBuilderTest.cpp b/clang/unittests/Interpreter/IncrementalCompilerBuilderTest.cpp index 7b4633bfc9e7a3..976df9d83ead6f 100644 --- a/clang/unittests/Interpreter/IncrementalCompilerBuilderTest.cpp +++ b/clang/unittests/Interpreter/IncrementalCompilerBuilderTest.cpp @@ -11,6 +11,7 @@ #include "clang/Interpreter/Interpreter.h" #include "clang/Lex/PreprocessorOptions.h" #include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" using namespace llvm; @@ -36,6 +37,28 @@ TEST(IncrementalCompilerBuilder, SetCompilerArgs) { cleanupRemappedFileBuffers(*CI); } +TEST(IncrementalCompilerBuilder, SetVerboseOutputStream) { + std::string Output; + llvm::raw_string_ostream OS(Output); + std::vector<const char *> ClangArgv = {"-v", "-nostdinc", "-nostdinc++"}; + auto CB = clang::IncrementalCompilerBuilder(); + CB.SetCompilerArgs(ClangArgv); + CB.SetVerboseOutputStream(OS); + + auto CI = cantFail(CB.CreateCpp()); + OS.flush(); + EXPECT_NE(Output.find("clang version"), std::string::npos); + EXPECT_NE(Output.find("-cc1"), std::string::npos); + + auto Interp = cantFail(clang::Interpreter::create(std::move(CI))); + OS.flush(); + EXPECT_NE(Output.find("clang -cc1 version"), std::string::npos); + EXPECT_NE(Output.find("#include \"...\" search starts here:"), + std::string::npos); + EXPECT_NE(Output.find("End of search list."), std::string::npos); + cleanupRemappedFileBuffers(*Interp->getCompilerInstance()); +} + TEST(IncrementalCompilerBuilder, SetTargetTriple) { // FIXME : This test doesn't current work for Emscripten builds. // It should be possible to make it work.For details on how it fails and diff --git a/clang/unittests/Tooling/ToolingTest.cpp b/clang/unittests/Tooling/ToolingTest.cpp index 11a5491dd35fb1..2648a1c5d372a6 100644 --- a/clang/unittests/Tooling/ToolingTest.cpp +++ b/clang/unittests/Tooling/ToolingTest.cpp @@ -26,6 +26,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetSelect.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Host.h" #include "gtest/gtest.h" #include <algorithm> @@ -56,6 +57,23 @@ class TestAction : public clang::ASTFrontendAction { std::unique_ptr<clang::ASTConsumer> TestConsumer; }; +class VerifyVerboseOutputAction : public ToolAction { +public: + VerifyVerboseOutputAction(llvm::raw_ostream &Expected, bool &Observed) + : Expected(Expected), Observed(Observed) {} + + bool runInvocation(std::shared_ptr<CompilerInvocation>, FileManager *, + std::shared_ptr<PCHContainerOperations>, + DiagnosticConsumer *) override { + Observed = getVerboseOutputStream() == &Expected; + return true; + } + +private: + llvm::raw_ostream &Expected; + bool &Observed; +}; + class FindTopLevelDeclConsumer : public clang::ASTConsumer { public: explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl) @@ -226,6 +244,54 @@ TEST(ToolInvocation, TestMapVirtualFile) { EXPECT_TRUE(Invocation.run()); } +TEST(ToolInvocation, VerboseOutputStream) { + auto OverlayFileSystem = + llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>( + llvm::vfs::getRealFileSystem()); + auto InMemoryFileSystem = + llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + auto Files = llvm::makeIntrusiveRefCnt<FileManager>(FileSystemOptions(), + OverlayFileSystem); + InMemoryFileSystem->addFile("test.cpp", 0, + llvm::MemoryBuffer::getMemBuffer("int value;\n")); + + std::vector<std::string> Args = {"tool-executable", "-v", "-fsyntax-only", + "test.cpp"}; + ToolInvocation Invocation(Args, std::make_unique<SyntaxOnlyAction>(), + Files.get()); + std::string Output; + llvm::raw_string_ostream OS(Output); + Invocation.setVerboseOutputStream(OS); + + EXPECT_TRUE(Invocation.run()); + OS.flush(); + EXPECT_NE(Output.find("clang version"), std::string::npos); + EXPECT_NE(Output.find("clang Invocation:"), std::string::npos); + EXPECT_NE(Output.find("-cc1"), std::string::npos); + EXPECT_NE(Output.find("clang -cc1 version"), std::string::npos); + EXPECT_NE(Output.find("End of search list."), std::string::npos); +} + +TEST(ToolInvocation, PropagatesVerboseOutputStreamToToolAction) { + auto FileSystem = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); + FileSystem->addFile("test.cpp", 0, + llvm::MemoryBuffer::getMemBuffer("int value;\n")); + FileManager Files(FileSystemOptions(), FileSystem); + std::string Output; + llvm::raw_string_ostream OS(Output); + bool Observed = false; + VerifyVerboseOutputAction Action(OS, Observed); + std::vector<std::string> Args = {"tool-executable", "-v", "-fsyntax-only", + "test.cpp"}; + ToolInvocation Invocation(Args, &Action, &Files, + std::make_shared<PCHContainerOperations>()); + Invocation.setVerboseOutputStream(OS); + + EXPECT_TRUE(Invocation.run()); + EXPECT_TRUE(Observed); +} + TEST(ToolInvocation, TestVirtualModulesCompilation) { // FIXME: Currently, this only tests that we don't exit with an error if a // mapped module.modulemap is found on the include path. In the future, expand _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
