Skip to content

Commit f52ac0f

Browse files
Nuri AmariNuriAmari
Nuri Amari
authored andcommitted
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 49405aa commit f52ac0f

File tree

8 files changed

+335
-18
lines changed

8 files changed

+335
-18
lines changed

include/swift/Frontend/FrontendOptions.h

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

481+
/// Augment modular imports in any emitted ObjC headers with equivalent
482+
/// textual imports
483+
bool EmitObjCHeaderWithTextualImports = false;
484+
481485
private:
482486
static bool canActionEmitDependencies(ActionType);
483487
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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
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 FrontendOptions;
2226
class IRGenOptions;
@@ -36,7 +40,8 @@ class ValueDecl;
3640
bool printAsClangHeader(raw_ostream &out, ModuleDecl *M,
3741
StringRef bridgingHeader,
3842
const FrontendOptions &frontendOpts,
39-
const IRGenOptions &irGenOpts);
43+
const IRGenOptions &irGenOpts,
44+
clang::HeaderSearch &headerSearchInfo);
4045
}
4146

4247
#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: 15 additions & 10 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,17 +181,17 @@ 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-
const FrontendOptions &frontendOpts,
185-
const IRGenOptions &irGenOpts) {
184+
static bool printAsClangHeaderIfNeeded(
185+
StringRef outputPath, ModuleDecl *M, StringRef bridgingHeader,
186+
const FrontendOptions &frontendOpts, const IRGenOptions &irGenOpts,
187+
clang::HeaderSearch &clangHeaderSearchInfo) {
186188
if (outputPath.empty())
187189
return false;
188-
return withOutputFile(M->getDiags(), outputPath,
189-
[&](raw_ostream &out) -> bool {
190-
return printAsClangHeader(out, M, bridgingHeader,
191-
frontendOpts, irGenOpts);
192-
});
190+
return withOutputFile(
191+
M->getDiags(), outputPath, [&](raw_ostream &out) -> bool {
192+
return printAsClangHeader(out, M, bridgingHeader, frontendOpts,
193+
irGenOpts, clangHeaderSearchInfo);
194+
});
193195
}
194196

195197
/// Prints the stable module interface for \p M to \p outputPath.
@@ -937,7 +939,10 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
937939
hadAnyError |= printAsClangHeaderIfNeeded(
938940
Invocation.getClangHeaderOutputPathForAtMostOnePrimary(),
939941
Instance.getMainModule(), BridgingHeaderPathForPrint, opts,
940-
Invocation.getIRGenOptions());
942+
Invocation.getIRGenOptions(),
943+
Context.getClangModuleLoader()
944+
->getClangPreprocessor()
945+
.getHeaderSearchInfo());
941946
}
942947

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

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 212 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
#include "swift/ClangImporter/ClangImporter.h"
2424
#include "swift/Frontend/FrontendOptions.h"
2525

26+
#include "clang/Basic/FileManager.h"
2627
#include "clang/Basic/Module.h"
28+
#include "clang/Lex/HeaderSearch.h"
2729

30+
#include "llvm/Support/FormatVariadic.h"
2831
#include "llvm/Support/raw_ostream.h"
2932

3033
using namespace swift;
@@ -386,9 +389,141 @@ static int compareImportModulesByName(const ImportModuleTy *left,
386389
return 1;
387390
}
388391

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

551+
clang::FileSystemOptions fileSystemOptions;
552+
clang::FileManager fileManager{fileSystemOptions};
553+
554+
llvm::SmallSet<llvm::SmallString<128>, 10> requiredTextualIncludes;
555+
llvm::SmallSet<const clang::Module *, 10> visitedModules;
556+
llvm::SmallSet<llvm::SmallString<128>, 10> includeDirs;
557+
558+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
559+
llvm::ErrorOr<std::string> cwd = fileSystem.getCurrentWorkingDirectory();
560+
561+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
562+
assert(cwd && "Access to current working directory required");
563+
564+
for (auto searchDir = clangHeaderSearchInfo.search_dir_begin();
565+
searchDir != clangHeaderSearchInfo.search_dir_end(); ++searchDir) {
566+
includeDirs.insert(makePathAbsolute(searchDir->getName(), cwd.get()));
567+
}
568+
569+
const clang::Module *foundationModule = clangHeaderSearchInfo.lookupModule(
570+
"Foundation", clang::SourceLocation(), false, false);
571+
const clang::Module *darwinModule = clangHeaderSearchInfo.lookupModule(
572+
"Darwin", clang::SourceLocation(), false, false);
573+
574+
std::function<void(const clang::Module *)>
575+
collectTransitiveSubmoduleClosure;
576+
collectTransitiveSubmoduleClosure = [&](const clang::Module *module) {
577+
if (!module)
578+
return;
579+
580+
visitedModules.insert(module);
581+
for (auto submodule : module->submodules()) {
582+
collectTransitiveSubmoduleClosure(submodule);
583+
}
584+
};
585+
586+
collectTransitiveSubmoduleClosure(foundationModule);
587+
collectTransitiveSubmoduleClosure(darwinModule);
588+
}
589+
416590
// Track printed names to handle overlay modules.
417591
llvm::SmallPtrSet<Identifier, 8> seenImports;
418592
bool includeUnderlying = false;
@@ -425,18 +599,46 @@ static void writeImports(raw_ostream &out,
425599
includeUnderlying = true;
426600
continue;
427601
}
428-
if (seenImports.insert(Name).second)
429-
out << importDirective << ' ' << Name.str() << ";\n";
602+
if (seenImports.insert(Name).second) {
603+
out << "@import " << Name.str() << ";\n";
604+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
605+
if (const clang::Module *underlyingClangModule =
606+
swiftModule->findUnderlyingClangModule()) {
607+
collectClangModuleHeaderIncludes(
608+
underlyingClangModule, fileManager, requiredTextualIncludes,
609+
visitedModules, includeDirs, cwd.get());
610+
} else if ((underlyingClangModule =
611+
clangHeaderSearchInfo.lookupModule(
612+
Name.str(), clang::SourceLocation(), true,
613+
true))) {
614+
collectClangModuleHeaderIncludes(
615+
underlyingClangModule, fileManager, requiredTextualIncludes,
616+
visitedModules, includeDirs, cwd.get());
617+
}
618+
}
619+
}
430620
} else {
431621
const auto *clangModule = import.get<const clang::Module *>();
432622
assert(clangModule->isSubModule() &&
433623
"top-level modules should use a normal swift::ModuleDecl");
434624
out << importDirective << ' ';
435625
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
436626
out << ";\n";
627+
628+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
629+
collectClangModuleHeaderIncludes(
630+
clangModule, fileManager, requiredTextualIncludes, visitedModules,
631+
includeDirs, cwd.get());
632+
}
437633
}
438634
}
439635

636+
if (frontendOpts.EmitObjCHeaderWithTextualImports) {
637+
out << "#else\n";
638+
for (auto header : requiredTextualIncludes) {
639+
out << "#import <" << header << ">\n";
640+
}
641+
}
440642
out << "#endif\n\n";
441643

442644
if (includeUnderlying) {
@@ -490,7 +692,8 @@ static std::string computeMacroGuard(const ModuleDecl *M) {
490692
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
491693
StringRef bridgingHeader,
492694
const FrontendOptions &frontendOpts,
493-
const IRGenOptions &irGenOpts) {
695+
const IRGenOptions &irGenOpts,
696+
clang::HeaderSearch &clangHeaderSearchInfo) {
494697
llvm::PrettyStackTraceString trace("While generating Clang header");
495698

496699
SwiftToClangInteropContext interopContext(*M, irGenOpts);
@@ -500,8 +703,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
500703
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
501704
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
502705
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
503-
emitObjCConditional(os,
504-
[&] { writeImports(os, imports, *M, bridgingHeader); });
706+
emitObjCConditional(os, [&] {
707+
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
708+
clangHeaderSearchInfo);
709+
});
505710
writePostImportPrologue(os, *M);
506711
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
507712
emitCxxConditional(os, [&] {
@@ -530,8 +735,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
530735
moduleContents, *M, interopContext,
531736
/*requiresExposedAttribute=*/requiresExplicitExpose);
532737
// FIXME: In ObjC++ mode, we do not need to reimport duplicate modules.
533-
writeImports(os, deps.imports, *M, bridgingHeader, /*useCxxImport=*/true);
534-
738+
writeImports(os, deps.imports, *M, bridgingHeader, frontendOpts,
739+
clangHeaderSearchInfo, /*useCxxImport=*/true);
535740
// Embed the standard library directly.
536741
if (defaultDependencyBehavior && deps.dependsOnStandardLibrary) {
537742
assert(!M->isStdlibModule());

0 commit comments

Comments
 (0)