Ben Langmuir <[email protected]> writes:
>     On Jun 18, 2014, at 9:11 AM, Argyrios Kyrtzidis <[email protected]>
>     wrote:
>
>         On Jun 18, 2014, at 1:08 AM, Justin Bogner <[email protected]>
>         wrote:
>        
>         Thanks for the feedback!
>        
>         Argyrios Kyrtzidis <[email protected]> writes:
>        
>             Thank you for your work on this.
>            
>             Some high level comments first; the way ModuleDependencyCollector
>             is
>             getting hooked up, results in:
>            
>             - If no module needs building (because module cache has it) no
>             module
>             dependency is getting written out
>
>         I realize this, but as far as I can tell there isn't really a
>         practical
>         way to get all of the module dependencies except for when we actually
>         create a module. Unless there's a way to extract this information from
>         an imported ModuleFile that I've missed, I think we need to collect
>         this
>         information as we parse the AST to build a module in the first place.
>        
>         If there is a more reliable way to do this, I'm all ears. I should
>         mention though, for the purposes of getting more useful crash reports
>         with modules this limitation isn't very important. Notably, it's
>         useful
>         to collect the .pcm files anyway, and using a fresh cache directory is
>         a
>         very simple way to do that.
>
>             - It doesn’t hook in the top level module, e.g. if I have "@import
>             Darwin;” then no module dependency is written out even if the
>             module
>             cache is empty.
>
>         This seems like a bigger problem, and I don't really understand it.
>         Where is the module built? I'm pretty convinced that the place I've
>         hooked into is the only one that creates a CompilerInstance with
>         BuildingModule set to true…
>
>     Ben, could you help out on hooking up ModuleDependencyCollector in a way
>     that will address the above ?
>
> Sure!  The problem is that the first time we come to attempt loading a
> top-level module (before it is compiled), we will be here and not attach
> anything since ModuleDepCollector hasn’t been filled in yet.
>
>     +    if (ModuleDepCollector)
>     +      ModuleDepCollector->attachToASTReader(*ModuleManager);
>
> If you call ModuleDepCollector = getModuleDepCollector() before that if, it
> works for me.

Okay, since ModuleDepCollector needs to have the same lifetime as the
top-level CompilerInstance, it makes more sense to create it during
CreatePreprocessor along with the other DependencyOption stuff. This is
a bit nicer in that it makes getModuleDepCollector a bit less special,
but it does mean that this feature doesn't interoperate well with
-fdisable-free, which is unfortunate.

I've attached an updated patch. I've rolled the VFS map generation into
the main patch, as it didn't make much sense to do it separately. I've
also added some tests and added handling for path traversal (ie,
bin/../lib). Also attached an up to date patch for llvm implementing
copy_file.

What do you think?

>From 7b59b12f5b2341e354ebe60df828f302857181a0 Mon Sep 17 00:00:00 2001
From: Justin Bogner <[email protected]>
Date: Mon, 12 May 2014 23:16:57 -0700
Subject: [PATCH] Frontend: Add a CC1 flag to dump module dependencies to a
 directory

This adds the -module-dependency-dir to clang -cc1, which specifies a
directory to copy all of a module's dependencies into in a form
suitable to be used as a VFS using -ivfsoverlay with the generated
vfs.yaml.

This is useful for crashdumps that involve modules, so that the module
dependencies will be intact when a crash report script is used to
reproduce a problem on another machine.

We currently encode the absolute path to the dump directory, due to
limitations in the VFS system. Until we can handle relative paths in
the VFS, users of the VFS map may need to run a simple search and
replace in the file.
---
 include/clang/Driver/Options.td                  |   2 +
 include/clang/Frontend/CompilerInstance.h        |   7 ++
 include/clang/Frontend/DependencyOutputOptions.h |   5 +-
 include/clang/Frontend/Utils.h                   |  26 ++++++
 lib/Frontend/CMakeLists.txt                      |   1 +
 lib/Frontend/CompilerInstance.cpp                |  22 +++++
 lib/Frontend/CompilerInvocation.cpp              |   2 +
 lib/Frontend/ModuleDependencyCollector.cpp       | 111 +++++++++++++++++++++++
 test/Modules/dependency-dump-dependent-module.m  |  24 +++++
 test/Modules/dependency-dump.m                   |  22 +++++
 10 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 lib/Frontend/ModuleDependencyCollector.cpp
 create mode 100644 test/Modules/dependency-dump-dependent-module.m
 create mode 100644 test/Modules/dependency-dump.m

diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td
index 85f2e0c..dd8722e 100644
--- a/include/clang/Driver/Options.td
+++ b/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/include/clang/Frontend/CompilerInstance.h b/include/clang/Frontend/CompilerInstance.h
index b144069..d72f904 100644
--- a/include/clang/Frontend/CompilerInstance.h
+++ b/include/clang/Frontend/CompilerInstance.h
@@ -105,6 +105,9 @@ class CompilerInstance : public ModuleLoader {
   /// \brief The ASTReader, if one exists.
   IntrusiveRefCntPtr<ASTReader> ModuleManager;
 
+  /// \brief The module dependency collector for crashdumps
+  std::shared_ptr<ModuleDependencyCollector> ModuleDepCollector;
+
   /// \brief The dependency file generator.
   std::unique_ptr<DependencyFileGenerator> TheDependencyFileGenerator;
 
@@ -464,6 +467,10 @@ public:
   IntrusiveRefCntPtr<ASTReader> getModuleManager() const;
   void setModuleManager(IntrusiveRefCntPtr<ASTReader> Reader);
 
+  std::shared_ptr<ModuleDependencyCollector> getModuleDepCollector() const;
+  void setModuleDepCollector(
+      std::shared_ptr<ModuleDependencyCollector> Collector);
+
   /// }
   /// @name Code Completion
   /// {
diff --git a/include/clang/Frontend/DependencyOutputOptions.h b/include/clang/Frontend/DependencyOutputOptions.h
index d275249..5da1459 100644
--- a/include/clang/Frontend/DependencyOutputOptions.h
+++ b/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/include/clang/Frontend/Utils.h b/include/clang/Frontend/Utils.h
index 8fb536f..a791cd5 100644
--- a/include/clang/Frontend/Utils.h
+++ b/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/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt
index b67e0ae..3fa7a2c 100644
--- a/lib/Frontend/CMakeLists.txt
+++ b/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/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp
index e8ca080..03a2c22 100644
--- a/lib/Frontend/CompilerInstance.cpp
+++ b/lib/Frontend/CompilerInstance.cpp
@@ -116,6 +116,16 @@ void CompilerInstance::setModuleManager(IntrusiveRefCntPtr<ASTReader> Reader) {
   ModuleManager = Reader;
 }
 
+std::shared_ptr<ModuleDependencyCollector>
+CompilerInstance::getModuleDepCollector() const {
+  return ModuleDepCollector;
+}
+
+void CompilerInstance::setModuleDepCollector(
+    std::shared_ptr<ModuleDependencyCollector> 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<ModuleDependencyCollector>(
+        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/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp
index 6d5faf9..beaa092 100644
--- a/lib/Frontend/CompilerInvocation.cpp
+++ b/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/lib/Frontend/ModuleDependencyCollector.cpp b/lib/Frontend/ModuleDependencyCollector.cpp
new file mode 100644
index 0000000..fdd7480
--- /dev/null
+++ b/lib/Frontend/ModuleDependencyCollector.cpp
@@ -0,0 +1,111 @@
+//===--- 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<char> &Root,
+                             SmallVectorImpl<char> &Nested) {
+  using namespace llvm::sys;
+  SmallVector<StringRef, 16> ComponentStack;
+
+  StringRef Rel = path::relative_path(StringRef(Nested.begin(), Nested.size()));
+  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))
+    return true;
+  if (copyToRoot(Filename))
+    Collector.setHasErrors();
+  return true;
+}
diff --git a/test/Modules/dependency-dump-dependent-module.m b/test/Modules/dependency-dump-dependent-module.m
new file mode 100644
index 0000000..e1264a6
--- /dev/null
+++ b/test/Modules/dependency-dump-dependent-module.m
@@ -0,0 +1,24 @@
+// 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"
+
+// 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/test/Modules/dependency-dump.m b/test/Modules/dependency-dump.m
new file mode 100644
index 0000000..dbf5f16
--- /dev/null
+++ b/test/Modules/dependency-dump.m
@@ -0,0 +1,22 @@
+// 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 < %t/vfs/vfs.yaml
+// VFS: 'name': "SubFramework.h"
+// VFS: 'name': "Treasure.h"
+// VFS: 'name': "Module.h"
+// VFS: 'name': "Sub.h"
+// VFS: 'name': "Sub2.h"
+
+// 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;
-- 
1.9.3 (Apple Git-50)

>From 5f0a318db1e1ecdd8c8a9447e2ce9b191611c84c Mon Sep 17 00:00:00 2001
From: Justin Bogner <[email protected]>
Date: Mon, 12 May 2014 23:39:04 -0700
Subject: [PATCH] Support: Add llvm::sys::fs::copy_file

A function to copy one file's contents to another.
---
 include/llvm/Support/FileSystem.h |  6 ++++++
 lib/Support/Path.cpp              | 29 +++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/include/llvm/Support/FileSystem.h b/include/llvm/Support/FileSystem.h
index 6abe904..e56e2b7 100644
--- a/include/llvm/Support/FileSystem.h
+++ b/include/llvm/Support/FileSystem.h
@@ -335,6 +335,12 @@ std::error_code remove(const Twine &path, bool IgnoreNonExisting = true);
 /// @param to The path to rename to. This is created.
 std::error_code rename(const Twine &from, const Twine &to);
 
+/// @brief Copy the contents of \a From to \a To.
+///
+/// @param From The path to copy from.
+/// @param To The path to copy to. This is created.
+std::error_code copy_file(const Twine &From, const Twine &To);
+
 /// @brief Resize path to size. File is resized as if by POSIX truncate().
 ///
 /// @param path Input path.
diff --git a/lib/Support/Path.cpp b/lib/Support/Path.cpp
index 15edf0dd..cdf41f6 100644
--- a/lib/Support/Path.cpp
+++ b/lib/Support/Path.cpp
@@ -846,6 +846,35 @@ std::error_code create_directories(const Twine &Path, bool IgnoreExisting) {
   return create_directory(P, IgnoreExisting);
 }
 
+std::error_code copy_file(const Twine &From, const Twine &To) {
+  int ReadFD, WriteFD;
+  if (std::error_code EC = openFileForRead(From, ReadFD))
+    return EC;
+  if (std::error_code EC = openFileForWrite(To, WriteFD, F_None)) {
+    close(ReadFD);
+    return EC;
+  }
+
+  const size_t BufSize = 4096;
+  char *Buf = new char[BufSize];
+  int Bytes;
+  for (;;) {
+    Bytes = read(ReadFD, Buf, BufSize);
+    if (Bytes <= 0)
+      break;
+    Bytes = write(WriteFD, Buf, Bytes);
+    if (Bytes <= 0)
+      break;
+  }
+  close(ReadFD);
+  close(WriteFD);
+  delete[] Buf;
+
+  if (Bytes < 0)
+    return std::error_code(errno, std::generic_category());
+  return std::error_code();
+}
+
 bool exists(file_status status) {
   return status_known(status) && status.type() != file_type::file_not_found;
 }
-- 
1.9.3 (Apple Git-50)

_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to