diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h --- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h +++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h @@ -92,9 +92,22 @@ llvm::Expected getDependencyFile(const std::vector &CommandLine, StringRef CWD); + /// Collect the module dependency in P1689 format for C++20 named modules. + /// + /// \param MakeformatOutput The output parameter for dependency information + /// in make format if the command line requires to generate make-format + /// dependency information by `-MD -MF `. + /// + /// \param MakeformatOutputPath The output parameter for the path to + /// \param MakeformatOutput. + /// + /// \returns A \c StringError with the diagnostic output if clang errors + /// occurred, P1689 dependency format rules otherwise. llvm::Expected - getP1689ModuleDependencyFile(const CompileCommand &Command, - StringRef CWD); + getP1689ModuleDependencyFile( + const clang::tooling::CompileCommand &Command, StringRef CWD, + std::string &MakeformatOutput, std::string &MakeformatOutputPath, + std::optional ModuleName = std::nullopt); /// Given a Clang driver command-line for a translation unit, gather the /// modular dependencies and return the information needed for explicit build. diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -19,66 +19,68 @@ llvm::IntrusiveRefCntPtr FS) : Worker(Service, std::move(FS)) {} -llvm::Expected DependencyScanningTool::getDependencyFile( - const std::vector &CommandLine, StringRef CWD) { - /// Prints out all of the gathered dependencies into a string. - class MakeDependencyPrinterConsumer : public DependencyConsumer { - public: - void handleBuildCommand(Command) override {} - - void - handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override { - this->Opts = std::make_unique(Opts); - } +namespace { +/// Prints out all of the gathered dependencies into a string. +class MakeDependencyPrinterConsumer : public DependencyConsumer { +public: + void handleBuildCommand(Command) override {} + + void + handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override { + this->Opts = std::make_unique(Opts); + } - void handleFileDependency(StringRef File) override { - Dependencies.push_back(std::string(File)); - } + void handleFileDependency(StringRef File) override { + Dependencies.push_back(std::string(File)); + } - void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override { - // Same as `handleModuleDependency`. - } + void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override { + // Same as `handleModuleDependency`. + } - void handleModuleDependency(ModuleDeps MD) override { - // These are ignored for the make format as it can't support the full - // set of deps, and handleFileDependency handles enough for implicitly - // built modules to work. - } + void handleModuleDependency(ModuleDeps MD) override { + // These are ignored for the make format as it can't support the full + // set of deps, and handleFileDependency handles enough for implicitly + // built modules to work. + } - void handleContextHash(std::string Hash) override {} + void handleContextHash(std::string Hash) override {} - std::string lookupModuleOutput(const ModuleID &ID, - ModuleOutputKind Kind) override { - llvm::report_fatal_error("unexpected call to lookupModuleOutput"); - } + std::string lookupModuleOutput(const ModuleID &ID, + ModuleOutputKind Kind) override { + llvm::report_fatal_error("unexpected call to lookupModuleOutput"); + } - void printDependencies(std::string &S) { - assert(Opts && "Handled dependency output options."); - - class DependencyPrinter : public DependencyFileGenerator { - public: - DependencyPrinter(DependencyOutputOptions &Opts, - ArrayRef Dependencies) - : DependencyFileGenerator(Opts) { - for (const auto &Dep : Dependencies) - addDependency(Dep); - } - - void printDependencies(std::string &S) { - llvm::raw_string_ostream OS(S); - outputDependencyFile(OS); - } - }; - - DependencyPrinter Generator(*Opts, Dependencies); - Generator.printDependencies(S); - } + void printDependencies(std::string &S) { + assert(Opts && "Handled dependency output options."); + + class DependencyPrinter : public DependencyFileGenerator { + public: + DependencyPrinter(DependencyOutputOptions &Opts, + ArrayRef Dependencies) + : DependencyFileGenerator(Opts) { + for (const auto &Dep : Dependencies) + addDependency(Dep); + } + + void printDependencies(std::string &S) { + llvm::raw_string_ostream OS(S); + outputDependencyFile(OS); + } + }; + + DependencyPrinter Generator(*Opts, Dependencies); + Generator.printDependencies(S); + } - private: - std::unique_ptr Opts; - std::vector Dependencies; - }; +protected: + std::unique_ptr Opts; + std::vector Dependencies; +}; +} // anonymous namespace +llvm::Expected DependencyScanningTool::getDependencyFile( + const std::vector &CommandLine, StringRef CWD) { MakeDependencyPrinterConsumer Consumer; auto Result = Worker.computeDependencies(CWD, CommandLine, Consumer); if (Result) @@ -89,8 +91,11 @@ } llvm::Expected DependencyScanningTool::getP1689ModuleDependencyFile( - const CompileCommand &Command, StringRef CWD) { - class P1689ModuleDependencyPrinterConsumer : public DependencyConsumer { + const CompileCommand &Command, StringRef CWD, + std::string &MakeformatOutput, std::string &MakeformatOutputPath, + std::optional ModuleName) { + class P1689ModuleDependencyPrinterConsumer + : public MakeDependencyPrinterConsumer { public: P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule, const CompileCommand &Command) @@ -98,17 +103,6 @@ Rule.PrimaryOutput = Command.Output; } - void - handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {} - void handleFileDependency(StringRef File) override {} - void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {} - void handleModuleDependency(ModuleDeps MD) override {} - void handleContextHash(std::string Hash) override {} - std::string lookupModuleOutput(const ModuleID &ID, - ModuleOutputKind Kind) override { - llvm::report_fatal_error("unexpected call to lookupModuleOutput"); - } - void handleProvidedAndRequiredStdCXXModules( std::optional Provided, std::vector Requires) override { @@ -118,6 +112,12 @@ Rule.Requires = Requires; } + StringRef getMakeFormatDependencyOutputPath() { + if (Opts->OutputFormat != DependencyOutputFormat::Make) + return {}; + return Opts->OutputFile; + } + private: StringRef Filename; P1689Rule &Rule; @@ -128,6 +128,10 @@ auto Result = Worker.computeDependencies(CWD, Command.CommandLine, Consumer); if (Result) return std::move(Result); + + MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath(); + if (!MakeformatOutputPath.empty()) + Consumer.printDependencies(MakeformatOutput); return Rule; } diff --git a/clang/test/ClangScanDeps/P1689.cppm b/clang/test/ClangScanDeps/P1689.cppm --- a/clang/test/ClangScanDeps/P1689.cppm +++ b/clang/test/ClangScanDeps/P1689.cppm @@ -1,3 +1,8 @@ +// It is annoying to handle different slash direction +// in the filesystem of Windows and Linux. +// So we disable the test on Windows here. +// REQUIRES: !system-windows +// // RUN: rm -fr %t // RUN: mkdir -p %t // RUN: split-file %s %t @@ -25,42 +30,50 @@ // RUN: clang-scan-deps -format=p1689 \ // RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/User.cpp -o %t/User.o \ // RUN: | FileCheck %t/User.cpp -DPREFIX=%/t +// +// Check we can generate the make-style dependencies as expected. +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/impl_part.cppm -o %t/impl_part.o \ +// RUN: -MT %t/impl_part.o.ddi -MD -MF %t/impl_part.dep +// RUN: cat %t/impl_part.dep | FileCheck %t/impl_part.cppm -DPREFIX=%/t --check-prefix=CHECK-MAKE +// +// Check that we can generate multiple make-style dependency information with compilation database. +// RUN: cat %t/P1689.dep | FileCheck %t/Checks.cpp -DPREFIX=%/t --check-prefix=CHECK-MAKE //--- P1689.json.in [ { "directory": "DIR", - "command": "clang++ -std=c++20 DIR/M.cppm -c -o DIR/M.o", + "command": "clang++ -std=c++20 DIR/M.cppm -c -o DIR/M.o -MT DIR/M.o.ddi -MD -MF DIR/P1689.dep", "file": "DIR/M.cppm", "output": "DIR/M.o" }, { "directory": "DIR", - "command": "clang++ -std=c++20 DIR/Impl.cpp -c -o DIR/Impl.o", + "command": "clang++ -std=c++20 DIR/Impl.cpp -c -o DIR/Impl.o -MT DIR/Impl.o.ddi -MD -MF DIR/P1689.dep", "file": "DIR/Impl.cpp", "output": "DIR/Impl.o" }, { "directory": "DIR", - "command": "clang++ -std=c++20 DIR/impl_part.cppm -c -o DIR/impl_part.o", + "command": "clang++ -std=c++20 DIR/impl_part.cppm -c -o DIR/impl_part.o -MT DIR/impl_part.o.ddi -MD -MF DIR/P1689.dep", "file": "DIR/impl_part.cppm", "output": "DIR/impl_part.o" }, { "directory": "DIR", - "command": "clang++ -std=c++20 DIR/interface_part.cppm -c -o DIR/interface_part.o", + "command": "clang++ -std=c++20 DIR/interface_part.cppm -c -o DIR/interface_part.o -MT DIR/interface_part.o.ddi -MD -MF DIR/P1689.dep", "file": "DIR/interface_part.cppm", "output": "DIR/interface_part.o" }, { "directory": "DIR", - "command": "clang++ -std=c++20 DIR/User.cpp -c -o DIR/User.o", + "command": "clang++ -std=c++20 DIR/User.cpp -c -o DIR/User.o -MT DIR/User.o.ddi -MD -MF DIR/P1689.dep", "file": "DIR/User.cpp", "output": "DIR/User.o" } ] - //--- M.cppm export module M; export import :interface_part; @@ -275,4 +288,17 @@ // CHECK-NEXT: "version": 1 // CHECK-NEXT: } +// CHECK-MAKE-DAG: [[PREFIX]]/impl_part.o.ddi: \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/impl_part.cppm \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/header.mock +// CHECK-MAKE-DAG: [[PREFIX]]/interface_part.o.ddi: \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/interface_part.cppm +// CHECK-MAKE-DAG: [[PREFIX]]/M.o.ddi: \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/M.cppm +// CHECK-MAKE-DAG: [[PREFIX]]/User.o.ddi: \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/User.cpp +// CHECK-MAKE-DAG: [[PREFIX]]/Impl.o.ddi: \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/Impl.cpp \ +// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/header.mock + //--- header.mock diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp --- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -825,10 +825,47 @@ Errs)) HadErrors = true; } else if (Format == ScanningOutputFormat::P1689) { - auto MaybeRule = - WorkerTools[I]->getP1689ModuleDependencyFile(*Input, CWD); - if (handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs)) - HadErrors = true; + // It is useful to generate the make-format dependency output during + // the scanning for P1689. Otherwise the users need to scan again for + // it. We will generate the make-format dependency output if we find + // `-MF` in the command lines. + std::string MakeformatOutputPath; + std::string MakeformatOutput; + + auto MaybeRule = WorkerTools[I]->getP1689ModuleDependencyFile( + *Input, CWD, MakeformatOutput, MakeformatOutputPath, + MaybeModuleName); + HadErrors = + handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs); + + if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() && + !HadErrors) { + static std::mutex Lock; + // With compilation database, we may open different files + // concurrently or we may write the same file concurrently. So we + // use a map here to allow multiple compile commands to write to the + // same file. Also we need a lock here to avoid data race. + static llvm::StringMap OSs; + std::unique_lock LockGuard(Lock); + + auto OSIter = OSs.find(MakeformatOutputPath); + if (OSIter == OSs.end()) { + std::error_code EC; + OSIter = OSs.try_emplace(MakeformatOutputPath, + MakeformatOutputPath, EC) + .first; + if (EC) + llvm::errs() + << "Failed to open P1689 make format output file \"" + << MakeformatOutputPath << "\" for " << EC.message() + << "\n"; + } + + SharedStream MakeformatOS(OSIter->second); + llvm::Expected MaybeOutput(MakeformatOutput); + HadErrors = handleMakeDependencyToolResult(Filename, MaybeOutput, + MakeformatOS, Errs); + } } else if (MaybeModuleName) { auto MaybeModuleDepsGraph = WorkerTools[I]->getModuleDependencies( *MaybeModuleName, Input->CommandLine, CWD, AlreadySeenModules,