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=[=]`` + 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=`` 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. Index: include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- include/clang/Basic/DiagnosticDriverKinds.td +++ include/clang/Basic/DiagnosticDriverKinds.td @@ -330,4 +330,6 @@ "unable to find a Visual Studio installation; " "try running Clang from a developer command prompt">, InGroup>; + +def err_drv_invalid_module_file_map : Error<"invalid module file mapping '%0'">; } Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -1130,6 +1130,9 @@ def fmodule_file : Joined<["-"], "fmodule-file=">, Group, Flags<[DriverOption,CC1Option]>, MetaVarName<"[=]">, 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, Flags<[DriverOption,CC1Option]>, MetaVarName<"[=]">, + HelpText<"Read the mapping of module names to precompiled module files from .">; def fmodules_ignore_macro : Joined<["-"], "fmodules-ignore-macro=">, Group, Flags<[CC1Option]>, HelpText<"Ignore the definition of the given macro when building and loading modules">; def fmodules_decluse : Flag <["-"], "fmodules-decluse">, Group, Index: lib/Driver/ToolChains/Clang.cpp =================================================================== --- lib/Driver/ToolChains/Clang.cpp +++ lib/Driver/ToolChains/Clang.cpp @@ -2565,6 +2565,14 @@ else Args.ClaimAllArgs(options::OPT_fmodule_file); + // -fmodule-file-map specifies the file containing the module names to + // precompiled module files mapping. + // + if (HaveModules) + 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: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -1531,8 +1531,80 @@ return P.str(); } +// Read the mapping of module names to precompiled module files from a file. +// The argument can include an optional line prefix ([]=), 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 (=) 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 = 0; 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.substr(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); @@ -1546,6 +1618,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 @@ -1561,6 +1634,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); @@ -2514,7 +2589,6 @@ } static void ParsePreprocessorArgs(PreprocessorOptions &Opts, ArgList &Args, - FileManager &FileMgr, DiagnosticsEngine &Diags, frontend::ActionKind Action) { using namespace options; @@ -2683,14 +2757,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 @@ -2731,12 +2810,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: 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;