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

Reply via email to