Update wrapped_clang to make -add_ast_path options absolute

This should allow us to debug Swift binaries without using dSYMs.

See
https://forums.swift.org/t/improving-swift-lldb-support-for-path-remappings/22694

PiperOrigin-RevId: 244896679
diff --git a/.bazelci/postsubmit.yml b/.bazelci/postsubmit.yml
index c3ce4a7..5f4963a 100644
--- a/.bazelci/postsubmit.yml
+++ b/.bazelci/postsubmit.yml
@@ -183,6 +183,7 @@
     - "//third_party/ijar/..."
     - "//tools/android/..."
     - "//tools/aquery_differ/..."
+    - "//tools/osx/crosstool/..."
     - "//tools/python/..."
     - "-//src/test/shell/integration:minimal_jdk_test"
       # C++ coverage is not supported on macOS yet.
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index d1759dd..eefe4a8 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -203,6 +203,7 @@
     - "//third_party/ijar/..."
     - "//tools/android/..."
     - "//tools/aquery_differ/..."
+    - "//tools/osx/crosstool/..."
     - "//tools/python/..."
       # C++ coverage is not supported on macOS yet.
     - "-//src/test/shell/bazel:bazel_cc_code_coverage_test"
diff --git a/tools/osx/crosstool/BUILD b/tools/osx/crosstool/BUILD
index bc74651..1695e6d 100644
--- a/tools/osx/crosstool/BUILD
+++ b/tools/osx/crosstool/BUILD
@@ -1,9 +1,33 @@
 package(default_visibility = ["//visibility:public"])
 
-# Files which shouldn't be publicly visible and dependencies of all objc_* or ios_* rules shouldn't be in this package.
-exports_files(glob(["**"]))
+# Files which shouldn't be publicly visible and dependencies of all objc_* or ios_* rules should be excluded.
+exports_files(glob(
+    ["**"],
+    exclude = ["wrapped_clang_test.sh"],
+))
 
 filegroup(
     name = "srcs",
     srcs = glob(["**"]),
 )
+
+cc_binary(
+    name = "wrapped_clang",
+    testonly = True,
+    srcs = [
+        "wrapped_clang.cc",
+    ],
+)
+
+sh_test(
+    name = "wrapped_clang_test",
+    size = "small",
+    srcs = [
+        "wrapped_clang_test.sh",
+    ],
+    data = [
+        ":wrapped_clang",
+        "//src/test/shell:bashunit",
+        "@bazel_tools//tools/bash/runfiles",
+    ],
+)
diff --git a/tools/osx/crosstool/wrapped_clang.cc b/tools/osx/crosstool/wrapped_clang.cc
index a087881..2115b8d 100644
--- a/tools/osx/crosstool/wrapped_clang.cc
+++ b/tools/osx/crosstool/wrapped_clang.cc
@@ -57,6 +57,8 @@
 
 namespace {
 
+constexpr char kAddASTPathPrefix[] = "-Wl,-add_ast_path,";
+
 // Returns the base name of the given filepath. For example, given
 // /foo/bar/baz.txt, returns 'baz.txt'.
 const char *Basename(const char *filepath) {
@@ -151,6 +153,21 @@
   return env_value;
 }
 
+// Returns true if `str` starts with the specified `prefix`.
+bool StartsWith(const std::string &str, const std::string &prefix) {
+  return str.compare(0, prefix.size(), prefix) == 0;
+}
+
+// If *`str` begins `prefix`, strip it out and return true.
+// Otherwise leave *`str` unchanged and return false.
+bool StripPrefixStringIfPresent(std::string *str, const std::string &prefix) {
+  if (StartsWith(*str, prefix)) {
+    *str = str->substr(prefix.size());
+    return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 int main(int argc, char *argv[]) {
@@ -203,9 +220,31 @@
     }
     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 (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);
   }
 
+  // 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 << ' ';
+    std::cout << "\n";
+    return 0;
+  }
+
   // Check to see if we should postprocess with dsymutil.
   bool postprocess = false;
   if ((!linked_binary.empty()) || (!dsym_path.empty())) {
diff --git a/tools/osx/crosstool/wrapped_clang_test.sh b/tools/osx/crosstool/wrapped_clang_test.sh
new file mode 100755
index 0000000..1b77d6b
--- /dev/null
+++ b/tools/osx/crosstool/wrapped_clang_test.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+# -*- coding: utf-8 -*-
+
+# Copyright 2019 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.
+
+# Unit tests for wrapped_clang.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  if [[ -f "$0.runfiles_manifest" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+  elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+  elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+    export RUNFILES_DIR="$0.runfiles"
+  fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+  source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+            "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+  echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+  exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+
+# Load test environment
+source "$(rlocation "io_bazel/src/test/shell/unittest.bash")" \
+  || { echo "unittest.bash not found!" >&2; exit 1; }
+WRAPPED_CLANG=$(rlocation "io_bazel/tools/osx/crosstool/wrapped_clang")
+
+
+# This env var tells wrapped_clang to log its command instead of running.
+export __WRAPPED_CLANG_LOG_ONLY=1
+
+
+# Test that add_ast_path is remapped properly.
+function test_add_ast_path_remapping() {
+  env DEVELOPER_DIR=dummy SDKROOT=a \
+      "${WRAPPED_CLANG}" "-Wl,-add_ast_path,foo" >$TEST_log || fail "wrapped_clang failed";
+  expect_log "-Wl,-add_ast_path,${PWD}/foo" "Expected add_ast_path to be remapped."
+}
+
+# Test that __BAZEL_XCODE_DEVELOPER_DIR__ is remapped properly.
+function test_developer_dir_remapping() {
+  env DEVELOPER_DIR=mydir SDKROOT=a \
+      "${WRAPPED_CLANG}" "developer_dir=__BAZEL_XCODE_DEVELOPER_DIR__" \
+      >$TEST_log || fail "wrapped_clang failed";
+  expect_log "developer_dir=mydir" "Expected developer dir to be remapped."
+}
+
+# Test that __BAZEL_XCODE_SDKROOT__ is remapped properly.
+function test_sdkroot_remapping() {
+  env DEVELOPER_DIR=dummy SDKROOT=mysdkroot \
+      "${WRAPPED_CLANG}" "sdkroot=__BAZEL_XCODE_SDKROOT__" \
+      >$TEST_log || fail "wrapped_clang failed";
+  expect_log "sdkroot=mysdkroot" "Expected sdkroot to be remapped."
+}
+
+run_suite "Wrapped clang tests"