1 | //===- DriverUtils.cpp ----------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file contains utility functions for the ctx.driver. Because there |
10 | // are so many small functions, we created this separate file to make |
11 | // Driver.cpp less cluttered. |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "Config.h" |
16 | #include "Driver.h" |
17 | #include "lld/Common/CommonLinkerContext.h" |
18 | #include "lld/Common/Reproduce.h" |
19 | #include "llvm/Option/Option.h" |
20 | #include "llvm/Support/CommandLine.h" |
21 | #include "llvm/Support/FileSystem.h" |
22 | #include "llvm/Support/Path.h" |
23 | #include "llvm/Support/TimeProfiler.h" |
24 | #include "llvm/TargetParser/Host.h" |
25 | #include "llvm/TargetParser/Triple.h" |
26 | #include <optional> |
27 | |
28 | using namespace llvm; |
29 | using namespace llvm::sys; |
30 | using namespace llvm::opt; |
31 | using namespace lld; |
32 | using namespace lld::elf; |
33 | |
34 | // Create OptTable |
35 | |
36 | #define OPTTABLE_STR_TABLE_CODE |
37 | #include "Options.inc" |
38 | #undef OPTTABLE_STR_TABLE_CODE |
39 | |
40 | // Create prefix string literals used in Options.td |
41 | #define OPTTABLE_PREFIXES_TABLE_CODE |
42 | #include "Options.inc" |
43 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
44 | |
45 | // Create table mapping all options defined in Options.td |
46 | static constexpr opt::OptTable::Info optInfo[] = { |
47 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
48 | #include "Options.inc" |
49 | #undef OPTION |
50 | }; |
51 | |
52 | ELFOptTable::ELFOptTable() |
53 | : GenericOptTable(OptionStrTable, OptionPrefixesTable, optInfo) {} |
54 | |
55 | // Set color diagnostics according to --color-diagnostics={auto,always,never} |
56 | // or --no-color-diagnostics flags. |
57 | static void handleColorDiagnostics(Ctx &ctx, opt::InputArgList &args) { |
58 | auto *arg = args.getLastArg(OPT_color_diagnostics); |
59 | if (!arg) |
60 | return; |
61 | StringRef s = arg->getValue(); |
62 | if (s == "always" ) |
63 | ctx.e.errs().enable_colors(enable: true); |
64 | else if (s == "never" ) |
65 | ctx.e.errs().enable_colors(enable: false); |
66 | else if (s != "auto" ) |
67 | ErrAlways(ctx) << "unknown option: --color-diagnostics=" << s; |
68 | } |
69 | |
70 | static cl::TokenizerCallback getQuotingStyle(Ctx &ctx, |
71 | opt::InputArgList &args) { |
72 | if (auto *arg = args.getLastArg(OPT_rsp_quoting)) { |
73 | StringRef s = arg->getValue(); |
74 | if (s != "windows" && s != "posix" ) |
75 | ErrAlways(ctx) << "invalid response file quoting: " << s; |
76 | if (s == "windows" ) |
77 | return cl::TokenizeWindowsCommandLine; |
78 | return cl::TokenizeGNUCommandLine; |
79 | } |
80 | if (Triple(sys::getProcessTriple()).isOSWindows()) |
81 | return cl::TokenizeWindowsCommandLine; |
82 | return cl::TokenizeGNUCommandLine; |
83 | } |
84 | |
85 | // Gold LTO plugin takes a `--plugin-opt foo=bar` option as an alias for |
86 | // `--plugin-opt=foo=bar`. We want to handle `--plugin-opt=foo=` as an |
87 | // option name and `bar` as a value. Unfortunately, OptParser cannot |
88 | // handle an option with a space in it. |
89 | // |
90 | // In this function, we concatenate command line arguments so that |
91 | // `--plugin-opt <foo>` is converted to `--plugin-opt=<foo>`. This is a |
92 | // bit hacky, but looks like it is still better than handling --plugin-opt |
93 | // options by hand. |
94 | static void concatLTOPluginOptions(Ctx &ctx, |
95 | SmallVectorImpl<const char *> &args) { |
96 | SmallVector<const char *, 256> v; |
97 | for (size_t i = 0, e = args.size(); i != e; ++i) { |
98 | StringRef s = args[i]; |
99 | if ((s == "-plugin-opt" || s == "--plugin-opt" ) && i + 1 != e) { |
100 | v.push_back(Elt: ctx.saver.save(S: s + "=" + args[i + 1]).data()); |
101 | ++i; |
102 | } else { |
103 | v.push_back(Elt: args[i]); |
104 | } |
105 | } |
106 | args = std::move(v); |
107 | } |
108 | |
109 | // Parses a given list of options. |
110 | opt::InputArgList ELFOptTable::parse(Ctx &ctx, ArrayRef<const char *> argv) { |
111 | // Make InputArgList from string vectors. |
112 | unsigned missingIndex; |
113 | unsigned missingCount; |
114 | SmallVector<const char *, 256> vec(argv.data(), argv.data() + argv.size()); |
115 | |
116 | // We need to get the quoting style for response files before parsing all |
117 | // options so we parse here before and ignore all the options but |
118 | // --rsp-quoting. |
119 | opt::InputArgList args = this->ParseArgs(Args: vec, MissingArgIndex&: missingIndex, MissingArgCount&: missingCount); |
120 | |
121 | // Expand response files (arguments in the form of @<filename>) |
122 | // and then parse the argument again. |
123 | cl::ExpandResponseFiles(Saver&: ctx.saver, Tokenizer: getQuotingStyle(ctx, args), Argv&: vec); |
124 | concatLTOPluginOptions(ctx, args&: vec); |
125 | args = this->ParseArgs(Args: vec, MissingArgIndex&: missingIndex, MissingArgCount&: missingCount); |
126 | |
127 | handleColorDiagnostics(ctx, args); |
128 | if (missingCount) |
129 | ErrAlways(ctx) << args.getArgString(Index: missingIndex) << ": missing argument" ; |
130 | |
131 | for (opt::Arg *arg : args.filtered(OPT_UNKNOWN)) { |
132 | std::string nearest; |
133 | if (findNearest(arg->getAsString(args), nearest) > 1) |
134 | ErrAlways(ctx) << "unknown argument '" << arg->getAsString(args) << "'" ; |
135 | else |
136 | ErrAlways(ctx) << "unknown argument '" << arg->getAsString(args) |
137 | << "', did you mean '" << nearest << "'" ; |
138 | } |
139 | return args; |
140 | } |
141 | |
142 | void elf::printHelp(Ctx &ctx) { |
143 | auto &outs = ctx.e.outs(); |
144 | ELFOptTable().printHelp( |
145 | OS&: outs, Usage: (ctx.arg.progName + " [options] file..." ).str().c_str(), Title: "lld" , |
146 | ShowHidden: false /*ShowHidden*/, ShowAllAliases: true /*ShowAllAliases*/); |
147 | outs << "\n" ; |
148 | |
149 | // Scripts generated by Libtool versions up to 2021-10 expect /: supported |
150 | // targets:.* elf/ in a message for the --help option. If it doesn't match, |
151 | // the scripts assume that the linker doesn't support very basic features |
152 | // such as shared libraries. Therefore, we need to print out at least "elf". |
153 | outs << ctx.arg.progName << ": supported targets: elf\n" ; |
154 | } |
155 | |
156 | static std::string rewritePath(StringRef s) { |
157 | if (fs::exists(Path: s)) |
158 | return relativeToRoot(path: s); |
159 | return std::string(s); |
160 | } |
161 | |
162 | // Reconstructs command line arguments so that so that you can re-run |
163 | // the same command with the same inputs. This is for --reproduce. |
164 | std::string elf::createResponseFile(const opt::InputArgList &args) { |
165 | SmallString<0> data; |
166 | raw_svector_ostream os(data); |
167 | os << "--chroot .\n" ; |
168 | |
169 | // Copy the command line to the output while rewriting paths. |
170 | for (auto *arg : args) { |
171 | switch (arg->getOption().getID()) { |
172 | case OPT_reproduce: |
173 | break; |
174 | case OPT_INPUT: |
175 | os << quote(s: rewritePath(s: arg->getValue())) << "\n" ; |
176 | break; |
177 | case OPT_o: |
178 | case OPT_Map: |
179 | case OPT_dependency_file: |
180 | case OPT_print_archive_stats: |
181 | case OPT_why_extract: |
182 | // If an output path contains directories, "lld @response.txt" will |
183 | // likely fail because the archive we are creating doesn't contain empty |
184 | // directories for the output path (-o doesn't create directories). |
185 | // Strip directories to prevent the issue. |
186 | os << arg->getSpelling(); |
187 | if (arg->getOption().getRenderStyle() == opt::Option::RenderSeparateStyle) |
188 | os << ' '; |
189 | os << quote(s: path::filename(path: arg->getValue())) << '\n'; |
190 | break; |
191 | case OPT_lto_sample_profile: |
192 | os << arg->getSpelling() << quote(s: rewritePath(s: arg->getValue())) << "\n" ; |
193 | break; |
194 | case OPT_call_graph_ordering_file: |
195 | case OPT_default_script: |
196 | case OPT_dynamic_list: |
197 | case OPT_export_dynamic_symbol_list: |
198 | case OPT_just_symbols: |
199 | case OPT_library_path: |
200 | case OPT_remap_inputs_file: |
201 | case OPT_retain_symbols_file: |
202 | case OPT_rpath: |
203 | case OPT_script: |
204 | case OPT_symbol_ordering_file: |
205 | case OPT_sysroot: |
206 | case OPT_version_script: |
207 | os << arg->getSpelling() << " " << quote(s: rewritePath(s: arg->getValue())) |
208 | << "\n" ; |
209 | break; |
210 | default: |
211 | os << toString(arg: *arg) << "\n" ; |
212 | } |
213 | } |
214 | return std::string(data); |
215 | } |
216 | |
217 | // Find a file by concatenating given paths. If a resulting path |
218 | // starts with "=", the character is replaced with a --sysroot value. |
219 | static std::optional<std::string> findFile(Ctx &ctx, StringRef path1, |
220 | const Twine &path2) { |
221 | SmallString<128> s; |
222 | if (path1.starts_with(Prefix: "=" )) |
223 | path::append(path&: s, a: ctx.arg.sysroot, b: path1.substr(Start: 1), c: path2); |
224 | else |
225 | path::append(path&: s, a: path1, b: path2); |
226 | |
227 | if (fs::exists(Path: s)) |
228 | return std::string(s); |
229 | return std::nullopt; |
230 | } |
231 | |
232 | std::optional<std::string> elf::findFromSearchPaths(Ctx &ctx, StringRef path) { |
233 | for (StringRef dir : ctx.arg.searchPaths) |
234 | if (std::optional<std::string> s = findFile(ctx, path1: dir, path2: path)) |
235 | return s; |
236 | return std::nullopt; |
237 | } |
238 | |
239 | // This is for -l<basename>. We'll look for lib<basename>.so or lib<basename>.a from |
240 | // search paths. |
241 | std::optional<std::string> elf::searchLibraryBaseName(Ctx &ctx, |
242 | StringRef name) { |
243 | for (StringRef dir : ctx.arg.searchPaths) { |
244 | if (!ctx.arg.isStatic) |
245 | if (std::optional<std::string> s = |
246 | findFile(ctx, path1: dir, path2: "lib" + name + ".so" )) |
247 | return s; |
248 | if (std::optional<std::string> s = findFile(ctx, path1: dir, path2: "lib" + name + ".a" )) |
249 | return s; |
250 | } |
251 | return std::nullopt; |
252 | } |
253 | |
254 | // This is for -l<namespec>. |
255 | std::optional<std::string> elf::searchLibrary(Ctx &ctx, StringRef name) { |
256 | llvm::TimeTraceScope timeScope("Locate library" , name); |
257 | if (name.starts_with(Prefix: ":" )) |
258 | return findFromSearchPaths(ctx, path: name.substr(Start: 1)); |
259 | return searchLibraryBaseName(ctx, name); |
260 | } |
261 | |
262 | // If a linker/version script doesn't exist in the current directory, we also |
263 | // look for the script in the '-L' search paths. This matches the behaviour of |
264 | // '-T', --version-script=, and linker script INPUT() command in ld.bfd. |
265 | std::optional<std::string> elf::searchScript(Ctx &ctx, StringRef name) { |
266 | if (fs::exists(Path: name)) |
267 | return name.str(); |
268 | return findFromSearchPaths(ctx, path: name); |
269 | } |
270 | |