Index: clang/include/clang/Frontend/DependencyOutputOptions.h =================================================================== --- clang/include/clang/Frontend/DependencyOutputOptions.h +++ clang/include/clang/Frontend/DependencyOutputOptions.h @@ -28,6 +28,10 @@ EDK_DepFileEntry, }; +/// StdCXXModuleDependencyFormat - Format for the std cxx module dependency +/// file. +enum class StdCXXModuleDependencyFormat { None, P1689 }; + /// DependencyOutputOptions - Options for controlling the compiler dependency /// file generation. class DependencyOutputOptions { @@ -76,6 +80,10 @@ /// The directory to copy module dependencies to when collecting them. std::string ModuleDependencyOutputDir; + /// The format for the module dependency file. + StdCXXModuleDependencyFormat StdModuleDepFormat = + StdCXXModuleDependencyFormat::None; + public: DependencyOutputOptions() : IncludeSystemHeaders(0), ShowHeaderIncludes(0), UsePhonyTargets(0), 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 @@ -66,6 +66,12 @@ std::vector DiscoveredModules; }; +struct P1689Rule { + std::string PrimaryOutput; + llvm::Optional Provides; + std::vector Requires; +}; + /// The high-level implementation of the dependency discovery tool that runs on /// an individual worker thread. class DependencyScanningTool { @@ -86,6 +92,11 @@ getDependencyFile(const std::vector &CommandLine, StringRef CWD, llvm::Optional ModuleName = None); + llvm::Expected + getP1689ModuleDependencyFile(const clang::tooling::CompileCommand &Command, + StringRef CWD, + llvm::Optional ModuleName = None); + /// 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 @@ -40,7 +40,11 @@ public: virtual ~DependencyConsumer() {} - virtual void handleBuildCommand(Command Cmd) = 0; + virtual void + handleProvidedAndRequiredStdCXXModules(llvm::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 @@ -56,6 +56,21 @@ /// treated as separate modules for the purpose of a build. std::string ContextHash; + /// 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, + ClangModuleMapModule + }; + ModuleType Type = ModuleType::ClangModuleMapModule; + bool operator==(const ModuleID &Other) const { return ModuleName == Other.ModuleName && ContextHash == Other.ContextHash; } @@ -221,6 +236,10 @@ /// Whether to set up command-lines to load PCM files eagerly. bool EagerLoadModules; + llvm::Optional ProvidedStdCXXModule; + std::vector RequiredStdCXXModules; + + bool isP1689Format() const; /// 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 @@ -110,6 +110,51 @@ return Output; } +llvm::Expected DependencyScanningTool::getP1689ModuleDependencyFile( + const clang::tooling::CompileCommand &Command, StringRef CWD, + llvm::Optional ModuleName) { + 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( + llvm::Optional Provided, + std::vector Requires) override { + Rule.Provides = Provided; + if (Rule.Provides) + Rule.Provides->SourcePath = Filename.str(); + Rule.Requires = Requires; + } + + private: + StringRef Filename; + clang::tooling::dependencies::P1689Rule &Rule; + }; + + P1689Rule Rule; + P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command); + auto Result = Worker.computeDependencies(CWD, Command.CommandLine, Consumer, + ModuleName); + 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 @@ -246,6 +246,9 @@ std::make_shared( std::move(Opts), WorkingDirectory, Consumer)); break; + case ScanningOutputFormat::P1689: + Opts->StdModuleDepFormat = StdCXXModuleDependencyFormat::P1689; + [[fallthrough]]; case ScanningOutputFormat::Full: MDC = std::make_shared( std::move(Opts), ScanInstance, Consumer, OriginalInvocation, Index: clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp =================================================================== --- clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -345,6 +345,14 @@ void ModuleDepCollectorPP::moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) { + if (MDC.ScanInstance.getPreprocessor().isImportingCXXNamedModules()) { + ModuleID RequiredModule; + RequiredModule.ModuleName = Path[0].first->getName().str(); + RequiredModule.Type = ModuleID::ModuleType::NamedCXXModule; + MDC.RequiredStdCXXModules.push_back(RequiredModule); + return; + } + handleImport(Imported); } @@ -367,6 +375,21 @@ .getFileEntryForID(MainFileID) ->getName()); + auto &PP = MDC.ScanInstance.getPreprocessor(); + if (PP.isNamedModule()) { + ModuleID ProvidedModule; + ProvidedModule.ModuleName = PP.getNamedModuleName(); + ProvidedModule.Type = ModuleID::ModuleType::NamedCXXModule; + ProvidedModule.IsStdCXXModuleInterface = PP.isNamedInterfaceUnit(); + // 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.isImplementationUnit()) + MDC.RequiredStdCXXModules.push_back(ProvidedModule); + else + MDC.ProvidedStdCXXModule = ProvidedModule; + } + if (!MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) MDC.addFileDep(MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude); @@ -387,6 +410,10 @@ MDC.Consumer.handleDependencyOutputOpts(*MDC.Opts); + if (MDC.isP1689Format()) + MDC.Consumer.handleProvidedAndRequiredStdCXXModules( + MDC.ProvidedStdCXXModule, MDC.RequiredStdCXXModules); + for (auto &&I : MDC.ModularDeps) MDC.Consumer.handleModuleDependency(*I.second); @@ -597,3 +624,7 @@ Path = makeAbsolute(ScanInstance, Path, Storage); MD.FileDeps.insert(Path); } + +bool ModuleDepCollector::isP1689Format() const { + return Opts->StdModuleDepFormat == StdCXXModuleDependencyFormat::P1689; +} 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 @@ -130,12 +130,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)); @@ -401,6 +404,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 ModuleID &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 (ModuleID &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); @@ -536,6 +621,7 @@ std::atomic HadErrors(false); FullDeps FD; + P1689Deps PD; std::mutex Lock; size_t Index = 0; @@ -544,7 +630,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) { @@ -580,6 +666,11 @@ if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS, Errs)) HadErrors = true; + } else if (Format == ScanningOutputFormat::P1689) { + auto MaybeRule = WorkerTools[I]->getP1689ModuleDependencyFile( + *Input, CWD, MaybeModuleName); + if (handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs)) + HadErrors = true; } else if (DeprecatedDriverCommand) { auto MaybeFullDeps = WorkerTools[I]->getFullDependenciesLegacyDriverCommand( @@ -603,6 +694,8 @@ if (Format == ScanningOutputFormat::Full) FD.printFullOutput(llvm::outs()); + else if (Format == ScanningOutputFormat::P1689) + PD.printDependencies(llvm::outs()); return HadErrors; }