Index: clang/include/clang/Tooling/DependencyScanning/DependencyScanningService.h =================================================================== --- clang/include/clang/Tooling/DependencyScanning/DependencyScanningService.h +++ clang/include/clang/Tooling/DependencyScanning/DependencyScanningService.h @@ -35,9 +35,13 @@ /// intermodule dependency information. Make, - /// This outputs the full module dependency graph suitable for use for + /// This outputs the full clang module dependency graph suitable for use for /// explicitly building modules. Full, + + /// This outputs the dependency graph for standard c++ modules in P1689R5 + /// format. + P1689, }; /// The dependency scanning service contains shared configuration and state that Index: clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h =================================================================== --- clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h +++ clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h @@ -67,6 +67,12 @@ std::vector DiscoveredModules; }; +struct P1689Rule { + std::string PrimaryOutput; + std::optional Provides; + std::vector Requires; +}; + /// The high-level implementation of the dependency discovery tool that runs on /// an individual worker thread. class DependencyScanningTool { @@ -87,6 +93,10 @@ getDependencyFile(const std::vector &CommandLine, StringRef CWD, std::optional ModuleName = std::nullopt); + llvm::Expected + getP1689ModuleDependencyFile(const CompileCommand &Command, + StringRef CWD); + /// Collect the full module dependency graph for the input, ignoring any /// modules which have already been seen. If \p ModuleName isn't empty, this /// function returns the full dependency information of module \p ModuleName. Index: clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h =================================================================== --- clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h +++ clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h @@ -41,7 +41,11 @@ public: virtual ~DependencyConsumer() {} - virtual void handleBuildCommand(Command Cmd) = 0; + virtual void handleProvidedAndRequiredStdCXXModules( + std::optional Provided, + std::vector Requires) {} + + virtual void handleBuildCommand(Command Cmd) {} virtual void handleDependencyOutputOpts(const DependencyOutputOptions &Opts) = 0; Index: clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h =================================================================== --- clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h +++ clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h @@ -62,6 +62,27 @@ } }; +/// P1689ModuleInfo - Represents the needed information of standard C++20 +/// modules for P1689 format. +struct P1689ModuleInfo { + /// The name of the module. This may include `:` for partitions. + std::string ModuleName; + + /// Optional. The source path to the module. + std::string SourcePath; + + /// If this module is a standard c++ interface unit. + bool IsStdCXXModuleInterface = true; + + enum class ModuleType { + NamedCXXModule + // To be supported + // AngleHeaderUnit, + // QuoteHeaderUnit + }; + ModuleType Type = ModuleType::NamedCXXModule; +}; + /// An output from a module compilation, such as the path of the module file. enum class ModuleOutputKind { /// The module file (.pcm). Required. @@ -181,7 +202,7 @@ ModuleDepCollector(std::unique_ptr Opts, CompilerInstance &ScanInstance, DependencyConsumer &C, CompilerInvocation OriginalCI, bool OptimizeArgs, - bool EagerLoadModules); + bool EagerLoadModules, bool IsStdModuleP1689Format); void attachToPreprocessor(Preprocessor &PP) override; void attachToASTReader(ASTReader &R) override; @@ -219,6 +240,12 @@ bool OptimizeArgs; /// Whether to set up command-lines to load PCM files eagerly. bool EagerLoadModules; + /// If we're generating dependency output in P1689 format + /// for standard C++ modules. + bool IsStdModuleP1689Format; + + std::optional ProvidedStdCXXModule; + std::vector RequiredStdCXXModules; /// Checks whether the module is known as being prebuilt. bool isPrebuiltModule(const Module *M); Index: clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp =================================================================== --- clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp +++ clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -111,6 +111,49 @@ return Output; } +llvm::Expected DependencyScanningTool::getP1689ModuleDependencyFile( + const CompileCommand &Command, StringRef CWD) { + class P1689ModuleDependencyPrinterConsumer : public DependencyConsumer { + public: + P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule, + const CompileCommand &Command) + : Filename(Command.Filename), Rule(Rule) { + 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 { + Rule.Provides = Provided; + if (Rule.Provides) + Rule.Provides->SourcePath = Filename.str(); + Rule.Requires = Requires; + } + + private: + StringRef Filename; + P1689Rule &Rule; + }; + + P1689Rule Rule; + P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command); + auto Result = Worker.computeDependencies(CWD, Command.CommandLine, Consumer); + if (Result) + return std::move(Result); + return Rule; +} + llvm::Expected DependencyScanningTool::getFullDependencies( const std::vector &CommandLine, StringRef CWD, Index: clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp =================================================================== --- clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp +++ clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -247,10 +247,12 @@ std::make_shared( std::move(Opts), WorkingDirectory, Consumer)); break; + case ScanningOutputFormat::P1689: case ScanningOutputFormat::Full: MDC = std::make_shared( std::move(Opts), ScanInstance, Consumer, OriginalInvocation, - OptimizeArgs, EagerLoadModules); + OptimizeArgs, EagerLoadModules, + Format == ScanningOutputFormat::P1689); ScanInstance.addDependencyCollector(MDC); break; } Index: clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp =================================================================== --- clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -339,6 +339,14 @@ void ModuleDepCollectorPP::moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) { + if (MDC.ScanInstance.getPreprocessor().isInImportingCXXNamedModules()) { + P1689ModuleInfo RequiredModule; + RequiredModule.ModuleName = Path[0].first->getName().str(); + RequiredModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule; + MDC.RequiredStdCXXModules.push_back(RequiredModule); + return; + } + handleImport(Imported); } @@ -361,6 +369,21 @@ .getFileEntryForID(MainFileID) ->getName()); + auto &PP = MDC.ScanInstance.getPreprocessor(); + if (PP.isInNamedModule()) { + P1689ModuleInfo ProvidedModule; + ProvidedModule.ModuleName = PP.getNamedModuleName(); + ProvidedModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule; + ProvidedModule.IsStdCXXModuleInterface = PP.isInNamedInterfaceUnit(); + // Don't put implementation (non partition) unit as Provide. + // Put the module as required instead. Since the implementation + // unit will import the primary module implicitly. + if (PP.isInImplementationUnit()) + MDC.RequiredStdCXXModules.push_back(ProvidedModule); + else + MDC.ProvidedStdCXXModule = ProvidedModule; + } + if (!MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) MDC.addFileDep(MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude); @@ -374,6 +397,10 @@ MDC.Consumer.handleDependencyOutputOpts(*MDC.Opts); + if (MDC.IsStdModuleP1689Format) + MDC.Consumer.handleProvidedAndRequiredStdCXXModules( + MDC.ProvidedStdCXXModule, MDC.RequiredStdCXXModules); + for (auto &&I : MDC.ModularDeps) MDC.Consumer.handleModuleDependency(*I.second); @@ -548,10 +575,12 @@ ModuleDepCollector::ModuleDepCollector( std::unique_ptr Opts, CompilerInstance &ScanInstance, DependencyConsumer &C, - CompilerInvocation OriginalCI, bool OptimizeArgs, bool EagerLoadModules) + CompilerInvocation OriginalCI, bool OptimizeArgs, bool EagerLoadModules, + bool IsStdModuleP1689Format) : ScanInstance(ScanInstance), Consumer(C), Opts(std::move(Opts)), OriginalInvocation(std::move(OriginalCI)), OptimizeArgs(OptimizeArgs), - EagerLoadModules(EagerLoadModules) {} + EagerLoadModules(EagerLoadModules), + IsStdModuleP1689Format(IsStdModuleP1689Format) {} void ModuleDepCollector::attachToPreprocessor(Preprocessor &PP) { PP.addPPCallbacks(std::make_unique(*this)); Index: clang/test/ClangScanDeps/P1689.cppm =================================================================== --- /dev/null +++ clang/test/ClangScanDeps/P1689.cppm @@ -0,0 +1,157 @@ +// RUN: rm -fr %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: sed "s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json +// RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t +// RUN: clang-scan-deps --mode=preprocess-dependency-directives -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t + +//--- P1689.json.in +[ +{ + "directory": "DIR", + "command": "clang++ -std=c++20 DIR/M.cppm -c -o DIR/M.o", + "file": "DIR/M.cppm", + "output": "DIR/M.o" +}, +{ + "directory": "DIR", + "command": "clang++ -std=c++20 DIR/Impl.cpp -c -o DIR/Impl.o", + "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", + "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", + "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", + "file": "DIR/User.cpp", + "output": "DIR/User.o" +} +] + + +//--- M.cppm +export module M; +export import :interface_part; +import :impl_part; +export void Hello(); + +//--- Impl.cpp +module; +#include "header.mock" +module M; +void Hello() { + std::cout << "Hello "; +} + +//--- impl_part.cppm +module; +#include "header.mock" +module M:impl_part; +import :interface_part; + +std::string W = "World."; +void World() { + std::cout << W << std::endl; +} + +//--- interface_part.cppm +export module M:interface_part; +export void World(); + +//--- User.cpp +import M; +import third_party_module; +int main() { + Hello(); + World(); + return 0; +} + +//--- Checks.cpp +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/Impl.o", +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M", +// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/M.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": true, +// CHECK-NEXT: "logical-name": "M", +// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:interface_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:impl_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/User.o", +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M", +// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "third_party_module" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/impl_part.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": false, +// CHECK-NEXT: "logical-name": "M:impl_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:interface_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/interface_part.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": true, +// CHECK-NEXT: "logical-name": "M:interface_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + +//--- header.mock Index: clang/tools/clang-scan-deps/ClangScanDeps.cpp =================================================================== --- clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -131,12 +131,15 @@ static llvm::cl::opt Format( "format", llvm::cl::desc("The output format for the dependencies"), - llvm::cl::values(clEnumValN(ScanningOutputFormat::Make, "make", - "Makefile compatible dep file"), - clEnumValN(ScanningOutputFormat::Full, "experimental-full", - "Full dependency graph suitable" - " for explicitly building modules. This format " - "is experimental and will change.")), + llvm::cl::values( + clEnumValN(ScanningOutputFormat::Make, "make", + "Makefile compatible dep file"), + clEnumValN(ScanningOutputFormat::P1689, "p1689", + "Generate standard c++ modules dependency P1689 format"), + clEnumValN(ScanningOutputFormat::Full, "experimental-full", + "Full dependency graph suitable" + " for explicitly building modules. This format " + "is experimental and will change.")), llvm::cl::init(ScanningOutputFormat::Make), llvm::cl::cat(DependencyScannerCategory)); @@ -402,6 +405,88 @@ return false; } +class P1689Deps { +public: + void printDependencies(raw_ostream &OS) { + addSourcePathsToRequires(); + // Sort the modules by name to get a deterministic order. + llvm::sort(Rules, [](const P1689Rule &A, const P1689Rule &B) { + return A.PrimaryOutput < B.PrimaryOutput; + }); + + using namespace llvm::json; + Array OutputRules; + for (const P1689Rule &R : Rules) { + Object O{{"primary-output", R.PrimaryOutput}}; + + if (R.Provides) { + Array Provides; + Object Provided{{"logical-name", R.Provides->ModuleName}, + {"source-path", R.Provides->SourcePath}, + {"is-interface", R.Provides->IsStdCXXModuleInterface}}; + Provides.push_back(std::move(Provided)); + O.insert({"provides", std::move(Provides)}); + } + + Array Requires; + for (const P1689ModuleInfo &Info : R.Requires) { + Object RequiredInfo{{"logical-name", Info.ModuleName}}; + if (!Info.SourcePath.empty()) + RequiredInfo.insert({"source-path", Info.SourcePath}); + Requires.push_back(std::move(RequiredInfo)); + } + + if (!Requires.empty()) + O.insert({"requires", std::move(Requires)}); + + OutputRules.push_back(std::move(O)); + } + + Object Output{ + {"version", 1}, {"revision", 0}, {"rules", std::move(OutputRules)}}; + + OS << llvm::formatv("{0:2}\n", Value(std::move(Output))); + } + + void addRules(P1689Rule &Rule) { Rules.push_back(Rule); } + +private: + void addSourcePathsToRequires() { + llvm::DenseMap ModuleSourceMapper; + for (const P1689Rule &R : Rules) + if (R.Provides && !R.Provides->SourcePath.empty()) + ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath; + + for (P1689Rule &R : Rules) { + for (P1689ModuleInfo &Info : R.Requires) { + auto Iter = ModuleSourceMapper.find(Info.ModuleName); + if (Iter != ModuleSourceMapper.end()) + Info.SourcePath = Iter->second; + } + } + } + + std::vector Rules; +}; + +static bool +handleP1689DependencyToolResult(const std::string &Input, + llvm::Expected &MaybeRule, + P1689Deps &PD, SharedStream &Errs) { + if (!MaybeRule) { + llvm::handleAllErrors( + MaybeRule.takeError(), [&Input, &Errs](llvm::StringError &Err) { + Errs.applyLocked([&](raw_ostream &OS) { + OS << "Error while scanning dependencies for " << Input << ":\n"; + OS << Err.getMessage(); + }); + }); + return true; + } + PD.addRules(*MaybeRule); + return false; +} + /// Construct a path for the explicitly built PCM. static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) { SmallString<256> ExplicitPCMPath(OutputDir); @@ -537,6 +622,7 @@ std::atomic HadErrors(false); FullDeps FD; + P1689Deps PD; std::mutex Lock; size_t Index = 0; @@ -545,7 +631,7 @@ << " files using " << Pool.getThreadCount() << " workers\n"; } for (unsigned I = 0; I < Pool.getThreadCount(); ++I) { - Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &WorkerTools, + Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &PD, &WorkerTools, &DependencyOS, &Errs]() { llvm::StringSet<> AlreadySeenModules; while (true) { @@ -581,6 +667,11 @@ if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS, Errs)) HadErrors = true; + } else if (Format == ScanningOutputFormat::P1689) { + auto MaybeRule = + WorkerTools[I]->getP1689ModuleDependencyFile(*Input, CWD); + if (handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs)) + HadErrors = true; } else if (DeprecatedDriverCommand) { auto MaybeFullDeps = WorkerTools[I]->getFullDependenciesLegacyDriverCommand( @@ -604,6 +695,8 @@ if (Format == ScanningOutputFormat::Full) FD.printFullOutput(llvm::outs()); + else if (Format == ScanningOutputFormat::P1689) + PD.printDependencies(llvm::outs()); return HadErrors; }