Skip to content

Add support for textual imports to -emit-objc-header #62386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/Frontend/FrontendOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,10 @@ class FrontendOptions {
/// crashes the compiler.
bool emptyABIDescriptor = false;

/// Augment modular imports in any emitted ObjC headers with equivalent
/// textual imports
bool EmitClangHeaderWithNonModularIncludes = false;

private:
static bool canActionEmitDependencies(ActionType);
static bool canActionEmitReferenceDependencies(ActionType);
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,9 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">,
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
SupplementaryOutput]>,
MetaVarName<"<path>">, HelpText<"Emit an Objective-C header file to <path>">;
def emit_clang_header_nonmodular_includes : Flag<["-"], "emit-clang-header-nonmodular-includes">,
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>,
HelpText<"Augment emitted Objective-C header with textual imports for every included modular import">;

def emit_clang_header_path : Separate<["-"], "emit-clang-header-path">,
Flags<[FrontendOption, NoDriverOption, NoInteractiveOption, ArgumentIsPath,
Expand Down
7 changes: 6 additions & 1 deletion include/swift/PrintAsClang/PrintAsClang.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include "swift/AST/AttrKind.h"
#include "swift/AST/Identifier.h"

namespace clang {
class HeaderSearch;
}

namespace swift {
class FrontendOptions;
class IRGenOptions;
Expand All @@ -36,7 +40,8 @@ class ValueDecl;
bool printAsClangHeader(raw_ostream &out, ModuleDecl *M,
StringRef bridgingHeader,
const FrontendOptions &frontendOpts,
const IRGenOptions &irGenOpts);
const IRGenOptions &irGenOpts,
clang::HeaderSearch &headerSearchInfo);
}

#endif
3 changes: 3 additions & 0 deletions lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ bool ArgsToFrontendOptionsConverter::convert(
Opts.FrontendParseableOutput |= Args.hasArg(OPT_frontend_parseable_output);
Opts.ExplicitInterfaceBuild |= Args.hasArg(OPT_explicit_interface_module_build);

Opts.EmitClangHeaderWithNonModularIncludes |=
Args.hasArg(OPT_emit_clang_header_nonmodular_includes);

// FIXME: Remove this flag
Opts.EnableLibraryEvolution |= Args.hasArg(OPT_enable_resilience);

Expand Down
25 changes: 15 additions & 10 deletions lib/FrontendTool/FrontendTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
#include "swift/Subsystems.h"
#include "swift/SymbolGraphGen/SymbolGraphOptions.h"

#include "clang/Lex/Preprocessor.h"

#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/IR/LLVMContext.h"
Expand Down Expand Up @@ -164,17 +166,17 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
/// \returns true if there were any errors
///
/// \see swift::printAsClangHeader
static bool printAsClangHeaderIfNeeded(StringRef outputPath, ModuleDecl *M,
StringRef bridgingHeader,
const FrontendOptions &frontendOpts,
const IRGenOptions &irGenOpts) {
static bool printAsClangHeaderIfNeeded(
StringRef outputPath, ModuleDecl *M, StringRef bridgingHeader,
const FrontendOptions &frontendOpts, const IRGenOptions &irGenOpts,
clang::HeaderSearch &clangHeaderSearchInfo) {
if (outputPath.empty())
return false;
return withOutputFile(M->getDiags(), outputPath,
[&](raw_ostream &out) -> bool {
return printAsClangHeader(out, M, bridgingHeader,
frontendOpts, irGenOpts);
});
return withOutputFile(
M->getDiags(), outputPath, [&](raw_ostream &out) -> bool {
return printAsClangHeader(out, M, bridgingHeader, frontendOpts,
irGenOpts, clangHeaderSearchInfo);
});
}

/// Prints the stable module interface for \p M to \p outputPath.
Expand Down Expand Up @@ -925,7 +927,10 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
hadAnyError |= printAsClangHeaderIfNeeded(
Invocation.getClangHeaderOutputPathForAtMostOnePrimary(),
Instance.getMainModule(), BridgingHeaderPathForPrint, opts,
Invocation.getIRGenOptions());
Invocation.getIRGenOptions(),
Context.getClangModuleLoader()
->getClangPreprocessor()
.getHeaderSearchInfo());
}

// Only want the header if there's been any errors, ie. there's not much
Expand Down
210 changes: 204 additions & 6 deletions lib/PrintAsClang/PrintAsClang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/Frontend/FrontendOptions.h"

#include "clang/Basic/FileManager.h"
#include "clang/Basic/Module.h"
#include "clang/Lex/HeaderSearch.h"

#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"

using namespace swift;
Expand Down Expand Up @@ -386,9 +390,133 @@ static int compareImportModulesByName(const ImportModuleTy *left,
return 1;
}

// Makes the provided path absolute and removes any "." or ".." segments from
// the path
static llvm::SmallString<128> normalizePath(const llvm::StringRef path) {
llvm::SmallString<128> result = path;
llvm::sys::path::remove_dots(result, /* remove_dot_dot */ true);
llvm::sys::fs::make_absolute(result);
return result;
}

// Collect the set of header includes needed to import the given Clang module
// into an ObjectiveC program. Modeled after collectModuleHeaderIncludes in the
// Clang frontend (FrontendAction.cpp)
// Augment requiredTextualIncludes with the set of headers required.
static void collectClangModuleHeaderIncludes(
const clang::Module *clangModule, clang::FileManager &fileManager,
llvm::SmallSet<llvm::SmallString<128>, 10> &requiredTextualIncludes,
llvm::SmallSet<const clang::Module *, 10> &visitedModules,
const llvm::SmallSet<llvm::SmallString<128>, 10> &includeDirs,
const llvm::StringRef cwd) {

if (!visitedModules.insert(clangModule).second)
return;

auto addHeader = [&](llvm::StringRef headerPath,
llvm::StringRef pathRelativeToRootModuleDir) {
if (!clangModule->Directory)
return;

llvm::SmallString<128> textualInclude = normalizePath(headerPath);
llvm::SmallString<128> containingSearchDirPath;

for (auto &includeDir : includeDirs) {
if (textualInclude.startswith(includeDir)) {
if (includeDir.size() > containingSearchDirPath.size()) {
containingSearchDirPath = includeDir;
}
}
}

if (!containingSearchDirPath.empty()) {
llvm::SmallString<128> prefixToRemove =
llvm::formatv("{0}/", containingSearchDirPath);
llvm::sys::path::replace_path_prefix(textualInclude, prefixToRemove, "");
} else {
// If we cannot find find the module map on the search path,
// fallback to including the header using the provided path relative
// to the module map
textualInclude = pathRelativeToRootModuleDir;
}

if (clangModule->getTopLevelModule()->IsFramework) {
llvm::SmallString<32> frameworkName =
clangModule->getTopLevelModuleName();
llvm::SmallString<64> oldFrameworkPrefix =
llvm::formatv("{0}.framework/Headers", frameworkName);
llvm::sys::path::replace_path_prefix(textualInclude, oldFrameworkPrefix,
frameworkName);
}

requiredTextualIncludes.insert(textualInclude);
};

if (clang::Module::Header umbrellaHeader = clangModule->getUmbrellaHeader()) {
addHeader(umbrellaHeader.Entry->tryGetRealPathName(),
umbrellaHeader.PathRelativeToRootModuleDirectory);
} else if (clang::Module::DirectoryName umbrellaDir =
clangModule->getUmbrellaDir()) {
SmallString<128> nativeUmbrellaDirPath;
std::error_code errorCode;
llvm::sys::path::native(umbrellaDir.Entry->getName(),
nativeUmbrellaDirPath);
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
for (llvm::vfs::recursive_directory_iterator
dir(fileSystem, nativeUmbrellaDirPath, errorCode),
end;
dir != end && !errorCode; dir.increment(errorCode)) {

if (llvm::StringSwitch<bool>(llvm::sys::path::extension(dir->path()))
.Cases(".h", ".H", ".hh", ".hpp", true)
.Default(false)) {

// Compute path to the header relative to the root of the module
// (location of the module map) First compute the relative path from
// umbrella directory to header file
SmallVector<StringRef> pathComponents;
auto pathIt = llvm::sys::path::rbegin(dir->path());

for (int i = 0; i != dir.level() + 1; ++i, ++pathIt)
pathComponents.push_back(*pathIt);
// Then append this to the path from module root to umbrella dir
SmallString<128> relativeHeaderPath;
if (umbrellaDir.PathRelativeToRootModuleDirectory != ".")
relativeHeaderPath += umbrellaDir.PathRelativeToRootModuleDirectory;

for (auto it = pathComponents.rbegin(), end = pathComponents.rend();
it != end; ++it) {
llvm::sys::path::append(relativeHeaderPath, *it);
}

addHeader(dir->path(), relativeHeaderPath);
}
}
} else {
for (clang::Module::HeaderKind headerKind :
{clang::Module::HK_Normal, clang::Module::HK_Textual}) {
for (const clang::Module::Header &header :
clangModule->Headers[headerKind]) {
addHeader(header.Entry->tryGetRealPathName(),
header.PathRelativeToRootModuleDirectory);
}
}
for (auto submodule : clangModule->submodules()) {
if (submodule->IsExplicit)
continue;

collectClangModuleHeaderIncludes(submodule, fileManager,
requiredTextualIncludes, visitedModules,
includeDirs, cwd);
}
}
}

static void writeImports(raw_ostream &out,
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
ModuleDecl &M, StringRef bridgingHeader,
const FrontendOptions &frontendOpts,
clang::HeaderSearch &clangHeaderSearchInfo,
bool useCxxImport = false) {
// Note: we can't use has_feature(modules) as it's always enabled in C++20
// mode.
Expand All @@ -413,6 +541,45 @@ static void writeImports(raw_ostream &out,
return import == importer->getImportedHeaderModule();
};

clang::FileSystemOptions fileSystemOptions;
clang::FileManager fileManager{fileSystemOptions};

llvm::SmallSet<llvm::SmallString<128>, 10> requiredTextualIncludes;
llvm::SmallSet<const clang::Module *, 10> visitedModules;
llvm::SmallSet<llvm::SmallString<128>, 10> includeDirs;

llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
llvm::ErrorOr<std::string> cwd = fileSystem.getCurrentWorkingDirectory();

if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
assert(cwd && "Access to current working directory required");

for (auto searchDir = clangHeaderSearchInfo.search_dir_begin();
searchDir != clangHeaderSearchInfo.search_dir_end(); ++searchDir) {
includeDirs.insert(normalizePath(searchDir->getName()));
}

const clang::Module *foundationModule = clangHeaderSearchInfo.lookupModule(
"Foundation", clang::SourceLocation(), false, false);
const clang::Module *darwinModule = clangHeaderSearchInfo.lookupModule(
"Darwin", clang::SourceLocation(), false, false);

std::function<void(const clang::Module *)>
collectTransitiveSubmoduleClosure;
collectTransitiveSubmoduleClosure = [&](const clang::Module *module) {
if (!module)
return;

visitedModules.insert(module);
for (auto submodule : module->submodules()) {
collectTransitiveSubmoduleClosure(submodule);
}
};

collectTransitiveSubmoduleClosure(foundationModule);
collectTransitiveSubmoduleClosure(darwinModule);
}

// Track printed names to handle overlay modules.
llvm::SmallPtrSet<Identifier, 8> seenImports;
bool includeUnderlying = false;
Expand All @@ -425,18 +592,46 @@ static void writeImports(raw_ostream &out,
includeUnderlying = true;
continue;
}
if (seenImports.insert(Name).second)
if (seenImports.insert(Name).second) {
out << importDirective << ' ' << Name.str() << ";\n";
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
if (const clang::Module *underlyingClangModule =
swiftModule->findUnderlyingClangModule()) {
collectClangModuleHeaderIncludes(
underlyingClangModule, fileManager, requiredTextualIncludes,
visitedModules, includeDirs, cwd.get());
} else if ((underlyingClangModule =
clangHeaderSearchInfo.lookupModule(
Name.str(), clang::SourceLocation(), true,
true))) {
collectClangModuleHeaderIncludes(
underlyingClangModule, fileManager, requiredTextualIncludes,
visitedModules, includeDirs, cwd.get());
}
}
}
} else {
const auto *clangModule = import.get<const clang::Module *>();
assert(clangModule->isSubModule() &&
"top-level modules should use a normal swift::ModuleDecl");
out << importDirective << ' ';
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
out << ";\n";

if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
collectClangModuleHeaderIncludes(
clangModule, fileManager, requiredTextualIncludes, visitedModules,
includeDirs, cwd.get());
}
}
}

if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
out << "#else\n";
for (auto header : requiredTextualIncludes) {
out << "#import <" << header << ">\n";
}
}
out << "#endif\n\n";

if (includeUnderlying) {
Expand Down Expand Up @@ -490,7 +685,8 @@ static std::string computeMacroGuard(const ModuleDecl *M) {
bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
StringRef bridgingHeader,
const FrontendOptions &frontendOpts,
const IRGenOptions &irGenOpts) {
const IRGenOptions &irGenOpts,
clang::HeaderSearch &clangHeaderSearchInfo) {
llvm::PrettyStackTraceString trace("While generating Clang header");

SwiftToClangInteropContext interopContext(*M, irGenOpts);
Expand All @@ -500,8 +696,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf};
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
emitObjCConditional(os,
[&] { writeImports(os, imports, *M, bridgingHeader); });
emitObjCConditional(os, [&] {
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
clangHeaderSearchInfo);
});
writePostImportPrologue(os, *M);
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
emitCxxConditional(os, [&] {
Expand Down Expand Up @@ -530,8 +728,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
moduleContents, *M, interopContext,
/*requiresExposedAttribute=*/requiresExplicitExpose);
// FIXME: In ObjC++ mode, we do not need to reimport duplicate modules.
writeImports(os, deps.imports, *M, bridgingHeader, /*useCxxImport=*/true);

writeImports(os, deps.imports, *M, bridgingHeader, frontendOpts,
clangHeaderSearchInfo, /*useCxxImport=*/true);
// Embed the standard library directly.
if (defaultDependencyBehavior && deps.dependsOnStandardLibrary) {
assert(!M->isStdlibModule());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation.h>

@interface Baz : NSObject
- (void)baz;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation.h>

@interface Foo : NSObject
- (void)baz;
@end
6 changes: 6 additions & 0 deletions test/PrintAsObjC/Inputs/custom-modules/module.map
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ module MiserablePileOfSecrets {
header "MiserablePileOfSecrets.h"
export *
}

module EmitClangHeaderNonmodularIncludesStressTest {
header "header_subdirectory/header-regular.h"
header "header_subdirectory/header-symlink.h"
export *
}
Loading