blob: bc3efd1b97aca94cf60bf8c4a09ba373ca6d5714 [file] [log] [blame]
kaipi149e95b2018-02-06 08:56:05 -08001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// wrapped_clang.cc: Pass args to 'xcrun clang' and zip dsym files.
16//
17// wrapped_clang passes its args to clang, but also supports a separate set of
18// invocations to generate dSYM files. If "DSYM_HINT" flags are passed in, they
19// are used to construct that separate set of invocations (instead of being
20// passed to clang).
21// The following "DSYM_HINT" flags control dsym generation. If any one if these
22// are passed in, then they all must be passed in.
23// "DSYM_HINT_LINKED_BINARY": Workspace-relative path to binary output of the
24// link action generating the dsym file.
davg97310af2018-10-09 10:27:38 -070025// "DSYM_HINT_DSYM_PATH": Workspace-relative path to dSYM dwarf file.
kaipi149e95b2018-02-06 08:56:05 -080026
27#include <libgen.h>
28#include <spawn.h>
Keith Smiley618f7c02020-05-13 01:42:13 -070029#include <string.h>
kaipi149e95b2018-02-06 08:56:05 -080030#include <sys/wait.h>
31#include <unistd.h>
Keith Smiley618f7c02020-05-13 01:42:13 -070032
kaipi149e95b2018-02-06 08:56:05 -080033#include <cerrno>
34#include <climits>
35#include <cstdio>
36#include <cstdlib>
37#include <fstream>
38#include <iostream>
39#include <map>
40#include <memory>
41#include <sstream>
kaipi149e95b2018-02-06 08:56:05 -080042#include <utility>
43#include <vector>
44
45extern char **environ;
46
47namespace {
48
davga8fa6e22019-04-23 11:52:59 -070049constexpr char kAddASTPathPrefix[] = "-Wl,-add_ast_path,";
50
kaipi149e95b2018-02-06 08:56:05 -080051// Returns the base name of the given filepath. For example, given
52// /foo/bar/baz.txt, returns 'baz.txt'.
53const char *Basename(const char *filepath) {
54 const char *base = strrchr(filepath, '/');
55 return base ? (base + 1) : filepath;
56}
57
Keith Smileyf08819b2020-11-12 03:13:54 -080058// Unescape and unquote an argument read from a line of a response file.
59static std::string Unescape(const std::string &arg) {
60 std::string result;
61 auto length = arg.size();
62 for (size_t i = 0; i < length; ++i) {
63 auto ch = arg[i];
64
65 // If it's a backslash, consume it and append the character that follows.
66 if (ch == '\\' && i + 1 < length) {
67 ++i;
68 result.push_back(arg[i]);
69 continue;
70 }
71
72 // If it's a quote, process everything up to the matching quote, unescaping
73 // backslashed characters as needed.
74 if (ch == '"' || ch == '\'') {
75 auto quote = ch;
76 ++i;
77 while (i != length && arg[i] != quote) {
78 if (arg[i] == '\\' && i + 1 < length) {
79 ++i;
80 }
81 result.push_back(arg[i]);
82 ++i;
83 }
84 if (i == length) {
85 break;
86 }
87 continue;
88 }
89
90 // It's a regular character.
91 result.push_back(ch);
92 }
93
94 return result;
95}
96
kaipi149e95b2018-02-06 08:56:05 -080097// Converts an array of string arguments to char *arguments.
98// The first arg is reduced to its basename as per execve conventions.
99// Note that the lifetime of the char* arguments in the returned array
100// are controlled by the lifetime of the strings in args.
101std::vector<const char *> ConvertToCArgs(const std::vector<std::string> &args) {
102 std::vector<const char *> c_args;
103 c_args.push_back(Basename(args[0].c_str()));
104 for (int i = 1; i < args.size(); i++) {
105 c_args.push_back(args[i].c_str());
106 }
107 c_args.push_back(nullptr);
108 return c_args;
109}
110
bbarenf341e0d2018-07-17 16:02:09 -0700111// Spawns a subprocess for given arguments args. The first argument is used
112// for the executable path.
113void RunSubProcess(const std::vector<std::string> &args) {
kaipi149e95b2018-02-06 08:56:05 -0800114 std::vector<const char *> exec_argv = ConvertToCArgs(args);
115 pid_t pid;
Vertexwahn26c7e102021-03-10 07:25:59 -0800116 int status = posix_spawn(&pid, args[0].c_str(), nullptr, nullptr,
kaipi149e95b2018-02-06 08:56:05 -0800117 const_cast<char **>(exec_argv.data()), environ);
118 if (status == 0) {
119 int wait_status;
120 do {
121 wait_status = waitpid(pid, &status, 0);
122 } while ((wait_status == -1) && (errno == EINTR));
123 if (wait_status < 0) {
Keith Smileyf08819b2020-11-12 03:13:54 -0800124 std::cerr << "Error waiting on child process '" << args[0] << "'. "
kaipi149e95b2018-02-06 08:56:05 -0800125 << strerror(errno) << "\n";
bbarenf341e0d2018-07-17 16:02:09 -0700126 abort();
kaipi149e95b2018-02-06 08:56:05 -0800127 }
128 if (WEXITSTATUS(status) != 0) {
Keith Smileyf08819b2020-11-12 03:13:54 -0800129 std::cerr << "Error in child process '" << args[0] << "'. "
kaipi149e95b2018-02-06 08:56:05 -0800130 << WEXITSTATUS(status) << "\n";
bbarenf341e0d2018-07-17 16:02:09 -0700131 abort();
kaipi149e95b2018-02-06 08:56:05 -0800132 }
133 } else {
Keith Smileyf08819b2020-11-12 03:13:54 -0800134 std::cerr << "Error forking process '" << args[0] << "'. "
kaipi149e95b2018-02-06 08:56:05 -0800135 << strerror(status) << "\n";
bbarenf341e0d2018-07-17 16:02:09 -0700136 abort();
kaipi149e95b2018-02-06 08:56:05 -0800137 }
138}
139
kaipi149e95b2018-02-06 08:56:05 -0800140// Finds and replaces all instances of oldsub with newsub, in-place on str.
141void FindAndReplace(const std::string &oldsub, const std::string &newsub,
142 std::string *str) {
143 int start = 0;
144 while ((start = str->find(oldsub, start)) != std::string::npos) {
145 str->replace(start, oldsub.length(), newsub);
146 start += newsub.length();
147 }
148}
149
150// If arg is of the classic flag form "foo=bar", and flagname is 'foo', sets
151// str to point to a new std::string 'bar' and returns true.
152// Otherwise, returns false.
153bool SetArgIfFlagPresent(const std::string &arg, const std::string &flagname,
154 std::string *str) {
155 std::string prefix_string = flagname + "=";
156 if (arg.compare(0, prefix_string.length(), prefix_string) == 0) {
157 *str = arg.substr(prefix_string.length());
158 return true;
159 }
160 return false;
161}
162
bbarenf341e0d2018-07-17 16:02:09 -0700163// Returns the DEVELOPER_DIR environment variable in the current process
164// environment. Aborts if this variable is unset.
kaipi149e95b2018-02-06 08:56:05 -0800165std::string GetMandatoryEnvVar(const std::string &var_name) {
166 char *env_value = getenv(var_name.c_str());
bbarenf341e0d2018-07-17 16:02:09 -0700167 if (env_value == nullptr) {
kaipi149e95b2018-02-06 08:56:05 -0800168 std::cerr << "Error: " << var_name << " not set.\n";
bbarenf341e0d2018-07-17 16:02:09 -0700169 abort();
kaipi149e95b2018-02-06 08:56:05 -0800170 }
171 return env_value;
172}
173
davga8fa6e22019-04-23 11:52:59 -0700174// Returns true if `str` starts with the specified `prefix`.
175bool StartsWith(const std::string &str, const std::string &prefix) {
176 return str.compare(0, prefix.size(), prefix) == 0;
177}
178
179// If *`str` begins `prefix`, strip it out and return true.
180// Otherwise leave *`str` unchanged and return false.
181bool StripPrefixStringIfPresent(std::string *str, const std::string &prefix) {
182 if (StartsWith(*str, prefix)) {
183 *str = str->substr(prefix.size());
184 return true;
185 }
186 return false;
187}
188
Keith Smileyf08819b2020-11-12 03:13:54 -0800189// An RAII temporary file.
190class TempFile {
191 public:
192 // Create a new temporary file using the given path template string (the same
193 // form used by `mkstemp`). The file will automatically be deleted when the
194 // object goes out of scope.
195 static std::unique_ptr<TempFile> Create(const std::string &path_template) {
196 const char *tmpDir = getenv("TMPDIR");
197 if (!tmpDir) {
198 tmpDir = "/tmp";
199 }
200 size_t size = strlen(tmpDir) + path_template.size() + 2;
201 std::unique_ptr<char[]> path(new char[size]);
202 snprintf(path.get(), size, "%s/%s", tmpDir, path_template.c_str());
203
204 if (mkstemp(path.get()) == -1) {
205 std::cerr << "Failed to create temporary file '" << path.get()
206 << "': " << strerror(errno) << "\n";
207 return nullptr;
208 }
209 return std::unique_ptr<TempFile>(new TempFile(path.get()));
210 }
211
212 // Explicitly make TempFile non-copyable and movable.
213 TempFile(const TempFile &) = delete;
214 TempFile &operator=(const TempFile &) = delete;
215 TempFile(TempFile &&) = default;
216 TempFile &operator=(TempFile &&) = default;
217
218 ~TempFile() { remove(path_.c_str()); }
219
220 // Gets the path to the temporary file.
221 std::string GetPath() const { return path_; }
222
223 private:
224 explicit TempFile(const std::string &path) : path_(path) {}
225
226 std::string path_;
227};
228
229static std::unique_ptr<TempFile> WriteResponseFile(
230 const std::vector<std::string> &args) {
231 auto response_file = TempFile::Create("wrapped_clang_params.XXXXXX");
232 std::ofstream response_file_stream(response_file->GetPath());
233
234 for (const auto &arg : args) {
235 // When Clang/Swift write out a response file to communicate from driver to
236 // frontend, they just quote every argument to be safe; we duplicate that
237 // instead of trying to be "smarter" and only quoting when necessary.
238 response_file_stream << '"';
239 for (auto ch : arg) {
240 if (ch == '"' || ch == '\\') {
241 response_file_stream << '\\';
242 }
243 response_file_stream << ch;
244 }
245 response_file_stream << "\"\n";
246 }
247
248 response_file_stream.close();
249 return response_file;
250}
251
252void ProcessArgument(const std::string arg, const std::string developer_dir,
253 const std::string sdk_root, const std::string cwd,
254 bool relative_ast_path, std::string &linked_binary,
255 std::string &dsym_path,
256 std::function<void(const std::string &)> consumer);
257
258bool ProcessResponseFile(const std::string arg, const std::string developer_dir,
259 const std::string sdk_root, const std::string cwd,
260 bool relative_ast_path, std::string &linked_binary,
261 std::string &dsym_path,
262 std::function<void(const std::string &)> consumer) {
263 auto path = arg.substr(1);
264 std::ifstream original_file(path);
265 // Ignore non-file args such as '@loader_path/...'
266 if (!original_file.good()) {
267 return false;
268 }
269
270 std::string arg_from_file;
271 while (std::getline(original_file, arg_from_file)) {
272 // Arguments in response files might be quoted/escaped, so we need to
273 // unescape them ourselves.
274 ProcessArgument(Unescape(arg_from_file), developer_dir, sdk_root, cwd,
275 relative_ast_path, linked_binary, dsym_path, consumer);
276 }
277
278 return true;
279}
280
281std::string GetCurrentDirectory() {
282 // Passing null,0 causes getcwd to allocate the buffer of the correct size.
283 char *buffer = getcwd(nullptr, 0);
284 std::string cwd(buffer);
285 free(buffer);
286 return cwd;
287}
288
289void ProcessArgument(const std::string arg, const std::string developer_dir,
290 const std::string sdk_root, const std::string cwd,
291 bool relative_ast_path, std::string &linked_binary,
292 std::string &dsym_path,
293 std::function<void(const std::string &)> consumer) {
294 auto new_arg = arg;
295 if (arg[0] == '@') {
296 if (ProcessResponseFile(arg, developer_dir, sdk_root, cwd,
297 relative_ast_path, linked_binary, dsym_path,
298 consumer)) {
299 return;
300 }
301 }
302
303 if (SetArgIfFlagPresent(arg, "DSYM_HINT_LINKED_BINARY", &linked_binary)) {
304 return;
305 }
306 if (SetArgIfFlagPresent(arg, "DSYM_HINT_DSYM_PATH", &dsym_path)) {
307 return;
308 }
309
310 std::string dest_dir, bitcode_symbol_map;
Keith Smiley58bb42a2021-02-02 02:44:04 -0800311 if (SetArgIfFlagPresent(arg, "DEBUG_PREFIX_MAP_PWD", &dest_dir)) {
312 new_arg = "-fdebug-prefix-map=" + cwd + "=" + dest_dir;
313 }
Keith Smileyf08819b2020-11-12 03:13:54 -0800314 if (arg.compare("OSO_PREFIX_MAP_PWD") == 0) {
315 new_arg = "-Wl,-oso_prefix," + cwd + "/";
316 }
317
318 FindAndReplace("__BAZEL_XCODE_DEVELOPER_DIR__", developer_dir, &new_arg);
319 FindAndReplace("__BAZEL_XCODE_SDKROOT__", sdk_root, &new_arg);
320
321 // Make the `add_ast_path` options used to embed Swift module references
322 // absolute to enable Swift debugging without dSYMs: see
323 // https://forums.swift.org/t/improving-swift-lldb-support-for-path-remappings/22694
324 if (!relative_ast_path &&
325 StripPrefixStringIfPresent(&new_arg, kAddASTPathPrefix)) {
326 // Only modify relative paths.
327 if (!StartsWith(arg, "/")) {
328 new_arg = std::string(kAddASTPathPrefix) + cwd + "/" + new_arg;
329 } else {
330 new_arg = std::string(kAddASTPathPrefix) + new_arg;
331 }
332 }
333
334 consumer(new_arg);
335}
336
kaipi149e95b2018-02-06 08:56:05 -0800337} // namespace
338
339int main(int argc, char *argv[]) {
340 std::string tool_name;
341
342 std::string binary_name = Basename(argv[0]);
343 if (binary_name == "wrapped_clang_pp") {
344 tool_name = "clang++";
345 } else if (binary_name == "wrapped_clang") {
346 tool_name = "clang";
347 } else {
348 std::cerr << "Binary must either be named 'wrapped_clang' or "
349 "'wrapped_clang_pp', not "
350 << binary_name << "\n";
bbarenf341e0d2018-07-17 16:02:09 -0700351 abort();
kaipi149e95b2018-02-06 08:56:05 -0800352 }
353
354 std::string developer_dir = GetMandatoryEnvVar("DEVELOPER_DIR");
355 std::string sdk_root = GetMandatoryEnvVar("SDKROOT");
Keith Smileya1471a82020-11-11 12:01:09 -0800356 std::string linked_binary, dsym_path;
ilist7ca5d7d2020-11-04 06:48:17 -0800357
Keith Smileyf08819b2020-11-12 03:13:54 -0800358 const std::string cwd = GetCurrentDirectory();
359 std::vector<std::string> invocation_args = {"/usr/bin/xcrun", tool_name};
360 std::vector<std::string> processed_args = {};
Googler3f46dd02018-07-25 10:52:12 -0700361
Keith Smiley3d6d0c92020-10-22 05:18:51 -0700362 bool relative_ast_path = getenv("RELATIVE_AST_PATH") != nullptr;
Keith Smileyf08819b2020-11-12 03:13:54 -0800363 auto consumer = [&](const std::string &arg) {
364 processed_args.push_back(arg);
365 };
kaipi149e95b2018-02-06 08:56:05 -0800366 for (int i = 1; i < argc; i++) {
367 std::string arg(argv[i]);
368
Keith Smileyf08819b2020-11-12 03:13:54 -0800369 ProcessArgument(arg, developer_dir, sdk_root, cwd, relative_ast_path,
370 linked_binary, dsym_path, consumer);
kaipi149e95b2018-02-06 08:56:05 -0800371 }
372
davga8fa6e22019-04-23 11:52:59 -0700373 // Special mode that only prints the command. Used for testing.
374 if (getenv("__WRAPPED_CLANG_LOG_ONLY")) {
Keith Smileyf08819b2020-11-12 03:13:54 -0800375 for (const std::string &arg : invocation_args) std::cout << arg << ' ';
376 for (const std::string &arg : processed_args) std::cout << arg << ' ';
davga8fa6e22019-04-23 11:52:59 -0700377 std::cout << "\n";
378 return 0;
379 }
380
Keith Smileyf08819b2020-11-12 03:13:54 -0800381 auto response_file = WriteResponseFile(processed_args);
382 invocation_args.push_back("@" + response_file->GetPath());
383
kaipi149e95b2018-02-06 08:56:05 -0800384 // Check to see if we should postprocess with dsymutil.
385 bool postprocess = false;
davg97310af2018-10-09 10:27:38 -0700386 if ((!linked_binary.empty()) || (!dsym_path.empty())) {
Googlerecab1c82018-07-10 08:57:30 -0700387 if ((linked_binary.empty()) || (dsym_path.empty())) {
kaipi149e95b2018-02-06 08:56:05 -0800388 const char *missing_dsym_flag;
389 if (linked_binary.empty()) {
390 missing_dsym_flag = "DSYM_HINT_LINKED_BINARY";
kaipi149e95b2018-02-06 08:56:05 -0800391 } else {
Googlerecab1c82018-07-10 08:57:30 -0700392 missing_dsym_flag = "DSYM_HINT_DSYM_PATH";
kaipi149e95b2018-02-06 08:56:05 -0800393 }
394 std::cerr << "Error in clang wrapper: If any dsym "
Keith Smileyf08819b2020-11-12 03:13:54 -0800395 "hint is defined, then "
396 << missing_dsym_flag << " must be defined\n";
bbarenf341e0d2018-07-17 16:02:09 -0700397 abort();
kaipi149e95b2018-02-06 08:56:05 -0800398 } else {
399 postprocess = true;
400 }
401 }
402
Keith Smileyf08819b2020-11-12 03:13:54 -0800403 RunSubProcess(invocation_args);
Keith Smiley0299cd72021-04-01 02:32:54 -0700404 if (!postprocess) {
405 return 0;
406 }
bbarenf341e0d2018-07-17 16:02:09 -0700407
Keith Smiley47edc572021-04-01 02:58:58 -0700408 std::vector<std::string> dsymutil_args = {"/usr/bin/xcrun",
409 "dsymutil",
410 linked_binary,
411 "-o",
412 dsym_path,
413 "--flat",
414 "--no-swiftmodule-timestamp"};
Keith Smiley0299cd72021-04-01 02:32:54 -0700415 RunSubProcess(dsymutil_args);
kaipi149e95b2018-02-06 08:56:05 -0800416 return 0;
417}