boris created this revision.

Add the -fmodule-file-map=[<prefix>=]<file> option which can be used to specify 
a file that contains module name to precompiled modules files mapping, similar 
to -fmodule-file=<name>=<file>. The <prefix> can be used to only consider 
certain lines which can be useful if we want to store the mapping in an already 
existing file (for example, as comments in the .d makefile fragment generated 
with the -M option or some such).

-

Additional notes:

1. Based on this mailing list discussion: 
http://lists.llvm.org/pipermail/cfe-dev/2017-June/054431.html

2. Based on the functionality implemented in: https://reviews.llvm.org/D35020


https://reviews.llvm.org/D37299

Files:
  docs/Modules.rst
  include/clang/Basic/DiagnosticDriverKinds.td
  include/clang/Driver/Options.td
  lib/Driver/ToolChains/Clang.cpp
  lib/Frontend/CompilerInvocation.cpp
  test/CXX/modules-ts/basic/basic.search/module-import.cpp

Index: test/CXX/modules-ts/basic/basic.search/module-import.cpp
===================================================================
--- test/CXX/modules-ts/basic/basic.search/module-import.cpp
+++ test/CXX/modules-ts/basic/basic.search/module-import.cpp
@@ -5,6 +5,8 @@
 // RUN: echo 'export module x; int a, b;' > %t/x.cppm
 // RUN: echo 'export module y; import x; int c;' > %t/y.cppm
 // RUN: echo 'export module z; import y; int d;' > %t/z.cppm
+// RUN: echo 'x=%t/x.pcm'  > %t/modmap
+// RUN: echo 'y=%t/y.pcm' >> %t/modmap
 //
 // RUN: %clang_cc1 -std=c++1z -fmodules-ts -emit-module-interface %t/x.cppm -o %t/x.pcm
 // RUN: %clang_cc1 -std=c++1z -fmodules-ts -emit-module-interface -fmodule-file=%t/x.pcm %t/y.cppm -o %t/y.pcm
@@ -19,7 +21,17 @@
 // RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file=y=%t/y.pcm -verify %s \
 // RUN:            -DMODULE_NAME=y
 //
+// RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file-map=%t/modmap -verify %s \
+// RUN:            -DMODULE_NAME=x
+// RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file-map=%t/modmap -verify %s \
+// RUN:            -DMODULE_NAME=y
+//
 // RUN: mv %t/x.pcm %t/a.pcm
+// RUN: echo 'foo.o: foo.cxx'                   > %t/modmap
+// RUN: echo '# Mmodule name to file mapping:' >> %t/modmap
+// RUN: echo '#@z=%t/z.pcm'                    >> %t/modmap
+// RUN: echo '#@ y=%t/y.pcm'                   >> %t/modmap
+// RUN: echo '#@x=%t/a.pcm '                   >> %t/modmap
 //
 // RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file=x=%t/a.pcm -verify %s \
 // RUN:            -DMODULE_NAME=x
@@ -33,6 +45,14 @@
 // RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file=z=%t/z.pcm -fmodule-file=y=%t/y.pcm -fmodule-file=x=%t/a.pcm -verify %s \
 // RUN:            -DMODULE_NAME=z
 //
+//
+// RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file-map=#@=%t/modmap -verify %s \
+// RUN:            -DMODULE_NAME=x
+// RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file-map=#@=%t/modmap -verify %s \
+// RUN:            -DMODULE_NAME=y
+// RUN: %clang_cc1 -std=c++1z -fmodules-ts -I%t -fmodule-file-map=#@=%t/modmap -verify %s \
+// RUN:            -DMODULE_NAME=z
+//
 
 import MODULE_NAME;
 
Index: lib/Frontend/CompilerInvocation.cpp
===================================================================
--- lib/Frontend/CompilerInvocation.cpp
+++ lib/Frontend/CompilerInvocation.cpp
@@ -1528,8 +1528,89 @@
   return P.str();
 }
 
+// Read the mapping of module names to precompiled module files from a file.
+// The argument can include an optional line prefix ([<prefix>]=<file>), in
+// which case only lines that start with the prefix are considered (with the
+// prefix and the following whitespaces, if any, ignored).
+//
+// Each mapping entry should be in the same form as the -fmodule-file option
+// value (<name>=<file>) with leading/trailing whitespaces ignored.
+//
+static void LoadModuleFileMap(HeaderSearchOptions &Opts,
+                              DiagnosticsEngine &Diags,
+                              FileManager &FileMgr,
+                              StringRef Val,
+                              const std::string &Arg) {
+  // See if we have the prefix.
+  StringRef File;
+  StringRef Prefix;
+  if (Val.find('=') != StringRef::npos)
+  {
+    auto Pair = Val.split('=');
+    Prefix = Pair.first;
+    File = Pair.second;
+    if (Prefix.empty())
+    {
+      Diags.Report(diag::err_drv_invalid_value) << Arg << Val;
+      return;
+    }
+  }
+  else
+    File = Val;
+
+  if (File.empty())
+  {
+    Diags.Report(diag::err_drv_invalid_value) << Arg << Val;
+    return;
+  }
+
+  auto Buf = FileMgr.getBufferForFile(File);
+  if (!Buf)
+  {
+    Diags.Report(diag::err_cannot_open_file) << File << Buf.getError().message();
+    return;
+  }
+
+  // Read the file line by line.
+  StringRef Str = Buf.get()->getBuffer();
+  for (size_t B(0), E(B); B < Str.size(); B = E + 1)
+  {
+    E = Str.find_first_of(StringRef("\n\0", 2), B);
+
+    if (E == StringRef::npos)
+      E = Str.size();
+    else if (Str[E] == '\0')
+      break; // The file (or the rest of it) is binary, bail out.
+
+    // [B, E) is our line. Compare and skip the prefix, if any.
+    StringRef Line(Str.data() + B, E - B);
+    if (!Prefix.empty())
+    {
+      if (!Line.startswith(Prefix))
+        continue;
+
+      Line = Line.substr(Prefix.size());
+    }
+
+    // Skip leading and trailing whitespaces and ignore blanks (even if they
+    // had prefix; think make comments).
+    Line = Line.trim();
+    if (Line.empty())
+      continue;
+
+    if (Line.find('=') == StringRef::npos)
+    {
+      Diags.Report(diag::err_drv_invalid_module_file_map) << Line;
+      continue;
+    }
+
+    Opts.PrebuiltModuleFiles.insert(Line.split('='));
+  }
+}
+
 static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
-                                  const std::string &WorkingDir) {
+                                  DiagnosticsEngine &Diags,
+                                  FileManager &FileMgr) {
   using namespace options;
   Opts.Sysroot = Args.getLastArgValue(OPT_isysroot, "/");
   Opts.Verbose = Args.hasArg(OPT_v);
@@ -1543,6 +1624,7 @@
   // Canonicalize -fmodules-cache-path before storing it.
   SmallString<128> P(Args.getLastArgValue(OPT_fmodules_cache_path));
   if (!(P.empty() || llvm::sys::path::is_absolute(P))) {
+    const std::string &WorkingDir (FileMgr.getFileSystemOpts().WorkingDir);
     if (WorkingDir.empty())
       llvm::sys::fs::make_absolute(P);
     else
@@ -1558,6 +1640,8 @@
     if (Val.find('=') != StringRef::npos)
       Opts.PrebuiltModuleFiles.insert(Val.split('='));
   }
+  for (const Arg *A : Args.filtered(OPT_fmodule_file_map))
+    LoadModuleFileMap(Opts, Diags, FileMgr, A->getValue(), A->getAsString(Args));
   for (const Arg *A : Args.filtered(OPT_fprebuilt_module_path))
     Opts.AddPrebuiltModulePath(A->getValue());
   Opts.DisableModuleHash = Args.hasArg(OPT_fdisable_module_hash);
@@ -2511,7 +2595,6 @@
 }
 
 static void ParsePreprocessorArgs(PreprocessorOptions &Opts, ArgList &Args,
-                                  FileManager &FileMgr,
                                   DiagnosticsEngine &Diags,
                                   frontend::ActionKind Action) {
   using namespace options;
@@ -2680,14 +2763,19 @@
                           false /*DefaultDiagColor*/, false /*DefaultShowOpt*/);
   ParseCommentArgs(LangOpts.CommentOpts, Args);
   ParseFileSystemArgs(Res.getFileSystemOpts(), Args);
+
+  // File manager used during option parsing (e.g., for loading map files,
+  // etc).
+  //
+  FileManager FileMgr(Res.getFileSystemOpts());
+
   // FIXME: We shouldn't have to pass the DashX option around here
   InputKind DashX = ParseFrontendArgs(Res.getFrontendOpts(), Args, Diags,
                                       LangOpts.IsHeaderFile);
   ParseTargetArgs(Res.getTargetOpts(), Args, Diags);
   Success &= ParseCodeGenArgs(Res.getCodeGenOpts(), Args, DashX, Diags,
                               Res.getTargetOpts());
-  ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args,
-                        Res.getFileSystemOpts().WorkingDir);
+  ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args, Diags, FileMgr);
   if (DashX.getFormat() == InputKind::Precompiled ||
       DashX.getLanguage() == InputKind::LLVM_IR) {
     // ObjCAAutoRefCount and Sanitize LangOpts are used to setup the
@@ -2728,12 +2816,7 @@
       !LangOpts.Sanitize.has(SanitizerKind::Address) &&
       !LangOpts.Sanitize.has(SanitizerKind::Memory);
 
-  // FIXME: ParsePreprocessorArgs uses the FileManager to read the contents of
-  // PCH file and find the original header name. Remove the need to do that in
-  // ParsePreprocessorArgs and remove the FileManager
-  // parameters from the function and the "FileManager.h" #include.
-  FileManager FileMgr(Res.getFileSystemOpts());
-  ParsePreprocessorArgs(Res.getPreprocessorOpts(), Args, FileMgr, Diags,
+  ParsePreprocessorArgs(Res.getPreprocessorOpts(), Args, Diags,
                         Res.getFrontendOpts().ProgramAction);
   ParsePreprocessorOutputArgs(Res.getPreprocessorOutputOpts(), Args,
                               Res.getFrontendOpts().ProgramAction);
Index: lib/Driver/ToolChains/Clang.cpp
===================================================================
--- lib/Driver/ToolChains/Clang.cpp
+++ lib/Driver/ToolChains/Clang.cpp
@@ -3685,6 +3685,15 @@
   else
     Args.ClaimAllArgs(options::OPT_fmodule_file);
 
+  // -fmodule-file-map specifies the file containing the module names to
+  // precompiled module files mapping.
+  //
+  if (HaveAnyModules)
+    Args.AddAllArgs(CmdArgs, options::OPT_fmodule_file_map);
+  else
+    Args.ClaimAllArgs(options::OPT_fmodule_file_map);
+
+
   // When building modules and generating crashdumps, we need to dump a module
   // dependency VFS alongside the output.
   if (HaveClangModules && C.isForDiagnostics()) {
Index: include/clang/Driver/Options.td
===================================================================
--- include/clang/Driver/Options.td
+++ include/clang/Driver/Options.td
@@ -1127,6 +1127,9 @@
 def fmodule_file : Joined<["-"], "fmodule-file=">,
   Group<i_Group>, Flags<[DriverOption,CC1Option]>, MetaVarName<"[<name>=]<file>">,
   HelpText<"Specify the mapping of module name to precompiled module file, or load a module file if name is omitted.">;
+def fmodule_file_map : Joined<["-"], "fmodule-file-map=">,
+  Group<i_Group>, Flags<[DriverOption,CC1Option]>, MetaVarName<"[<prefix>=]<file>">,
+  HelpText<"Read the mapping of module names to precompiled module files from <file>.">;
 def fmodules_ignore_macro : Joined<["-"], "fmodules-ignore-macro=">, Group<f_Group>, Flags<[CC1Option]>,
   HelpText<"Ignore the definition of the given macro when building and loading modules">;
 def fmodules_decluse : Flag <["-"], "fmodules-decluse">, Group<f_Group>,
Index: include/clang/Basic/DiagnosticDriverKinds.td
===================================================================
--- include/clang/Basic/DiagnosticDriverKinds.td
+++ include/clang/Basic/DiagnosticDriverKinds.td
@@ -326,4 +326,6 @@
   "unable to find a Visual Studio installation; "
   "try running Clang from a developer command prompt">,
   InGroup<DiagGroup<"msvc-not-found">>;
+
+def err_drv_invalid_module_file_map : Error<"invalid module file mapping '%0'">;
 }
Index: docs/Modules.rst
===================================================================
--- docs/Modules.rst
+++ docs/Modules.rst
@@ -222,6 +222,16 @@
   specified file also overrides this module's paths that might be embedded
   in other precompiled module files.
 
+``-fmodule-file-map=[<prefix>=]<file>``
+  Read the mapping of module names to precompiled module files from file. If
+  the argument includes optional prefix, then only lines starting with this
+  string are considered (with the prefix itself ignored). Each mapping entry
+  should be in the same form as the ``-fmodule-file`` value with module
+  name. Leading and trailing whitespaces in the value as well as blank lines
+  are ignored. The line prefix can be used to store the mapping in an already
+  existing file, for example, as comments in makefile fragments produced by
+  the ``-M`` option family.
+
 ``-fprebuilt-module-path=<directory>``
   Specify the path to the prebuilt modules. If specified, we will look for modules in this directory for a given top-level module name. We don't need a module map for loading prebuilt modules in this directory and the compiler will not try to rebuild these modules. This can be specified multiple times.
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to