From f0b11de279e7ef31c36b7baa71f9b1b172591dab Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Thu, 14 Sep 2017 23:38:44 +0000 Subject: [PATCH] [Module map] Introduce a private module re-export directive. Introduce a new "export_as" directive for top-level modules, which indicates that the current module is a "private" module whose symbols will eventually be exported through the named "public" module. This is in support of a common pattern in the Darwin ecosystem where a single public framework is constructed of several private frameworks, with (currently) header duplication and some support from the linker. Addresses rdar://problem/34438420. llvm-svn: 313316 --- clang/docs/Modules.rst | 29 +++++++++++++- clang/include/clang/Basic/DiagnosticLexKinds.td | 7 ++++ clang/include/clang/Basic/Module.h | 4 ++ clang/include/clang/Serialization/ASTBitCodes.h | 3 ++ clang/lib/Basic/Module.cpp | 5 +++ clang/lib/Lex/ModuleMap.cpp | 44 ++++++++++++++++++++++ clang/lib/Serialization/ASTReader.cpp | 7 +++- clang/lib/Serialization/ASTWriter.cpp | 13 +++++++ clang/test/Modules/Inputs/export_as_test.modulemap | 9 +++++ clang/test/Modules/export_as_test.c | 9 +++++ 10 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 clang/test/Modules/Inputs/export_as_test.modulemap create mode 100644 clang/test/Modules/export_as_test.c diff --git a/clang/docs/Modules.rst b/clang/docs/Modules.rst index 9650279..6dde22c 100644 --- a/clang/docs/Modules.rst +++ b/clang/docs/Modules.rst @@ -323,11 +323,12 @@ Module map files use a simplified form of the C99 lexer, with the same rules for .. parsed-literal:: - ``config_macros`` ``export`` ``private`` + ``config_macros`` ``export_as`` ``private`` ``conflict`` ``framework`` ``requires`` ``exclude`` ``header`` ``textual`` ``explicit`` ``link`` ``umbrella`` ``extern`` ``module`` ``use`` + ``export`` Module map file --------------- @@ -387,6 +388,7 @@ Modules can have a number of different kinds of members, each of which is descri *umbrella-dir-declaration* *submodule-declaration* *export-declaration* + *export-as-declaration* *use-declaration* *link-declaration* *config-macros-declaration* @@ -666,6 +668,31 @@ Note that, if ``Derived.h`` includes ``Base.h``, one can simply use a wildcard e compatibility for programs that rely on transitive inclusion (i.e., all of them). +Re-export Declaration +~~~~~~~~~~~~~~~~~~ +An *export-as-declaration* specifies that the current module is a private +module whose interface will be re-exported by the named public module. + +.. parsed-literal:: + + *export-as-declaration*: + ``export_as`` *identifier* + +The *export-as-declaration* names the public module that the current +(private) module will be re-exported through. Only top-level modules +can be re-exported, and any given module may only be re-exported +through a single public module. + +**Example:** In the following example, the (private) module +``MyFrameworkCore`` will be re-exported via the public module +``MyFramework``: + +.. parsed-literal:: + + module MyFrameworkCore { + export_as MyFramework + } + Use declaration ~~~~~~~~~~~~~~~ A *use-declaration* specifies another module that the current top-level module diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 9f35ed5..ec95dca 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -674,6 +674,13 @@ def err_mmap_invalid_header_attribute_value : Error< "expected integer literal as value for header attribute '%0'">; def err_mmap_expected_header_attribute : Error< "expected a header attribute name ('size' or 'mtime')">; +def err_mmap_conflicting_export_as : Error< + "conflicting re-export of module '%0' as '%1' or '%2'">; +def warn_mmap_redundant_export_as : Warning< + "module '%0' already re-exported as '%1'">, + InGroup; +def err_mmap_submodule_export_as : Error< + "only top-level modules can be re-exported as public">; def warn_auto_module_import : Warning< "treating #%select{include|import|include_next|__include_macros}0 as an " diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index edc2f8e..0b2a665 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -99,6 +99,10 @@ public: /// \brief The name of the umbrella entry, as written in the module map. std::string UmbrellaAsWritten; + + /// \brief The module through which entities defined in this module will + /// eventually be exposed, for use in "private" modules. + std::string ExportAsModule; private: /// \brief The submodules of this module, indexed by name. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index ab4b82e..5ecba87 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -716,6 +716,9 @@ namespace clang { /// \brief Specifies some declarations with initializers that must be /// emitted to initialize the module. SUBMODULE_INITIALIZERS = 16, + /// \brief Specifies the name of the module that will eventually + /// re-export the entities in this module. + SUBMODULE_EXPORT_AS = 17, }; /// \brief Record types used within a comments block. diff --git a/clang/lib/Basic/Module.cpp b/clang/lib/Basic/Module.cpp index 1d96afd..621b1b2 100644 --- a/clang/lib/Basic/Module.cpp +++ b/clang/lib/Basic/Module.cpp @@ -440,6 +440,11 @@ void Module::print(raw_ostream &OS, unsigned Indent) const { } } + if (!ExportAsModule.empty()) { + OS.indent(Indent + 2); + OS << "export_as" << ExportAsModule << "\n"; + } + for (submodule_const_iterator MI = submodule_begin(), MIEnd = submodule_end(); MI != MIEnd; ++MI) // Print inferred subframework modules so that we don't need to re-infer diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index b01080e..fc7fe13 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -1201,6 +1201,7 @@ namespace clang { ExcludeKeyword, ExplicitKeyword, ExportKeyword, + ExportAsKeyword, ExternKeyword, FrameworkKeyword, LinkKeyword, @@ -1312,6 +1313,7 @@ namespace clang { SourceLocation LeadingLoc); void parseUmbrellaDirDecl(SourceLocation UmbrellaLoc); void parseExportDecl(); + void parseExportAsDecl(); void parseUseDecl(); void parseLinkDecl(); void parseConfigMacros(); @@ -1363,6 +1365,7 @@ retry: .Case("exclude", MMToken::ExcludeKeyword) .Case("explicit", MMToken::ExplicitKeyword) .Case("export", MMToken::ExportKeyword) + .Case("export_as", MMToken::ExportAsKeyword) .Case("extern", MMToken::ExternKeyword) .Case("framework", MMToken::FrameworkKeyword) .Case("header", MMToken::HeaderKeyword) @@ -1590,6 +1593,7 @@ namespace { /// header-declaration /// submodule-declaration /// export-declaration +/// export-as-declaration /// link-declaration /// /// submodule-declaration: @@ -1824,6 +1828,10 @@ void ModuleMapParser::parseModuleDecl() { parseExportDecl(); break; + case MMToken::ExportAsKeyword: + parseExportAsDecl(); + break; + case MMToken::UseKeyword: parseUseDecl(); break; @@ -2284,6 +2292,41 @@ void ModuleMapParser::parseExportDecl() { ActiveModule->UnresolvedExports.push_back(Unresolved); } +/// \brief Parse a module export_as declaration. +/// +/// export-as-declaration: +/// 'export_as' identifier +void ModuleMapParser::parseExportAsDecl() { + assert(Tok.is(MMToken::ExportAsKeyword)); + consumeToken(); + + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_module_id); + HadError = true; + return; + } + + if (ActiveModule->Parent) { + Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as); + consumeToken(); + return; + } + + if (!ActiveModule->ExportAsModule.empty()) { + if (ActiveModule->ExportAsModule == Tok.getString()) { + Diags.Report(Tok.getLocation(), diag::warn_mmap_redundant_export_as) + << ActiveModule->Name << Tok.getString(); + } else { + Diags.Report(Tok.getLocation(), diag::err_mmap_conflicting_export_as) + << ActiveModule->Name << ActiveModule->ExportAsModule + << Tok.getString(); + } + } + + ActiveModule->ExportAsModule = Tok.getString(); + consumeToken(); +} + /// \brief Parse a module use declaration. /// /// use-declaration: @@ -2689,6 +2732,7 @@ bool ModuleMapParser::parseModuleMapFile() { case MMToken::Exclaim: case MMToken::ExcludeKeyword: case MMToken::ExportKeyword: + case MMToken::ExportAsKeyword: case MMToken::HeaderKeyword: case MMToken::Identifier: case MMToken::LBrace: diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 3dba299..4682303 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -5123,7 +5123,7 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { break; } - case SUBMODULE_INITIALIZERS: + case SUBMODULE_INITIALIZERS: { if (!ContextObj) break; SmallVector Inits; @@ -5132,6 +5132,11 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { ContextObj->addLazyModuleInitializers(CurrentModule, Inits); break; } + + case SUBMODULE_EXPORT_AS: + CurrentModule->ExportAsModule = Blob.str(); + break; + } } } diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index 8f4a908..063d6f4 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -1130,6 +1130,7 @@ void ASTWriter::WriteBlockInfoBlock() { RECORD(SUBMODULE_TEXTUAL_HEADER); RECORD(SUBMODULE_PRIVATE_TEXTUAL_HEADER); RECORD(SUBMODULE_INITIALIZERS); + RECORD(SUBMODULE_EXPORT_AS); // Comments Block. BLOCK(COMMENTS_BLOCK); @@ -2791,6 +2792,12 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) { Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Message unsigned ConflictAbbrev = Stream.EmitAbbrev(std::move(Abbrev)); + Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(SUBMODULE_EXPORT_AS)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Macro name + unsigned ExportAsAbbrev = Stream.EmitAbbrev(std::move(Abbrev)); + + // Write the submodule metadata block. RecordData::value_type Record[] = { getNumberOfModules(WritingModule), @@ -2925,6 +2932,12 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) { if (!Inits.empty()) Stream.EmitRecord(SUBMODULE_INITIALIZERS, Inits); + // Emit the name of the re-exported module, if any. + if (!Mod->ExportAsModule.empty()) { + RecordData::value_type Record[] = {SUBMODULE_EXPORT_AS}; + Stream.EmitRecordWithBlob(ExportAsAbbrev, Record, Mod->ExportAsModule); + } + // Queue up the submodules of this module. for (auto *M : Mod->submodules()) Q.push(M); diff --git a/clang/test/Modules/Inputs/export_as_test.modulemap b/clang/test/Modules/Inputs/export_as_test.modulemap new file mode 100644 index 0000000..4aaec41 --- /dev/null +++ b/clang/test/Modules/Inputs/export_as_test.modulemap @@ -0,0 +1,9 @@ +module PrivateFoo { + export_as Foo + export_as Bar + export_as Bar + + module Sub { + export_as Wibble + } +} diff --git a/clang/test/Modules/export_as_test.c b/clang/test/Modules/export_as_test.c new file mode 100644 index 0000000..a73d6bf --- /dev/null +++ b/clang/test/Modules/export_as_test.c @@ -0,0 +1,9 @@ +// RUN: rm -rf %t +// RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/export_as_test.modulemap %s 2> %t.err +// RUN: FileCheck %s < %t.err + +// CHECK: export_as_test.modulemap:3:13: error: conflicting re-export of module 'PrivateFoo' as 'Foo' or 'Bar' +// CHECK: export_as_test.modulemap:4:13: warning: module 'PrivateFoo' already re-exported as 'Bar' +// CHECK: export_as_test.modulemap:7:15: error: only top-level modules can be re-exported as public + + -- 2.7.4