]> granicus.if.org Git - clang/commitdiff
[Module map] Introduce a private module re-export directive.
authorDouglas Gregor <dgregor@apple.com>
Thu, 14 Sep 2017 23:38:44 +0000 (23:38 +0000)
committerDouglas Gregor <dgregor@apple.com>
Thu, 14 Sep 2017 23:38:44 +0000 (23:38 +0000)
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.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@313316 91177308-0d34-0410-b5e6-96231b3b80d8

docs/Modules.rst
include/clang/Basic/DiagnosticLexKinds.td
include/clang/Basic/Module.h
include/clang/Serialization/ASTBitCodes.h
lib/Basic/Module.cpp
lib/Lex/ModuleMap.cpp
lib/Serialization/ASTReader.cpp
lib/Serialization/ASTWriter.cpp
test/Modules/Inputs/export_as_test.modulemap [new file with mode: 0644]
test/Modules/export_as_test.c [new file with mode: 0644]

index 96502792613c5b5cba8a6a7612360522db408bdc..6dde22c237e7329dca2456bfd9f1bedb623e653f 100644 (file)
@@ -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
index 9f35ed57620bfbadea418dc739598740f60b93f6..ec95dca6ac9d071371d0509403c9ad769e3d2fbc 100644 (file)
@@ -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<PrivateModule>;
+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 "
index edc2f8e72329974303699dcb41045fd9aae01e08..0b2a665f05ec1568832fad41d5654ba5d5a6b886 100644 (file)
@@ -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.
index ab4b82e5d3fec56fa63378f9144eb018b4689886..5ecba87aa37dd44ef3e19c4c021c1a8677d8b57c 100644 (file)
@@ -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.
index 1d96afd476ef4a7c2d17b9024755d94b39655a45..621b1b23d70bd1eb14f10dd61725fdc30f6848d8 100644 (file)
@@ -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
index b01080e55a533f8e8bd444d5bf94eedf92561db1..fc7fe138c5df35c14a2ee2f9351e8554cfd829c7 100644 (file)
@@ -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:
index 3dba29979050483789a43e2b3df70ef155821078..46823030d3080fcd9bec0bad47cc32941356bba5 100644 (file)
@@ -5123,7 +5123,7 @@ ASTReader::ReadSubmoduleBlock(ModuleFile &F, unsigned ClientLoadCapabilities) {
       break;
     }
 
-    case SUBMODULE_INITIALIZERS:
+    case SUBMODULE_INITIALIZERS: {
       if (!ContextObj)
         break;
       SmallVector<uint32_t, 16> 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;
+    }
   }
 }
 
index 8f4a908f540760510ff4986a78023c6875a08bd4..063d6f46e34cadddf3220d2c4b1230beda36e7a1 100644 (file)
@@ -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<BitCodeAbbrev>();
+  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/test/Modules/Inputs/export_as_test.modulemap b/test/Modules/Inputs/export_as_test.modulemap
new file mode 100644 (file)
index 0000000..4aaec41
--- /dev/null
@@ -0,0 +1,9 @@
+module PrivateFoo {
+  export_as Foo
+  export_as Bar
+  export_as Bar
+
+  module Sub {
+    export_as Wibble
+  }
+}
diff --git a/test/Modules/export_as_test.c b/test/Modules/export_as_test.c
new file mode 100644 (file)
index 0000000..a73d6bf
--- /dev/null
@@ -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
+
+