Skip to content

Commit f3a54a7

Browse files
authored
Merge pull request #62386 from NuriAmari/emit-clang-header-nonmodular-includes
Add support for textual imports to -emit-objc-header
2 parents 5b9aef9 + cd4b9a6 commit f3a54a7

14 files changed

+351
-17
lines changed

include/swift/Frontend/FrontendOptions.h

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

488+
/// Augment modular imports in any emitted ObjC headers with equivalent
489+
/// textual imports
490+
bool EmitClangHeaderWithNonModularIncludes = false;
491+
488492
private:
489493
static bool canActionEmitDependencies(ActionType);
490494
static bool canActionEmitReferenceDependencies(ActionType);

include/swift/Option/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,9 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">,
566566
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
567567
SupplementaryOutput]>,
568568
MetaVarName<"<path>">, HelpText<"Emit an Objective-C header file to <path>">;
569+
def emit_clang_header_nonmodular_includes : Flag<["-"], "emit-clang-header-nonmodular-includes">,
570+
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>,
571+
HelpText<"Augment emitted Objective-C header with textual imports for every included modular import">;
569572

570573
def emit_clang_header_path : Separate<["-"], "emit-clang-header-path">,
571574
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.EmitClangHeaderWithNonModularIncludes |=
89+
Args.hasArg(OPT_emit_clang_header_nonmodular_includes);
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
@@ -69,6 +69,8 @@
6969
#include "swift/Subsystems.h"
7070
#include "swift/SymbolGraphGen/SymbolGraphOptions.h"
7171

72+
#include "clang/Lex/Preprocessor.h"
73+
7274
#include "llvm/ADT/Statistic.h"
7375
#include "llvm/ADT/StringMap.h"
7476
#include "llvm/IR/LLVMContext.h"
@@ -164,17 +166,17 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
164166
/// \returns true if there were any errors
165167
///
166168
/// \see swift::printAsClangHeader
167-
static bool printAsClangHeaderIfNeeded(StringRef outputPath, ModuleDecl *M,
168-
StringRef bridgingHeader,
169-
const FrontendOptions &frontendOpts,
170-
const IRGenOptions &irGenOpts) {
169+
static bool printAsClangHeaderIfNeeded(
170+
StringRef outputPath, ModuleDecl *M, StringRef bridgingHeader,
171+
const FrontendOptions &frontendOpts, const IRGenOptions &irGenOpts,
172+
clang::HeaderSearch &clangHeaderSearchInfo) {
171173
if (outputPath.empty())
172174
return false;
173-
return withOutputFile(M->getDiags(), outputPath,
174-
[&](raw_ostream &out) -> bool {
175-
return printAsClangHeader(out, M, bridgingHeader,
176-
frontendOpts, irGenOpts);
177-
});
175+
return withOutputFile(
176+
M->getDiags(), outputPath, [&](raw_ostream &out) -> bool {
177+
return printAsClangHeader(out, M, bridgingHeader, frontendOpts,
178+
irGenOpts, clangHeaderSearchInfo);
179+
});
178180
}
179181

180182
/// Prints the stable module interface for \p M to \p outputPath.
@@ -925,7 +927,10 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
925927
hadAnyError |= printAsClangHeaderIfNeeded(
926928
Invocation.getClangHeaderOutputPathForAtMostOnePrimary(),
927929
Instance.getMainModule(), BridgingHeaderPathForPrint, opts,
928-
Invocation.getIRGenOptions());
930+
Invocation.getIRGenOptions(),
931+
Context.getClangModuleLoader()
932+
->getClangPreprocessor()
933+
.getHeaderSearchInfo());
929934
}
930935

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

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 204 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@
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"
31+
#include "llvm/Support/Path.h"
2832
#include "llvm/Support/raw_ostream.h"
2933

3034
using namespace swift;
@@ -386,9 +390,133 @@ static int compareImportModulesByName(const ImportModuleTy *left,
386390
return 1;
387391
}
388392

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

544+
clang::FileSystemOptions fileSystemOptions;
545+
clang::FileManager fileManager{fileSystemOptions};
546+
547+
llvm::SmallSet<llvm::SmallString<128>, 10> requiredTextualIncludes;
548+
llvm::SmallSet<const clang::Module *, 10> visitedModules;
549+
llvm::SmallSet<llvm::SmallString<128>, 10> includeDirs;
550+
551+
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
552+
llvm::ErrorOr<std::string> cwd = fileSystem.getCurrentWorkingDirectory();
553+
554+
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
555+
assert(cwd && "Access to current working directory required");
556+
557+
for (auto searchDir = clangHeaderSearchInfo.search_dir_begin();
558+
searchDir != clangHeaderSearchInfo.search_dir_end(); ++searchDir) {
559+
includeDirs.insert(normalizePath(searchDir->getName()));
560+
}
561+
562+
const clang::Module *foundationModule = clangHeaderSearchInfo.lookupModule(
563+
"Foundation", clang::SourceLocation(), false, false);
564+
const clang::Module *darwinModule = clangHeaderSearchInfo.lookupModule(
565+
"Darwin", clang::SourceLocation(), false, false);
566+
567+
std::function<void(const clang::Module *)>
568+
collectTransitiveSubmoduleClosure;
569+
collectTransitiveSubmoduleClosure = [&](const clang::Module *module) {
570+
if (!module)
571+
return;
572+
573+
visitedModules.insert(module);
574+
for (auto submodule : module->submodules()) {
575+
collectTransitiveSubmoduleClosure(submodule);
576+
}
577+
};
578+
579+
collectTransitiveSubmoduleClosure(foundationModule);
580+
collectTransitiveSubmoduleClosure(darwinModule);
581+
}
582+
416583
// Track printed names to handle overlay modules.
417584
llvm::SmallPtrSet<Identifier, 8> seenImports;
418585
bool includeUnderlying = false;
@@ -425,18 +592,46 @@ static void writeImports(raw_ostream &out,
425592
includeUnderlying = true;
426593
continue;
427594
}
428-
if (seenImports.insert(Name).second)
595+
if (seenImports.insert(Name).second) {
429596
out << importDirective << ' ' << Name.str() << ";\n";
597+
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
598+
if (const clang::Module *underlyingClangModule =
599+
swiftModule->findUnderlyingClangModule()) {
600+
collectClangModuleHeaderIncludes(
601+
underlyingClangModule, fileManager, requiredTextualIncludes,
602+
visitedModules, includeDirs, cwd.get());
603+
} else if ((underlyingClangModule =
604+
clangHeaderSearchInfo.lookupModule(
605+
Name.str(), clang::SourceLocation(), true,
606+
true))) {
607+
collectClangModuleHeaderIncludes(
608+
underlyingClangModule, fileManager, requiredTextualIncludes,
609+
visitedModules, includeDirs, cwd.get());
610+
}
611+
}
612+
}
430613
} else {
431614
const auto *clangModule = import.get<const clang::Module *>();
432615
assert(clangModule->isSubModule() &&
433616
"top-level modules should use a normal swift::ModuleDecl");
434617
out << importDirective << ' ';
435618
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
436619
out << ";\n";
620+
621+
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
622+
collectClangModuleHeaderIncludes(
623+
clangModule, fileManager, requiredTextualIncludes, visitedModules,
624+
includeDirs, cwd.get());
625+
}
437626
}
438627
}
439628

629+
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
630+
out << "#else\n";
631+
for (auto header : requiredTextualIncludes) {
632+
out << "#import <" << header << ">\n";
633+
}
634+
}
440635
out << "#endif\n\n";
441636

442637
if (includeUnderlying) {
@@ -490,7 +685,8 @@ static std::string computeMacroGuard(const ModuleDecl *M) {
490685
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
491686
StringRef bridgingHeader,
492687
const FrontendOptions &frontendOpts,
493-
const IRGenOptions &irGenOpts) {
688+
const IRGenOptions &irGenOpts,
689+
clang::HeaderSearch &clangHeaderSearchInfo) {
494690
llvm::PrettyStackTraceString trace("While generating Clang header");
495691

496692
SwiftToClangInteropContext interopContext(*M, irGenOpts);
@@ -500,8 +696,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
500696
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
501697
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
502698
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
503-
emitObjCConditional(os,
504-
[&] { writeImports(os, imports, *M, bridgingHeader); });
699+
emitObjCConditional(os, [&] {
700+
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
701+
clangHeaderSearchInfo);
702+
});
505703
writePostImportPrologue(os, *M);
506704
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
507705
emitCxxConditional(os, [&] {
@@ -530,8 +728,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
530728
moduleContents, *M, interopContext,
531729
/*requiresExposedAttribute=*/requiresExplicitExpose);
532730
// FIXME: In ObjC++ mode, we do not need to reimport duplicate modules.
533-
writeImports(os, deps.imports, *M, bridgingHeader, /*useCxxImport=*/true);
534-
731+
writeImports(os, deps.imports, *M, bridgingHeader, frontendOpts,
732+
clangHeaderSearchInfo, /*useCxxImport=*/true);
535733
// Embed the standard library directly.
536734
if (defaultDependencyBehavior && deps.dependsOnStandardLibrary) {
537735
assert(!M->isStdlibModule());
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <Foundation.h>
2+
3+
@interface Baz : NSObject
4+
- (void)baz;
5+
@end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../header_symlink_targets/foo.h
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <Foundation.h>
2+
3+
@interface Foo : NSObject
4+
- (void)baz;
5+
@end

test/PrintAsObjC/Inputs/custom-modules/module.map

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,9 @@ module MiserablePileOfSecrets {
5252
header "MiserablePileOfSecrets.h"
5353
export *
5454
}
55+
56+
module EmitClangHeaderNonmodularIncludesStressTest {
57+
header "header_subdirectory/header-regular.h"
58+
header "header_subdirectory/header-symlink.h"
59+
export *
60+
}

0 commit comments

Comments
 (0)