Index: include/clang/Basic/Module.h =================================================================== --- include/clang/Basic/Module.h +++ include/clang/Basic/Module.h @@ -200,6 +200,16 @@ /// built. unsigned ConfigMacrosExhaustive : 1; + /// \brief Whether this module uses the 'requires excluded' hack to mark its + /// contents as 'textual'. + /// + /// On older Darwin SDK versions, 'requires excluded' is used to mark the + /// contents of the Darwin.C.excluded (assert.h) and Tcl.Private modules as + /// non-modular headers. For backwards compatibility, we continue to support + /// this idiom for just these modules, and map the headers to 'textual' to + /// match the original intent. + unsigned UsesRequiresExcludedHack : 1; + /// \brief Describes the visibility of the various names within a /// particular module. enum NameVisibilityKind { Index: lib/Basic/Module.cpp =================================================================== --- lib/Basic/Module.cpp +++ lib/Basic/Module.cpp @@ -32,7 +32,8 @@ IsFramework(IsFramework), IsExplicit(IsExplicit), IsSystem(false), IsExternC(false), IsInferred(false), InferSubmodules(false), InferExplicitSubmodules(false), InferExportWildcard(false), - ConfigMacrosExhaustive(false), NameVisibility(Hidden) { + ConfigMacrosExhaustive(false), UsesRequiresExcludedHack(false), + NameVisibility(Hidden) { if (Parent) { if (!Parent->isAvailable()) IsAvailable = false; Index: lib/Lex/ModuleMap.cpp =================================================================== --- lib/Lex/ModuleMap.cpp +++ lib/Lex/ModuleMap.cpp @@ -1570,6 +1570,41 @@ : File->getDir(), ExternLoc); } +/// Whether to add the requirement \p Feature to the module \p M. +/// +/// This preserves backwards compatibility for two hacks in the Darwin system +/// module map files: +/// +/// 1. The use of 'requires excluded' to make headers non-modular, which +/// should really be mapped to 'textual' now that we have this feature. We +/// drop the 'excluded' requirement, and set \p IsRequiresExcludedHack to +/// true. Later, this bit will be used to map all the headers inside this +/// module to 'textual'. +/// +/// This affects Darwin.C.excluded (for assert.h) and Tcl.Private. +/// +/// 2. Removes a bogus cplusplus requirement from IOKit.avc. This requirement +/// was never correct and causes issues now that we check it, so drop it. +static bool shouldAddRequirement(Module *M, StringRef Feature, + bool &IsRequiresExcludedHack) { + if (Feature == "excluded" || Feature == "cplusplus") { + std::string FullName = M->getFullModuleName(); + if (FullName == "Darwin.C.excluded" || FullName == "Tcl.Private") { + // We will mark the module contents non-modular. See doc comment for + // Module::UsesRequiresExcludedHack. + IsRequiresExcludedHack = true; + return false; + } else if (FullName == "IOKit.avc") { + // This module was mistakenly marked 'requires cplusplus' in older Darwin + // SDK versions. As a backwards compatibility hack, don't add the + // requirement. + return false; + } + } + + return true; +} + /// \brief Parse a requires declaration. /// /// requires-declaration: @@ -1605,9 +1640,16 @@ std::string Feature = Tok.getString(); consumeToken(); - // Add this feature. - ActiveModule->addRequirement(Feature, RequiredState, - Map.LangOpts, *Map.Target); + bool IsRequiresExcludedHack = false; + bool ShouldAddRequirement = + shouldAddRequirement(ActiveModule, Feature, IsRequiresExcludedHack); + ActiveModule->UsesRequiresExcludedHack |= IsRequiresExcludedHack; + + if (ShouldAddRequirement) { + // Add this feature. + ActiveModule->addRequirement(Feature, RequiredState, Map.LangOpts, + *Map.Target); + } if (!Tok.is(MMToken::Comma)) break; @@ -1657,9 +1699,16 @@ consumeToken(); } } + if (LeadingToken == MMToken::TextualKeyword) Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader); + if (ActiveModule->UsesRequiresExcludedHack) { + // Mark this header 'textual' (see doc comment for + // Module::UsesRequiresExcludedHack). + Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader); + } + if (LeadingToken != MMToken::HeaderKeyword) { if (!Tok.is(MMToken::HeaderKeyword)) { Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) @@ -1838,14 +1887,33 @@ HadError = true; return; } - + + if (ActiveModule->UsesRequiresExcludedHack) { + // Mark this header 'textual' (see doc comment for + // Module::UsesRequiresExcludedHack). Although iterating over the directory + // is relatively expensive, in practice this only applies to the uncommonly + // used Tcl module on Darwin platforms. + std::error_code EC; + for (llvm::sys::fs::recursive_directory_iterator I(Dir->getName(), EC), E; + I != E && !EC; I.increment(EC)) { + if (const FileEntry *FE = SourceMgr.getFileManager().getFile(I->path())) { + // FIXME: Taking the name from the FileEntry is unstable and can give + // different results depending on how we've previously named that file + // in this build. + Module::Header Header = {FE->getName(), FE}; + Map.addHeader(ActiveModule, Header, ModuleMap::TextualHeader); + } + } + return; + } + if (Module *OwningModule = Map.UmbrellaDirs[Dir]) { Diags.Report(UmbrellaLoc, diag::err_mmap_umbrella_clash) << OwningModule->getFullModuleName(); HadError = true; return; - } - + } + // Record this umbrella directory. Map.setUmbrellaDir(ActiveModule, Dir, DirName); } Index: test/Modules/Inputs/System/usr/include/assert.h =================================================================== --- /dev/null +++ test/Modules/Inputs/System/usr/include/assert.h @@ -0,0 +1,2 @@ +// assert.h +#define DARWIN_C_EXCLUDED 1 Index: test/Modules/Inputs/System/usr/include/module.map =================================================================== --- test/Modules/Inputs/System/usr/include/module.map +++ test/Modules/Inputs/System/usr/include/module.map @@ -30,3 +30,25 @@ header "uses_other_constants.h" export * } + +module Darwin { + module C { + module excluded { + requires excluded + header "assert.h" + } + } +} + +module Tcl { + module Private { + requires excluded + umbrella "" + } +} + +module IOKit { + module avc { + requires cplusplus + } +} Index: test/Modules/Inputs/System/usr/include/tcl-private/header.h =================================================================== --- /dev/null +++ test/Modules/Inputs/System/usr/include/tcl-private/header.h @@ -0,0 +1,2 @@ +// tcl-private/header.h +#define TCL_PRIVATE 1 Index: test/Modules/darwin_specific_modulemap_hacks.m =================================================================== --- /dev/null +++ test/Modules/darwin_specific_modulemap_hacks.m @@ -0,0 +1,22 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t -fimplicit-module-maps -isystem %S/Inputs/System/usr/include -triple x86_64-apple-darwin10 %s -verify -fsyntax-only +// expected-no-diagnostics + +@import Darwin.C.excluded; // no error, header is implicitly 'textual' +@import Tcl.Private; // no error, header is implicitly 'textual' +@import IOKit.avc; // no error, cplusplus requirement removed + +#if defined(DARWIN_C_EXCLUDED) +#error assert.h should be textual +#elif defined(TCL_PRIVATE) +#error tcl-private/header.h should be textual +#endif + +#import +#import + +#if !defined(DARWIN_C_EXCLUDED) +#error assert.h missing +#elif !defined(TCL_PRIVATE) +#error tcl-private/header.h missing +#endif