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
2122using 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+ }
0 commit comments