Author: Qiongsi Wu
Date: 2026-09-04T21:13:46-07:00
New Revision: a638ce4ea02fe59d0ffba4a81408ddb86cfa0ecc

URL: 
https://github.com/llvm/llvm-project/commit/a638ce4ea02fe59d0ffba4a81408ddb86cfa0ecc
DIFF: 
https://github.com/llvm/llvm-project/commit/a638ce4ea02fe59d0ffba4a81408ddb86cfa0ecc.diff

LOG: [clang][DependencyScanning] Add a Clang Driver Option to Enable Dependency 
Scanning Logging (#211966)

This PR adds a clang driver option to enable dependency scanning
logging. The important implementation detail is that the
`DependencyScanningService` holds a single instance of the logger, and
we need to route the option to the logger per scan. An `enable` method
is added to the logger's API to enable it with thread safety. A second
detail worth calling out is that the option is a driver option, so if a
`cc1` command is passed directly to the scanner, the option will not be
processed.

Added: 
    clang/test/ClangScanDeps/depscan-log-path.c
    clang/test/ClangScanDeps/logging-driver-flag.c
    clang/test/Driver/modules-driver-depscan-log.cpp

Modified: 
    clang/include/clang/Basic/AtomicLineLogger.h
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/DependencyScanning/DependencyScanningWorker.h
    clang/include/clang/Options/Options.td
    clang/lib/Basic/AtomicLineLogger.cpp
    clang/lib/Driver/ModulesDriver.cpp
    clang/lib/Tooling/DependencyScanningTool.cpp
    clang/test/ClangScanDeps/logging-simple-by-name.c
    clang/test/ClangScanDeps/logging-simple.c
    clang/unittests/Basic/AtomicLineLoggerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/AtomicLineLogger.h 
b/clang/include/clang/Basic/AtomicLineLogger.h
index 3d06ddfce0262..f1489214a9c56 100644
--- a/clang/include/clang/Basic/AtomicLineLogger.h
+++ b/clang/include/clang/Basic/AtomicLineLogger.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Support/raw_ostream.h"
 #include <atomic>
+#include <mutex>
 #include <optional>
 #include <string>
 
@@ -60,9 +61,18 @@ class LogLine {
 };
 
 class AtomicLineLogger {
-  int FD = -1;
+  std::atomic<int> FD{-1};
   std::string LogPath;
   std::atomic<uint64_t> DroppedLines{0};
+  std::mutex EnableMtx;
+  enum class LogPathSource {
+    None,
+    Constructor, // The path is from the constructor.
+    EnableMethod // The path is set through calling the enable() method.
+  };
+  LogPathSource PathSource = LogPathSource::None;
+
+  void initialize(StringRef LogFilePath);
 
 public:
   AtomicLineLogger() {}
@@ -75,6 +85,14 @@ class AtomicLineLogger {
 
   ~AtomicLineLogger();
 
+  /// Enables the logger if it is not already enabled. Thread safe.
+  ///
+  /// \returns false if the logger is not enabled consistently during its
+  /// lifetime.
+  bool enable(StringRef RequestedLogPath);
+
+  StringRef getLogPath() const { return LogPath; }
+
   LogLine log();
 };
 

diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td 
b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 5e8ca1d822a22..df6ff0c2cf399 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -667,6 +667,12 @@ def remark_printing_module_graph : Remark<
 def err_module_defined_outside_of_module_source : Error<
   "module '%0' is defined in file '%1', but module declarations are only "
   "allowed in C++ module inputs; use the '.cppm' extension or '-x c++module'">;
+def err_drv_depscan_log_path_empty : Error<
+  "'-fdepscan-log-path=' requires a non-empty file path">;
+def err_drv_depscan_log_path_inconsistent : Error<
+  "'-fdepscan-log-path' set inconsistently within a dependency scan: "
+  "%select{no log path|log path '%1'}0 conflicts with "
+  "%select{no log path|log path '%3'}2 requested earlier">;
 
 def warn_drv_delayed_template_parsing_after_cxx20 : Warning<
   "-fdelayed-template-parsing is deprecated after C++20">,

diff  --git a/clang/include/clang/DependencyScanning/DependencyScanningWorker.h 
b/clang/include/clang/DependencyScanning/DependencyScanningWorker.h
index 638c317d9ce64..2c5c7fbb8f9a0 100644
--- a/clang/include/clang/DependencyScanning/DependencyScanningWorker.h
+++ b/clang/include/clang/DependencyScanning/DependencyScanningWorker.h
@@ -90,6 +90,8 @@ class DependencyScanningWorker {
     return TracingFS.get();
   }
 
+  DependencyScanningService &getService() const { return Service; }
+
   // MaxNumOfByNameQueries is the upper limit of the number of names the 
by-name
   // scanning API (computeDependenciesByName) can drain per call. At the time 
of
   // this commit, the estimated number of total unique importable names is

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 348739c0c15ed..37e5c3199a003 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3822,6 +3822,13 @@ def fno_modules_driver :
   Group<f_Group>,
   Visibility<[ClangOption]>,
   HelpText<"Disable support for driver managed module builds (experimental)">;
+def fdepscan_log_path : Joined<["-"], "fdepscan-log-path=">,
+  Group<f_Group>,
+  Visibility<[ClangOption]>,
+  Flags<[NoArgumentUnused]>,
+  MetaVarName<"<file>">,
+  HelpText<"Log the timing of dependency scanning actions to <file>. Only "
+           "takes effect while running the dependency scanner.">;
 
 def fincremental_extensions :
   Flag<["-"], "fincremental-extensions">,

diff  --git a/clang/lib/Basic/AtomicLineLogger.cpp 
b/clang/lib/Basic/AtomicLineLogger.cpp
index dc0f2bf5adc6c..592e1cd63594c 100644
--- a/clang/lib/Basic/AtomicLineLogger.cpp
+++ b/clang/lib/Basic/AtomicLineLogger.cpp
@@ -14,6 +14,7 @@
 #include "clang/Basic/AtomicLineLogger.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Errno.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/Process.h"
@@ -41,17 +42,38 @@ static uint64_t getTimestampMillis() {
 #endif
 }
 
+static int openLogFile(StringRef Path) {
+#ifdef _WIN32
+  // Logging is always disabled on Windows. openLogFile implements this policy
+  // by never returning a valid FD, so the logger and the LogLines it creates
+  // stay dormant (FD == -1). The reason is that writes to files opened with
+  // OF_Append are not guaranteed atomic on Windows. If a use case arises we'll
+  // need a 
diff erent strategy to write LogLines atomically.
+  (void)Path;
+  return -1;
+#else
+  int FD = -1;
+  std::error_code EC = llvm::sys::fs::openFileForWrite(
+      Path, FD, llvm::sys::fs::CD_OpenAlways, llvm::sys::fs::OF_Append);
+  if (EC) {
+    llvm::errs() << "warning: unable to open log file '" << Path
+                 << "': " << EC.message() << "\n";
+    return -1;
+  }
+  return FD;
+#endif
+}
+
 // Writes the whole line into an FD that is opened with OF_Append.
 // This function only does one write (up to retry due to interrupts), and the
 // single write is blocking and atomic on POSIX systems.
 static bool writeLineToFD(int FD, const char *Data, size_t Size) {
-#ifndef _WIN32
+#ifdef _WIN32
+  (void)FD, (void)Data, (void)Size;
+  llvm_unreachable("dependency scanning logging is unsupported on Windows");
+#else
   ssize_t Written = llvm::sys::RetryAfterSignal(-1, write, FD, Data, Size);
   return Written >= 0 && (static_cast<size_t>(Written) == Size);
-#else
-  (void)FD, (void)Data, (void)Size;
-  llvm_unreachable("Logging not supported on Windows!");
-  return false;
 #endif
 }
 
@@ -83,34 +105,51 @@ LogLine::~LogLine() {
     DroppedLines->fetch_add(1, std::memory_order_relaxed);
 }
 
-AtomicLineLogger::AtomicLineLogger(StringRef LogFilePath)
-    : LogPath(LogFilePath.str()) {
-#ifndef _WIN32
-  if (LogFilePath.empty())
+void AtomicLineLogger::initialize(StringRef LogFilePath) {
+  LogPath = LogFilePath.str();
+  int NewFD = openLogFile(LogFilePath);
+  if (NewFD == -1)
     return;
+  FD.store(NewFD, std::memory_order_relaxed);
+  log() << "logging_start";
+}
 
-  std::error_code EC = llvm::sys::fs::openFileForWrite(
-      LogFilePath, FD, llvm::sys::fs::CD_OpenAlways, llvm::sys::fs::OF_Append);
-  if (EC) {
-    llvm::errs() << "warning: unable to open log file '" << LogFilePath
-                 << "': " << EC.message() << "\n";
-    FD = -1;
+AtomicLineLogger::AtomicLineLogger(StringRef LogFilePath) {
+  if (LogFilePath.empty())
     return;
+  initialize(LogFilePath);
+  PathSource = LogPathSource::Constructor;
+}
+
+bool AtomicLineLogger::enable(StringRef RequestedLogPath) {
+  std::lock_guard<std::mutex> Lock(EnableMtx);
+  switch (PathSource) {
+  case LogPathSource::None:
+    PathSource = LogPathSource::EnableMethod;
+    if (!RequestedLogPath.empty())
+      initialize(RequestedLogPath);
+    return true;
+  case LogPathSource::Constructor:
+    return RequestedLogPath.empty() || RequestedLogPath == LogPath;
+  case LogPathSource::EnableMethod:
+    return RequestedLogPath == LogPath;
   }
-#endif
-  // Write to files opened with OF_Append may not be guaranteed to be atomic
-  // on Windows, so we do not enable logging on Windows.
+
+  llvm_unreachable("unhandled LogPathSource");
 }
 
 LogLine AtomicLineLogger::log() {
-  if (FD != -1)
-    return LogLine(FD, &DroppedLines);
+  int CurFD = FD.load(std::memory_order_relaxed);
+  if (CurFD != -1)
+    return LogLine(CurFD, &DroppedLines);
   return LogLine();
 }
 
 AtomicLineLogger::~AtomicLineLogger() {
-  if (FD == -1)
+  int CurFD = FD.load(std::memory_order_relaxed);
+  if (CurFD == -1)
     return;
+  log() << "logging_end";
   if (uint64_t Dropped = DroppedLines.load(std::memory_order_relaxed))
     llvm::errs() << "warning: log '" << LogPath
                  << "' is incomplete: " << Dropped

diff  --git a/clang/lib/Driver/ModulesDriver.cpp 
b/clang/lib/Driver/ModulesDriver.cpp
index f0f312769fab1..9e6984fa8ef6b 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -607,7 +607,7 @@ static std::optional<DependencyScanResult> scanDependencies(
     ArrayRef<std::unique_ptr<Command>> Jobs,
     llvm::DenseMap<StringRef, const StdModuleManifest::Module *> 
ManifestLookup,
     StringRef ModuleCachePath, StringRef WorkingDirectory,
-    DiagnosticsEngine &Diags) {
+    StringRef DepScanLogPath, DiagnosticsEngine &Diags) {
   llvm::PrettyStackTraceString CrashInfo("Performing module dependency scan.");
 
   // Classify the jobs based on scan eligibility.
@@ -645,6 +645,7 @@ static std::optional<DependencyScanResult> scanDependencies(
   const bool HasStdlibModuleInputs = !StdlibModuleScanIndexByID.empty();
 
   deps::DependencyScanningServiceOptions Opts;
+  Opts.LogPath = DepScanLogPath.str();
   deps::DependencyScanningService ScanningService(std::move(Opts));
 
   std::unique_ptr<llvm::ThreadPoolInterface> ThreadPool;
@@ -1655,8 +1656,18 @@ void driver::modules::runModulesDriver(
   auto MaybeCWD = C.getDriver().getVFS().getCurrentWorkingDirectory();
   const auto CWD = MaybeCWD ? std::move(*MaybeCWD) : ".";
 
-  auto MaybeScanResults = scanDependencies(Jobs, ManifestEntryBySource,
-                                           *MaybeModuleCachePath, CWD, Diags);
+  const llvm::opt::Arg *LogPathArg =
+      C.getArgs().getLastArg(options::OPT_fdepscan_log_path);
+  StringRef DepScanLogPath =
+      LogPathArg ? StringRef(LogPathArg->getValue()).trim() : StringRef();
+  if (LogPathArg && DepScanLogPath.empty()) {
+    Diags.Report(diag::err_drv_depscan_log_path_empty);
+    return;
+  }
+
+  auto MaybeScanResults =
+      scanDependencies(Jobs, ManifestEntryBySource, *MaybeModuleCachePath, CWD,
+                       DepScanLogPath, Diags);
   if (!MaybeScanResults) {
     Diags.Report(diag::err_dependency_scan_failed);
     return;

diff  --git a/clang/lib/Tooling/DependencyScanningTool.cpp 
b/clang/lib/Tooling/DependencyScanningTool.cpp
index b3332d9d49cd8..a937879706522 100644
--- a/clang/lib/Tooling/DependencyScanningTool.cpp
+++ b/clang/lib/Tooling/DependencyScanningTool.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/Tooling/DependencyScanningTool.h"
+#include "clang/Basic/AtomicLineLogger.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticFrontend.h"
 #include "clang/DependencyScanning/DependencyScanningWorker.h"
@@ -15,6 +16,7 @@
 #include "clang/Driver/Tool.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/Utils.h"
+#include "clang/Options/Options.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/iterator.h"
 #include "llvm/TargetParser/Host.h"
@@ -90,7 +92,7 @@ static std::pair<std::unique_ptr<driver::Driver>,
                  std::unique_ptr<driver::Compilation>>
 buildCompilation(ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags,
                  IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
-                 llvm::BumpPtrAllocator &Alloc) {
+                 llvm::BumpPtrAllocator &Alloc, AtomicLineLogger &Logger) {
   SmallVector<const char *, 256> Argv;
   Argv.reserve(ArgStrs.size());
   for (const std::string &Arg : ArgStrs)
@@ -125,6 +127,24 @@ buildCompilation(ArrayRef<std::string> ArgStrs, 
DiagnosticsEngine &Diags,
     return std::make_pair(nullptr, nullptr);
   }
 
+  const llvm::opt::Arg *LogPathArg =
+      Compilation->getArgs().getLastArg(options::OPT_fdepscan_log_path);
+  StringRef LogPath =
+      LogPathArg ? StringRef(LogPathArg->getValue()).trim() : StringRef();
+
+  // Forbid -fdepscan-log-path="".
+  if (LogPathArg && LogPath.empty()) {
+    Diags.Report(diag::err_drv_depscan_log_path_empty);
+    return std::make_pair(nullptr, nullptr);
+  }
+
+  if (!Logger.enable(LogPath)) {
+    Diags.Report(diag::err_drv_depscan_log_path_inconsistent)
+        << unsigned(!LogPath.empty()) << LogPath
+        << unsigned(!Logger.getLogPath().empty()) << Logger.getLogPath();
+    return std::make_pair(nullptr, nullptr);
+  }
+
   return std::make_pair(std::move(Driver), std::move(Compilation));
 }
 
@@ -155,8 +175,8 @@ static bool computeDependenciesForDriverCommandLine(
   auto DiagEngine =
       CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DiagConsumer,
                                           /*ShouldOwnClient=*/false);
-  const auto [Driver, Compilation] =
-      buildCompilation(CommandLine, *DiagEngine, FS, Alloc);
+  const auto [Driver, Compilation] = buildCompilation(
+      CommandLine, *DiagEngine, FS, Alloc, Worker.getService().getLogger());
   if (!Compilation)
     return false;
 
@@ -326,13 +346,14 @@ DependencyScanningTool::getTranslationUnitDependencies(
 static std::optional<SmallVector<std::string, 0>>
 getFirstCC1CommandLine(ArrayRef<std::string> CommandLine,
                        DiagnosticsEngine &Diags,
-                       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) {
+                       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
+                       AtomicLineLogger &Logger) {
   // Compilation holds a non-owning a reference to the Driver, hence we need to
   // keep the Driver alive when we use Compilation. Arguments to commands may 
be
   // owned by Alloc when expanded from response files.
   llvm::BumpPtrAllocator Alloc;
   const auto [Driver, Compilation] =
-      buildCompilation(CommandLine, Diags, std::move(FS), Alloc);
+      buildCompilation(CommandLine, Diags, std::move(FS), Alloc, Logger);
   if (!Compilation)
     return std::nullopt;
 
@@ -362,8 +383,8 @@ bool DependencyScanningTool::getByNameDependencies(
     auto DiagEngine =
         CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DiagConsumer,
                                             /*ShouldOwnClient=*/false);
-    auto MaybeFirstCC1 =
-        getFirstCC1CommandLine(ModifiedCommandLine, *DiagEngine, FS);
+    auto MaybeFirstCC1 = getFirstCC1CommandLine(
+        ModifiedCommandLine, *DiagEngine, FS, Worker.getService().getLogger());
     if (!MaybeFirstCC1)
       return false;
     CC1CommandLine.assign(MaybeFirstCC1->begin(), MaybeFirstCC1->end());

diff  --git a/clang/test/ClangScanDeps/depscan-log-path.c 
b/clang/test/ClangScanDeps/depscan-log-path.c
new file mode 100644
index 0000000000000..4a543c511eb67
--- /dev/null
+++ b/clang/test/ClangScanDeps/depscan-log-path.c
@@ -0,0 +1,65 @@
+// Diagnostics for the -fdepscan-log-path driver flag during dependency 
scanning:
+// an empty value is rejected, an inconsistent value across commands in one 
scan
+// is rejected, and a consistent value is accepted (and aggregated).
+
+// UNSUPPORTED: system-windows
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s|DIR|%/t|g" %t/empty.json.template        > %t/empty.json
+// RUN: sed -e "s|DIR|%/t|g" %t/inconsistent.json.template > 
%t/inconsistent.json
+// RUN: sed -e "s|DIR|%/t|g" %t/inconsistent2.json.template > 
%t/inconsistent2.json
+// RUN: sed -e "s|DIR|%/t|g" %t/consistent.json.template   > %t/consistent.json
+
+// An explicitly empty value is rejected.
+// RUN: not clang-scan-deps -compilation-database %t/empty.json \
+// RUN:   -format experimental-full -j 1 2>&1 | FileCheck %s 
--check-prefix=EMPTY
+// EMPTY: error: '-fdepscan-log-path=' requires a non-empty file path
+
+// Different log paths across commands in one scan are rejected.
+// RUN: not clang-scan-deps -compilation-database %t/inconsistent.json \
+// RUN:   -format experimental-full -j 1 2>&1 | FileCheck %s 
--check-prefix=CONFLICT
+
+// One command with a valid flag, the other does not have a flag in effect.
+// RUN: not clang-scan-deps -compilation-database %t/inconsistent2.json \
+// RUN:   -format experimental-full -j 1 2>&1 | FileCheck %s 
--check-prefix=CONFLICT
+// CONFLICT: error: '-fdepscan-log-path' set inconsistently within a 
dependency scan
+
+// The same log path across commands is fine; both are aggregated into one log.
+// RUN: clang-scan-deps -compilation-database %t/consistent.json \
+// RUN:   -format experimental-full -j 1 -o %t/deps.json
+// RUN: FileCheck %s --check-prefix=OK --input-file %t/scan.log
+// OK: logging_start
+// OK: starting scanning command:{{.*}}tu.c
+// OK: starting scanning command:{{.*}}tu2.c
+// OK: logging_end
+
+
+//--- empty.json.template
+[{
+  "directory": "DIR",
+  "command": "clang -fsyntax-only DIR/tu.c -fdepscan-log-path=",
+  "file": "DIR/tu.c"
+}]
+
+//--- inconsistent.json.template
+[
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu.c 
-fdepscan-log-path=DIR/a.log",  "file": "DIR/tu.c" },
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu2.c 
-fdepscan-log-path=DIR/b.log", "file": "DIR/tu2.c" }
+]
+
+//--- consistent.json.template
+[
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu.c 
-fdepscan-log-path=DIR/scan.log",  "file": "DIR/tu.c" },
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu2.c 
-fdepscan-log-path=DIR/scan.log", "file": "DIR/tu2.c" }
+]
+
+//--- inconsistent2.json.template
+[
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu.c",  "file": 
"DIR/tu.c" },
+{ "directory": "DIR", "command": "clang -fsyntax-only DIR/tu2.c 
-fdepscan-log-path=DIR/b.log", "file": "DIR/tu2.c" }
+]
+
+//--- tu.c
+void foo(void) {}
+//--- tu2.c
+void bar(void) {}

diff  --git a/clang/test/ClangScanDeps/logging-driver-flag.c 
b/clang/test/ClangScanDeps/logging-driver-flag.c
new file mode 100644
index 0000000000000..144099ee5dce0
--- /dev/null
+++ b/clang/test/ClangScanDeps/logging-driver-flag.c
@@ -0,0 +1,43 @@
+// UNSUPPORTED: system-windows
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
+// RUN: sed -e "s|DIR|%/t|g" %t/cdb-by-name.json.template > %t/cdb-by-name.json
+
+// RUN: clang-scan-deps -compilation-database %t/cdb.json \
+// RUN:   -format experimental-full -j 1 -o %t/deps.json
+// RUN: FileCheck %s --check-prefix=TU --input-file %t/tu.log
+
+// TU: [{{[0-9]+\.[0-9]+}}] [[#PID:]] [[#TID:]]: starting scanning 
command:{{.*}}tu.c
+// TU: [{{[0-9]+\.[0-9]+}}] {{.*}}: pcm_write: {{.*}}.pcm
+// TU: [{{[0-9]+\.[0-9]+}}] {{.*}}: finished scanning command:{{.*}}tu.c
+
+// RUN: clang-scan-deps -compilation-database %t/cdb-by-name.json \
+// RUN:   -format experimental-full -j 1 -module-names=A
+// RUN: FileCheck %s --check-prefix=BY-NAME --input-file %t/by-name.log
+
+// BY-NAME: [{{[0-9]+\.[0-9]+}}] {{.*}}: start scan_by_name: A
+// BY-NAME: [{{[0-9]+\.[0-9]+}}] {{.*}}: finish scan_by_name: A
+
+//--- cdb.json.template
+[{
+  "directory": "DIR",
+  "command": "clang -fsyntax-only DIR/tu.c -fmodules -fimplicit-module-maps 
-fmodules-cache-path=DIR/cache -fbuild-session-timestamp=1
+-fmodules-validate-once-per-build-session -fdepscan-log-path=DIR/tu.log",
+  "file": "DIR/tu.c"
+}]
+
+//--- cdb-by-name.json.template
+[{
+  "directory": "DIR",
+  "command": "clang -fmodules -fimplicit-module-maps 
-fmodules-cache-path=DIR/cache -I DIR -x c -fdepscan-log-path=DIR/by-name.log",
+  "file": ""
+}]
+
+//--- module.modulemap
+module A { header "A.h" }
+//--- A.h
+void A_func(void);
+//--- tu.c
+#include "A.h"
+void foo(void) { A_func(); }

diff  --git a/clang/test/ClangScanDeps/logging-simple-by-name.c 
b/clang/test/ClangScanDeps/logging-simple-by-name.c
index 954d25acdd1f7..b02fad5117ea9 100644
--- a/clang/test/ClangScanDeps/logging-simple-by-name.c
+++ b/clang/test/ClangScanDeps/logging-simple-by-name.c
@@ -17,7 +17,8 @@
 // RUN:   -module-names=M,N -o %t/deps.json
 // RUN: FileCheck %s < %t/scan.log
 
-// CHECK: [{{[0-9]+\.[0-9]+}}] [[#PID:]] [[#TID1:]]: 
init_compiler_instance_with_context:{{.*}}
+// CHECK: [{{[0-9]+\.[0-9]+}}] [[#PID:]] [[#TID1:]]: logging_start
+// CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: 
init_compiler_instance_with_context:{{.*}}
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: start scan_by_name: M
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: timestamp_read: 
{{.*}}[[MPCMFILE:.*\.pcm]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: pcm_read_cached: 
{{.*}}[[MPCMFILE]]
@@ -46,6 +47,7 @@
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: timestamp_read: 
{{.*}}[[NPCMFILE]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: pcm_finalized: 
{{.*}}[[NPCMFILE]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: finish scan_by_name: N
+// CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID1]]: logging_end
 
 //--- cdb.json.template
 [{

diff  --git a/clang/test/ClangScanDeps/logging-simple.c 
b/clang/test/ClangScanDeps/logging-simple.c
index 0022fb97cbc73..444fdf1ab6e20 100644
--- a/clang/test/ClangScanDeps/logging-simple.c
+++ b/clang/test/ClangScanDeps/logging-simple.c
@@ -13,7 +13,8 @@
 // build a single scanning pcm. We should only log these events, no more and no
 // less, strictly in this order. Changes to this list should be intentional.
 
-// CHECK: [{{[0-9]+\.[0-9]+}}] [[#PID:]] [[#TID:]]: starting scanning 
command:{{.*}}tu.c
+// CHECK: [{{[0-9]+\.[0-9]+}}] [[#PID:]] [[#TID:]]: logging_start
+// CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: starting scanning 
command:{{.*}}tu.c
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: 
init_compiler_instance_with_context:{{.*}}
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: timestamp_read: 
{{.*}}[[PCMFILE:.*\.pcm]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: pcm_read_cached: 
{{.*}}[[PCMFILE]]
@@ -26,6 +27,7 @@
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: pcm_read_cached: 
{{.*}}[[PCMFILE]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: pcm_finalized: 
{{.*}}[[PCMFILE]]
 // CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: finished scanning 
command:{{.*}}tu.c
+// CHECK-NEXT: [{{[0-9]+\.[0-9]+}}] [[#PID]] [[#TID]]: logging_end
 
 //--- cdb.json.template
 [{

diff  --git a/clang/test/Driver/modules-driver-depscan-log.cpp 
b/clang/test/Driver/modules-driver-depscan-log.cpp
new file mode 100644
index 0000000000000..62f0ae859d56a
--- /dev/null
+++ b/clang/test/Driver/modules-driver-depscan-log.cpp
@@ -0,0 +1,17 @@
+// Check that -fdepscan-log-path enables dependency scanning logging.
+
+// UNSUPPORTED: system-windows
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang -c -std=c++23 -fmodules-driver -fdepscan-log-path=%t/scan.log \
+// RUN:   %t/A.cppm
+// RUN: FileCheck %s --input-file %t/scan.log
+
+// CHECK: logging_start
+// CHECK: starting scanning command:
+// CHECK: logging_end
+
+//--- A.cppm
+export module A;
+export int a() { return 0; }

diff  --git a/clang/unittests/Basic/AtomicLineLoggerTest.cpp 
b/clang/unittests/Basic/AtomicLineLoggerTest.cpp
index b1dfb616d08c2..97e600a1a17eb 100644
--- a/clang/unittests/Basic/AtomicLineLoggerTest.cpp
+++ b/clang/unittests/Basic/AtomicLineLoggerTest.cpp
@@ -16,6 +16,22 @@
 
 using namespace clang;
 
+#ifndef _WIN32
+static StringRef logBody(StringRef Line) { return Line.split(": ").second; }
+
+static SmallVector<StringRef> logBodyLines(StringRef Content) {
+  SmallVector<StringRef> Lines;
+  Content.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
+  if (Lines.size() < 2) {
+    ADD_FAILURE() << "log not framed by logging_start/logging_end; got "
+                  << Lines.size() << " line(s)";
+    return {};
+  }
+  EXPECT_EQ(logBody(Lines.front()), "logging_start");
+  EXPECT_EQ(logBody(Lines.back()), "logging_end");
+  return SmallVector<StringRef>(ArrayRef(Lines).drop_front().drop_back());
+}
+
 TEST(AtomicLineLoggerTest, DisabledLoggerDoesNotCrash) {
   AtomicLineLogger Logger;
   Logger.log() << "this goes nowhere";
@@ -24,7 +40,6 @@ TEST(AtomicLineLoggerTest, DisabledLoggerDoesNotCrash) {
   EXPECT_TRUE(true);
 }
 
-#ifndef _WIN32
 TEST(AtomicLineLoggerTest, LogLineMoveConstructor) {
   llvm::unittest::TempDir Dir("atomic-logger-test", /*Unique=*/true);
   SmallString<128> LogPath(Dir.path());
@@ -41,9 +56,10 @@ TEST(AtomicLineLoggerTest, LogLineMoveConstructor) {
   ASSERT_TRUE(BufOrErr) << "Failed to read log file";
   StringRef Content = (*BufOrErr)->getBuffer();
 
-  // Only one line should be written (from Moved, not from Original).
-  EXPECT_EQ(Content.count('\n'), 1u);
-  EXPECT_TRUE(Content.contains("after_move"));
+  // Only one log body line should be written (from Moved, not from Original).
+  auto Body = logBodyLines(Content);
+  ASSERT_EQ(Body.size(), 1u);
+  EXPECT_EQ(logBody(Body.front()), "after_move");
 }
 
 TEST(AtomicLineLoggerTest, LogLinePIDTIDMsg) {
@@ -60,17 +76,18 @@ TEST(AtomicLineLoggerTest, LogLinePIDTIDMsg) {
   ASSERT_TRUE(BufOrErr) << "Failed to read log file";
   StringRef Content = (*BufOrErr)->getBuffer();
 
-  // Ends with message + newline.
-  EXPECT_TRUE(Content.ends_with("test_event: some_file.pcm\n"));
+  auto Body = logBodyLines(Content);
+  ASSERT_EQ(Body.size(), 1u);
+  EXPECT_EQ(logBody(Body.front()), "test_event: some_file.pcm");
 
   // Prefix has the form: "<timestamp> <pid> <tid>: "
   // Verify PID matches this process.
   std::string ExpectedPID = std::to_string(llvm::sys::Process::getProcessId());
-  EXPECT_TRUE(Content.contains(ExpectedPID));
+  EXPECT_TRUE(Body.front().contains(ExpectedPID));
 
   // Verify TID is present.
   std::string ExpectedTID = std::to_string(llvm::get_threadid());
-  EXPECT_TRUE(Content.contains(ExpectedTID));
+  EXPECT_TRUE(Body.front().contains(ExpectedTID));
 }
 
 TEST(AtomicLineLoggerTest, LogLineLogArray) {
@@ -149,11 +166,9 @@ TEST(AtomicLineLoggerTest, SingleLineWrittenToFile) {
   StringRef Content = (*BufOrErr)->getBuffer();
 
   // Verify the message is present and the line ends with a newline.
-  EXPECT_TRUE(Content.contains("pcm_write: module.pcm"));
-  EXPECT_TRUE(Content.ends_with("\n"));
-
-  // Verify there is exactly one line.
-  EXPECT_EQ(Content.count('\n'), 1u);
+  auto Body = logBodyLines(Content);
+  ASSERT_EQ(Body.size(), 1u);
+  EXPECT_EQ(logBody(Body.front()), "pcm_write: module.pcm");
 }
 
 TEST(AtomicLineLoggerTest, ConcurrentWritesProduceCompleteLines) {
@@ -167,6 +182,8 @@ TEST(AtomicLineLoggerTest, 
ConcurrentWritesProduceCompleteLines) {
   constexpr unsigned NumThreads = 8;
   constexpr unsigned LinesPerThread = 100;
   constexpr unsigned MessageLen = 32;
+  // One LoggerEven and one LoggerOdd.
+  constexpr unsigned NumLoggers = 2;
 
   {
     // Creating two loggers based on the same file to make sure
@@ -204,9 +221,25 @@ TEST(AtomicLineLoggerTest, 
ConcurrentWritesProduceCompleteLines) {
   SmallVector<StringRef> Lines;
   Content.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
 
-  EXPECT_EQ(Lines.size(), (size_t)(NumThreads * LinesPerThread));
+  SmallVector<StringRef> MessageLines;
+  unsigned Starts = 0, Ends = 0;
+  for (StringRef Line : Lines) {
+    StringRef Body = logBody(Line);
+    if (Body == "logging_start") {
+      ++Starts;
+      continue;
+    }
+    if (Body == "logging_end") {
+      ++Ends;
+      continue;
+    }
+    MessageLines.push_back(Line);
+  }
+  EXPECT_EQ(Starts, NumLoggers);
+  EXPECT_EQ(Ends, NumLoggers);
+  EXPECT_EQ(MessageLines.size(), (size_t)(NumThreads * LinesPerThread));
 
-  for (const auto &Line : Lines) {
+  for (const auto &Line : MessageLines) {
     // For each line, we check the separator, message length, message start and
     // the prefix format to make sure no lines are interleved.
 


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to