diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 85f2e0c740b2..dd8722e1b4ed 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -342,6 +342,8 @@ def dependency_file : Separate<["-"], "dependency-file">, Flags<[CC1Option]>, HelpText<"Filename (or -) to write dependency output to">; def dependency_dot : Separate<["-"], "dependency-dot">, Flags<[CC1Option]>, HelpText<"Filename to write DOT-formatted header dependencies to">; +def module_dependency_dir : Separate<["-"], "module-dependency-dir">, + Flags<[CC1Option]>, HelpText<"Directory to dump module dependencies to">; def dumpmachine : Flag<["-"], "dumpmachine">; def dumpspecs : Flag<["-"], "dumpspecs">, Flags<[Unsupported]>; def dumpversion : Flag<["-"], "dumpversion">; diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h index b144069b975b..d72f9046cfad 100644 --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -105,6 +105,9 @@ class CompilerInstance : public ModuleLoader { /// \brief The ASTReader, if one exists. IntrusiveRefCntPtr ModuleManager; + /// \brief The module dependency collector for crashdumps + std::shared_ptr ModuleDepCollector; + /// \brief The dependency file generator. std::unique_ptr TheDependencyFileGenerator; @@ -464,6 +467,10 @@ public: IntrusiveRefCntPtr getModuleManager() const; void setModuleManager(IntrusiveRefCntPtr Reader); + std::shared_ptr getModuleDepCollector() const; + void setModuleDepCollector( + std::shared_ptr Collector); + /// } /// @name Code Completion /// { diff --git a/clang/include/clang/Frontend/DependencyOutputOptions.h b/clang/include/clang/Frontend/DependencyOutputOptions.h index d275249987f1..5da14597b646 100644 --- a/clang/include/clang/Frontend/DependencyOutputOptions.h +++ b/clang/include/clang/Frontend/DependencyOutputOptions.h @@ -43,7 +43,10 @@ public: /// \brief The file to write GraphViz-formatted header dependencies to. std::string DOTOutputFile; - + + /// \brief The directory to copy module dependencies to when collecting them. + std::string ModuleDependencyOutputDir; + public: DependencyOutputOptions() { IncludeSystemHeaders = 0; diff --git a/clang/include/clang/Frontend/Utils.h b/clang/include/clang/Frontend/Utils.h index 8fb536ff574c..a791cd55dc4d 100644 --- a/clang/include/clang/Frontend/Utils.h +++ b/clang/include/clang/Frontend/Utils.h @@ -15,8 +15,10 @@ #define LLVM_CLANG_FRONTEND_UTILS_H #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/VirtualFileSystem.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Option/OptSpecifier.h" namespace llvm { @@ -80,6 +82,30 @@ public: void AttachToASTReader(ASTReader &R); }; +/// Collects the dependencies for imported modules into a directory. Users +/// should attach to the AST reader whenever a module is loaded. +class ModuleDependencyCollector { + std::string DestDir; + bool HasErrors; + llvm::StringSet<> Seen; + vfs::YAMLVFSWriter VFSWriter; + +public: + StringRef getDest() { return DestDir; } + bool insertSeen(StringRef Filename) { return Seen.insert(Filename); } + void setHasErrors() { HasErrors = true; } + void addFileMapping(StringRef VPath, StringRef RPath) { + VFSWriter.addFileMapping(VPath, RPath); + } + + void attachToASTReader(ASTReader &R); + void writeFileMap(); + bool hasErrors() { return HasErrors; } + ModuleDependencyCollector(std::string DestDir) + : DestDir(DestDir), HasErrors(false) {} + ~ModuleDependencyCollector() { writeFileMap(); } +}; + /// AttachDependencyGraphGen - Create a dependency graph generator, and attach /// it to the given preprocessor. void AttachDependencyGraphGen(Preprocessor &PP, StringRef OutputFile, diff --git a/clang/lib/Frontend/CMakeLists.txt b/clang/lib/Frontend/CMakeLists.txt index b67e0aed24ca..3fa7a2cf01b2 100644 --- a/clang/lib/Frontend/CMakeLists.txt +++ b/clang/lib/Frontend/CMakeLists.txt @@ -25,6 +25,7 @@ add_clang_library(clangFrontend LangStandards.cpp LayoutOverrideSource.cpp LogDiagnosticPrinter.cpp + ModuleDependencyCollector.cpp MultiplexConsumer.cpp PrintPreprocessedOutput.cpp SerializedDiagnosticPrinter.cpp diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index e8ca0804090e..03a2c229d0be 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -116,6 +116,16 @@ void CompilerInstance::setModuleManager(IntrusiveRefCntPtr Reader) { ModuleManager = Reader; } +std::shared_ptr +CompilerInstance::getModuleDepCollector() const { + return ModuleDepCollector; +} + +void CompilerInstance::setModuleDepCollector( + std::shared_ptr Collector) { + ModuleDepCollector = Collector; +} + // Diagnostics static void SetUpDiagnosticLog(DiagnosticOptions *DiagOpts, const CodeGenOptions *CodeGenOpts, @@ -278,6 +288,11 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) { AttachDependencyGraphGen(*PP, DepOpts.DOTOutputFile, getHeaderSearchOpts().Sysroot); + // If we don't have a collector, but we are collecting module dependencies, + // then we're the top level compiler instance and need to create one. + if (!ModuleDepCollector && !DepOpts.ModuleDependencyOutputDir.empty()) + ModuleDepCollector = std::make_shared( + DepOpts.ModuleDependencyOutputDir); // Handle generating header include information, if requested. if (DepOpts.ShowHeaderIncludes) @@ -851,6 +866,10 @@ static void compileModuleImpl(CompilerInstance &ImportingInstance, SourceMgr.pushModuleBuildStack(Module->getTopLevelModuleName(), FullSourceLoc(ImportLoc, ImportingInstance.getSourceManager())); + // If we're collecting module dependencies, we need to share a collector + // between all of the module CompilerInstances. + Instance.setModuleDepCollector(ImportingInstance.getModuleDepCollector()); + // Get or create the module map that we'll use to build this module. std::string InferredModuleMapContent; if (const FileEntry *ModuleMapFile = @@ -1211,6 +1230,9 @@ CompilerInstance::loadModule(SourceLocation ImportLoc, if (TheDependencyFileGenerator) TheDependencyFileGenerator->AttachToASTReader(*ModuleManager); + if (ModuleDepCollector) + ModuleDepCollector->attachToASTReader(*ModuleManager); + // Try to load the module file. unsigned ARRFlags = ASTReader::ARR_OutOfDate | ASTReader::ARR_Missing; switch (ModuleManager->ReadAST(ModuleFileName, serialization::MK_Module, diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 6d5faf935e95..beaa092c1e5a 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -578,6 +578,8 @@ static void ParseDependencyOutputArgs(DependencyOutputOptions &Opts, Opts.AddMissingHeaderDeps = Args.hasArg(OPT_MG); Opts.PrintShowIncludes = Args.hasArg(OPT_show_includes); Opts.DOTOutputFile = Args.getLastArgValue(OPT_dependency_dot); + Opts.ModuleDependencyOutputDir = + Args.getLastArgValue(OPT_module_dependency_dir); } bool clang::ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args, diff --git a/clang/lib/Frontend/ModuleDependencyCollector.cpp b/clang/lib/Frontend/ModuleDependencyCollector.cpp new file mode 100644 index 000000000000..8e4402b8eda7 --- /dev/null +++ b/clang/lib/Frontend/ModuleDependencyCollector.cpp @@ -0,0 +1,109 @@ +//===--- ModuleDependencyCollector.cpp - Collect module dependencies ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Collect the dependencies of a set of modules. +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/Utils.h" +#include "clang/Serialization/ASTReader.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Filesystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; + +namespace { +/// Private implementation for ModuleDependencyCollector +class ModuleDependencyListener : public ASTReaderListener { + ModuleDependencyCollector &Collector; + + std::error_code copyToRoot(StringRef Src); +public: + ModuleDependencyListener(ModuleDependencyCollector &Collector) + : Collector(Collector) {} + bool needsInputFileVisitation() override { return true; } + bool needsSystemInputFileVisitation() override { return true; } + bool visitInputFile(StringRef Filename, bool IsSystem, + bool IsOverridden) override; +}; +} + +void ModuleDependencyCollector::attachToASTReader(ASTReader &R) { + R.addListener(new ModuleDependencyListener(*this)); +} + +void ModuleDependencyCollector::writeFileMap() { + if (Seen.empty()) + return; + + SmallString<256> Dest = getDest(); + llvm::sys::path::append(Dest, "vfs.yaml"); + + std::string ErrorInfo; + llvm::raw_fd_ostream OS(Dest.c_str(), ErrorInfo, llvm::sys::fs::F_Text); + if (!ErrorInfo.empty()) { + setHasErrors(); + return; + } + VFSWriter.write(OS); +} + +/// Append the absolute path in Nested to the path given by Root. This will +/// remove directory traversal from the resulting nested path. +static void appendNestedPath(SmallVectorImpl &Root, StringRef Nested) { + using namespace llvm::sys; + SmallVector ComponentStack; + + StringRef Rel = path::relative_path(Nested); + for (StringRef C : llvm::make_range(path::begin(Rel), path::end(Rel))) { + if (C == ".") + continue; + if (C == "..") { + assert(ComponentStack.size() && "Path traverses out of parent"); + ComponentStack.pop_back(); + } else + ComponentStack.push_back(C); + } + // The stack is now the path without any directory traversal. + for (StringRef C : ComponentStack) + path::append(Root, C); +} + +std::error_code ModuleDependencyListener::copyToRoot(StringRef Src) { + using namespace llvm::sys; + + // We need an absolute path to append to the root. + SmallString<256> AbsoluteSrc = Src; + fs::make_absolute(AbsoluteSrc); + // Build the destination path. + SmallString<256> Dest = Collector.getDest(); + size_t RootLen = Dest.size(); + appendNestedPath(Dest, AbsoluteSrc); + + // Copy the file into place. + if (std::error_code EC = fs::create_directories(path::parent_path(Dest), + /*IgnoreExisting=*/true)) + return EC; + if (std::error_code EC = fs::copy_file(AbsoluteSrc.str(), Dest.str())) + return EC; + // Use the absolute path under the root for the file mapping. + Collector.addFileMapping(Dest.substr(RootLen), Dest.str()); + return std::error_code(); +} + +bool ModuleDependencyListener::visitInputFile(StringRef Filename, bool IsSystem, + bool IsOverridden) { + if (Collector.insertSeen(Filename)) + if (copyToRoot(Filename)) + Collector.setHasErrors(); + return true; +} diff --git a/clang/test/Modules/dependency-dump-dependent-module.m b/clang/test/Modules/dependency-dump-dependent-module.m new file mode 100644 index 000000000000..5308f7efba30 --- /dev/null +++ b/clang/test/Modules/dependency-dump-dependent-module.m @@ -0,0 +1,27 @@ +// When a module depends on another, check that we dump the dependency header +// files for both. + +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t/cache -module-dependency-dir %t/vfs -F %S/Inputs -I %S/Inputs -verify %s +// expected-no-diagnostics + +// RUN: FileCheck %s -check-prefix=VFS < %t/vfs/vfs.yaml +// VFS: 'name': "AlsoDependsOnModule.h" +// VFS: 'name': "SubFramework.h" +// VFS: 'name': "Treasure.h" +// VFS: 'name': "Module.h" +// VFS: 'name': "Sub.h" +// VFS: 'name': "Sub2.h" + +// TODO: We need shell to use find here. Is there a simpler way? +// REQUIRES: shell + +// RUN: find %t/vfs -type f | FileCheck %s -check-prefix=DUMP +// DUMP: AlsoDependsOnModule.framework/Headers/AlsoDependsOnModule.h +// DUMP: Module.framework/Frameworks/SubFramework.framework/Headers/SubFramework.h +// DUMP: Module.framework/Headers/Buried/Treasure.h +// DUMP: Module.framework/Headers/Module.h +// DUMP: Module.framework/Headers/Sub.h +// DUMP: Module.framework/Headers/Sub2.h + +@import AlsoDependsOnModule; diff --git a/clang/test/Modules/dependency-dump.m b/clang/test/Modules/dependency-dump.m new file mode 100644 index 000000000000..58d6c1572da2 --- /dev/null +++ b/clang/test/Modules/dependency-dump.m @@ -0,0 +1,25 @@ +// Check that we can dump all of the headers a module depends on, and a VFS map +// for the same. + +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t/cache -module-dependency-dir %t/vfs -F %S/Inputs -I %S/Inputs -verify %s +// expected-no-diagnostics + +// RUN: FileCheck %s -check-prefix=VFS -input-file %t/vfs/vfs.yaml +// VFS: 'name': "SubFramework.h" +// VFS: 'name': "Treasure.h" +// VFS: 'name': "Module.h" +// VFS: 'name': "Sub.h" +// VFS: 'name': "Sub2.h" + +// TODO: We need shell to use find here. Is there a simpler way? +// REQUIRES: shell + +// RUN: find %t/vfs -type f | FileCheck %s -check-prefix=DUMP +// DUMP: Module.framework/Frameworks/SubFramework.framework/Headers/SubFramework.h +// DUMP: Module.framework/Headers/Buried/Treasure.h +// DUMP: Module.framework/Headers/Module.h +// DUMP: Module.framework/Headers/Sub.h +// DUMP: Module.framework/Headers/Sub2.h + +@import Module;