Skip to content

Commit 78efbab

Browse files
author
Nuri Amari
committed
Add support for textual imports to -emit-objc-header
Currently headers produced with `-emit-objc-header` / `-emit-objc-header-path` produce headers that include modular imports. If the consumer wishes to operate without modules enabled, these headers cannot be used. This patch introduces a new flag (`-emit-objc-header-textually`) that when enabled attempts to argument each modular import included in such a header with a set of equivalent textual imports.
1 parent 15e17b2 commit 78efbab

File tree

7 files changed

+183
-9
lines changed

7 files changed

+183
-9
lines changed

include/swift/Frontend/FrontendOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,10 @@ class FrontendOptions {
466466
/// crashes the compiler.
467467
bool emptyABIDescriptor = false;
468468

469+
/// Augment modular imports in any emitted ObjC headers with equivalent
470+
/// textual imports
471+
bool EmitObjCHeaderWithTextualImports = false;
472+
469473
private:
470474
static bool canActionEmitDependencies(ActionType);
471475
static bool canActionEmitReferenceDependencies(ActionType);

include/swift/Option/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,9 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">,
543543
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
544544
SupplementaryOutput]>,
545545
MetaVarName<"<path>">, HelpText<"Emit an Objective-C header file to <path>">;
546+
def emit_objc_header_textually : Flag<["-"], "emit-objc-header-textually">,
547+
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>,
548+
HelpText<"Augment emitted Objective-C header with textual imports for every included modular import">;
546549

547550
def emit_clang_header_path : Separate<["-"], "emit-clang-header-path">,
548551
Flags<[FrontendOption, NoDriverOption, NoInteractiveOption, ArgumentIsPath,

include/swift/PrintAsClang/PrintAsClang.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace swift {
2121
class IRGenOptions;
2222
class ModuleDecl;
2323
class ValueDecl;
24+
class FrontendOptions;
2425

2526
/// Print the exposed declarations in a module into a Clang header.
2627
///
@@ -35,7 +36,8 @@ class ValueDecl;
3536
bool printAsClangHeader(raw_ostream &out, ModuleDecl *M,
3637
StringRef bridgingHeader,
3738
bool ExposePublicDeclsInClangHeader,
38-
const IRGenOptions &irGenOpts);
39+
const IRGenOptions &irGenOpts,
40+
const FrontendOptions &frontendOpts);
3941
}
4042

4143
#endif

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ bool ArgsToFrontendOptionsConverter::convert(
8585
Opts.FrontendParseableOutput |= Args.hasArg(OPT_frontend_parseable_output);
8686
Opts.ExplicitInterfaceBuild |= Args.hasArg(OPT_explicit_interface_module_build);
8787

88+
Opts.EmitObjCHeaderWithTextualImports |=
89+
Args.hasArg(OPT_emit_objc_header_textually);
90+
8891
// FIXME: Remove this flag
8992
Opts.EnableLibraryEvolution |= Args.hasArg(OPT_enable_resilience);
9093

lib/FrontendTool/FrontendTool.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,15 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
182182
static bool printAsClangHeaderIfNeeded(StringRef outputPath, ModuleDecl *M,
183183
StringRef bridgingHeader,
184184
bool ExposePublicDeclsInClangHeader,
185-
const IRGenOptions &irGenOpts) {
185+
const IRGenOptions &irGenOpts,
186+
const FrontendOptions &frontendOpts) {
186187
if (outputPath.empty())
187188
return false;
188189
return withOutputFile(
189190
M->getDiags(), outputPath, [&](raw_ostream &out) -> bool {
190191
return printAsClangHeader(out, M, bridgingHeader,
191-
ExposePublicDeclsInClangHeader, irGenOpts);
192+
ExposePublicDeclsInClangHeader, irGenOpts,
193+
frontendOpts);
192194
});
193195
}
194196

@@ -937,7 +939,8 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
937939
hadAnyError |= printAsClangHeaderIfNeeded(
938940
Invocation.getClangHeaderOutputPathForAtMostOnePrimary(),
939941
Instance.getMainModule(), BridgingHeaderPathForPrint,
940-
opts.ExposePublicDeclsInClangHeader, Invocation.getIRGenOptions());
942+
opts.ExposePublicDeclsInClangHeader, Invocation.getIRGenOptions(),
943+
opts);
941944
}
942945

943946
// Only want the header if there's been any errors, ie. there's not much

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include "swift/AST/PrettyStackTrace.h"
2121
#include "swift/Basic/Version.h"
2222
#include "swift/ClangImporter/ClangImporter.h"
23+
#include "swift/Frontend/FrontendOptions.h"
2324

25+
#include "clang/Basic/FileManager.h"
2426
#include "clang/Basic/Module.h"
2527

2628
#include "llvm/Support/raw_ostream.h"
@@ -393,9 +395,93 @@ static int compareImportModulesByName(const ImportModuleTy *left,
393395
return 1;
394396
}
395397

398+
// Collect the set of header includes needed to import the given Clang module
399+
// into an ObjectiveC program. Modeled after collectModuleHeaderIncludes in the
400+
// Clang frontend (FrontendAction.cpp)
401+
// Augment requiredTextualIncludes with the set of headers required.
402+
static void collectClangModuleHeaderIncludes(
403+
const clang::Module *clangModule, clang::FileManager &fileManager,
404+
llvm::SmallSet<llvm::SmallString<32>, 10> &requiredTextualIncludes) {
405+
406+
auto addHeader = [&](const clang::Module *module, StringRef header) {
407+
llvm::SmallString<32> fullHeaderName = header;
408+
if (clangModule->IsFramework) {
409+
// Frameworks store their headers in an explicit header directory
410+
// Transform "Headers/Foundation.h" -> "Foundation/Foundation.h"
411+
llvm::sys::path::replace_path_prefix(fullHeaderName, "Headers",
412+
clangModule->Name);
413+
} else {
414+
// Prepend the name of the directory containing the module to the header
415+
// include, unless the directory seems to be on a typical include path
416+
// (ends in include)
417+
SmallString<32> headerPrefix =
418+
*(++llvm::sys::path::rbegin(clangModule->PresumedModuleMapFile));
419+
if (headerPrefix != "include") {
420+
llvm::sys::path::append(headerPrefix, header);
421+
fullHeaderName = headerPrefix;
422+
}
423+
}
424+
requiredTextualIncludes.insert(fullHeaderName);
425+
};
426+
427+
for (clang::Module::HeaderKind headerKind :
428+
{clang::Module::HK_Normal, clang::Module::HK_Private}) {
429+
for (const clang::Module::Header &header :
430+
clangModule->Headers[headerKind]) {
431+
addHeader(clangModule, header.PathRelativeToRootModuleDirectory);
432+
}
433+
}
434+
435+
if (clang::Module::Header umbrellaHeader = clangModule->getUmbrellaHeader()) {
436+
addHeader(clangModule, umbrellaHeader.PathRelativeToRootModuleDirectory);
437+
} else if (clang::Module::DirectoryName umbrellaDir =
438+
clangModule->getUmbrellaDir()) {
439+
SmallString<128> nativeUmbrellaDirPath;
440+
std::error_code errorCode;
441+
llvm::sys::path::native(umbrellaDir.Entry->getName(),
442+
nativeUmbrellaDirPath);
443+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
444+
for (llvm::vfs::recursive_directory_iterator
445+
dir(fileSystem, nativeUmbrellaDirPath, errorCode),
446+
end;
447+
dir != end && !errorCode; dir.increment(errorCode)) {
448+
449+
if (llvm::StringSwitch<bool>(llvm::sys::path::extension(dir->path()))
450+
.Cases(".h", ".H", ".hh", ".hpp", true)
451+
.Default(false)) {
452+
453+
// Computer path to the header relative to the root of the module
454+
// (location of the module map) First compute the relative path from
455+
// umbrella directory to header file
456+
SmallVector<StringRef> pathComponents;
457+
auto pathIt = llvm::sys::path::rbegin(dir->path());
458+
459+
for (int i = 0; i != dir.level() + 1; ++i, ++pathIt)
460+
pathComponents.push_back(*pathIt);
461+
// Then append this to the path from module root to umbrella dir
462+
SmallString<128> relativeHeaderPath;
463+
if (umbrellaDir.PathRelativeToRootModuleDirectory != ".")
464+
relativeHeaderPath += umbrellaDir.PathRelativeToRootModuleDirectory;
465+
466+
for (auto it = pathComponents.rbegin(), end = pathComponents.rend();
467+
it != end; ++it) {
468+
llvm::sys::path::append(relativeHeaderPath, *it);
469+
}
470+
addHeader(clangModule, relativeHeaderPath);
471+
}
472+
}
473+
}
474+
475+
for (auto submodule : clangModule->submodules()) {
476+
collectClangModuleHeaderIncludes(submodule, fileManager,
477+
requiredTextualIncludes);
478+
}
479+
}
480+
396481
static void writeImports(raw_ostream &out,
397482
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
398-
ModuleDecl &M, StringRef bridgingHeader) {
483+
ModuleDecl &M, StringRef bridgingHeader,
484+
const FrontendOptions &frontendOpts) {
399485
out << "#if __has_feature(modules)\n";
400486

401487
out << "#if __has_warning(\"-Watimport-in-framework-header\")\n"
@@ -417,6 +503,11 @@ static void writeImports(raw_ostream &out,
417503
return import == importer->getImportedHeaderModule();
418504
};
419505

506+
clang::FileSystemOptions fileSystemOptions;
507+
clang::FileManager fileManager{fileSystemOptions};
508+
509+
llvm::SmallSet<llvm::SmallString<32>, 10> requiredTextualIncludes;
510+
420511
// Track printed names to handle overlay modules.
421512
llvm::SmallPtrSet<Identifier, 8> seenImports;
422513
bool includeUnderlying = false;
@@ -427,18 +518,39 @@ static void writeImports(raw_ostream &out,
427518
includeUnderlying = true;
428519
continue;
429520
}
430-
if (seenImports.insert(Name).second)
521+
if (seenImports.insert(Name).second) {
431522
out << "@import " << Name.str() << ";\n";
523+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
524+
if (const clang::Module *underlyingClangModule =
525+
swiftModule->findUnderlyingClangModule()) {
526+
collectClangModuleHeaderIncludes(underlyingClangModule, fileManager,
527+
requiredTextualIncludes);
528+
}
529+
}
530+
}
432531
} else {
433532
const auto *clangModule = import.get<const clang::Module *>();
434533
assert(clangModule->isSubModule() &&
435534
"top-level modules should use a normal swift::ModuleDecl");
436535
out << "@import ";
437536
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
438537
out << ";\n";
538+
539+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
540+
collectClangModuleHeaderIncludes(clangModule, fileManager,
541+
requiredTextualIncludes);
542+
}
439543
}
440544
}
441545

546+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
547+
out << "#else\n";
548+
for (auto header : requiredTextualIncludes) {
549+
out << "#if __has_include(<" << header << ">)\n";
550+
out << "#import <" << header << ">\n";
551+
out << "#endif\n";
552+
}
553+
}
442554
out << "#endif\n\n";
443555

444556
if (includeUnderlying) {
@@ -504,7 +616,8 @@ getModuleContentsCxxString(ModuleDecl &M,
504616
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
505617
StringRef bridgingHeader,
506618
bool ExposePublicDeclsInClangHeader,
507-
const IRGenOptions &irGenOpts) {
619+
const IRGenOptions &irGenOpts,
620+
const FrontendOptions &frontendOpts) {
508621
llvm::PrettyStackTraceString trace("While generating Clang header");
509622

510623
SwiftToClangInteropContext interopContext(*M, irGenOpts);
@@ -514,8 +627,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
514627
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
515628
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
516629
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
517-
emitObjCConditional(os,
518-
[&] { writeImports(os, imports, *M, bridgingHeader); });
630+
emitObjCConditional(
631+
os, [&] { writeImports(os, imports, *M, bridgingHeader, frontendOpts); });
519632
writePostImportPrologue(os, *M);
520633
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
521634
emitCxxConditional(os, [&] {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// REQUIRES: objc_interop
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -F %S/Inputs/ -typecheck -emit-objc-header-path %t/textual-imports.h -emit-objc-header-textually %s
5+
// RUN: %FileCheck %s < %t/textual-imports.h
6+
// RUN: %check-in-clang -fno-modules -Qunused-arguments %t/textual-imports.h -include %S/Inputs/Mixed.framework/Headers/Mixed.h
7+
8+
import Foundation
9+
import Mixed
10+
11+
public class HelloWorld: NSObject {
12+
@objc public func sayHello() {
13+
print("Hello, World!")
14+
}
15+
16+
@objc public func getPoint() -> CGPoint {
17+
return CGPoint(x: 1, y: 1)
18+
}
19+
20+
@objc public func getIntAlias() -> CIntAlias {
21+
let result: CInt = 0
22+
return result
23+
}
24+
}
25+
26+
// CHECK: #if __has_feature(modules)
27+
// CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header")
28+
// CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header"
29+
// CHECK-NEXT: #endif
30+
// CHECK-NEXT: @import CoreGraphics;
31+
// CHECK-NEXT: @import Mixed;
32+
// CHECK-NEXT: @import ObjectiveC;
33+
// CHECK-NEXT: #else
34+
// CHECK-NEXT: #if __has_include(<CoreGraphics.h>)
35+
// CHECK-NEXT: #import <CoreGraphics.h>
36+
// CHECK-NEXT: #endif
37+
// CHECK-NEXT: #if __has_include(<Mixed/Mixed.h>)
38+
// CHECK-NEXT: #import <Mixed/Mixed.h>
39+
// CHECK-NEXT: #endif
40+
// CHECK-NEXT: #if __has_include(<objc/objc.h>)
41+
// CHECK-NEXT: #import <objc/objc.h>
42+
// CHECK-NEXT: #endif
43+
// CHECK-NEXT: #if __has_include(<objc/NSObject.h>)
44+
// CHECK-NEXT: #import <objc/NSObject.h>
45+
// CHECK-NEXT: #endif
46+
// CHECK-NEXT: #endif

0 commit comments

Comments
 (0)