blob: 0f92db9b3c4809b91360d78aa8eb6ecd970795e7 [file] [log] [blame] [edit]
// Copyright 2017 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// wrapped_clang.cc: Pass args to 'xcrun clang' and zip dsym files.
//
// wrapped_clang passes its args to clang, but also supports a separate set of
// invocations to generate dSYM files. If "DSYM_HINT" flags are passed in, they
// are used to construct that separate set of invocations (instead of being
// passed to clang).
// The following "DSYM_HINT" flags control dsym generation. If any one if these
// are passed in, then they all must be passed in.
// "DSYM_HINT_LINKED_BINARY": Workspace-relative path to binary output of the
// link action generating the dsym file.
// "DSYM_HINT_DSYM_PATH": Workspace-relative path to dSYM directory.
// "DSYM_HINT_DSYM_BUNDLE_ZIP": Workspace-relative path to dSYM zip.
//
// Likewise, this wrapper also contains a workaround for a bug in ld that causes
// flaky builds when using Bitcode symbol maps. ld allows the
// -bitcode_symbol_map to be either a directory (into which the file will be
// written) or a file, but the return value of the call to ::stat is never
// checked so examining the S_ISDIR bit of the struct afterwards returns
// true/false randomly depending on what data happened to be in memory at the
// time it was called:
// https://github.com/michaelweiser/ld64/blob/9c3700b64ed03e2d55ba094176bf6a172bf2bc6b/src/ld/Options.cpp#L3261
// To address this, we prepend a special "BITCODE_TOUCH_SYMBOL_MAP=" flag to the
// symbol map filename and touch it before passing it along to clang, forcing
// the file to exist.
#include <libgen.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
extern char **environ;
namespace {
// Returns the base name of the given filepath. For example, given
// /foo/bar/baz.txt, returns 'baz.txt'.
const char *Basename(const char *filepath) {
const char *base = strrchr(filepath, '/');
return base ? (base + 1) : filepath;
}
// Converts an array of string arguments to char *arguments.
// The first arg is reduced to its basename as per execve conventions.
// Note that the lifetime of the char* arguments in the returned array
// are controlled by the lifetime of the strings in args.
std::vector<const char *> ConvertToCArgs(const std::vector<std::string> &args) {
std::vector<const char *> c_args;
c_args.push_back(Basename(args[0].c_str()));
for (int i = 1; i < args.size(); i++) {
c_args.push_back(args[i].c_str());
}
c_args.push_back(nullptr);
return c_args;
}
// Turn our current process into a new process. Avoids fork overhead.
// Never returns.
void ExecProcess(const std::vector<std::string> &args) {
std::vector<const char *> exec_argv = ConvertToCArgs(args);
execv(args[0].c_str(), const_cast<char **>(exec_argv.data()));
std::cerr << "Error executing child process.'" << args[0] << "'. "
<< strerror(errno) << "\n";
abort();
}
// Spawns a subprocess for given arguments args. The first argument is used
// for the executable path.
void RunSubProcess(const std::vector<std::string> &args) {
std::vector<const char *> exec_argv = ConvertToCArgs(args);
pid_t pid;
int status = posix_spawn(&pid, args[0].c_str(), NULL, NULL,
const_cast<char **>(exec_argv.data()), environ);
if (status == 0) {
int wait_status;
do {
wait_status = waitpid(pid, &status, 0);
} while ((wait_status == -1) && (errno == EINTR));
if (wait_status < 0) {
std::cerr << "Error waiting on child process '" << args[0] << "'. "
<< strerror(errno) << "\n";
abort();
}
if (WEXITSTATUS(status) != 0) {
std::cerr << "Error in child process '" << args[0] << "'. "
<< WEXITSTATUS(status) << "\n";
abort();
}
} else {
std::cerr << "Error forking process '" << args[0] << "'. "
<< strerror(status) << "\n";
abort();
}
}
// Splits txt using whitespace delimiter, pushing each substring into strs.
void SplitAndAdd(const std::string &txt, std::vector<std::string> &strs) {
for (std::istringstream stream(txt); !stream.eof();) {
std::string substring;
stream >> substring;
if (!substring.empty()) {
strs.push_back(substring);
}
}
}
// Finds and replaces all instances of oldsub with newsub, in-place on str.
void FindAndReplace(const std::string &oldsub, const std::string &newsub,
std::string *str) {
int start = 0;
while ((start = str->find(oldsub, start)) != std::string::npos) {
str->replace(start, oldsub.length(), newsub);
start += newsub.length();
}
}
// If arg is of the classic flag form "foo=bar", and flagname is 'foo', sets
// str to point to a new std::string 'bar' and returns true.
// Otherwise, returns false.
bool SetArgIfFlagPresent(const std::string &arg, const std::string &flagname,
std::string *str) {
std::string prefix_string = flagname + "=";
if (arg.compare(0, prefix_string.length(), prefix_string) == 0) {
*str = arg.substr(prefix_string.length());
return true;
}
return false;
}
// Returns the DEVELOPER_DIR environment variable in the current process
// environment. Aborts if this variable is unset.
std::string GetMandatoryEnvVar(const std::string &var_name) {
char *env_value = getenv(var_name.c_str());
if (env_value == nullptr) {
std::cerr << "Error: " << var_name << " not set.\n";
abort();
}
return env_value;
}
} // namespace
int main(int argc, char *argv[]) {
std::string tool_name;
std::string binary_name = Basename(argv[0]);
if (binary_name == "wrapped_clang_pp") {
tool_name = "clang++";
} else if (binary_name == "wrapped_clang") {
tool_name = "clang";
} else {
std::cerr << "Binary must either be named 'wrapped_clang' or "
"'wrapped_clang_pp', not "
<< binary_name << "\n";
abort();
}
std::string developer_dir = GetMandatoryEnvVar("DEVELOPER_DIR");
std::string sdk_root = GetMandatoryEnvVar("SDKROOT");
std::vector<std::string> processed_args = {"/usr/bin/xcrun", tool_name};
std::string linked_binary, dsym_path, dsym_bundle_zip, bitcode_symbol_map;
for (int i = 1; i < argc; i++) {
std::string arg(argv[i]);
if (SetArgIfFlagPresent(arg, "DSYM_HINT_LINKED_BINARY", &linked_binary)) {
continue;
}
if (SetArgIfFlagPresent(arg, "DSYM_HINT_DSYM_PATH", &dsym_path)) {
continue;
}
if (SetArgIfFlagPresent(arg, "DSYM_HINT_DSYM_BUNDLE_ZIP",
&dsym_bundle_zip)) {
continue;
}
if (SetArgIfFlagPresent(arg, "BITCODE_TOUCH_SYMBOL_MAP",
&bitcode_symbol_map)) {
// Touch bitcode_symbol_map.
std::ofstream bitcode_symbol_map_file(bitcode_symbol_map);
arg = bitcode_symbol_map;
}
FindAndReplace("__BAZEL_XCODE_DEVELOPER_DIR__", developer_dir, &arg);
FindAndReplace("__BAZEL_XCODE_SDKROOT__", sdk_root, &arg);
SplitAndAdd(arg, processed_args);
}
// Check to see if we should postprocess with dsymutil.
bool postprocess = false;
if ((!linked_binary.empty()) || (!dsym_path.empty()) ||
(!dsym_bundle_zip.empty())) {
if ((linked_binary.empty()) || (dsym_path.empty()) ||
(dsym_bundle_zip.empty())) {
const char *missing_dsym_flag;
if (linked_binary.empty()) {
missing_dsym_flag = "DSYM_HINT_LINKED_BINARY";
} else if (dsym_path.empty()) {
missing_dsym_flag = "DSYM_HINT_DSYM_PATH";
} else {
missing_dsym_flag = "DSYM_HINT_DSYM_BUNDLE_ZIP";
}
std::cerr << "Error in clang wrapper: If any dsym "
"hint is defined, then "
<< missing_dsym_flag << " must be defined\n";
abort();
} else {
postprocess = true;
}
}
if (!postprocess) {
ExecProcess(processed_args);
std::cerr << "ExecProcess should not return. Please fix!\n";
abort();
}
RunSubProcess(processed_args);
std::vector<std::string> dsymutil_args = {"/usr/bin/xcrun", "dsymutil",
linked_binary, "-o", dsym_path};
RunSubProcess(dsymutil_args);
std::unique_ptr<char, decltype(std::free) *> cwd{getcwd(nullptr, 0),
std::free};
if (cwd == nullptr) {
std::cerr << "Error determining current working directory\n";
abort();
}
std::vector<std::string> zip_args = {
"/usr/bin/zip", "-q", "-r",
std::string(cwd.get()) + "/" + dsym_bundle_zip, "."};
if (chdir(dsym_path.c_str()) < 0) {
std::cerr << "Error changing directory to '" << dsym_path << "'\n";
abort();
}
ExecProcess(zip_args);
std::cerr << "Should never get to end of wrapped_clang. Please fix!\n";
abort();
return 0;
}