Module: add -fprebuilt-module-path to support loading prebuilt modules.
In this mode, there is no need to load any module map and the programmer can
simply use "@import" syntax to load the module directly from a prebuilt
module path. When loading from prebuilt module path, we don't support
rebuilding of the module files and we ignore compatible configuration
mismatches.
rdar://27290316
Differential Revision: http://reviews.llvm.org/D23125
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@279096 91177308-0d34-0410-b5e6-96231b3b80d8
Conflicts:
lib/Driver/Tools.cpp
diff --git a/docs/Modules.rst b/docs/Modules.rst
index 0ea3b5b..4187654 100644
--- a/docs/Modules.rst
+++ b/docs/Modules.rst
@@ -213,6 +213,9 @@
``-fmodule-file=<file>``
Load the given precompiled module file.
+``-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.
+
Module Semantics
================
diff --git a/include/clang/Basic/DiagnosticCommonKinds.td b/include/clang/Basic/DiagnosticCommonKinds.td
index 9d9737f..0be704c 100644
--- a/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/include/clang/Basic/DiagnosticCommonKinds.td
@@ -96,6 +96,8 @@
"timed out waiting to acquire lock file for module '%0'">, DefaultFatal;
def err_module_cycle : Error<"cyclic dependency in module '%0': %1">,
DefaultFatal;
+def err_module_prebuilt : Error<
+ "error in loading module '%0' from prebuilt module path">, DefaultFatal;
def note_pragma_entered_here : Note<"#pragma entered here">;
def note_decl_hiding_tag_type : Note<
"%1 %0 is hidden by a non-type declaration of %0 here">;
diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td
index 5120d07..735eca2 100644
--- a/include/clang/Driver/Options.td
+++ b/include/clang/Driver/Options.td
@@ -763,6 +763,9 @@
def fmodules_user_build_path : Separate<["-"], "fmodules-user-build-path">, Group<i_Group>,
Flags<[DriverOption, CC1Option]>, MetaVarName<"<directory>">,
HelpText<"Specify the module user build path">;
+def fprebuilt_module_path : Joined<["-"], "fprebuilt-module-path=">, Group<i_Group>,
+ Flags<[DriverOption, CC1Option]>, MetaVarName<"<directory>">,
+ HelpText<"Specify the prebuilt module path">;
def fmodules_prune_interval : Joined<["-"], "fmodules-prune-interval=">, Group<i_Group>,
Flags<[CC1Option]>, MetaVarName<"<seconds>">,
HelpText<"Specify the interval (in seconds) between attempts to prune the module cache">;
diff --git a/include/clang/Lex/HeaderSearch.h b/include/clang/Lex/HeaderSearch.h
index 4dfd5f2..6ebeb8b 100644
--- a/include/clang/Lex/HeaderSearch.h
+++ b/include/clang/Lex/HeaderSearch.h
@@ -483,9 +483,12 @@
/// \param ModuleMapPath A path that when combined with \c ModuleName
/// uniquely identifies this module. See Module::ModuleMap.
///
+ /// \param UsePrebuiltPath Whether we should use the prebuilt module path.
+ ///
/// \returns The name of the module file that corresponds to this module,
/// or an empty string if this module does not correspond to any module file.
- std::string getModuleFileName(StringRef ModuleName, StringRef ModuleMapPath);
+ std::string getModuleFileName(StringRef ModuleName, StringRef ModuleMapPath,
+ bool UsePrebuiltPath);
/// \brief Lookup a module Search for a module with the given name.
///
diff --git a/include/clang/Lex/HeaderSearchOptions.h b/include/clang/Lex/HeaderSearchOptions.h
index 9727f56..53909e6 100644
--- a/include/clang/Lex/HeaderSearchOptions.h
+++ b/include/clang/Lex/HeaderSearchOptions.h
@@ -93,6 +93,9 @@
/// \brief The directory used for a user build.
std::string ModuleUserBuildPath;
+ /// \brief The directories used to load prebuilt module files.
+ std::vector<std::string> PrebuiltModulePaths;
+
/// The module/pch container format.
std::string ModuleFormat;
@@ -201,6 +204,10 @@
void AddVFSOverlayFile(StringRef Name) {
VFSOverlayFiles.push_back(Name);
}
+
+ void AddPrebuiltModulePath(StringRef Name) {
+ PrebuiltModulePaths.push_back(Name);
+ }
};
} // end namespace clang
diff --git a/include/clang/Serialization/Module.h b/include/clang/Serialization/Module.h
index d6d16a0..0e56dfba 100644
--- a/include/clang/Serialization/Module.h
+++ b/include/clang/Serialization/Module.h
@@ -48,7 +48,8 @@
MK_ExplicitModule, ///< File is an explicitly-loaded module.
MK_PCH, ///< File is a PCH file treated as such.
MK_Preamble, ///< File is a PCH file treated as the preamble.
- MK_MainFile ///< File is a PCH file treated as the actual main file.
+ MK_MainFile, ///< File is a PCH file treated as the actual main file.
+ MK_PrebuiltModule ///< File is from a prebuilt module path.
};
/// \brief The input file that has been loaded from this AST file, along with
@@ -461,7 +462,8 @@
/// \brief Is this a module file for a module (rather than a PCH or similar).
bool isModule() const {
- return Kind == MK_ImplicitModule || Kind == MK_ExplicitModule;
+ return Kind == MK_ImplicitModule || Kind == MK_ExplicitModule ||
+ Kind == MK_PrebuiltModule;
}
/// \brief Dump debugging output for this module.
diff --git a/lib/Driver/Tools.cpp b/lib/Driver/Tools.cpp
index c5729ca..bbe6437 100644
--- a/lib/Driver/Tools.cpp
+++ b/lib/Driver/Tools.cpp
@@ -4973,6 +4973,13 @@
CmdArgs.push_back(Args.MakeArgString(Path));
}
+ if (HaveModules) {
+ // -fprebuilt-module-path specifies where to load the prebuilt module files.
+ for (const Arg *A : Args.filtered(options::OPT_fprebuilt_module_path))
+ CmdArgs.push_back(Args.MakeArgString(
+ std::string("-fprebuilt-module-path=") + A->getValue()));
+ }
+
// When building modules and generating crashdumps, we need to dump a module
// dependency VFS alongside the output.
if (HaveModules && C.isForDiagnostics()) {
diff --git a/lib/Frontend/ASTUnit.cpp b/lib/Frontend/ASTUnit.cpp
index 276d863..9551e4c 100644
--- a/lib/Frontend/ASTUnit.cpp
+++ b/lib/Frontend/ASTUnit.cpp
@@ -2796,6 +2796,7 @@
switch (M.Kind) {
case serialization::MK_ImplicitModule:
case serialization::MK_ExplicitModule:
+ case serialization::MK_PrebuiltModule:
return true; // skip dependencies.
case serialization::MK_PCH:
Mod = &M;
diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp
index f9b6c9a..5df9cad 100644
--- a/lib/Frontend/CompilerInstance.cpp
+++ b/lib/Frontend/CompilerInstance.cpp
@@ -1430,7 +1430,25 @@
} else {
// Search for a module with the given name.
Module = PP->getHeaderSearchInfo().lookupModule(ModuleName);
- if (!Module) {
+ HeaderSearchOptions &HSOpts =
+ PP->getHeaderSearchInfo().getHeaderSearchOpts();
+
+ std::string ModuleFileName;
+ bool LoadFromPrebuiltModulePath = false;
+ // We try to load the module from the prebuilt module paths. If not
+ // successful, we then try to find it in the module cache.
+ if (!HSOpts.PrebuiltModulePaths.empty()) {
+ // Load the module from the prebuilt module path.
+ ModuleFileName = PP->getHeaderSearchInfo().getModuleFileName(
+ ModuleName, "", /*UsePrebuiltPath*/ true);
+ if (!ModuleFileName.empty())
+ LoadFromPrebuiltModulePath = true;
+ }
+ if (!LoadFromPrebuiltModulePath && Module) {
+ // Load the module from the module cache.
+ ModuleFileName = PP->getHeaderSearchInfo().getModuleFileName(Module);
+ } else if (!LoadFromPrebuiltModulePath) {
+ // We can't find a module, error out here.
getDiagnostics().Report(ModuleNameLoc, diag::err_module_not_found)
<< ModuleName
<< SourceRange(ImportLoc, ModuleNameLoc);
@@ -1438,10 +1456,8 @@
return ModuleLoadResult();
}
- std::string ModuleFileName =
- PP->getHeaderSearchInfo().getModuleFileName(Module);
if (ModuleFileName.empty()) {
- if (Module->HasIncompatibleModuleFile) {
+ if (Module && Module->HasIncompatibleModuleFile) {
// We tried and failed to load a module file for this module. Fall
// back to textual inclusion for its headers.
return ModuleLoadResult(nullptr, /*missingExpected*/true);
@@ -1462,16 +1478,46 @@
Timer.init("Loading " + ModuleFileName, *FrontendTimerGroup);
llvm::TimeRegion TimeLoading(FrontendTimerGroup ? &Timer : nullptr);
- // Try to load the module file.
- unsigned ARRFlags = ASTReader::ARR_OutOfDate | ASTReader::ARR_Missing;
+ // Try to load the module file. If we are trying to load from the prebuilt
+ // module path, we don't have the module map files and don't know how to
+ // rebuild modules.
+ unsigned ARRFlags = LoadFromPrebuiltModulePath ?
+ ASTReader::ARR_ConfigurationMismatch :
+ ASTReader::ARR_OutOfDate | ASTReader::ARR_Missing;
switch (ModuleManager->ReadAST(ModuleFileName,
+ LoadFromPrebuiltModulePath ?
+ serialization::MK_PrebuiltModule :
serialization::MK_ImplicitModule,
- ImportLoc, ARRFlags)) {
- case ASTReader::Success:
+ ImportLoc,
+ ARRFlags)) {
+ case ASTReader::Success: {
+ if (LoadFromPrebuiltModulePath && !Module) {
+ Module = PP->getHeaderSearchInfo().lookupModule(ModuleName);
+ if (!Module || !Module->getASTFile() ||
+ FileMgr->getFile(ModuleFileName) != Module->getASTFile()) {
+ // Error out if Module does not refer to the file in the prebuilt
+ // module path.
+ getDiagnostics().Report(ModuleNameLoc, diag::err_module_prebuilt)
+ << ModuleName;
+ ModuleBuildFailed = true;
+ KnownModules[Path[0].first] = nullptr;
+ return ModuleLoadResult();
+ }
+ }
break;
+ }
case ASTReader::OutOfDate:
case ASTReader::Missing: {
+ if (LoadFromPrebuiltModulePath) {
+ // We can't rebuild the module without a module map. Since ReadAST
+ // already produces diagnostics for these two cases, we simply
+ // error out here.
+ ModuleBuildFailed = true;
+ KnownModules[Path[0].first] = nullptr;
+ return ModuleLoadResult();
+ }
+
// The module file is missing or out-of-date. Build it.
assert(Module && "missing module file");
// Check whether there is a cycle in the module graph.
@@ -1522,8 +1568,13 @@
break;
}
- case ASTReader::VersionMismatch:
case ASTReader::ConfigurationMismatch:
+ if (LoadFromPrebuiltModulePath)
+ getDiagnostics().Report(SourceLocation(),
+ diag::warn_module_config_mismatch)
+ << ModuleFileName;
+ // Fall through to error out.
+ case ASTReader::VersionMismatch:
case ASTReader::HadErrors:
ModuleLoader::HadFatalFailure = true;
// FIXME: The ASTReader will already have complained, but can we shoehorn
diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp
index be75759..8b0bb9c 100644
--- a/lib/Frontend/CompilerInvocation.cpp
+++ b/lib/Frontend/CompilerInvocation.cpp
@@ -1267,6 +1267,8 @@
Opts.ResourceDir = Args.getLastArgValue(OPT_resource_dir);
Opts.ModuleCachePath = Args.getLastArgValue(OPT_fmodules_cache_path);
Opts.ModuleUserBuildPath = Args.getLastArgValue(OPT_fmodules_user_build_path);
+ for (const Arg *A : Args.filtered(OPT_fprebuilt_module_path))
+ Opts.AddPrebuiltModulePath(A->getValue());
Opts.DisableModuleHash = Args.hasArg(OPT_fdisable_module_hash);
Opts.ModulesValidateDiagnosticOptions =
!Args.hasArg(OPT_fmodules_disable_diagnostic_validation);
diff --git a/lib/Frontend/FrontendActions.cpp b/lib/Frontend/FrontendActions.cpp
index edd888d..eb91a59 100644
--- a/lib/Frontend/FrontendActions.cpp
+++ b/lib/Frontend/FrontendActions.cpp
@@ -396,7 +396,8 @@
HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo();
CI.getFrontendOpts().OutputFile =
HS.getModuleFileName(CI.getLangOpts().CurrentModule,
- ModuleMapForUniquing->getName());
+ ModuleMapForUniquing->getName(),
+ /*UsePrebuiltPath=*/false);
}
// We use createOutputFile here because this is exposed via libclang, and we
diff --git a/lib/Lex/HeaderSearch.cpp b/lib/Lex/HeaderSearch.cpp
index 8d960cd..ea65197 100644
--- a/lib/Lex/HeaderSearch.cpp
+++ b/lib/Lex/HeaderSearch.cpp
@@ -122,11 +122,29 @@
std::string HeaderSearch::getModuleFileName(Module *Module) {
const FileEntry *ModuleMap =
getModuleMap().getModuleMapFileForUniquing(Module);
- return getModuleFileName(Module->Name, ModuleMap->getName());
+ return getModuleFileName(Module->Name, ModuleMap->getName(),
+ /*UsePrebuiltPath*/false);
}
std::string HeaderSearch::getModuleFileName(StringRef ModuleName,
- StringRef ModuleMapPath) {
+ StringRef ModuleMapPath,
+ bool UsePrebuiltPath) {
+ if (UsePrebuiltPath) {
+ if (HSOpts->PrebuiltModulePaths.empty())
+ return std::string();
+
+ // Go though each prebuilt module path and try to find the pcm file.
+ for (const std::string &Dir : HSOpts->PrebuiltModulePaths) {
+ SmallString<256> Result(Dir);
+ llvm::sys::fs::make_absolute(Result);
+
+ llvm::sys::path::append(Result, ModuleName + ".pcm");
+ if (getFileMgr().getFile(Result.str()))
+ return Result.str().str();
+ }
+ return std::string();
+ }
+
// If we don't have a module cache path or aren't supposed to use one, we
// can't do anything.
if (getModuleCachePath().empty())
diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp
index 6b3bb10..6a1ae65 100644
--- a/lib/Serialization/ASTReader.cpp
+++ b/lib/Serialization/ASTReader.cpp
@@ -1284,8 +1284,7 @@
SrcMgr::CharacteristicKind
FileCharacter = (SrcMgr::CharacteristicKind)Record[2];
SourceLocation IncludeLoc = ReadSourceLocation(*F, Record[1]);
- if (IncludeLoc.isInvalid() &&
- (F->Kind == MK_ImplicitModule || F->Kind == MK_ExplicitModule)) {
+ if (IncludeLoc.isInvalid() && F->isModule()) {
IncludeLoc = getImportLocation(F);
}
unsigned Code = SLocEntryCursor.ReadCode();
@@ -1331,7 +1330,7 @@
// Find which module file this entry lands in.
ModuleFile *M = GlobalSLocEntryMap.find(-ID)->second;
- if (M->Kind != MK_ImplicitModule && M->Kind != MK_ExplicitModule)
+ if (!M->isModule())
return std::make_pair(SourceLocation(), "");
// FIXME: Can we map this down to a particular submodule? That would be
@@ -1841,7 +1840,7 @@
// Don't read the directive history for a module; we don't have anywhere
// to put it.
- if (M.Kind == MK_ImplicitModule || M.Kind == MK_ExplicitModule)
+ if (M.isModule())
return;
// Deserialize the macro directives history in reverse source-order.
@@ -2183,7 +2182,8 @@
// All user input files reside at the index range [0, NumUserInputs), and
// system input files reside at [NumUserInputs, NumInputs). For explicitly
// loaded module files, ignore missing inputs.
- if (!DisableValidation && F.Kind != MK_ExplicitModule) {
+ if (!DisableValidation && F.Kind != MK_ExplicitModule &&
+ F.Kind != MK_PrebuiltModule) {
bool Complain = (ClientLoadCapabilities & ARR_OutOfDate) == 0;
// If we are reading a module, we will create a verification timestamp,
@@ -2214,7 +2214,8 @@
bool IsSystem = I >= NumUserInputs;
InputFileInfo FI = readInputFileInfo(F, I+1);
Listener->visitInputFile(FI.Filename, IsSystem, FI.Overridden,
- F.Kind == MK_ExplicitModule);
+ F.Kind == MK_ExplicitModule ||
+ F.Kind == MK_PrebuiltModule);
}
}
@@ -2244,7 +2245,7 @@
//
// FIXME: Allow this for files explicitly specified with -include-pch.
bool AllowCompatibleConfigurationMismatch =
- F.Kind == MK_ExplicitModule;
+ F.Kind == MK_ExplicitModule || F.Kind == MK_PrebuiltModule;
const HeaderSearchOptions &HSOpts =
PP.getHeaderSearchInfo().getHeaderSearchOpts();
@@ -2406,7 +2407,7 @@
if (M && M->Directory) {
// If we're implicitly loading a module, the base directory can't
// change between the build and use.
- if (F.Kind != MK_ExplicitModule) {
+ if (F.Kind != MK_ExplicitModule && F.Kind != MK_PrebuiltModule) {
const DirectoryEntry *BuildDir =
PP.getFileManager().getDirectory(Blob);
if (!BuildDir || BuildDir != M->Directory) {
@@ -3163,7 +3164,7 @@
break;
case IMPORTED_MODULES: {
- if (F.Kind != MK_ImplicitModule && F.Kind != MK_ExplicitModule) {
+ if (!F.isModule()) {
// If we aren't loading a module (which has its own exports), make
// all of the imported modules visible.
// FIXME: Deal with macros-only imports.
@@ -3230,7 +3231,7 @@
unsigned Idx = 0;
F.ModuleMapPath = ReadPath(F, Record, Idx);
- if (F.Kind == MK_ExplicitModule) {
+ if (F.Kind == MK_ExplicitModule || F.Kind == MK_PrebuiltModule) {
// For an explicitly-loaded module, we don't care whether the original
// module map file exists or matches.
return Success;
@@ -3605,7 +3606,8 @@
}
if (!Context.getLangOpts().CPlusPlus ||
- (Type != MK_ImplicitModule && Type != MK_ExplicitModule)) {
+ (Type != MK_ImplicitModule && Type != MK_ExplicitModule &&
+ Type != MK_PrebuiltModule)) {
// Mark all of the identifiers in the identifier table as being out of date,
// so that various accessors know to check the loaded modules when the
// identifier is used.
@@ -3722,6 +3724,7 @@
return 0; // PCH
case MK_ImplicitModule:
case MK_ExplicitModule:
+ case MK_PrebuiltModule:
return 1; // module
case MK_MainFile:
case MK_Preamble:
@@ -3827,7 +3830,8 @@
//
// FIXME: Should we also perform the converse check? Loading a module as
// a PCH file sort of works, but it's a bit wonky.
- if ((Type == MK_ImplicitModule || Type == MK_ExplicitModule) &&
+ if ((Type == MK_ImplicitModule || Type == MK_ExplicitModule ||
+ Type == MK_PrebuiltModule) &&
F.ModuleName.empty()) {
auto Result = (Type == MK_ImplicitModule) ? OutOfDate : Failure;
if (Result != OutOfDate ||
@@ -8399,16 +8403,14 @@
for (unsigned IDIdx = 0, NumIDs = GlobalIDs.size(); IDIdx != NumIDs;
++IDIdx) {
const PendingMacroInfo &Info = GlobalIDs[IDIdx];
- if (Info.M->Kind != MK_ImplicitModule &&
- Info.M->Kind != MK_ExplicitModule)
+ if (!Info.M->isModule())
resolvePendingMacro(II, Info);
}
// Handle module imports.
for (unsigned IDIdx = 0, NumIDs = GlobalIDs.size(); IDIdx != NumIDs;
++IDIdx) {
const PendingMacroInfo &Info = GlobalIDs[IDIdx];
- if (Info.M->Kind == MK_ImplicitModule ||
- Info.M->Kind == MK_ExplicitModule)
+ if (Info.M->isModule())
resolvePendingMacro(II, Info);
}
}
diff --git a/lib/Serialization/ASTReaderDecl.cpp b/lib/Serialization/ASTReaderDecl.cpp
index 6f60b63..243db02 100644
--- a/lib/Serialization/ASTReaderDecl.cpp
+++ b/lib/Serialization/ASTReaderDecl.cpp
@@ -1326,7 +1326,7 @@
// any other module's anonymous namespaces, so don't attach the anonymous
// namespace at all.
NamespaceDecl *Anon = cast<NamespaceDecl>(Reader.GetDecl(AnonNamespace));
- if (F.Kind != MK_ImplicitModule && F.Kind != MK_ExplicitModule)
+ if (!F.isModule())
D->setAnonymousNamespace(Anon);
}
}
@@ -3612,8 +3612,7 @@
// Each module has its own anonymous namespace, which is disjoint from
// any other module's anonymous namespaces, so don't attach the anonymous
// namespace at all.
- if (ModuleFile.Kind != MK_ImplicitModule &&
- ModuleFile.Kind != MK_ExplicitModule) {
+ if (!ModuleFile.isModule()) {
if (TranslationUnitDecl *TU = dyn_cast<TranslationUnitDecl>(D))
TU->setAnonymousNamespace(Anon);
else
diff --git a/lib/Serialization/ModuleManager.cpp b/lib/Serialization/ModuleManager.cpp
index 74f75a1..03a0266 100644
--- a/lib/Serialization/ModuleManager.cpp
+++ b/lib/Serialization/ModuleManager.cpp
@@ -67,7 +67,7 @@
// Look for the file entry. This only fails if the expected size or
// modification time differ.
const FileEntry *Entry;
- if (Type == MK_ExplicitModule) {
+ if (Type == MK_ExplicitModule || Type == MK_PrebuiltModule) {
// If we're not expecting to pull this file out of the module cache, it
// might have a different mtime due to being moved across filesystems in
// a distributed build. The size must still match, though. (As must the
diff --git a/test/Driver/modules.m b/test/Driver/modules.m
index fd66478..ec82cc1 100644
--- a/test/Driver/modules.m
+++ b/test/Driver/modules.m
@@ -39,6 +39,13 @@
// RUN: %clang -fmodules-disable-diagnostic-validation -### %s 2>&1 | FileCheck -check-prefix=MODULES_DISABLE_DIAGNOSTIC_VALIDATION %s
// MODULES_DISABLE_DIAGNOSTIC_VALIDATION: -fmodules-disable-diagnostic-validation
+// RUN: %clang -fmodules -### %s 2>&1 | FileCheck -check-prefix=MODULES_PREBUILT_PATH_DEFAULT %s
+// MODULES_PREBUILT_PATH_DEFAULT-NOT: -fprebuilt-module-path
+
+// RUN: %clang -fmodules -fprebuilt-module-path=foo -fprebuilt-module-path=bar -### %s 2>&1 | FileCheck -check-prefix=MODULES_PREBUILT_PATH %s
+// MODULES_PREBUILT_PATH: "-fprebuilt-module-path=foo"
+// MODULES_PREBUILT_PATH: "-fprebuilt-module-path=bar"
+
// RUN: %clang -fmodules -fmodule-map-file=foo.map -fmodule-map-file=bar.map -### %s 2>&1 | FileCheck -check-prefix=CHECK-MODULE-MAP-FILES %s
// CHECK-MODULE-MAP-FILES: "-fmodules"
// CHECK-MODULE-MAP-FILES: "-fmodule-map-file=foo.map"
diff --git a/test/Modules/Inputs/prebuilt-module/a.h b/test/Modules/Inputs/prebuilt-module/a.h
new file mode 100644
index 0000000..f86587a
--- /dev/null
+++ b/test/Modules/Inputs/prebuilt-module/a.h
@@ -0,0 +1 @@
+const int a = 1;
diff --git a/test/Modules/Inputs/prebuilt-module/module.modulemap b/test/Modules/Inputs/prebuilt-module/module.modulemap
new file mode 100644
index 0000000..54459bd
--- /dev/null
+++ b/test/Modules/Inputs/prebuilt-module/module.modulemap
@@ -0,0 +1 @@
+module prebuilt { header "a.h" }
diff --git a/test/Modules/prebuilt-module.m b/test/Modules/prebuilt-module.m
new file mode 100644
index 0000000..334fc5e
--- /dev/null
+++ b/test/Modules/prebuilt-module.m
@@ -0,0 +1,10 @@
+// RUN: rm -rf %t
+//
+// RUN: %clang_cc1 -fmodules -x objective-c -I %S/Inputs/prebuilt-module -triple %itanium_abi_triple -emit-module %S/Inputs/prebuilt-module/module.modulemap -fmodule-name=prebuilt -o %t/prebuilt.pcm
+// RUN: %clang_cc1 -fmodules -triple %itanium_abi_triple -fprebuilt-module-path=%t/ -fdisable-module-hash %s -verify
+
+// expected-no-diagnostics
+@import prebuilt;
+int test() {
+ return a;
+}