diff --git a/tools/cpp/osx_cc_configure.bzl b/tools/cpp/osx_cc_configure.bzl index a3728044ec685a..4666cdac39f9c5 100644 --- a/tools/cpp/osx_cc_configure.bzl +++ b/tools/cpp/osx_cc_configure.bzl @@ -49,11 +49,39 @@ def _get_escaped_xcode_cxx_inc_directories(repository_ctx, cc, xcode_toolchains) include_dirs.append("/Applications/") return include_dirs +def compile_cc_file(repository_ctx, src_name, out_name): + xcrun_result = repository_ctx.execute([ + "env", + "-i", + "xcrun", + "--sdk", + "macosx", + "clang", + "-mmacosx-version-min=10.9", + "-std=c++11", + "-lc++", + "-o", + out_name, + src_name, + ], 30) + if (xcrun_result.return_code != 0): + error_msg = ( + "return code {code}, stderr: {err}, stdout: {out}" + ).format( + code = xcrun_result.return_code, + err = xcrun_result.stderr, + out = xcrun_result.stdout, + ) + fail(out_name + " failed to generate. Please file an issue at " + + "https://github.com/bazelbuild/bazel/issues with the following:\n" + + error_msg) + def configure_osx_toolchain(repository_ctx, overriden_tools): """Configure C++ toolchain on macOS.""" paths = resolve_labels(repository_ctx, [ "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", "@bazel_tools//tools/objc:libtool.sh", + "@bazel_tools//tools/objc:libtool_check_unique.cc", "@bazel_tools//tools/objc:make_hashed_objlist.py", "@bazel_tools//tools/objc:xcrunwrapper.sh", "@bazel_tools//tools/osx/crosstool:BUILD.tpl", @@ -108,36 +136,15 @@ def configure_osx_toolchain(repository_ctx, overriden_tools): paths["@bazel_tools//tools/osx/crosstool:cc_toolchain_config.bzl"], "cc_toolchain_config.bzl", ) + libtool_check_unique_src_path = str(repository_ctx.path( + paths["@bazel_tools//tools/objc:libtool_check_unique.cc"], + )) + compile_cc_file(repository_ctx, libtool_check_unique_src_path, "libtool_check_unique") wrapped_clang_src_path = str(repository_ctx.path( paths["@bazel_tools//tools/osx/crosstool:wrapped_clang.cc"], )) - xcrun_result = repository_ctx.execute([ - "env", - "-i", - "xcrun", - "--sdk", - "macosx", - "clang", - "-mmacosx-version-min=10.9", - "-std=c++11", - "-lc++", - "-o", - "wrapped_clang", - wrapped_clang_src_path, - ], 30) - if (xcrun_result.return_code == 0): - repository_ctx.symlink("wrapped_clang", "wrapped_clang_pp") - else: - error_msg = ( - "return code {code}, stderr: {err}, stdout: {out}" - ).format( - code = xcrun_result.return_code, - err = xcrun_result.stderr, - out = xcrun_result.stdout, - ) - fail("wrapped_clang failed to generate. Please file an issue at " + - "https://github.com/bazelbuild/bazel/issues with the following:\n" + - error_msg) + compile_cc_file(repository_ctx, wrapped_clang_src_path, "wrapped_clang") + repository_ctx.symlink("wrapped_clang", "wrapped_clang_pp") tool_paths = {} gcov_path = repository_ctx.os.environ.get("GCOV") diff --git a/tools/objc/libtool.sh b/tools/objc/libtool.sh index 8743bd91cf99c7..321190ff265735 100755 --- a/tools/objc/libtool.sh +++ b/tools/objc/libtool.sh @@ -34,6 +34,15 @@ fi WRAPPER="${MY_LOCATION}/xcrunwrapper.sh" +# Ensure 0 timestamping for hermetic results. +export ZERO_AR_DATE=1 + +if ${MY_LOCATION}/libtool_check_unique "$@"; then + # If there are no duplicate .o basenames, libtool can be invoked with the original arguments + "${WRAPPER}" libtool "$@" + exit +fi + TEMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/libtool.XXXXXXXX")" trap "rm -rf \"${TEMPDIR}\"" EXIT @@ -111,11 +120,4 @@ while [[ $# -gt 0 ]]; do esac done -# Ensure 0 timestamping for hermetic results. -export ZERO_AR_DATE=1 - "${WRAPPER}" libtool "${ARGS[@]}" - -# Prevents a pre-Xcode-8 bug in which passing zero-date archive files to ld -# would cause ld to error. -touch "$OUTPUTFILE" diff --git a/tools/objc/libtool_check_unique.cc b/tools/objc/libtool_check_unique.cc new file mode 100644 index 00000000000000..ad83c93f5517e8 --- /dev/null +++ b/tools/objc/libtool_check_unique.cc @@ -0,0 +1,71 @@ +// Copyright 2020 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. + +#include +#include +#include +#include + +using namespace std; + +string getBasename(const string &path) { + // Assumes we're on an OS with "/" as the path separator + auto idx = path.find_last_of("/"); + if (idx == string::npos) { + return path; + } + return path.substr(idx + 1); +} + +// Returns 0 if there are no duplicate basenames in the object files (both via -filelist as well as shell args), +// 1 otherwise +int main(int argc, const char * argv[]) { + unordered_set basenames; + const regex libRegex = regex(".*\\.a$"); + const regex noArgFlags = regex("-static|-s|-a|-c|-L|-T|-D|-no_warning_for_no_symbols"); + const regex singleArgFlags = regex("-arch_only|-syslibroot|-o"); + // Set i to 1 to skip executable path + for (int i = 1; argv[i] != nullptr; i++) { + string arg = argv[i]; + if (arg == "-filelist") { + ifstream list(argv[i + 1]); + for (string line; getline(list, line); ) { + const string basename = getBasename(line); + const auto pair = basenames.insert(basename); + if (!pair.second) { + return 1; + } + } + list.close(); + i++; + } else if (regex_match(arg, noArgFlags)) { + // No arg flags + } else if (regex_match(arg, singleArgFlags)) { + // Single-arg flags + i++; + } else if (arg[0] == '-') { + return 1; + // Unrecognized flag, let the wrapper deal with it + } else if (regex_match(arg, libRegex)) { + // Archive inputs can remain untouched, as they come from other targets. + } else { + const string basename = getBasename(arg); + const auto pair = basenames.insert(basename); + if (!pair.second) { + return 1; + } + } + } + return 0; +}