Skip to content

Commit 6170437

Browse files
authored
Merge pull request #59846 from apple/egorzhdan/glibc-vfs-inject
[Glibc] Use VFS to inject modulemap into Glibc include path
2 parents ceae9fd + cf33542 commit 6170437

File tree

4 files changed

+210
-74
lines changed

4 files changed

+210
-74
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ WARNING(nonmutating_without_mutable_fields,none,
117117

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

120+
WARNING(glibc_not_found, none,
121+
"glibc not found for '%0'; C stdlib may be unavailable",
122+
(StringRef))
120123
WARNING(libstdcxx_not_found, none,
121124
"libstdc++ not found for '%0'; C++ stdlib may be unavailable",
122125
(StringRef))

lib/ClangImporter/ClangImporter.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,13 +648,6 @@ importer::getNormalInvocationArguments(
648648
break;
649649
}
650650
}
651-
652-
SmallString<128> buffer;
653-
if (auto path = getGlibcModuleMapPath(searchPathOpts, triple, buffer)) {
654-
invocationArgStrs.push_back((Twine("-fmodule-map-file=") + *path).str());
655-
} else {
656-
// FIXME: Emit a warning of some kind.
657-
}
658651
}
659652

660653
if (searchPathOpts.getSDKPath().empty()) {

lib/ClangImporter/ClangIncludePaths.cpp

Lines changed: 206 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,84 +16,143 @@
1616
#include "swift/AST/DiagnosticsClangImporter.h"
1717
#include "swift/Basic/Platform.h"
1818
#include "clang/Driver/Driver.h"
19+
#include "clang/Driver/ToolChain.h"
1920
#include "clang/Frontend/CompilerInstance.h"
2021

2122
using namespace swift;
2223

23-
static Optional<StringRef> getModuleMapFilePath(StringRef name,
24-
SearchPathOptions &Opts,
25-
llvm::Triple triple,
26-
SmallVectorImpl<char> &buffer) {
24+
using Path = SmallString<128>;
25+
26+
static Optional<Path> getActualModuleMapPath(StringRef name,
27+
SearchPathOptions &Opts,
28+
const llvm::Triple &triple) {
2729
StringRef platform = swift::getPlatformNameForTriple(triple);
2830
StringRef arch = swift::getMajorArchitectureName(triple);
2931

32+
Path result;
33+
3034
StringRef SDKPath = Opts.getSDKPath();
3135
if (!SDKPath.empty()) {
32-
buffer.clear();
33-
buffer.append(SDKPath.begin(), SDKPath.end());
34-
llvm::sys::path::append(buffer, "usr", "lib", "swift");
35-
llvm::sys::path::append(buffer, platform, arch, name);
36+
result.append(SDKPath.begin(), SDKPath.end());
37+
llvm::sys::path::append(result, "usr", "lib", "swift");
38+
llvm::sys::path::append(result, platform, arch, name);
3639

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

4447
if (!Opts.RuntimeResourcePath.empty()) {
45-
buffer.clear();
46-
buffer.append(Opts.RuntimeResourcePath.begin(),
48+
result.clear();
49+
result.append(Opts.RuntimeResourcePath.begin(),
4750
Opts.RuntimeResourcePath.end());
48-
llvm::sys::path::append(buffer, platform, arch, name);
51+
llvm::sys::path::append(result, platform, arch, name);
4952

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

5760
return None;
5861
}
5962

60-
Optional<StringRef>
61-
swift::getGlibcModuleMapPath(SearchPathOptions &Opts, llvm::Triple triple,
62-
SmallVectorImpl<char> &buffer) {
63-
return getModuleMapFilePath("glibc.modulemap", Opts, triple, buffer);
63+
/// Given an include path directory, returns a path to inject the module map to.
64+
/// If a module map already exists, returns `None`.
65+
static llvm::Optional<Path> getInjectedModuleMapPath(const Path &dir) {
66+
Path legacyPath(dir);
67+
llvm::sys::path::append(legacyPath, "module.map");
68+
if (llvm::sys::fs::exists(legacyPath))
69+
return None;
70+
71+
Path path(dir);
72+
llvm::sys::path::append(path, "module.modulemap");
73+
if (llvm::sys::fs::exists(path))
74+
return None;
75+
76+
return path;
6477
}
6578

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

72-
SmallVector<std::pair<std::string, std::string>, 16>
73-
swift::getClangInvocationFileMapping(ASTContext &ctx) {
74-
using Path = SmallString<128>;
89+
static Optional<Path>
90+
getLibStdCxxModuleMapPath(SearchPathOptions &opts, const llvm::Triple &triple) {
91+
return getActualModuleMapPath("libstdcxx.modulemap", opts, triple);
92+
}
7593

76-
const llvm::Triple &triple = ctx.LangOpts.Target;
77-
// We currently only need this when building for Linux.
78-
if (!triple.isOSLinux())
79-
return {};
80-
// Android uses libc++.
81-
if (triple.isAndroid())
82-
return {};
94+
static llvm::opt::InputArgList
95+
parseClangDriverArgs(const clang::driver::Driver &clangDriver,
96+
const ArrayRef<const char *> args) {
97+
unsigned unused1, unused2;
98+
return clangDriver.getOpts().ParseArgs(args, unused1, unused2);
99+
}
83100

84-
// Extract the libstdc++ installation path from Clang driver.
101+
static clang::driver::Driver createClangDriver(const ASTContext &ctx) {
85102
auto clangDiags = clang::CompilerInstance::createDiagnostics(
86103
new clang::DiagnosticOptions());
87104
clang::driver::Driver clangDriver(ctx.ClangImporterOpts.clangPath,
88-
triple.str(), *clangDiags);
105+
ctx.LangOpts.Target.str(), *clangDiags);
106+
return clangDriver;
107+
}
108+
109+
/// Given a list of include paths and a list of file names, finds the first
110+
/// include path that contains files with all the names. This is useful for
111+
/// finding the include path for a specific library among a list of include
112+
/// paths.
113+
///
114+
/// \return a path without dots (`../`, './').
115+
static llvm::Optional<Path>
116+
findFirstIncludeDir(const llvm::opt::InputArgList &args,
117+
const ArrayRef<const char *> expectedFileNames) {
118+
// C++ stdlib paths are added as `-internal-isystem`.
119+
std::vector<std::string> includeDirs =
120+
args.getAllArgValues(clang::driver::options::OPT_internal_isystem);
121+
// C stdlib paths are added as `-internal-externc-isystem`.
122+
llvm::append_range(includeDirs,
123+
args.getAllArgValues(
124+
clang::driver::options::OPT_internal_externc_isystem));
125+
126+
for (const auto &includeDir : includeDirs) {
127+
Path dir(includeDir);
128+
bool allExpectedExist = true;
129+
for (auto expectedFileName : expectedFileNames) {
130+
Path expectedFile(dir);
131+
llvm::sys::path::append(expectedFile, expectedFileName);
132+
if (!llvm::sys::fs::exists(expectedFile)) {
133+
allExpectedExist = false;
134+
break;
135+
}
136+
}
137+
138+
if (allExpectedExist) {
139+
// VFS does not allow mapping paths that contain `../` or `./`.
140+
llvm::sys::path::remove_dots(dir, /*remove_dot_dot=*/true);
141+
return dir;
142+
}
143+
}
144+
return None;
145+
}
146+
147+
static llvm::opt::InputArgList
148+
createClangArgs(const ASTContext &ctx, clang::driver::Driver &clangDriver) {
89149
// Flags passed to Swift with `-Xcc` might affect include paths.
90-
unsigned unused1, unused2;
91150
std::vector<const char *> clangArgs;
92151
for (const auto &each : ctx.ClangImporterOpts.ExtraArgs) {
93152
clangArgs.push_back(each.c_str());
94153
}
95154
llvm::opt::InputArgList clangDriverArgs =
96-
clangDriver.getOpts().ParseArgs(clangArgs, unused1, unused2);
155+
parseClangDriverArgs(clangDriver, clangArgs);
97156
// If an SDK path was explicitly passed to Swift, make sure to pass it to
98157
// Clang driver as well. It affects the resulting include paths.
99158
auto sdkPath = ctx.SearchPathOpts.getSDKPath();
@@ -103,23 +162,106 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
103162
clangDriver.getOpts().getOption(clang::driver::options::OPT__sysroot),
104163
sdkPath, argIndex));
105164
}
106-
auto cxxStdlibDirs =
107-
clangDriver.getLibStdCxxIncludePaths(clangDriverArgs, triple);
108-
if (cxxStdlibDirs.empty()) {
109-
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
165+
return clangDriverArgs;
166+
}
167+
168+
static SmallVector<std::pair<std::string, std::string>, 2>
169+
getGlibcFileMapping(ASTContext &ctx) {
170+
const llvm::Triple &triple = ctx.LangOpts.Target;
171+
// We currently only need this when building for Linux.
172+
if (!triple.isOSLinux())
173+
return {};
174+
175+
// Extract the Glibc path from Clang driver.
176+
auto clangDriver = createClangDriver(ctx);
177+
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
178+
179+
llvm::opt::ArgStringList includeArgStrings;
180+
const auto &clangToolchain =
181+
clangDriver.getToolChain(clangDriverArgs, triple);
182+
clangToolchain.AddClangSystemIncludeArgs(clangDriverArgs, includeArgStrings);
183+
auto parsedIncludeArgs = parseClangDriverArgs(clangDriver, includeArgStrings);
184+
185+
// Find the include path that contains Glibc headers. We use three arbitrarily
186+
// chosen headers to determine if the include path actually contains Glibc.
187+
// Ideally we would check that all of the headers referenced from the
188+
// modulemap are present.
189+
Path glibcDir;
190+
if (auto dir = findFirstIncludeDir(parsedIncludeArgs,
191+
{"inttypes.h", "unistd.h", "stdint.h"})) {
192+
glibcDir = dir.getValue();
193+
} else {
194+
ctx.Diags.diagnose(SourceLoc(), diag::glibc_not_found, triple.str());
110195
return {};
111196
}
112-
Path cxxStdlibDir(cxxStdlibDirs.front());
113-
// VFS does not allow mapping paths that contain `../` or `./`.
114-
llvm::sys::path::remove_dots(cxxStdlibDir, /*remove_dot_dot=*/true);
115197

116-
// Currently only a modulemap for libstdc++ is injected.
117-
if (!ctx.LangOpts.EnableCXXInterop)
198+
Path actualModuleMapPath;
199+
if (auto path = getGlibcModuleMapPath(ctx.SearchPathOpts, triple))
200+
actualModuleMapPath = path.getValue();
201+
else
202+
// FIXME: Emit a warning of some kind.
203+
return {};
204+
205+
// Only inject the module map if it actually exists. It may not, for example
206+
// if `swiftc -target x86_64-unknown-linux-gnu -emit-ir` is invoked using
207+
// a Swift compiler not built for Linux targets.
208+
if (!llvm::sys::fs::exists(actualModuleMapPath))
209+
// FIXME: emit a warning of some kind.
210+
return {};
211+
212+
// TODO: remove the SwiftGlibc.h header and reference all Glibc headers
213+
// directly from the modulemap.
214+
Path actualHeaderPath = actualModuleMapPath;
215+
llvm::sys::path::remove_filename(actualHeaderPath);
216+
llvm::sys::path::append(actualHeaderPath, "SwiftGlibc.h");
217+
218+
Path injectedModuleMapPath(glibcDir);
219+
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
220+
221+
Path injectedHeaderPath(glibcDir);
222+
llvm::sys::path::append(injectedHeaderPath, "SwiftGlibc.h");
223+
224+
return {
225+
{std::string(injectedModuleMapPath), std::string(actualModuleMapPath)},
226+
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
227+
};
228+
}
229+
230+
static SmallVector<std::pair<std::string, std::string>, 2>
231+
getLibStdCxxFileMapping(ASTContext &ctx) {
232+
assert(ctx.LangOpts.EnableCXXInterop &&
233+
"libstdc++ is only injected if C++ interop is enabled");
234+
235+
const llvm::Triple &triple = ctx.LangOpts.Target;
236+
// We currently only need this when building for Linux.
237+
if (!triple.isOSLinux())
238+
return {};
239+
// Android uses libc++.
240+
if (triple.isAndroid())
241+
return {};
242+
243+
// Extract the libstdc++ installation path from Clang driver.
244+
auto clangDriver = createClangDriver(ctx);
245+
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
246+
247+
llvm::opt::ArgStringList stdlibArgStrings;
248+
const auto &clangToolchain =
249+
clangDriver.getToolChain(clangDriverArgs, triple);
250+
clangToolchain.AddClangCXXStdlibIncludeArgs(clangDriverArgs,
251+
stdlibArgStrings);
252+
auto parsedStdlibArgs = parseClangDriverArgs(clangDriver, stdlibArgStrings);
253+
254+
Path cxxStdlibDir;
255+
if (auto dir = findFirstIncludeDir(parsedStdlibArgs,
256+
{"cstdlib", "string", "vector"})) {
257+
cxxStdlibDir = dir.getValue();
258+
} else {
259+
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
118260
return {};
261+
}
119262

120263
Path actualModuleMapPath;
121-
Path buffer;
122-
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple, buffer))
264+
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple))
123265
actualModuleMapPath = path.getValue();
124266
else
125267
return {};
@@ -140,14 +282,10 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
140282
// Inject a modulemap into VFS for the libstdc++ directory.
141283
// Only inject the module map if the module does not already exist at
142284
// {sysroot}/usr/include/module.{map,modulemap}.
143-
Path injectedModuleMapLegacyPath(cxxStdlibDir);
144-
llvm::sys::path::append(injectedModuleMapLegacyPath, "module.map");
145-
if (llvm::sys::fs::exists(injectedModuleMapLegacyPath))
146-
return {};
147-
148-
Path injectedModuleMapPath(cxxStdlibDir);
149-
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
150-
if (llvm::sys::fs::exists(injectedModuleMapPath))
285+
Path injectedModuleMapPath;
286+
if (auto path = getInjectedModuleMapPath(cxxStdlibDir))
287+
injectedModuleMapPath = path.getValue();
288+
else
151289
return {};
152290

153291
Path injectedHeaderPath(cxxStdlibDir);
@@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
158296
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
159297
};
160298
}
299+
300+
SmallVector<std::pair<std::string, std::string>, 2>
301+
swift::getClangInvocationFileMapping(ASTContext &ctx) {
302+
SmallVector<std::pair<std::string, std::string>, 2> result;
303+
304+
result.append(getGlibcFileMapping(ctx));
305+
306+
if (ctx.LangOpts.EnableCXXInterop) {
307+
result.append(getLibStdCxxFileMapping(ctx));
308+
}
309+
return result;
310+
}

lib/ClangImporter/ClangIncludePaths.h

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,13 @@
1414
#define SWIFT_CLANG_INCLUDE_PATHS_H
1515

1616
#include "swift/AST/ASTContext.h"
17-
#include "swift/AST/SearchPathOptions.h"
1817

1918
namespace swift {
2019

21-
/// Finds the glibc.modulemap file relative to the provided resource dir.
22-
///
23-
/// Note that the module map used for Glibc depends on the target we're
24-
/// compiling for, and is not included in the resource directory with the other
25-
/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap.
26-
Optional<StringRef> getGlibcModuleMapPath(SearchPathOptions &Opts,
27-
llvm::Triple triple,
28-
SmallVectorImpl<char> &buffer);
29-
3020
/// On Linux, some platform libraries (glibc, libstdc++) are not modularized.
3121
/// We inject modulemaps for those libraries into their include directories
3222
/// to allow using them from Swift.
33-
SmallVector<std::pair<std::string, std::string>, 16>
23+
SmallVector<std::pair<std::string, std::string>, 2>
3424
getClangInvocationFileMapping(ASTContext &ctx);
3525

3626
} // namespace swift

0 commit comments

Comments
 (0)