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/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
index ec0bc12..289d7f7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
@@ -532,6 +532,10 @@
case PIC_STATIC_LIBRARY:
case ALWAYS_LINK_STATIC_LIBRARY:
case ALWAYS_LINK_PIC_STATIC_LIBRARY:
+ case OBJC_ARCHIVE:
+ case OBJC_FULLY_LINKED_ARCHIVE:
+ case OBJC_EXECUTABLE:
+ case OBJCPP_EXECUTABLE:
return true;
default:
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
index 2e90615..6cc0b8c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationSupport.java
@@ -1183,7 +1183,7 @@
ruleContext,
ruleContext.getLabel(),
binaryToLink,
- ruleContext.getConfiguration(),
+ buildConfiguration,
toolchain,
toolchain.getFdoContext(),
getFeatureConfiguration(ruleContext, toolchain, buildConfiguration),
@@ -1358,7 +1358,7 @@
ruleContext,
ruleContext.getLabel(),
outputArchive,
- ruleContext.getConfiguration(),
+ buildConfiguration,
toolchain,
toolchain.getFdoContext(),
getFeatureConfiguration(ruleContext, toolchain, buildConfiguration),
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index 445f335..e36068e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -604,6 +604,18 @@
builder.setLinkType(LinkTargetType.STATIC_LIBRARY);
assertThat(builder.canSplitCommandLine()).isTrue();
+ builder.setLinkType(LinkTargetType.OBJC_ARCHIVE);
+ assertThat(builder.canSplitCommandLine()).isTrue();
+
+ builder.setLinkType(LinkTargetType.OBJC_EXECUTABLE);
+ assertThat(builder.canSplitCommandLine()).isTrue();
+
+ builder.setLinkType(LinkTargetType.OBJCPP_EXECUTABLE);
+ assertThat(builder.canSplitCommandLine()).isTrue();
+
+ builder.setLinkType(LinkTargetType.OBJC_FULLY_LINKED_ARCHIVE);
+ assertThat(builder.canSplitCommandLine()).isTrue();
+
builder.setLinkType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY);
assertThat(builder.canSplitCommandLine()).isTrue();
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh
index b237b3e..7819ba3 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -711,10 +711,6 @@
}
function test_aquery_include_param_file_cc_binary() {
- if is_darwin; then
- return 0
- fi
-
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg" || fail "mkdir -p $pkg"
cat > "$pkg/BUILD" <<'EOF'
@@ -739,10 +735,6 @@
}
function test_aquery_include_param_file_starlark_rule() {
- if is_darwin; then
- return 0
- fi
-
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg" || fail "mkdir -p $pkg"
cat > "$pkg/test_rule.bzl" <<'EOF'
@@ -793,10 +785,6 @@
}
function test_aquery_include_param_file_not_enabled_by_default() {
- if is_darwin; then
- return 0
- fi
-
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg" || fail "mkdir -p $pkg"
cat > "$pkg/BUILD" <<'EOF'
diff --git a/tools/cpp/BUILD.tools b/tools/cpp/BUILD.tools
index dc72e23..db27b19 100644
--- a/tools/cpp/BUILD.tools
+++ b/tools/cpp/BUILD.tools
@@ -277,7 +277,7 @@
linker_files = ":osx_wrapper",
objcopy_files = ":empty",
strip_files = ":empty",
- supports_param_files = 0,
+ supports_param_files = 1,
toolchain_config = ":local_darwin",
toolchain_identifier = "local_darwin",
)
diff --git a/tools/cpp/BUILD.tpl b/tools/cpp/BUILD.tpl
index 37986a9..d3514d1 100644
--- a/tools/cpp/BUILD.tpl
+++ b/tools/cpp/BUILD.tpl
@@ -66,7 +66,7 @@
linker_files = ":compiler_deps",
objcopy_files = ":empty",
strip_files = ":empty",
- supports_param_files = %{supports_param_files},
+ supports_param_files = 1,
module_map = %{modulemap},
)
diff --git a/tools/cpp/osx_cc_wrapper.sh b/tools/cpp/osx_cc_wrapper.sh
index bbb5d7e..8c9c111 100755
--- a/tools/cpp/osx_cc_wrapper.sh
+++ b/tools/cpp/osx_cc_wrapper.sh
@@ -34,20 +34,33 @@
LIB_DIRS=
RPATHS=
OUTPUT=
-# let parse the option list
-for i in "$@"; do
+
+function parse_option() {
+ local -r opt="$1"
if [[ "${OUTPUT}" = "1" ]]; then
- OUTPUT=$i
- elif [[ "$i" =~ ^-l(.*)$ ]]; then
+ OUTPUT=$opt
+ elif [[ "$opt" =~ ^-l(.*)$ ]]; then
LIBS="${BASH_REMATCH[1]} $LIBS"
- elif [[ "$i" =~ ^-L(.*)$ ]]; then
+ elif [[ "$opt" =~ ^-L(.*)$ ]]; then
LIB_DIRS="${BASH_REMATCH[1]} $LIB_DIRS"
- elif [[ "$i" =~ ^-Wl,-rpath,\@loader_path/(.*)$ ]]; then
+ elif [[ "$opt" =~ ^-Wl,-rpath,\@loader_path/(.*)$ ]]; then
RPATHS="${BASH_REMATCH[1]} ${RPATHS}"
- elif [[ "$i" = "-o" ]]; then
+ elif [[ "$opt" = "-o" ]]; then
# output is coming
OUTPUT=1
fi
+}
+
+# let parse the option list
+for i in "$@"; do
+ if [[ "$i" = @* ]]; then
+ while IFS= read -r opt
+ do
+ parse_option "$opt"
+ done < "${i:1}" || exit 1
+ else
+ parse_option "$i"
+ fi
done
# Call gcc
diff --git a/tools/cpp/osx_cc_wrapper.sh.tpl b/tools/cpp/osx_cc_wrapper.sh.tpl
index 4c85cd9..28bd47ba 100644
--- a/tools/cpp/osx_cc_wrapper.sh.tpl
+++ b/tools/cpp/osx_cc_wrapper.sh.tpl
@@ -33,20 +33,33 @@
LIB_DIRS=
RPATHS=
OUTPUT=
-# let parse the option list
-for i in "$@"; do
+
+function parse_option() {
+ local -r opt="$1"
if [[ "${OUTPUT}" = "1" ]]; then
- OUTPUT=$i
- elif [[ "$i" =~ ^-l(.*)$ ]]; then
+ OUTPUT=$opt
+ elif [[ "$opt" =~ ^-l(.*)$ ]]; then
LIBS="${BASH_REMATCH[1]} $LIBS"
- elif [[ "$i" =~ ^-L(.*)$ ]]; then
+ elif [[ "$opt" =~ ^-L(.*)$ ]]; then
LIB_DIRS="${BASH_REMATCH[1]} $LIB_DIRS"
- elif [[ "$i" =~ ^-Wl,-rpath,\@loader_path/(.*)$ ]]; then
+ elif [[ "$opt" =~ ^-Wl,-rpath,\@loader_path/(.*)$ ]]; then
RPATHS="${BASH_REMATCH[1]} ${RPATHS}"
- elif [[ "$i" = "-o" ]]; then
+ elif [[ "$opt" = "-o" ]]; then
# output is coming
OUTPUT=1
fi
+}
+
+# let parse the option list
+for i in "$@"; do
+ if [[ "$i" = @* ]]; then
+ while IFS= read -r opt
+ do
+ parse_option "$opt"
+ done < "${i:1}" || exit 1
+ else
+ parse_option "$i"
+ fi
done
# Set-up the environment
diff --git a/tools/cpp/unix_cc_configure.bzl b/tools/cpp/unix_cc_configure.bzl
index 5270e9e..5cb1a9b 100644
--- a/tools/cpp/unix_cc_configure.bzl
+++ b/tools/cpp/unix_cc_configure.bzl
@@ -447,7 +447,6 @@
"%{cc_toolchain_identifier}": cc_toolchain_identifier,
"%{name}": cpu_value,
"%{modulemap}": ("\":module.modulemap\"" if is_clang else "None"),
- "%{supports_param_files}": "0" if darwin else "1",
"%{cc_compiler_deps}": get_starlark_list([":builtin_include_directory_paths"] + (
[":cc_wrapper"] if darwin else []
)),
diff --git a/tools/osx/crosstool/BUILD.tpl b/tools/osx/crosstool/BUILD.tpl
index a2e6e92..ce8a0ad 100644
--- a/tools/osx/crosstool/BUILD.tpl
+++ b/tools/osx/crosstool/BUILD.tpl
@@ -70,7 +70,7 @@
linker_files = ":osx_tools_" + arch,
objcopy_files = ":empty",
strip_files = ":osx_tools_" + arch,
- supports_param_files = 0,
+ supports_param_files = 1,
toolchain_config = ":" + (
arch if arch != "armeabi-v7a" else "stub_armeabi-v7a"
),
diff --git a/tools/osx/crosstool/cc_toolchain_config.bzl b/tools/osx/crosstool/cc_toolchain_config.bzl
index f23eddb..d222c75 100644
--- a/tools/osx/crosstool/cc_toolchain_config.bzl
+++ b/tools/osx/crosstool/cc_toolchain_config.bzl
@@ -5198,16 +5198,13 @@
name = "linker_param_file",
flag_sets = [
flag_set(
- actions = all_link_actions,
- flag_groups = [
- flag_group(
- flags = ["-Wl,@%{linker_param_file}"],
- expand_if_available = "linker_param_file",
- ),
+ actions = all_link_actions + [
+ ACTION_NAMES.cpp_link_static_library,
+ ACTION_NAMES.objc_archive,
+ ACTION_NAMES.objc_fully_link,
+ ACTION_NAMES.objc_executable,
+ ACTION_NAMES.objcpp_executable,
],
- ),
- flag_set(
- actions = [ACTION_NAMES.cpp_link_static_library],
flag_groups = [
flag_group(
flags = ["@%{linker_param_file}"],
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();
diff --git a/tools/osx/crosstool/wrapped_clang_test.sh b/tools/osx/crosstool/wrapped_clang_test.sh
index 921b108..484b2dc 100755
--- a/tools/osx/crosstool/wrapped_clang_test.sh
+++ b/tools/osx/crosstool/wrapped_clang_test.sh
@@ -80,4 +80,20 @@
expect_log "sdkroot=mysdkroot" "Expected sdkroot to be remapped."
}
+function test_params_expansion() {
+ params=$(mktemp)
+ {
+ echo "first"
+ echo "-rpath"
+ echo "@loader_path"
+ echo "sdkroot=__BAZEL_XCODE_SDKROOT__"
+ echo "developer_dir=__BAZEL_XCODE_DEVELOPER_DIR__"
+ } > "$params"
+
+ env DEVELOPER_DIR=dummy SDKROOT=mysdkroot \
+ "${WRAPPED_CLANG}" "@$params" \
+ >"$TEST_log" || fail "wrapped_clang failed";
+ expect_log "/usr/bin/xcrun clang first -rpath @loader_path sdkroot=mysdkroot developer_dir=dummy"
+}
+
run_suite "Wrapped clang tests"