diff --git a/clang/include/clang/Frontend/FrontendActions.h b/clang/include/clang/Frontend/FrontendActions.h index ff8d4417eaa496..545a7e842c4ff7 100644 --- a/clang/include/clang/Frontend/FrontendActions.h +++ b/clang/include/clang/Frontend/FrontendActions.h @@ -299,6 +299,15 @@ class PrintPreprocessedAction : public PreprocessorFrontendAction { bool hasPCHSupport() const override { return true; } }; +class GetDependenciesByModuleNameAction : public PreprocessOnlyAction { + StringRef ModuleName; + void ExecuteAction() override; + +public: + GetDependenciesByModuleNameAction(StringRef ModuleName) + : ModuleName(ModuleName) {} +}; + } // end namespace clang #endif diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h index f88dc472c80b10..c26b6e91d90ce8 100644 --- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h +++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningTool.h @@ -77,16 +77,18 @@ class DependencyScanningTool { /// Print out the dependency information into a string using the dependency /// file format that is specified in the options (-MD is the default) and - /// return it. + /// return it. If \p ModuleName isn't empty, this function returns the + /// dependency information of module \p ModuleName. /// /// \returns A \c StringError with the diagnostic output if clang errors /// occurred, dependency file contents otherwise. llvm::Expected getDependencyFile(const tooling::CompilationDatabase &Compilations, - StringRef CWD); + StringRef CWD, llvm::Optional ModuleName = None); /// Collect the full module dependency graph for the input, ignoring any - /// modules which have already been seen. + /// modules which have already been seen. If \p ModuleName isn't empty, this + /// function returns the full dependency information of module \p ModuleName. /// /// \param AlreadySeen This stores modules which have previously been /// reported. Use the same instance for all calls to this @@ -98,7 +100,8 @@ class DependencyScanningTool { /// occurred, \c FullDependencies otherwise. llvm::Expected getFullDependencies(const tooling::CompilationDatabase &Compilations, - StringRef CWD, const llvm::StringSet<> &AlreadySeen); + StringRef CWD, const llvm::StringSet<> &AlreadySeen, + llvm::Optional ModuleName = None); private: DependencyScanningWorker Worker; diff --git a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h index c6f7d239b8eb57..3ae3bcda723924 100644 --- a/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h +++ b/clang/include/clang/Tooling/DependencyScanning/DependencyScanningWorker.h @@ -76,14 +76,16 @@ class DependencyScanningWorker { /// Run the dependency scanning tool for a given clang driver invocation (as /// specified for the given Input in the CDB), and report the discovered - /// dependencies to the provided consumer. + /// dependencies to the provided consumer. If \p ModuleName isn't empty, this + /// function reports the dependencies of module \p ModuleName. /// /// \returns A \c StringError with the diagnostic output if clang errors /// occurred, success otherwise. llvm::Error computeDependencies(const std::string &Input, StringRef WorkingDirectory, const CompilationDatabase &CDB, - DependencyConsumer &Consumer); + DependencyConsumer &Consumer, + llvm::Optional ModuleName = None); private: IntrusiveRefCntPtr DiagOpts; diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index c6ebbdc8c04e1f..b5544afa9f2484 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -993,3 +993,17 @@ void PrintDependencyDirectivesSourceMinimizerAction::ExecuteAction() { } llvm::outs() << Output; } + +void GetDependenciesByModuleNameAction::ExecuteAction() { + CompilerInstance &CI = getCompilerInstance(); + Preprocessor &PP = CI.getPreprocessor(); + SourceManager &SM = PP.getSourceManager(); + FileID MainFileID = SM.getMainFileID(); + SourceLocation FileStart = SM.getLocForStartOfFile(MainFileID); + SmallVector, 2> Path; + IdentifierInfo *ModuleID = PP.getIdentifierInfo(ModuleName); + Path.push_back(std::make_pair(ModuleID, FileStart)); + auto ModResult = CI.loadModule(FileStart, Path, Module::Hidden, false); + PPCallbacks *CB = PP.getPPCallbacks(); + CB->moduleImport(SourceLocation(), Path, ModResult); +} diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp index 43b6a0c95f0476..a7969b358d9cc1 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -50,7 +50,8 @@ DependencyScanningTool::DependencyScanningTool( : Worker(Service) {} llvm::Expected DependencyScanningTool::getDependencyFile( - const tooling::CompilationDatabase &Compilations, StringRef CWD) { + const tooling::CompilationDatabase &Compilations, StringRef CWD, + llvm::Optional ModuleName) { /// Prints out all of the gathered dependencies into a string. class MakeDependencyPrinterConsumer : public DependencyConsumer { public: @@ -112,7 +113,8 @@ llvm::Expected DependencyScanningTool::getDependencyFile( std::string Input = Compilations.getAllCompileCommands().front().Filename; MakeDependencyPrinterConsumer Consumer; - auto Result = Worker.computeDependencies(Input, CWD, Compilations, Consumer); + auto Result = Worker.computeDependencies(Input, CWD, Compilations, Consumer, + ModuleName); if (Result) return std::move(Result); std::string Output; @@ -123,7 +125,8 @@ llvm::Expected DependencyScanningTool::getDependencyFile( llvm::Expected DependencyScanningTool::getFullDependencies( const tooling::CompilationDatabase &Compilations, StringRef CWD, - const llvm::StringSet<> &AlreadySeen) { + const llvm::StringSet<> &AlreadySeen, + llvm::Optional ModuleName) { class FullDependencyPrinterConsumer : public DependencyConsumer { public: FullDependencyPrinterConsumer(const llvm::StringSet<> &AlreadySeen) @@ -196,8 +199,8 @@ DependencyScanningTool::getFullDependencies( std::string Input = Compilations.getAllCompileCommands().front().Filename; FullDependencyPrinterConsumer Consumer(AlreadySeen); - llvm::Error Result = - Worker.computeDependencies(Input, CWD, Compilations, Consumer); + llvm::Error Result = Worker.computeDependencies(Input, CWD, Compilations, + Consumer, ModuleName); if (Result) return std::move(Result); return Consumer.getFullDependencies(); diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp index d651ff23b387a8..10385ab3566920 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -141,10 +141,11 @@ class DependencyScanningAction : public tooling::ToolAction { StringRef WorkingDirectory, DependencyConsumer &Consumer, llvm::IntrusiveRefCntPtr DepFS, ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings, - ScanningOutputFormat Format) + ScanningOutputFormat Format, llvm::Optional ModuleName = None, + llvm::Optional FakeMemBuffer = None) : WorkingDirectory(WorkingDirectory), Consumer(Consumer), - DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings), - Format(Format) {} + DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings), Format(Format), + ModuleName(ModuleName), FakeMemBuffer(FakeMemBuffer) {} bool runInvocation(std::shared_ptr Invocation, FileManager *FileMgr, @@ -214,6 +215,16 @@ class DependencyScanningAction : public tooling::ToolAction { .ExcludedConditionalDirectiveSkipMappings = PPSkipMappings; } + if (ModuleName.hasValue()) { + SmallString<128> FullPath(*ModuleName); + llvm::sys::fs::make_absolute(WorkingDirectory, FullPath); + SourceManager &SrcMgr = Compiler.getSourceManager(); + FileMgr->getVirtualFile(FullPath.c_str(), FakeMemBuffer->getBufferSize(), + 0); + FileID MainFileID = SrcMgr.createFileID(*FakeMemBuffer); + SrcMgr.setMainFileID(MainFileID); + } + // Create the dependency collector that will collect the produced // dependencies. // @@ -249,7 +260,13 @@ class DependencyScanningAction : public tooling::ToolAction { // the impact of strict context hashing. Compiler.getHeaderSearchOpts().ModulesStrictContextHash = true; - auto Action = std::make_unique(); + std::unique_ptr Action; + + if (ModuleName.hasValue()) + Action = std::make_unique(*ModuleName); + else + Action = std::make_unique(); + const bool Result = Compiler.ExecuteAction(*Action); if (!DepFS) FileMgr->clearStatCache(); @@ -262,6 +279,8 @@ class DependencyScanningAction : public tooling::ToolAction { llvm::IntrusiveRefCntPtr DepFS; ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings; ScanningOutputFormat Format; + llvm::Optional ModuleName; + llvm::Optional FakeMemBuffer; }; } // end anonymous namespace @@ -307,19 +326,42 @@ static llvm::Error runWithDiags( llvm::Error DependencyScanningWorker::computeDependencies( const std::string &Input, StringRef WorkingDirectory, - const CompilationDatabase &CDB, DependencyConsumer &Consumer) { + const CompilationDatabase &CDB, DependencyConsumer &Consumer, + llvm::Optional ModuleName) { RealFS->setCurrentWorkingDirectory(WorkingDirectory); + std::unique_ptr FakeMemBuffer = + ModuleName.hasValue() ? llvm::MemoryBuffer::getMemBuffer(" ") : nullptr; return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) { /// Create the tool that uses the underlying file system to ensure that any /// file system requests that are made by the driver do not go through the /// dependency scanning filesystem. - tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files); + SmallString<128> FullPath; + tooling::ClangTool Tool(CDB, + ModuleName.hasValue() ? ModuleName->str() : Input, + PCHContainerOps, RealFS, Files); Tool.clearArgumentsAdjusters(); Tool.setRestoreWorkingDir(false); Tool.setPrintErrorMessage(false); Tool.setDiagnosticConsumer(&DC); - DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS, - PPSkipMappings.get(), Format); + DependencyScanningAction Action( + WorkingDirectory, Consumer, DepFS, PPSkipMappings.get(), Format, + ModuleName, + FakeMemBuffer + ? llvm::Optional(*FakeMemBuffer.get()) + : None); + + if (ModuleName.hasValue()) { + Tool.mapVirtualFile(*ModuleName, FakeMemBuffer->getBuffer()); + FullPath = *ModuleName; + llvm::sys::fs::make_absolute(WorkingDirectory, FullPath); + Tool.appendArgumentsAdjuster( + [&](const tooling::CommandLineArguments &Args, StringRef FileName) { + tooling::CommandLineArguments AdjustedArgs(Args); + AdjustedArgs.push_back(std::string(FullPath)); + return AdjustedArgs; + }); + } + return !Tool.run(&Action); }); } diff --git a/clang/test/ClangScanDeps/Inputs/modules_cdb_by_mod_name.json b/clang/test/ClangScanDeps/Inputs/modules_cdb_by_mod_name.json new file mode 100644 index 00000000000000..f95749f11be2a7 --- /dev/null +++ b/clang/test/ClangScanDeps/Inputs/modules_cdb_by_mod_name.json @@ -0,0 +1,12 @@ +[ +{ + "directory": "DIR", + "command": "clang -E -IInputs -D INCLUDE_HEADER2 -MD -MF DIR/modules_cdb2.d -fmodules -fcxx-modules -fmodules-cache-path=DIR/module-cache -fimplicit-modules -fimplicit-module-maps -gmodules -x c++", + "file": "" +}, +{ + "directory": "DIR", + "command": "clang -E -IInputs -fmodules -fcxx-modules -fmodules-cache-path=DIR/module-cache -fimplicit-modules -fimplicit-module-maps -x c++", + "file": "" +}, +] diff --git a/clang/test/ClangScanDeps/Inputs/modules_cdb_clangcl_by_mod_name.json b/clang/test/ClangScanDeps/Inputs/modules_cdb_clangcl_by_mod_name.json new file mode 100644 index 00000000000000..ef44985a8f9159 --- /dev/null +++ b/clang/test/ClangScanDeps/Inputs/modules_cdb_clangcl_by_mod_name.json @@ -0,0 +1,12 @@ +[ +{ + "directory": "DIR", + "command": "clang-cl /E /IInputs /D INCLUDE_HEADER2 /clang:-MD /clang:-MF /clang:DIR/modules_cdb2_clangcl.d /clang:-fmodules /clang:-fcxx-modules /clang:-fmodules-cache-path=DIR/module-cache_clangcl /clang:-fimplicit-modules /clang:-fimplicit-module-maps /clang:-x /clang:c++ --", + "file": "" +}, +{ + "directory": "DIR", + "command": "clang-cl /E /IInputs /clang:-fmodules /clang:-fcxx-modules /clang:-fmodules-cache-path=DIR/module-cache_clangcl /clang:-fimplicit-modules /clang:-fimplicit-module-maps /clang:-x /clang:c++ --", + "file": "" +}, +] diff --git a/clang/test/ClangScanDeps/modules-full-by-mod-name.cpp b/clang/test/ClangScanDeps/modules-full-by-mod-name.cpp new file mode 100644 index 00000000000000..cecc76840e1d51 --- /dev/null +++ b/clang/test/ClangScanDeps/modules-full-by-mod-name.cpp @@ -0,0 +1,79 @@ +// RUN: rm -rf %t.dir +// RUN: rm -rf %t.cdb +// RUN: mkdir -p %t.dir +// RUN: cp %s %t.dir/modules_cdb_input.cpp +// RUN: cp %s %t.dir/modules_cdb_input2.cpp +// RUN: mkdir %t.dir/Inputs +// RUN: cp %S/Inputs/header.h %t.dir/Inputs/header.h +// RUN: cp %S/Inputs/header2.h %t.dir/Inputs/header2.h +// RUN: cp %S/Inputs/module.modulemap %t.dir/Inputs/module.modulemap +// RUN: sed -e "s|DIR|%/t.dir|g" %S/Inputs/modules_cdb_by_mod_name.json > %t.cdb +// RUN: sed -e "s|DIR|%/t.dir|g" %S/Inputs/modules_cdb_clangcl_by_mod_name.json > %t_clangcl.cdb +// +// RUN: echo %t.dir > %t.result +// RUN: clang-scan-deps -compilation-database %t.cdb -j 4 -format experimental-full \ +// RUN: -mode preprocess-minimized-sources -module-name=header1 >> %t.result +// RUN: cat %t.result | sed 's:\\\\\?:/:g' | FileCheck --check-prefixes=CHECK %s +// +// RUN: echo %t.dir > %t_clangcl.result +// RUN: clang-scan-deps -compilation-database %t_clangcl.cdb -j 4 -format experimental-full \ +// RUN: -mode preprocess-minimized-sources -module-name=header1 >> %t_clangcl.result +// RUN: cat %t_clangcl.result | sed 's:\\\\\?:/:g' | FileCheck --check-prefixes=CHECK %s + +// CHECK: [[PREFIX:.*]] +// CHECK-NEXT: { +// CHECK-NEXT: "modules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [ +// CHECK-NEXT: { +// CHECK-NEXT: "context-hash": "[[HASH_H2_DINCLUDE:[A-Z0-9]+]]", +// CHECK-NEXT: "module-name": "header2" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-cc1" +// CHECK: "-emit-module" +// CHECK: "-fmodule-name=header1" +// CHECK: "-fno-implicit-modules" +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_H1_DINCLUDE:[A-Z0-9]+]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/Inputs/header.h", +// CHECK-NEXT: "[[PREFIX]]/Inputs/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "name": "header1" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-cc1", +// CHECK: "-emit-module", +// CHECK: "-fmodule-name=header1", +// CHECK: "-fno-implicit-modules", +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_H1:[A-Z0-9]+]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/Inputs/header.h", +// CHECK-NEXT: "[[PREFIX]]/Inputs/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "name": "header1" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "clang-module-deps": [], +// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/Inputs/module.modulemap", +// CHECK-NEXT: "command-line": [ +// CHECK-NEXT: "-cc1", +// CHECK: "-emit-module", +// CHECK: "-fmodule-name=header2", +// CHECK: "-fno-implicit-modules", +// CHECK: ], +// CHECK-NEXT: "context-hash": "[[HASH_H2_DINCLUDE]]", +// CHECK-NEXT: "file-deps": [ +// CHECK-NEXT: "[[PREFIX]]/Inputs/header2.h", +// CHECK-NEXT: "[[PREFIX]]/Inputs/module.modulemap" +// CHECK-NEXT: ], +// CHECK-NEXT: "name": "header2" +// CHECK-NEXT: } +// CHECK-NEXT: ], diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp index 92b9bdd83e396d..d3630c94b5bf2a 100644 --- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -194,6 +194,11 @@ llvm::cl::opt SkipExcludedPPRanges( "until reaching the end directive."), llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); +llvm::cl::opt ModuleName( + "module-name", llvm::cl::Optional, + llvm::cl::desc("the module of which the dependencies are to be computed"), + llvm::cl::cat(DependencyScannerCategory)); + llvm::cl::opt Verbose("v", llvm::cl::Optional, llvm::cl::desc("Use verbose output."), llvm::cl::init(false), @@ -544,13 +549,20 @@ int main(int argc, const char **argv) { } // Run the tool on it. if (Format == ScanningOutputFormat::Make) { - auto MaybeFile = WorkerTools[I]->getDependencyFile(*Input, CWD); + auto MaybeFile = WorkerTools[I]->getDependencyFile( + *Input, CWD, + ModuleName.empty() + ? None + : llvm::Optional(ModuleName.c_str())); if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS, Errs)) HadErrors = true; } else { auto MaybeFullDeps = WorkerTools[I]->getFullDependencies( - *Input, CWD, AlreadySeenModules); + *Input, CWD, AlreadySeenModules, + ModuleName.empty() + ? None + : llvm::Optional(ModuleName.c_str())); if (handleFullDependencyToolResult(Filename, MaybeFullDeps, FD, LocalIndex, DependencyOS, Errs)) HadErrors = true;