Skip to content

[Glibc] Use VFS to inject modulemap into Glibc include path #59846

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 1 commit into from
Jul 4, 2022
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsClangImporter.def
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ WARNING(nonmutating_without_mutable_fields,none,

ERROR(module_map_not_found, none, "module map file '%0' not found", (StringRef))

WARNING(glibc_not_found, none,
"glibc not found for '%0'; C stdlib may be unavailable",
(StringRef))
WARNING(libstdcxx_not_found, none,
"libstdc++ not found for '%0'; C++ stdlib may be unavailable",
(StringRef))
Expand Down
7 changes: 0 additions & 7 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,13 +648,6 @@ importer::getNormalInvocationArguments(
break;
}
}

SmallString<128> buffer;
if (auto path = getGlibcModuleMapPath(searchPathOpts, triple, buffer)) {
invocationArgStrs.push_back((Twine("-fmodule-map-file=") + *path).str());
} else {
// FIXME: Emit a warning of some kind.
}
}

if (searchPathOpts.getSDKPath().empty()) {
Expand Down
262 changes: 206 additions & 56 deletions lib/ClangImporter/ClangIncludePaths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,84 +16,143 @@
#include "swift/AST/DiagnosticsClangImporter.h"
#include "swift/Basic/Platform.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/ToolChain.h"
#include "clang/Frontend/CompilerInstance.h"

using namespace swift;

static Optional<StringRef> getModuleMapFilePath(StringRef name,
SearchPathOptions &Opts,
llvm::Triple triple,
SmallVectorImpl<char> &buffer) {
using Path = SmallString<128>;

static Optional<Path> getActualModuleMapPath(StringRef name,
SearchPathOptions &Opts,
const llvm::Triple &triple) {
StringRef platform = swift::getPlatformNameForTriple(triple);
StringRef arch = swift::getMajorArchitectureName(triple);

Path result;

StringRef SDKPath = Opts.getSDKPath();
if (!SDKPath.empty()) {
buffer.clear();
buffer.append(SDKPath.begin(), SDKPath.end());
llvm::sys::path::append(buffer, "usr", "lib", "swift");
llvm::sys::path::append(buffer, platform, arch, name);
result.append(SDKPath.begin(), SDKPath.end());
llvm::sys::path::append(result, "usr", "lib", "swift");
llvm::sys::path::append(result, platform, arch, name);

// Only specify the module map if that file actually exists. It may not;
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
// -emit-ir` is invoked using a Swift compiler not built for Linux targets.
if (llvm::sys::fs::exists(buffer))
return StringRef(buffer.data(), buffer.size());
if (llvm::sys::fs::exists(result))
return result;
}

if (!Opts.RuntimeResourcePath.empty()) {
buffer.clear();
buffer.append(Opts.RuntimeResourcePath.begin(),
result.clear();
result.append(Opts.RuntimeResourcePath.begin(),
Opts.RuntimeResourcePath.end());
llvm::sys::path::append(buffer, platform, arch, name);
llvm::sys::path::append(result, platform, arch, name);

// Only specify the module map if that file actually exists. It may not;
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
// -emit-ir` is invoked using a Swift compiler not built for Linux targets.
if (llvm::sys::fs::exists(buffer))
return StringRef(buffer.data(), buffer.size());
if (llvm::sys::fs::exists(result))
return result;
}

return None;
}

Optional<StringRef>
swift::getGlibcModuleMapPath(SearchPathOptions &Opts, llvm::Triple triple,
SmallVectorImpl<char> &buffer) {
return getModuleMapFilePath("glibc.modulemap", Opts, triple, buffer);
/// Given an include path directory, returns a path to inject the module map to.
/// If a module map already exists, returns `None`.
static llvm::Optional<Path> getInjectedModuleMapPath(const Path &dir) {
Path legacyPath(dir);
llvm::sys::path::append(legacyPath, "module.map");
if (llvm::sys::fs::exists(legacyPath))
return None;

Path path(dir);
llvm::sys::path::append(path, "module.modulemap");
if (llvm::sys::fs::exists(path))
return None;

return path;
}

static Optional<StringRef>
getLibStdCxxModuleMapPath(SearchPathOptions &opts, llvm::Triple triple,
SmallVectorImpl<char> &buffer) {
return getModuleMapFilePath("libstdcxx.modulemap", opts, triple, buffer);
/// Finds the glibc.modulemap file relative to the provided resource dir.
///
/// Note that the module map used for Glibc depends on the target we're
/// compiling for, and is not included in the resource directory with the other
/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap.
static Optional<Path>
getGlibcModuleMapPath(SearchPathOptions &Opts, const llvm::Triple &triple) {
return getActualModuleMapPath("glibc.modulemap", Opts, triple);
}

SmallVector<std::pair<std::string, std::string>, 16>
swift::getClangInvocationFileMapping(ASTContext &ctx) {
using Path = SmallString<128>;
static Optional<Path>
getLibStdCxxModuleMapPath(SearchPathOptions &opts, const llvm::Triple &triple) {
return getActualModuleMapPath("libstdcxx.modulemap", opts, triple);
}

const llvm::Triple &triple = ctx.LangOpts.Target;
// We currently only need this when building for Linux.
if (!triple.isOSLinux())
return {};
// Android uses libc++.
if (triple.isAndroid())
return {};
static llvm::opt::InputArgList
parseClangDriverArgs(const clang::driver::Driver &clangDriver,
const ArrayRef<const char *> args) {
unsigned unused1, unused2;
return clangDriver.getOpts().ParseArgs(args, unused1, unused2);
}

// Extract the libstdc++ installation path from Clang driver.
static clang::driver::Driver createClangDriver(const ASTContext &ctx) {
auto clangDiags = clang::CompilerInstance::createDiagnostics(
new clang::DiagnosticOptions());
clang::driver::Driver clangDriver(ctx.ClangImporterOpts.clangPath,
triple.str(), *clangDiags);
ctx.LangOpts.Target.str(), *clangDiags);
return clangDriver;
}

/// Given a list of include paths and a list of file names, finds the first
/// include path that contains files with all the names. This is useful for
/// finding the include path for a specific library among a list of include
/// paths.
///
/// \return a path without dots (`../`, './').
static llvm::Optional<Path>
findFirstIncludeDir(const llvm::opt::InputArgList &args,
const ArrayRef<const char *> expectedFileNames) {
// C++ stdlib paths are added as `-internal-isystem`.
std::vector<std::string> includeDirs =
args.getAllArgValues(clang::driver::options::OPT_internal_isystem);
// C stdlib paths are added as `-internal-externc-isystem`.
llvm::append_range(includeDirs,
args.getAllArgValues(
clang::driver::options::OPT_internal_externc_isystem));

for (const auto &includeDir : includeDirs) {
Path dir(includeDir);
bool allExpectedExist = true;
for (auto expectedFileName : expectedFileNames) {
Path expectedFile(dir);
llvm::sys::path::append(expectedFile, expectedFileName);
if (!llvm::sys::fs::exists(expectedFile)) {
allExpectedExist = false;
break;
}
}

if (allExpectedExist) {
// VFS does not allow mapping paths that contain `../` or `./`.
llvm::sys::path::remove_dots(dir, /*remove_dot_dot=*/true);
return dir;
}
}
return None;
}

static llvm::opt::InputArgList
createClangArgs(const ASTContext &ctx, clang::driver::Driver &clangDriver) {
// Flags passed to Swift with `-Xcc` might affect include paths.
unsigned unused1, unused2;
std::vector<const char *> clangArgs;
for (const auto &each : ctx.ClangImporterOpts.ExtraArgs) {
clangArgs.push_back(each.c_str());
}
llvm::opt::InputArgList clangDriverArgs =
clangDriver.getOpts().ParseArgs(clangArgs, unused1, unused2);
parseClangDriverArgs(clangDriver, clangArgs);
// If an SDK path was explicitly passed to Swift, make sure to pass it to
// Clang driver as well. It affects the resulting include paths.
auto sdkPath = ctx.SearchPathOpts.getSDKPath();
Expand All @@ -103,23 +162,106 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
clangDriver.getOpts().getOption(clang::driver::options::OPT__sysroot),
sdkPath, argIndex));
}
auto cxxStdlibDirs =
clangDriver.getLibStdCxxIncludePaths(clangDriverArgs, triple);
if (cxxStdlibDirs.empty()) {
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
return clangDriverArgs;
}

static SmallVector<std::pair<std::string, std::string>, 2>
getGlibcFileMapping(ASTContext &ctx) {
const llvm::Triple &triple = ctx.LangOpts.Target;
// We currently only need this when building for Linux.
if (!triple.isOSLinux())
return {};

// Extract the Glibc path from Clang driver.
auto clangDriver = createClangDriver(ctx);
auto clangDriverArgs = createClangArgs(ctx, clangDriver);

llvm::opt::ArgStringList includeArgStrings;
const auto &clangToolchain =
clangDriver.getToolChain(clangDriverArgs, triple);
clangToolchain.AddClangSystemIncludeArgs(clangDriverArgs, includeArgStrings);
auto parsedIncludeArgs = parseClangDriverArgs(clangDriver, includeArgStrings);

// Find the include path that contains Glibc headers. We use three arbitrarily
// chosen headers to determine if the include path actually contains Glibc.
// Ideally we would check that all of the headers referenced from the
// modulemap are present.
Path glibcDir;
if (auto dir = findFirstIncludeDir(parsedIncludeArgs,
{"inttypes.h", "unistd.h", "stdint.h"})) {
glibcDir = dir.getValue();
} else {
ctx.Diags.diagnose(SourceLoc(), diag::glibc_not_found, triple.str());
return {};
}
Path cxxStdlibDir(cxxStdlibDirs.front());
// VFS does not allow mapping paths that contain `../` or `./`.
llvm::sys::path::remove_dots(cxxStdlibDir, /*remove_dot_dot=*/true);

// Currently only a modulemap for libstdc++ is injected.
if (!ctx.LangOpts.EnableCXXInterop)
Path actualModuleMapPath;
if (auto path = getGlibcModuleMapPath(ctx.SearchPathOpts, triple))
actualModuleMapPath = path.getValue();
else
// FIXME: Emit a warning of some kind.
return {};

// Only inject the module map if it actually exists. It may not, for example
// if `swiftc -target x86_64-unknown-linux-gnu -emit-ir` is invoked using
// a Swift compiler not built for Linux targets.
if (!llvm::sys::fs::exists(actualModuleMapPath))
// FIXME: emit a warning of some kind.
return {};

// TODO: remove the SwiftGlibc.h header and reference all Glibc headers
// directly from the modulemap.
Path actualHeaderPath = actualModuleMapPath;
llvm::sys::path::remove_filename(actualHeaderPath);
llvm::sys::path::append(actualHeaderPath, "SwiftGlibc.h");

Path injectedModuleMapPath(glibcDir);
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");

Path injectedHeaderPath(glibcDir);
llvm::sys::path::append(injectedHeaderPath, "SwiftGlibc.h");

return {
{std::string(injectedModuleMapPath), std::string(actualModuleMapPath)},
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
};
}

static SmallVector<std::pair<std::string, std::string>, 2>
getLibStdCxxFileMapping(ASTContext &ctx) {
assert(ctx.LangOpts.EnableCXXInterop &&
"libstdc++ is only injected if C++ interop is enabled");

const llvm::Triple &triple = ctx.LangOpts.Target;
// We currently only need this when building for Linux.
if (!triple.isOSLinux())
return {};
// Android uses libc++.
if (triple.isAndroid())
return {};

// Extract the libstdc++ installation path from Clang driver.
auto clangDriver = createClangDriver(ctx);
auto clangDriverArgs = createClangArgs(ctx, clangDriver);

llvm::opt::ArgStringList stdlibArgStrings;
const auto &clangToolchain =
clangDriver.getToolChain(clangDriverArgs, triple);
clangToolchain.AddClangCXXStdlibIncludeArgs(clangDriverArgs,
stdlibArgStrings);
auto parsedStdlibArgs = parseClangDriverArgs(clangDriver, stdlibArgStrings);

Path cxxStdlibDir;
if (auto dir = findFirstIncludeDir(parsedStdlibArgs,
{"cstdlib", "string", "vector"})) {
cxxStdlibDir = dir.getValue();
} else {
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
return {};
}

Path actualModuleMapPath;
Path buffer;
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple, buffer))
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple))
actualModuleMapPath = path.getValue();
else
return {};
Expand All @@ -140,14 +282,10 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
// Inject a modulemap into VFS for the libstdc++ directory.
// Only inject the module map if the module does not already exist at
// {sysroot}/usr/include/module.{map,modulemap}.
Path injectedModuleMapLegacyPath(cxxStdlibDir);
llvm::sys::path::append(injectedModuleMapLegacyPath, "module.map");
if (llvm::sys::fs::exists(injectedModuleMapLegacyPath))
return {};

Path injectedModuleMapPath(cxxStdlibDir);
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
if (llvm::sys::fs::exists(injectedModuleMapPath))
Path injectedModuleMapPath;
if (auto path = getInjectedModuleMapPath(cxxStdlibDir))
injectedModuleMapPath = path.getValue();
else
return {};

Path injectedHeaderPath(cxxStdlibDir);
Expand All @@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
};
}

SmallVector<std::pair<std::string, std::string>, 2>
swift::getClangInvocationFileMapping(ASTContext &ctx) {
SmallVector<std::pair<std::string, std::string>, 2> result;

result.append(getGlibcFileMapping(ctx));

if (ctx.LangOpts.EnableCXXInterop) {
result.append(getLibStdCxxFileMapping(ctx));
}
return result;
}
12 changes: 1 addition & 11 deletions lib/ClangImporter/ClangIncludePaths.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,13 @@
#define SWIFT_CLANG_INCLUDE_PATHS_H

#include "swift/AST/ASTContext.h"
#include "swift/AST/SearchPathOptions.h"

namespace swift {

/// Finds the glibc.modulemap file relative to the provided resource dir.
///
/// Note that the module map used for Glibc depends on the target we're
/// compiling for, and is not included in the resource directory with the other
/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap.
Optional<StringRef> getGlibcModuleMapPath(SearchPathOptions &Opts,
llvm::Triple triple,
SmallVectorImpl<char> &buffer);

/// On Linux, some platform libraries (glibc, libstdc++) are not modularized.
/// We inject modulemaps for those libraries into their include directories
/// to allow using them from Swift.
SmallVector<std::pair<std::string, std::string>, 16>
SmallVector<std::pair<std::string, std::string>, 2>
getClangInvocationFileMapping(ASTContext &ctx);

} // namespace swift
Expand Down