16
16
#include " swift/AST/DiagnosticsClangImporter.h"
17
17
#include " swift/Basic/Platform.h"
18
18
#include " clang/Driver/Driver.h"
19
+ #include " clang/Driver/ToolChain.h"
19
20
#include " clang/Frontend/CompilerInstance.h"
20
21
21
22
using namespace swift ;
22
23
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) {
27
29
StringRef platform = swift::getPlatformNameForTriple (triple);
28
30
StringRef arch = swift::getMajorArchitectureName (triple);
29
31
32
+ Path result;
33
+
30
34
StringRef SDKPath = Opts.getSDKPath ();
31
35
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);
36
39
37
40
// Only specify the module map if that file actually exists. It may not;
38
41
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
39
42
// -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 ;
42
45
}
43
46
44
47
if (!Opts.RuntimeResourcePath .empty ()) {
45
- buffer .clear ();
46
- buffer .append (Opts.RuntimeResourcePath .begin (),
48
+ result .clear ();
49
+ result .append (Opts.RuntimeResourcePath .begin (),
47
50
Opts.RuntimeResourcePath .end ());
48
- llvm::sys::path::append (buffer , platform, arch, name);
51
+ llvm::sys::path::append (result , platform, arch, name);
49
52
50
53
// Only specify the module map if that file actually exists. It may not;
51
54
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
52
55
// -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 ;
55
58
}
56
59
57
60
return None;
58
61
}
59
62
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;
64
77
}
65
78
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);
70
87
}
71
88
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
+ }
75
93
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
+ }
83
100
84
- // Extract the libstdc++ installation path from Clang driver.
101
+ static clang::driver::Driver createClangDriver ( const ASTContext &ctx) {
85
102
auto clangDiags = clang::CompilerInstance::createDiagnostics (
86
103
new clang::DiagnosticOptions ());
87
104
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) {
89
149
// Flags passed to Swift with `-Xcc` might affect include paths.
90
- unsigned unused1, unused2;
91
150
std::vector<const char *> clangArgs;
92
151
for (const auto &each : ctx.ClangImporterOpts .ExtraArgs ) {
93
152
clangArgs.push_back (each.c_str ());
94
153
}
95
154
llvm::opt::InputArgList clangDriverArgs =
96
- clangDriver. getOpts (). ParseArgs (clangArgs, unused1, unused2 );
155
+ parseClangDriverArgs (clangDriver, clangArgs );
97
156
// If an SDK path was explicitly passed to Swift, make sure to pass it to
98
157
// Clang driver as well. It affects the resulting include paths.
99
158
auto sdkPath = ctx.SearchPathOpts .getSDKPath ();
@@ -103,23 +162,106 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
103
162
clangDriver.getOpts ().getOption (clang::driver::options::OPT__sysroot),
104
163
sdkPath, argIndex));
105
164
}
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 ());
110
195
return {};
111
196
}
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 );
115
197
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 ());
118
260
return {};
261
+ }
119
262
120
263
Path actualModuleMapPath;
121
- Path buffer;
122
- if (auto path = getLibStdCxxModuleMapPath (ctx.SearchPathOpts , triple, buffer))
264
+ if (auto path = getLibStdCxxModuleMapPath (ctx.SearchPathOpts , triple))
123
265
actualModuleMapPath = path.getValue ();
124
266
else
125
267
return {};
@@ -140,14 +282,10 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
140
282
// Inject a modulemap into VFS for the libstdc++ directory.
141
283
// Only inject the module map if the module does not already exist at
142
284
// {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
151
289
return {};
152
290
153
291
Path injectedHeaderPath (cxxStdlibDir);
@@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
158
296
{std::string (injectedHeaderPath), std::string (actualHeaderPath)},
159
297
};
160
298
}
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
+ }
0 commit comments