Skip to content

Commit 0047ecf

Browse files
author
Nuri Amari
committed
Add support for textual includes 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 translate each modular import included in such a header with a set of equivalent textual imports.
1 parent 15e17b2 commit 0047ecf

File tree

7 files changed

+160
-9
lines changed

7 files changed

+160
-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 textual
470+
/// 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: 115 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,90 @@ 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
416+
SmallString<32> newHeaderName =
417+
*(++llvm::sys::path::rbegin(clangModule->PresumedModuleMapFile));
418+
llvm::sys::path::append(newHeaderName, header);
419+
fullHeaderName = newHeaderName;
420+
}
421+
requiredTextualIncludes.insert(fullHeaderName);
422+
};
423+
424+
for (clang::Module::HeaderKind headerKind :
425+
{clang::Module::HK_Normal, clang::Module::HK_Private}) {
426+
for (const clang::Module::Header &header :
427+
clangModule->Headers[headerKind]) {
428+
addHeader(clangModule, header.PathRelativeToRootModuleDirectory);
429+
}
430+
}
431+
432+
if (clang::Module::Header umbrellaHeader = clangModule->getUmbrellaHeader()) {
433+
addHeader(clangModule, umbrellaHeader.PathRelativeToRootModuleDirectory);
434+
} else if (clang::Module::DirectoryName umbrellaDir =
435+
clangModule->getUmbrellaDir()) {
436+
SmallString<128> nativeUmbrellaDirPath;
437+
std::error_code errorCode;
438+
llvm::sys::path::native(umbrellaDir.Entry->getName(),
439+
nativeUmbrellaDirPath);
440+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
441+
for (llvm::vfs::recursive_directory_iterator
442+
dir(fileSystem, nativeUmbrellaDirPath, errorCode),
443+
end;
444+
dir != end && !errorCode; dir.increment(errorCode)) {
445+
446+
if (llvm::StringSwitch<bool>(llvm::sys::path::extension(dir->path()))
447+
.Cases(".h", ".H", ".hh", ".hpp", true)
448+
.Default(false)) {
449+
450+
// Computer path to the header relative to the root of the module
451+
// (location of the module map) First compute the relative path from
452+
// umbrella directory to header file
453+
SmallVector<StringRef> pathComponents;
454+
auto pathIt = llvm::sys::path::rbegin(dir->path());
455+
456+
for (int i = 0; i != dir.level() + 1; ++i, ++pathIt)
457+
pathComponents.push_back(*pathIt);
458+
// Then append this to the path from module root to umbrella dir
459+
SmallString<128> relativeHeaderPath;
460+
if (umbrellaDir.PathRelativeToRootModuleDirectory != ".")
461+
relativeHeaderPath += umbrellaDir.PathRelativeToRootModuleDirectory;
462+
463+
for (auto it = pathComponents.rbegin(), end = pathComponents.rend();
464+
it != end; ++it) {
465+
llvm::sys::path::append(relativeHeaderPath, *it);
466+
}
467+
addHeader(clangModule, relativeHeaderPath);
468+
}
469+
}
470+
}
471+
472+
for (auto submodule : clangModule->submodules()) {
473+
collectClangModuleHeaderIncludes(submodule, fileManager,
474+
requiredTextualIncludes);
475+
}
476+
}
477+
396478
static void writeImports(raw_ostream &out,
397479
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
398-
ModuleDecl &M, StringRef bridgingHeader) {
480+
ModuleDecl &M, StringRef bridgingHeader,
481+
const FrontendOptions &frontendOpts) {
399482
out << "#if __has_feature(modules)\n";
400483

401484
out << "#if __has_warning(\"-Watimport-in-framework-header\")\n"
@@ -417,6 +500,11 @@ static void writeImports(raw_ostream &out,
417500
return import == importer->getImportedHeaderModule();
418501
};
419502

503+
clang::FileSystemOptions fileSystemOptions;
504+
clang::FileManager fileManager{fileSystemOptions};
505+
506+
llvm::SmallSet<llvm::SmallString<32>, 10> requiredTextualIncludes;
507+
420508
// Track printed names to handle overlay modules.
421509
llvm::SmallPtrSet<Identifier, 8> seenImports;
422510
bool includeUnderlying = false;
@@ -427,18 +515,39 @@ static void writeImports(raw_ostream &out,
427515
includeUnderlying = true;
428516
continue;
429517
}
430-
if (seenImports.insert(Name).second)
518+
if (seenImports.insert(Name).second) {
431519
out << "@import " << Name.str() << ";\n";
520+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
521+
if (const clang::Module *underlyingClangModule =
522+
swiftModule->findUnderlyingClangModule()) {
523+
collectClangModuleHeaderIncludes(underlyingClangModule, fileManager,
524+
requiredTextualIncludes);
525+
}
526+
}
527+
}
432528
} else {
433529
const auto *clangModule = import.get<const clang::Module *>();
434530
assert(clangModule->isSubModule() &&
435531
"top-level modules should use a normal swift::ModuleDecl");
436532
out << "@import ";
437533
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
438534
out << ";\n";
535+
536+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
537+
collectClangModuleHeaderIncludes(clangModule, fileManager,
538+
requiredTextualIncludes);
539+
}
439540
}
440541
}
441542

543+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
544+
out << "#else\n";
545+
for (auto header : requiredTextualIncludes) {
546+
out << "#if __has_include(<" << header << ">)\n";
547+
out << "#import <" << header << ">\n";
548+
out << "#endif\n";
549+
}
550+
}
442551
out << "#endif\n\n";
443552

444553
if (includeUnderlying) {
@@ -504,7 +613,8 @@ getModuleContentsCxxString(ModuleDecl &M,
504613
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
505614
StringRef bridgingHeader,
506615
bool ExposePublicDeclsInClangHeader,
507-
const IRGenOptions &irGenOpts) {
616+
const IRGenOptions &irGenOpts,
617+
const FrontendOptions &frontendOpts) {
508618
llvm::PrettyStackTraceString trace("While generating Clang header");
509619

510620
SwiftToClangInteropContext interopContext(*M, irGenOpts);
@@ -514,8 +624,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
514624
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
515625
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
516626
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
517-
emitObjCConditional(os,
518-
[&] { writeImports(os, imports, *M, bridgingHeader); });
627+
emitObjCConditional(
628+
os, [&] { writeImports(os, imports, *M, bridgingHeader, frontendOpts); });
519629
writePostImportPrologue(os, *M);
520630
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
521631
emitCxxConditional(os, [&] {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t) -typecheck -emit-objc-header-path %t/textual-imports.h -emit-objc-header-textually %s
3+
// RUN: %FileCheck %s < %t/textual-imports.h
4+
// RUN: %check-in-clang %t/textual-imports.h
5+
6+
import Foundation
7+
8+
public class HelloWorld: NSObject {
9+
@objc public func sayHello() {
10+
print("Hello, World!")
11+
}
12+
}
13+
14+
// CHECK: #if __has_feature(modules)
15+
// CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header")
16+
// CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header"
17+
// CHECK-NEXT: #endif
18+
// CHECK-NEXT: @import ObjectiveC;
19+
// CHECK-NEXT: #else
20+
// CHECK-NEXT: #if __has_include(<objc/objc.h>)
21+
// CHECK-NEXT: #import <objc/objc.h>
22+
// CHECK-NEXT: #endif
23+
// CHECK-NEXT: #if __has_include(<objc/NSObject.h>)
24+
// CHECK-NEXT: #import <objc/NSObject.h>
25+
// CHECK-NEXT: #endif
26+
// CHECK-NEXT: #endif

0 commit comments

Comments
 (0)