| // 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; |
| } |