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