From 21cae2059a06f7d89eee169409c9266def1b1aca Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Mon, 12 Sep 2011 23:31:24 +0000 Subject: [PATCH] When an import statement fails to find a module in the module cache, but there is a corresponding umbrella header in a framework, build the module on-the-fly so it can be immediately loaded at the import statement. This is very much proof-of-concept code, with details to be fleshed out over time. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@139558 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticCommonKinds.td | 1 + include/clang/Basic/FileManager.h | 4 + include/clang/Lex/HeaderSearch.h | 8 +- lib/Basic/FileManager.cpp | 4 + lib/Frontend/CompilerInstance.cpp | 78 ++++++++++++++++++- lib/Lex/HeaderSearch.cpp | 40 +++++++++- .../Inputs/Module.framework/Headers/Module.h | 6 ++ test/Modules/on-demand-build.m | 11 +++ 8 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 test/Modules/Inputs/Module.framework/Headers/Module.h create mode 100644 test/Modules/on-demand-build.m diff --git a/include/clang/Basic/DiagnosticCommonKinds.td b/include/clang/Basic/DiagnosticCommonKinds.td index 29bf270d11..25df8c9bd0 100644 --- a/include/clang/Basic/DiagnosticCommonKinds.td +++ b/include/clang/Basic/DiagnosticCommonKinds.td @@ -60,6 +60,7 @@ def err_friends_define_only_namespace_scope : Error< def err_deleted_non_function : Error< "only functions can have deleted definitions">; def err_module_not_found : Error<"module '%0' not found">, DefaultFatal; +def err_module_not_built : Error<"could not build module '%0'">, DefaultFatal; // Sema && Lex def ext_longlong : Extension< diff --git a/include/clang/Basic/FileManager.h b/include/clang/Basic/FileManager.h index 83fb4e0c5a..826859fb50 100644 --- a/include/clang/Basic/FileManager.h +++ b/include/clang/Basic/FileManager.h @@ -187,6 +187,10 @@ public: /// \param openFile if true and the file exists, it will be opened. const FileEntry *getFile(StringRef Filename, bool openFile = false); + /// \brief Forget any information about the given file name, because (for + /// example) something within this process has changed the file in some way. + void forgetFile(StringRef Filename); + /// \brief Returns the current file system options const FileSystemOptions &getFileSystemOptions() { return FileSystemOpts; } diff --git a/include/clang/Lex/HeaderSearch.h b/include/clang/Lex/HeaderSearch.h index f6552a4014..74c1080df8 100644 --- a/include/clang/Lex/HeaderSearch.h +++ b/include/clang/Lex/HeaderSearch.h @@ -319,9 +319,15 @@ public: /// \brief Search in the module cache path for a module with the given /// name. /// + /// \param UmbrellaHeader If non-NULL, and no module was found in the module + /// cache, this routine will search in the framework paths to determine + /// whether a module can be built from an umbrella header. If so, the pointee + /// will be set to the path of the umbrella header. + /// /// \returns A file describing the named module, if available, or NULL to /// indicate that the module could not be found. - const FileEntry *lookupModule(StringRef ModuleName); + const FileEntry *lookupModule(StringRef ModuleName, + std::string *UmbrellaHeader = 0); void IncrementFrameworkLookupCount() { ++NumFrameworkLookups; } diff --git a/lib/Basic/FileManager.cpp b/lib/Basic/FileManager.cpp index 4ac8e4da1d..adc9705405 100644 --- a/lib/Basic/FileManager.cpp +++ b/lib/Basic/FileManager.cpp @@ -380,6 +380,10 @@ const FileEntry *FileManager::getFile(StringRef Filename, bool openFile) { return &UFE; } +void FileManager::forgetFile(StringRef Filename) { + SeenFileEntries.erase(Filename); +} + const FileEntry * FileManager::getVirtualFile(StringRef Filename, off_t Size, time_t ModificationTime) { diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp index 1106e48e91..1f1a2f77bb 100644 --- a/lib/Frontend/CompilerInstance.cpp +++ b/lib/Frontend/CompilerInstance.cpp @@ -21,6 +21,7 @@ #include "clang/Lex/PTHManager.h" #include "clang/Frontend/ChainedDiagnosticClient.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/LogDiagnosticPrinter.h" #include "clang/Frontend/TextDiagnosticPrinter.h" @@ -624,6 +625,64 @@ bool CompilerInstance::ExecuteAction(FrontendAction &Act) { return !getDiagnostics().getClient()->getNumErrors(); } +/// \brief Determine the appropriate source input kind based on language +/// options. +static InputKind getSourceInputKindFromOptions(const LangOptions &LangOpts) { + if (LangOpts.OpenCL) + return IK_OpenCL; + if (LangOpts.CUDA) + return IK_CUDA; + if (LangOpts.ObjC1) + return LangOpts.CPlusPlus? IK_ObjCXX : IK_ObjC; + return LangOpts.CPlusPlus? IK_CXX : IK_C; +} + +/// \brief Compile a module file for the given module name with the given +/// umbrella header, using the options provided by the importing compiler +/// instance. +static void compileModule(CompilerInstance &ImportingInstance, + StringRef ModuleName, + StringRef UmbrellaHeader) { + // Determine the file that we'll be writing to. + llvm::SmallString<128> ModuleFile; + ModuleFile += + ImportingInstance.getInvocation().getHeaderSearchOpts().ModuleCachePath; + llvm::sys::path::append(ModuleFile, ModuleName + ".pcm"); + + // Construct a compiler invocation for creating this module. + llvm::IntrusiveRefCntPtr Invocation + (new CompilerInvocation(ImportingInstance.getInvocation())); + FrontendOptions &FrontendOpts = Invocation->getFrontendOpts(); + FrontendOpts.OutputFile = ModuleFile.str(); + FrontendOpts.DisableFree = false; + FrontendOpts.Inputs.clear(); + FrontendOpts.Inputs.push_back( + std::make_pair(getSourceInputKindFromOptions(Invocation->getLangOpts()), + UmbrellaHeader)); + // FIXME: Strip away all of the compilation options that won't be transferred + // down to the module. This presumably includes -D flags, optimization + // settings, etc. + + // Construct a compiler instance that will be used to actually create the + // module. + CompilerInstance Instance; + Instance.setInvocation(&*Invocation); + // Instance.setDiagnostics(&ImportingInstance.getDiagnostics()); + // FIXME: Need to route diagnostics over to the same diagnostic client! + Instance.createDiagnostics(0, 0, 0); + + // Construct a module-generating action. + GeneratePCHAction CreateModuleAction(true); + + // Execute the action to actually build the module in-place. + // FIXME: Need to synchronize when multiple processes do this. + Instance.ExecuteAction(CreateModuleAction); + + // Tell the importing instance's file manager to forget about the module + // file, since we've just created it. + ImportingInstance.getFileManager().forgetFile(ModuleFile); +} + ModuleKey CompilerInstance::loadModule(SourceLocation ImportLoc, IdentifierInfo &ModuleName, SourceLocation ModuleNameLoc) { @@ -636,10 +695,25 @@ ModuleKey CompilerInstance::loadModule(SourceLocation ImportLoc, CurFile = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); // Search for a module with the given name. + std::string UmbrellaHeader; const FileEntry *ModuleFile - = PP->getHeaderSearchInfo().lookupModule(ModuleName.getName()); + = PP->getHeaderSearchInfo().lookupModule(ModuleName.getName(), + &UmbrellaHeader); + + bool BuildingModule = false; + if (!ModuleFile && !UmbrellaHeader.empty()) { + // We didn't find the module, but there is an umbrella header that + // can be used to create the module file. Create a separate compilation + // module to do so. + BuildingModule = true; + compileModule(*this, ModuleName.getName(), UmbrellaHeader); + ModuleFile = PP->getHeaderSearchInfo().lookupModule(ModuleName.getName()); + } + if (!ModuleFile) { - getDiagnostics().Report(ModuleNameLoc, diag::err_module_not_found) + getDiagnostics().Report(ModuleNameLoc, + BuildingModule? diag::err_module_not_built + : diag::err_module_not_found) << ModuleName.getName() << SourceRange(ImportLoc, ModuleNameLoc); return 0; diff --git a/lib/Lex/HeaderSearch.cpp b/lib/Lex/HeaderSearch.cpp index 36826756b8..5cf65cbb0d 100644 --- a/lib/Lex/HeaderSearch.cpp +++ b/lib/Lex/HeaderSearch.cpp @@ -98,14 +98,48 @@ const HeaderMap *HeaderSearch::CreateHeaderMap(const FileEntry *FE) { return 0; } -const FileEntry *HeaderSearch::lookupModule(StringRef ModuleName) { +const FileEntry *HeaderSearch::lookupModule(StringRef ModuleName, + std::string *UmbrellaHeader) { // If we don't have a module cache path, we can't do anything. if (ModuleCachePath.empty()) return 0; - + + // Try to find the module path. llvm::SmallString<256> FileName(ModuleCachePath); llvm::sys::path::append(FileName, ModuleName + ".pcm"); - return getFileMgr().getFile(FileName); + if (const FileEntry *ModuleFile = getFileMgr().getFile(FileName)) + return ModuleFile; + + // We didn't find the module. If we're not supposed to look for an + // umbrella header, this is the end of the road. + if (!UmbrellaHeader) + return 0; + + // Look in each of the framework directories for an umbrella header with + // the same name as the module. + // FIXME: We need a way for non-frameworks to provide umbrella headers. + llvm::SmallString<128> UmbrellaHeaderName; + UmbrellaHeaderName = ModuleName; + UmbrellaHeaderName += '/'; + UmbrellaHeaderName += ModuleName; + UmbrellaHeaderName += ".h"; + for (unsigned Idx = 0, N = SearchDirs.size(); Idx != N; ++Idx) { + // Skip non-framework include paths + if (!SearchDirs[Idx].isFramework()) + continue; + + // Look for the umbrella header in this directory. + if (const FileEntry *HeaderFile + = SearchDirs[Idx].LookupFile(UmbrellaHeaderName, *this, 0, 0)) { + *UmbrellaHeader = HeaderFile->getName(); + return 0; + } + } + + // We did not find an umbrella header. Clear out the UmbrellaHeader pointee + // so our caller knows that we failed. + UmbrellaHeader->clear(); + return 0; } //===----------------------------------------------------------------------===// diff --git a/test/Modules/Inputs/Module.framework/Headers/Module.h b/test/Modules/Inputs/Module.framework/Headers/Module.h new file mode 100644 index 0000000000..1d9278b0ca --- /dev/null +++ b/test/Modules/Inputs/Module.framework/Headers/Module.h @@ -0,0 +1,6 @@ +const char *getModuleVersion(void); + +@interface Module ++(const char *)version; +@end + diff --git a/test/Modules/on-demand-build.m b/test/Modules/on-demand-build.m new file mode 100644 index 0000000000..2191499cec --- /dev/null +++ b/test/Modules/on-demand-build.m @@ -0,0 +1,11 @@ +// RUN: mkdir -p %t +// RUN: rm -f %t/Module.pcm +// RUN: %clang_cc1 -fmodule-cache-path %t -F %S/Inputs -verify %s + +__import_module__ Module; +void test_getModuleVersion() { + int version = getModuleVersion(); // expected-warning{{incompatible pointer to integer conversion initializing 'int' with an expression of type 'const char *'}} + int version2 = [Module version]; // expected-warning{{incompatible pointer to integer conversion initializing 'int' with an expression of type 'const char *'}} +} + +