Skip to content

[cxx-interop] Use VFS to inject modulemap into libstdc++ installation #58843

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 2 commits into from
May 13, 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
98 changes: 89 additions & 9 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
#include "llvm/Support/Path.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <algorithm>
#include <memory>

Expand Down Expand Up @@ -693,14 +694,6 @@ importer::getNormalInvocationArguments(
} else {
// FIXME: Emit a warning of some kind.
}

if (EnableCXXInterop) {
if (auto path =
getLibStdCxxModuleMapPath(searchPathOpts, triple, buffer)) {
invocationArgStrs.push_back(
(Twine("-fmodule-map-file=") + *path).str());
}
}
}

if (searchPathOpts.getSDKPath().empty()) {
Expand Down Expand Up @@ -870,6 +863,92 @@ importer::addCommonInvocationArguments(
}
}

/// 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.
static SmallVector<std::pair<std::string, std::string>>
getClangInvocationFileMapping(ASTContext &ctx) {
using Path = SmallString<128>;

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

SearchPathOptions &searchPathOpts = ctx.SearchPathOpts;

Path sdkPath(searchPathOpts.getSDKPath());
if (sdkPath.empty())
sdkPath = "/";

// Currently only a modulemap for libstdc++ is injected.
if (!ctx.LangOpts.EnableCXXInterop)
return {};

Path actualModuleMapPath;
Path buffer;
if (auto path = getLibStdCxxModuleMapPath(searchPathOpts, triple, buffer))
actualModuleMapPath = path.getValue();
else
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 libstdcxx.h header and reference all libstdc++ headers
// directly from the modulemap.
Path actualHeaderPath = actualModuleMapPath;
llvm::sys::path::remove_filename(actualHeaderPath);
llvm::sys::path::append(actualHeaderPath, "libstdcxx.h");

Path cxxStdlibsRoot(sdkPath);
llvm::sys::path::append(cxxStdlibsRoot, "usr", "include", "c++");
if (!llvm::sys::fs::exists(cxxStdlibsRoot))
return {};

// Collect all installed versions of libstdc++. We currently have no way to
// know which libstdc++ version will be used for this Clang invocation.
// TODO: extract this information from the Clang driver.
SmallVector<Path, 1> cxxStdlibDirs;
std::error_code errorCode;
for (llvm::vfs::directory_iterator
iter = ctx.SourceMgr.getFileSystem()->dir_begin(cxxStdlibsRoot,
errorCode),
endIter;
!errorCode && iter != endIter; iter = iter.increment(errorCode)) {
cxxStdlibDirs.push_back(Path(iter->path()));
}

SmallVector<std::pair<std::string, std::string>> result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ Here

// Inject a modulemap into the VFS for each of the libstdc++ versions.
for (const Path &cxxStdlibDir : cxxStdlibDirs) {
// 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))
continue;

Path injectedModuleMapPath = cxxStdlibDir;
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
if (llvm::sys::fs::exists(injectedModuleMapPath))
continue;

Path injectedHeaderPath = cxxStdlibDir;
llvm::sys::path::append(injectedHeaderPath, "libstdcxx.h");

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

bool ClangImporter::canReadPCH(StringRef PCHFilename) {
if (!llvm::sys::fs::exists(PCHFilename))
return false;
Expand Down Expand Up @@ -1122,9 +1201,10 @@ ClangImporter::create(ASTContext &ctx,
}
}

auto fileMapping = getClangInvocationFileMapping(ctx);
// Wrap Swift's FS to allow Clang to override the working directory
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS =
llvm::vfs::RedirectingFileSystem::create({}, true,
llvm::vfs::RedirectingFileSystem::create(fileMapping, true,
*ctx.SourceMgr.getFileSystem());

// Create a new Clang compiler invocation.
Expand Down
136 changes: 68 additions & 68 deletions stdlib/public/Cxx/libstdcxx.h
Original file line number Diff line number Diff line change
@@ -1,79 +1,79 @@
#include <algorithm>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did these have to change? Is it ever going to be a problem for users if they use angle brackets in their includes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll work fine with angle brackets as well. I just wanted to make it more clear that both the modulemap & the header are now located in the same directory as the stdlib headers, so there's no need to use angle brackets.

#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <typeinfo>
#include <valarray>
#include <vector>
#include <array>
#include <atomic>
#include <chrono>
#include <codecvt>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include "algorithm"
#include "bitset"
#include "complex"
#include "deque"
#include "exception"
#include "fstream"
#include "functional"
#include "iomanip"
#include "ios"
#include "iosfwd"
#include "iostream"
#include "istream"
#include "iterator"
#include "limits"
#include "list"
#include "locale"
#include "map"
#include "memory"
#include "new"
#include "numeric"
#include "ostream"
#include "queue"
#include "set"
#include "sstream"
#include "stack"
#include "stdexcept"
#include "streambuf"
#include "string"
#include "utility"
#include "typeinfo"
#include "valarray"
#include "vector"
#include "array"
#include "atomic"
#include "chrono"
#include "codecvt"
#include "condition_variable"
#include "forward_list"
#include "future"
#include "initializer_list"
#include "mutex"
#include "random"
#include "ratio"
#include "regex"
#include "scoped_allocator"
#include "system_error"
#include "thread"
#include "tuple"
#include "typeindex"
#include "type_traits"
#include "unordered_map"
#include "unordered_set"

// C++17 and newer:

#if __has_include(<any>)
#include <any>
#if __has_include("any")
#include "any"
#endif
#if __has_include(<charconv>)
#include <charconv>
#if __has_include("charconv")
#include "charconv"
#endif
#if __has_include(<execution>)
#include <execution>
#if __has_include("execution")
#include "execution"
#endif
#if __has_include(<filesystem>)
#include <filesystem>
#if __has_include("filesystem")
#include "filesystem"
#endif
#if __has_include(<memory_resource>)
#include <memory_resource>
#if __has_include("memory_resource")
#include "memory_resource"
#endif
#if __has_include(<optional>)
#include <optional>
#if __has_include("optional")
#include "optional"
#endif
#if __has_include(<string_view>)
#include <string_view>
#if __has_include("string_view")
#include "string_view"
#endif
#if __has_include(<variant>)
#include <variant>
#if __has_include("variant")
#include "variant"
#endif
9 changes: 9 additions & 0 deletions stdlib/public/Cxx/libstdcxx.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ module std {
header "libstdcxx.h"
requires cplusplus
export *

/// C compatibility headers.
module compat {
module cassert {
header "cassert"
requires cplusplus
export *
}
}
}