diff --git a/include/swift/AST/DiagnosticsClangImporter.def b/include/swift/AST/DiagnosticsClangImporter.def index 6914d2e633059..52ce6815e0a8b 100644 --- a/include/swift/AST/DiagnosticsClangImporter.def +++ b/include/swift/AST/DiagnosticsClangImporter.def @@ -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)) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 265b2261cd535..31422e88f92d0 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -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()) { diff --git a/lib/ClangImporter/ClangIncludePaths.cpp b/lib/ClangImporter/ClangIncludePaths.cpp index 92565a5ad14e2..6a6b9bb68c6e5 100644 --- a/lib/ClangImporter/ClangIncludePaths.cpp +++ b/lib/ClangImporter/ClangIncludePaths.cpp @@ -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 getModuleMapFilePath(StringRef name, - SearchPathOptions &Opts, - llvm::Triple triple, - SmallVectorImpl &buffer) { +using Path = SmallString<128>; + +static Optional 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 -swift::getGlibcModuleMapPath(SearchPathOptions &Opts, llvm::Triple triple, - SmallVectorImpl &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 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 -getLibStdCxxModuleMapPath(SearchPathOptions &opts, llvm::Triple triple, - SmallVectorImpl &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 +getGlibcModuleMapPath(SearchPathOptions &Opts, const llvm::Triple &triple) { + return getActualModuleMapPath("glibc.modulemap", Opts, triple); } -SmallVector, 16> -swift::getClangInvocationFileMapping(ASTContext &ctx) { - using Path = SmallString<128>; +static Optional +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 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 +findFirstIncludeDir(const llvm::opt::InputArgList &args, + const ArrayRef expectedFileNames) { + // C++ stdlib paths are added as `-internal-isystem`. + std::vector 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 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(); @@ -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, 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, 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 {}; @@ -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); @@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) { {std::string(injectedHeaderPath), std::string(actualHeaderPath)}, }; } + +SmallVector, 2> +swift::getClangInvocationFileMapping(ASTContext &ctx) { + SmallVector, 2> result; + + result.append(getGlibcFileMapping(ctx)); + + if (ctx.LangOpts.EnableCXXInterop) { + result.append(getLibStdCxxFileMapping(ctx)); + } + return result; +} diff --git a/lib/ClangImporter/ClangIncludePaths.h b/lib/ClangImporter/ClangIncludePaths.h index 86e6ab959f4bc..5c8ee711ac74e 100644 --- a/lib/ClangImporter/ClangIncludePaths.h +++ b/lib/ClangImporter/ClangIncludePaths.h @@ -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 getGlibcModuleMapPath(SearchPathOptions &Opts, - llvm::Triple triple, - SmallVectorImpl &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, 16> +SmallVector, 2> getClangInvocationFileMapping(ASTContext &ctx); } // namespace swift