Add support for params files for darwin

Clang has supported params files for a while now. This updates the cc
toolchain for darwin to use them.

The logic for processing response files is mostly copied from
rules_swift where similar processing is done.

Closes #12265.

PiperOrigin-RevId: 342013390
diff --git a/tools/osx/crosstool/wrapped_clang.cc b/tools/osx/crosstool/wrapped_clang.cc
index 886cc12..febf3cb 100644
--- a/tools/osx/crosstool/wrapped_clang.cc
+++ b/tools/osx/crosstool/wrapped_clang.cc
@@ -55,6 +55,45 @@
   return base ? (base + 1) : filepath;
 }
 
+// Unescape and unquote an argument read from a line of a response file.
+static std::string Unescape(const std::string &arg) {
+  std::string result;
+  auto length = arg.size();
+  for (size_t i = 0; i < length; ++i) {
+    auto ch = arg[i];
+
+    // If it's a backslash, consume it and append the character that follows.
+    if (ch == '\\' && i + 1 < length) {
+      ++i;
+      result.push_back(arg[i]);
+      continue;
+    }
+
+    // If it's a quote, process everything up to the matching quote, unescaping
+    // backslashed characters as needed.
+    if (ch == '"' || ch == '\'') {
+      auto quote = ch;
+      ++i;
+      while (i != length && arg[i] != quote) {
+        if (arg[i] == '\\' && i + 1 < length) {
+          ++i;
+        }
+        result.push_back(arg[i]);
+        ++i;
+      }
+      if (i == length) {
+        break;
+      }
+      continue;
+    }
+
+    // It's a regular character.
+    result.push_back(ch);
+  }
+
+  return result;
+}
+
 // 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
@@ -74,7 +113,7 @@
 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] << "'. "
+  std::cerr << "Error executing child process.'" << args[0] << "'. "
             << strerror(errno) << "\n";
   abort();
 }
@@ -92,17 +131,17 @@
       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] << "'. "
+      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] << "'. "
+      std::cerr << "Error in child process '" << args[0] << "'. "
                 << WEXITSTATUS(status) << "\n";
       abort();
     }
   } else {
-    std::cerr << "Error forking process '" <<  args[0] << "'. "
+    std::cerr << "Error forking process '" << args[0] << "'. "
               << strerror(status) << "\n";
     abort();
   }
@@ -157,6 +196,151 @@
   return false;
 }
 
+// An RAII temporary file.
+class TempFile {
+ public:
+  // Create a new temporary file using the given path template string (the same
+  // form used by `mkstemp`). The file will automatically be deleted when the
+  // object goes out of scope.
+  static std::unique_ptr<TempFile> Create(const std::string &path_template) {
+    const char *tmpDir = getenv("TMPDIR");
+    if (!tmpDir) {
+      tmpDir = "/tmp";
+    }
+    size_t size = strlen(tmpDir) + path_template.size() + 2;
+    std::unique_ptr<char[]> path(new char[size]);
+    snprintf(path.get(), size, "%s/%s", tmpDir, path_template.c_str());
+
+    if (mkstemp(path.get()) == -1) {
+      std::cerr << "Failed to create temporary file '" << path.get()
+                << "': " << strerror(errno) << "\n";
+      return nullptr;
+    }
+    return std::unique_ptr<TempFile>(new TempFile(path.get()));
+  }
+
+  // Explicitly make TempFile non-copyable and movable.
+  TempFile(const TempFile &) = delete;
+  TempFile &operator=(const TempFile &) = delete;
+  TempFile(TempFile &&) = default;
+  TempFile &operator=(TempFile &&) = default;
+
+  ~TempFile() { remove(path_.c_str()); }
+
+  // Gets the path to the temporary file.
+  std::string GetPath() const { return path_; }
+
+ private:
+  explicit TempFile(const std::string &path) : path_(path) {}
+
+  std::string path_;
+};
+
+static std::unique_ptr<TempFile> WriteResponseFile(
+    const std::vector<std::string> &args) {
+  auto response_file = TempFile::Create("wrapped_clang_params.XXXXXX");
+  std::ofstream response_file_stream(response_file->GetPath());
+
+  for (const auto &arg : args) {
+    // When Clang/Swift write out a response file to communicate from driver to
+    // frontend, they just quote every argument to be safe; we duplicate that
+    // instead of trying to be "smarter" and only quoting when necessary.
+    response_file_stream << '"';
+    for (auto ch : arg) {
+      if (ch == '"' || ch == '\\') {
+        response_file_stream << '\\';
+      }
+      response_file_stream << ch;
+    }
+    response_file_stream << "\"\n";
+  }
+
+  response_file_stream.close();
+  return response_file;
+}
+
+void ProcessArgument(const std::string arg, const std::string developer_dir,
+                     const std::string sdk_root, const std::string cwd,
+                     bool relative_ast_path, std::string &linked_binary,
+                     std::string &dsym_path,
+                     std::function<void(const std::string &)> consumer);
+
+bool ProcessResponseFile(const std::string arg, const std::string developer_dir,
+                         const std::string sdk_root, const std::string cwd,
+                         bool relative_ast_path, std::string &linked_binary,
+                         std::string &dsym_path,
+                         std::function<void(const std::string &)> consumer) {
+  auto path = arg.substr(1);
+  std::ifstream original_file(path);
+  // Ignore non-file args such as '@loader_path/...'
+  if (!original_file.good()) {
+    return false;
+  }
+
+  std::string arg_from_file;
+  while (std::getline(original_file, arg_from_file)) {
+    // Arguments in response files might be quoted/escaped, so we need to
+    // unescape them ourselves.
+    ProcessArgument(Unescape(arg_from_file), developer_dir, sdk_root, cwd,
+                    relative_ast_path, linked_binary, dsym_path, consumer);
+  }
+
+  return true;
+}
+
+std::string GetCurrentDirectory() {
+  // Passing null,0 causes getcwd to allocate the buffer of the correct size.
+  char *buffer = getcwd(nullptr, 0);
+  std::string cwd(buffer);
+  free(buffer);
+  return cwd;
+}
+
+void ProcessArgument(const std::string arg, const std::string developer_dir,
+                     const std::string sdk_root, const std::string cwd,
+                     bool relative_ast_path, std::string &linked_binary,
+                     std::string &dsym_path,
+                     std::function<void(const std::string &)> consumer) {
+  auto new_arg = arg;
+  if (arg[0] == '@') {
+    if (ProcessResponseFile(arg, developer_dir, sdk_root, cwd,
+                            relative_ast_path, linked_binary, dsym_path,
+                            consumer)) {
+      return;
+    }
+  }
+
+  if (SetArgIfFlagPresent(arg, "DSYM_HINT_LINKED_BINARY", &linked_binary)) {
+    return;
+  }
+  if (SetArgIfFlagPresent(arg, "DSYM_HINT_DSYM_PATH", &dsym_path)) {
+    return;
+  }
+
+  std::string dest_dir, bitcode_symbol_map;
+  if (arg.compare("OSO_PREFIX_MAP_PWD") == 0) {
+    new_arg = "-Wl,-oso_prefix," + cwd + "/";
+  }
+
+  FindAndReplace("__BAZEL_XCODE_DEVELOPER_DIR__", developer_dir, &new_arg);
+  FindAndReplace("__BAZEL_XCODE_SDKROOT__", sdk_root, &new_arg);
+
+  // Make the `add_ast_path` options used to embed Swift module references
+  // absolute to enable Swift debugging without dSYMs: see
+  // https://forums.swift.org/t/improving-swift-lldb-support-for-path-remappings/22694
+  if (!relative_ast_path &&
+      StripPrefixStringIfPresent(&new_arg, kAddASTPathPrefix)) {
+    // Only modify relative paths.
+    if (!StartsWith(arg, "/")) {
+      new_arg = std::string(kAddASTPathPrefix) + cwd + "/" + new_arg;
+    } else {
+      new_arg = std::string(kAddASTPathPrefix) + new_arg;
+    }
+  }
+
+  consumer(new_arg);
+}
+
 }  // namespace
 
 int main(int argc, char *argv[]) {
@@ -176,60 +360,34 @@
 
   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;
-  std::string dest_dir;
 
-  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();
-  }
+  const std::string cwd = GetCurrentDirectory();
+  std::vector<std::string> invocation_args = {"/usr/bin/xcrun", tool_name};
+  std::vector<std::string> processed_args = {};
 
   bool relative_ast_path = getenv("RELATIVE_AST_PATH") != nullptr;
+  auto consumer = [&](const std::string &arg) {
+    processed_args.push_back(arg);
+  };
   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 (arg.compare("OSO_PREFIX_MAP_PWD") == 0) {
-      arg = "-Wl,-oso_prefix," + std::string(cwd.get()) + "/";
-    }
-    FindAndReplace("__BAZEL_XCODE_DEVELOPER_DIR__", developer_dir, &arg);
-    FindAndReplace("__BAZEL_XCODE_SDKROOT__", sdk_root, &arg);
-
-    // Make the `add_ast_path` options used to embed Swift module references
-    // absolute to enable Swift debugging without dSYMs: see
-    // https://forums.swift.org/t/improving-swift-lldb-support-for-path-remappings/22694
-    if (!relative_ast_path &&
-        StripPrefixStringIfPresent(&arg, kAddASTPathPrefix)) {
-      // Only modify relative paths.
-      if (!StartsWith(arg, "/")) {
-        arg = std::string(kAddASTPathPrefix) +
-              std::string(cwd.get()) + "/" + arg;
-      } else {
-        arg = std::string(kAddASTPathPrefix) + arg;
-      }
-    }
-
-    processed_args.push_back(arg);
+    ProcessArgument(arg, developer_dir, sdk_root, cwd, relative_ast_path,
+                    linked_binary, dsym_path, consumer);
   }
 
   // Special mode that only prints the command. Used for testing.
   if (getenv("__WRAPPED_CLANG_LOG_ONLY")) {
-    for (const std::string &arg : processed_args)
-        std::cout << arg << ' ';
+    for (const std::string &arg : invocation_args) std::cout << arg << ' ';
+    for (const std::string &arg : processed_args) std::cout << arg << ' ';
     std::cout << "\n";
     return 0;
   }
 
+  auto response_file = WriteResponseFile(processed_args);
+  invocation_args.push_back("@" + response_file->GetPath());
+
   // Check to see if we should postprocess with dsymutil.
   bool postprocess = false;
   if ((!linked_binary.empty()) || (!dsym_path.empty())) {
@@ -241,8 +399,8 @@
         missing_dsym_flag = "DSYM_HINT_DSYM_PATH";
       }
       std::cerr << "Error in clang wrapper: If any dsym "
-              "hint is defined, then "
-           << missing_dsym_flag << " must be defined\n";
+                   "hint is defined, then "
+                << missing_dsym_flag << " must be defined\n";
       abort();
     } else {
       postprocess = true;
@@ -250,16 +408,15 @@
   }
 
   if (!postprocess) {
-    ExecProcess(processed_args);
+    ExecProcess(invocation_args);
     std::cerr << "ExecProcess should not return. Please fix!\n";
     abort();
   }
 
-  RunSubProcess(processed_args);
+  RunSubProcess(invocation_args);
 
-  std::vector<std::string> dsymutil_args = {"/usr/bin/xcrun", "dsymutil",
-                                            linked_binary, "-o", dsym_path,
-                                            "--flat"};
+  std::vector<std::string> dsymutil_args = {
+      "/usr/bin/xcrun", "dsymutil", linked_binary, "-o", dsym_path, "--flat"};
   ExecProcess(dsymutil_args);
   std::cerr << "ExecProcess should not return. Please fix!\n";
   abort();