Skip to content

Commit 5fbec4c

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 f07c72b commit 5fbec4c

File tree

8 files changed

+335
-12
lines changed

8 files changed

+335
-12
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
@@ -552,6 +552,9 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">,
552552
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
553553
SupplementaryOutput]>,
554554
MetaVarName<"<path>">, HelpText<"Emit an Objective-C header file to <path>">;
555+
def emit_objc_header_textually : Flag<["-"], "emit-objc-header-textually">,
556+
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>,
557+
HelpText<"Augment emitted Objective-C header with textual imports for every included modular import">;
555558

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

include/swift/PrintAsClang/PrintAsClang.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
#include "swift/AST/AttrKind.h"
1818
#include "swift/AST/Identifier.h"
1919

20+
namespace clang {
21+
class HeaderSearch;
22+
}
23+
2024
namespace swift {
2125
class IRGenOptions;
2226
class ModuleDecl;
2327
class ValueDecl;
28+
class FrontendOptions;
2429

2530
/// Print the exposed declarations in a module into a Clang header.
2631
///
@@ -35,7 +40,9 @@ class ValueDecl;
3540
bool printAsClangHeader(raw_ostream &out, ModuleDecl *M,
3641
StringRef bridgingHeader,
3742
bool ExposePublicDeclsInClangHeader,
38-
const IRGenOptions &irGenOpts);
43+
const IRGenOptions &irGenOpts,
44+
const FrontendOptions &frontendOpts,
45+
clang::HeaderSearch &headerSearchInfo);
3946
}
4047

4148
#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: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
#include "swift/Syntax/SyntaxNodes.h"
7373
#include "swift/TBDGen/TBDGen.h"
7474

75+
#include "clang/Lex/Preprocessor.h"
76+
7577
#include "llvm/ADT/Statistic.h"
7678
#include "llvm/ADT/StringMap.h"
7779
#include "llvm/IR/LLVMContext.h"
@@ -179,16 +181,18 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
179181
/// \returns true if there were any errors
180182
///
181183
/// \see swift::printAsClangHeader
182-
static bool printAsClangHeaderIfNeeded(StringRef outputPath, ModuleDecl *M,
183-
StringRef bridgingHeader,
184-
bool ExposePublicDeclsInClangHeader,
185-
const IRGenOptions &irGenOpts) {
184+
static bool printAsClangHeaderIfNeeded(
185+
StringRef outputPath, ModuleDecl *M, StringRef bridgingHeader,
186+
bool ExposePublicDeclsInClangHeader, const IRGenOptions &irGenOpts,
187+
const FrontendOptions &frontendOpts,
188+
clang::HeaderSearch &clangHeaderSearchInfo) {
186189
if (outputPath.empty())
187190
return false;
188191
return withOutputFile(
189192
M->getDiags(), outputPath, [&](raw_ostream &out) -> bool {
190193
return printAsClangHeader(out, M, bridgingHeader,
191-
ExposePublicDeclsInClangHeader, irGenOpts);
194+
ExposePublicDeclsInClangHeader, irGenOpts,
195+
frontendOpts, clangHeaderSearchInfo);
192196
});
193197
}
194198

@@ -937,7 +941,10 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
937941
hadAnyError |= printAsClangHeaderIfNeeded(
938942
Invocation.getClangHeaderOutputPathForAtMostOnePrimary(),
939943
Instance.getMainModule(), BridgingHeaderPathForPrint,
940-
opts.ExposePublicDeclsInClangHeader, Invocation.getIRGenOptions());
944+
opts.ExposePublicDeclsInClangHeader, Invocation.getIRGenOptions(), opts,
945+
Context.getClangModuleLoader()
946+
->getClangPreprocessor()
947+
.getHeaderSearchInfo());
941948
}
942949

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

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 212 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
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"
27+
#include "clang/Lex/HeaderSearch.h"
2528

29+
#include "llvm/Support/FormatVariadic.h"
2630
#include "llvm/Support/raw_ostream.h"
2731

2832
using namespace swift;
@@ -393,9 +397,140 @@ static int compareImportModulesByName(const ImportModuleTy *left,
393397
return 1;
394398
}
395399

400+
static llvm::SmallString<128> makePathAbsolute(const llvm::StringRef path,
401+
const llvm::StringRef cwd) {
402+
if (llvm::sys::path::is_absolute(path))
403+
return llvm::SmallString<128>(path);
404+
405+
llvm::SmallString<128> result = cwd;
406+
llvm::sys::path::append(result,
407+
llvm::sys::path::remove_leading_dotslash(path));
408+
return result;
409+
}
410+
411+
// Collect the set of header includes needed to import the given Clang module
412+
// into an ObjectiveC program. Modeled after collectModuleHeaderIncludes in the
413+
// Clang frontend (FrontendAction.cpp)
414+
// Augment requiredTextualIncludes with the set of headers required.
415+
static void collectClangModuleHeaderIncludes(
416+
const clang::Module *clangModule, clang::FileManager &fileManager,
417+
llvm::SmallSet<llvm::SmallString<128>, 10> &requiredTextualIncludes,
418+
llvm::SmallSet<const clang::Module *, 10> &visitedModules,
419+
const llvm::SmallSet<llvm::SmallString<128>, 10> &includeDirs,
420+
const llvm::StringRef cwd) {
421+
422+
if (!visitedModules.insert(clangModule).second)
423+
return;
424+
425+
auto addHeader = [&](llvm::StringRef headerPath,
426+
llvm::StringRef pathRelativeToRootModuleDir) {
427+
if (!clangModule->Directory)
428+
return;
429+
430+
llvm::SmallString<128> textualInclude = makePathAbsolute(headerPath, cwd);
431+
llvm::SmallString<128> containingSearchDirPath;
432+
433+
// Check if the module map lies either in an include dir or in a direct
434+
// subdirectory of an include dir
435+
llvm::SmallString<128> moduleMapDir =
436+
makePathAbsolute(clangModule->Directory->getName(), cwd);
437+
llvm::SmallString<128> moduleMapDirParent =
438+
llvm::sys::path::parent_path(moduleMapDir);
439+
440+
for (auto &dir : {moduleMapDir, moduleMapDirParent}) {
441+
if (includeDirs.contains(dir)) {
442+
containingSearchDirPath = dir;
443+
break;
444+
}
445+
}
446+
447+
if (!containingSearchDirPath.empty()) {
448+
llvm::SmallString<128> prefixToRemove =
449+
llvm::formatv("{0}/", containingSearchDirPath);
450+
llvm::sys::path::replace_path_prefix(textualInclude, prefixToRemove, "");
451+
} else {
452+
// If we cannot find find the module map on the search path,
453+
// fallback to including the header using the provided path relative
454+
// to the module map
455+
textualInclude = pathRelativeToRootModuleDir;
456+
}
457+
458+
if (clangModule->getTopLevelModule()->IsFramework) {
459+
llvm::SmallString<32> frameworkName =
460+
clangModule->getTopLevelModuleName();
461+
llvm::SmallString<64> oldFrameworkPrefix =
462+
llvm::formatv("{0}.framework/Headers", frameworkName);
463+
llvm::sys::path::replace_path_prefix(textualInclude, oldFrameworkPrefix,
464+
frameworkName);
465+
}
466+
467+
requiredTextualIncludes.insert(textualInclude);
468+
};
469+
470+
if (clang::Module::Header umbrellaHeader = clangModule->getUmbrellaHeader()) {
471+
addHeader(umbrellaHeader.Entry->getName(),
472+
umbrellaHeader.PathRelativeToRootModuleDirectory);
473+
} else if (clang::Module::DirectoryName umbrellaDir =
474+
clangModule->getUmbrellaDir()) {
475+
SmallString<128> nativeUmbrellaDirPath;
476+
std::error_code errorCode;
477+
llvm::sys::path::native(umbrellaDir.Entry->getName(),
478+
nativeUmbrellaDirPath);
479+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
480+
for (llvm::vfs::recursive_directory_iterator
481+
dir(fileSystem, nativeUmbrellaDirPath, errorCode),
482+
end;
483+
dir != end && !errorCode; dir.increment(errorCode)) {
484+
485+
if (llvm::StringSwitch<bool>(llvm::sys::path::extension(dir->path()))
486+
.Cases(".h", ".H", ".hh", ".hpp", true)
487+
.Default(false)) {
488+
489+
// Compute path to the header relative to the root of the module
490+
// (location of the module map) First compute the relative path from
491+
// umbrella directory to header file
492+
SmallVector<StringRef> pathComponents;
493+
auto pathIt = llvm::sys::path::rbegin(dir->path());
494+
495+
for (int i = 0; i != dir.level() + 1; ++i, ++pathIt)
496+
pathComponents.push_back(*pathIt);
497+
// Then append this to the path from module root to umbrella dir
498+
SmallString<128> relativeHeaderPath;
499+
if (umbrellaDir.PathRelativeToRootModuleDirectory != ".")
500+
relativeHeaderPath += umbrellaDir.PathRelativeToRootModuleDirectory;
501+
502+
for (auto it = pathComponents.rbegin(), end = pathComponents.rend();
503+
it != end; ++it) {
504+
llvm::sys::path::append(relativeHeaderPath, *it);
505+
}
506+
507+
addHeader(dir->path(), relativeHeaderPath);
508+
}
509+
}
510+
} else {
511+
for (clang::Module::HeaderKind headerKind :
512+
{clang::Module::HK_Normal, clang::Module::HK_Textual}) {
513+
for (const clang::Module::Header &header :
514+
clangModule->Headers[headerKind]) {
515+
addHeader(header.Entry->getName(),
516+
header.PathRelativeToRootModuleDirectory);
517+
}
518+
}
519+
llvm::SmallVector<clang::Module *> exportedModules;
520+
clangModule->getExportedModules(exportedModules);
521+
for (auto exportedModule : exportedModules) {
522+
collectClangModuleHeaderIncludes(exportedModule, fileManager,
523+
requiredTextualIncludes, visitedModules,
524+
includeDirs, cwd);
525+
}
526+
}
527+
}
528+
396529
static void writeImports(raw_ostream &out,
397530
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
398531
ModuleDecl &M, StringRef bridgingHeader,
532+
const FrontendOptions &frontendOpts,
533+
clang::HeaderSearch &clangHeaderSearchInfo,
399534
bool useCxxImport = false) {
400535
// Note: we can't use has_feature(modules) as it's always enabled in C++20
401536
// mode.
@@ -420,6 +555,45 @@ static void writeImports(raw_ostream &out,
420555
return import == importer->getImportedHeaderModule();
421556
};
422557

558+
clang::FileSystemOptions fileSystemOptions;
559+
clang::FileManager fileManager{fileSystemOptions};
560+
561+
llvm::SmallSet<llvm::SmallString<128>, 10> requiredTextualIncludes;
562+
llvm::SmallSet<const clang::Module *, 10> visitedModules;
563+
llvm::SmallSet<llvm::SmallString<128>, 10> includeDirs;
564+
565+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
566+
llvm::ErrorOr<std::string> cwd = fileSystem.getCurrentWorkingDirectory();
567+
568+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
569+
assert(cwd && "Access to current working directory required");
570+
571+
for (auto searchDir = clangHeaderSearchInfo.search_dir_begin();
572+
searchDir != clangHeaderSearchInfo.search_dir_end(); ++searchDir) {
573+
includeDirs.insert(makePathAbsolute(searchDir->getName(), cwd.get()));
574+
}
575+
576+
const clang::Module *foundationModule = clangHeaderSearchInfo.lookupModule(
577+
"Foundation", clang::SourceLocation(), false, false);
578+
const clang::Module *darwinModule = clangHeaderSearchInfo.lookupModule(
579+
"Darwin", clang::SourceLocation(), false, false);
580+
581+
std::function<void(const clang::Module *)>
582+
collectTransitiveSubmoduleClosure;
583+
collectTransitiveSubmoduleClosure = [&](const clang::Module *module) {
584+
if (!module)
585+
return;
586+
587+
visitedModules.insert(module);
588+
for (auto submodule : module->submodules()) {
589+
collectTransitiveSubmoduleClosure(submodule);
590+
}
591+
};
592+
593+
collectTransitiveSubmoduleClosure(foundationModule);
594+
collectTransitiveSubmoduleClosure(darwinModule);
595+
}
596+
423597
// Track printed names to handle overlay modules.
424598
llvm::SmallPtrSet<Identifier, 8> seenImports;
425599
bool includeUnderlying = false;
@@ -432,18 +606,46 @@ static void writeImports(raw_ostream &out,
432606
includeUnderlying = true;
433607
continue;
434608
}
435-
if (seenImports.insert(Name).second)
609+
if (seenImports.insert(Name).second) {
436610
out << importDirective << ' ' << Name.str() << ";\n";
611+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
612+
if (const clang::Module *underlyingClangModule =
613+
swiftModule->findUnderlyingClangModule()) {
614+
collectClangModuleHeaderIncludes(
615+
underlyingClangModule, fileManager, requiredTextualIncludes,
616+
visitedModules, includeDirs, cwd.get());
617+
} else if ((underlyingClangModule =
618+
clangHeaderSearchInfo.lookupModule(
619+
Name.str(), clang::SourceLocation(), true,
620+
true))) {
621+
collectClangModuleHeaderIncludes(
622+
underlyingClangModule, fileManager, requiredTextualIncludes,
623+
visitedModules, includeDirs, cwd.get());
624+
}
625+
}
626+
}
437627
} else {
438628
const auto *clangModule = import.get<const clang::Module *>();
439629
assert(clangModule->isSubModule() &&
440630
"top-level modules should use a normal swift::ModuleDecl");
441631
out << importDirective << ' ';
442632
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
443633
out << ";\n";
634+
635+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
636+
collectClangModuleHeaderIncludes(
637+
clangModule, fileManager, requiredTextualIncludes, visitedModules,
638+
includeDirs, cwd.get());
639+
}
444640
}
445641
}
446642

643+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
644+
out << "#else\n";
645+
for (auto header : requiredTextualIncludes) {
646+
out << "#import <" << header << ">\n";
647+
}
648+
}
447649
out << "#endif\n\n";
448650

449651
if (includeUnderlying) {
@@ -507,7 +709,9 @@ static std::string getModuleContentsCxxString(
507709
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
508710
StringRef bridgingHeader,
509711
bool ExposePublicDeclsInClangHeader,
510-
const IRGenOptions &irGenOpts) {
712+
const IRGenOptions &irGenOpts,
713+
const FrontendOptions &frontendOpts,
714+
clang::HeaderSearch &clangHeaderSearchInfo) {
511715
llvm::PrettyStackTraceString trace("While generating Clang header");
512716

513717
SwiftToClangInteropContext interopContext(*M, irGenOpts);
@@ -517,8 +721,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
517721
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
518722
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
519723
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
520-
emitObjCConditional(os,
521-
[&] { writeImports(os, imports, *M, bridgingHeader); });
724+
emitObjCConditional(os, [&] {
725+
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
726+
clangHeaderSearchInfo);
727+
});
522728
writePostImportPrologue(os, *M);
523729
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
524730
emitCxxConditional(os, [&] {
@@ -530,7 +736,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
530736
*M, imports, interopContext,
531737
/*requiresExposedAttribute=*/!ExposePublicDeclsInClangHeader);
532738
// FIXME: In ObjC++ mode, we do not need to reimport duplicate modules.
533-
writeImports(os, imports, *M, bridgingHeader, /*useCxxImport=*/true);
739+
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
740+
clangHeaderSearchInfo, /*useCxxImport=*/true);
534741
os << contents;
535742
}
536743
});

0 commit comments

Comments
 (0)