Rollforward of "Workaround for duplicate input basename bug in apple's libtool", with regex fix

--
MOS_MIGRATED_REVID=121390817
diff --git a/src/tools/xcode/xcrunwrapper/xcrunwrapper.sh b/src/tools/xcode/xcrunwrapper/xcrunwrapper.sh
index dfb36aa..ae57722 100755
--- a/src/tools/xcode/xcrunwrapper/xcrunwrapper.sh
+++ b/src/tools/xcode/xcrunwrapper/xcrunwrapper.sh
@@ -14,14 +14,29 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# xcrunwrapper runs the command passed to it using xcrun.
-# It replaces __BAZEL_XCODE_DEVELOPER_DIR__ with $DEVELOPER_DIR (or reasonable
-# default) and __BAZEL_XCODE_SDKROOT__ with a valid path based on SDKROOT (or
-# reasonable default).
+# xcrunwrapper runs the command passed to it using xcrun. The first arg
+# passed is the name of the tool to be invoked via xcrun. (For example, libtool
+# or clang).
+# xcrunwrapper replaces __BAZEL_XCODE_DEVELOPER_DIR__ with $DEVELOPER_DIR (or
+# reasonable default) and __BAZEL_XCODE_SDKROOT__ with a valid path based on
+# SDKROOT (or reasonable default).
 # These values (__BAZEL_XCODE_*) are a shared secret withIosSdkCommands.java.
 
 set -eu
 
+TOOLNAME=$1
+shift
+
+# Creates a symbolic link to the input argument file and returns the symlink
+# file path.
+function hash_objfile() {
+  ORIGINAL_NAME="$1"
+  ORIGINAL_HASH="$(/sbin/md5 -q "${ORIGINAL_NAME}")"
+  SYMLINK_NAME="${ORIGINAL_NAME%.o}_${ORIGINAL_HASH}.o"
+  ln -s "$(basename "$ORIGINAL_NAME")" "$SYMLINK_NAME"
+  echo "$SYMLINK_NAME"
+}
+
 # Pick values for DEVELOPER_DIR and SDKROOT as appropriate (if they weren't set)
 
 WRAPPER_DEVDIR="${DEVELOPER_DIR:-}"
@@ -47,8 +62,59 @@
   WRAPPER_SDKROOT="$(/usr/bin/xcrun --show-sdk-path --sdk ${WRAPPER_SDK})"
 fi
 
+ARGS=("$TOOLNAME")
+while [[ $# -gt 0 ]]; do
+  ARG="$1"
+  shift
+
+  # Libtool artifact symlinking. Apple's libtool has a bug when there are two
+  # input files with the same basename. We thus create symlinks that are named
+  # with a hash suffix for each input, and pass them to libtool.
+  # See b/28186497.
+  # TODO(b/28347228): Handle this in a separate wrapper.
+  if [ "$TOOLNAME" = "libtool" ] ; then
+    case "${ARG}" in
+      # Filelist flag, need to symlink each input in the contents of file and
+      # pass a new filelist which contains the symlinks.
+      -filelist)
+        ARGS+=("${ARG}")
+        ARG="$1"
+        shift
+        HASHED_FILELIST="${ARG//.objlist/_hashes.objlist}"
+        while read INPUT_FILE || [ -n "$INPUT_FILE" ]; do
+          echo "$(hash_objfile "${INPUT_FILE}")" >> "$HASHED_FILELIST"
+        done < "${ARG}"
+        ARGS+=("${HASHED_FILELIST}")
+        ;;
+     # Flags with no args
+      -static|-s|-a|-c|-L|-T|-no_warning_for_no_symbols)
+        ARGS+=("${ARG}")
+        ;;
+     # Single-arg flags
+     -o|-arch_only|-syslibroot)
+       ARGS+=("${ARG}")
+       ARG="$1"
+       shift
+       ARGS+=("${ARG}")
+       ;;
+     # Any remaining flags are unexpected and may ruin flag parsing.
+     -*)
+       echo "Unrecognized libtool flag ${ARG}"
+       exit 1
+       ;;
+     # Remaining args are input objects
+     *)
+       ARGS+=("$(echo "$(hash_objfile "${ARG}")")")
+       ;;
+     esac
+  else
+    ARGS+=("${ARG}")
+  fi
+done
+
+# Subsitute toolkit path placeholders.
 UPDATEDARGS=()
-for ARG in "$@" ; do
+for ARG in "${ARGS[@]}" ; do
   ARG="${ARG//__BAZEL_XCODE_DEVELOPER_DIR__/${WRAPPER_DEVDIR}}"
   ARG="${ARG//__BAZEL_XCODE_SDKROOT__/${WRAPPER_SDKROOT}}"
   UPDATEDARGS+=("${ARG}")
diff --git a/tools/objc/j2objc_dead_code_pruner.py b/tools/objc/j2objc_dead_code_pruner.py
index 503d818..9f787cb 100755
--- a/tools/objc/j2objc_dead_code_pruner.py
+++ b/tools/objc/j2objc_dead_code_pruner.py
@@ -30,6 +30,7 @@
 import multiprocessing
 import os
 import Queue
+import re
 import shutil
 import subprocess
 import threading
@@ -295,10 +296,34 @@
              file_shutil)
 
 
+def MatchObjectNamesInArchive(xcrunwrapper, archive, object_names):
+  """Returns object names matching their identity in an archive file.
+
+  The linker that blaze uses appends an md5 hash to object file
+  names prior to inclusion in the archive file. Thus, object names
+  such as 'foo.o' need to be matched to their appropriate name in
+  the archive file, such as 'foo_<hash>.o'.
+
+  Args:
+    xcrunwrapper: A wrapper script over xcrun.
+    archive: The location of the archive file.
+    object_names: The expected basenames of object files to match,
+        sans extension. For example 'foo' (not 'foo.o').
+  Returns:
+    A list of basenames of matching members of the given archive
+  """
+  ar_contents_cmd = '%s ar -t %s' % (xcrunwrapper, archive)
+  real_object_names = subprocess.check_output(ar_contents_cmd, shell=True)
+  expected_object_name_regex = '^(?:%s)_[0-9a-f]{32}.o' % (
+      '|'.join([re.escape(name) for name in object_names]))
+  return re.findall(expected_object_name_regex, real_object_names,
+                    flags=re.MULTILINE)
+
+
 def PruneArchiveFile(input_archive, output_archive, dummy_archive,
                      dependency_mapping_files, header_mapping_files,
                      archive_source_mapping_files, entry_classes, xcrunwrapper,
-                     file_open=open, proc_exe=subprocess.check_call):
+                     file_open=open):
   """Remove unreachable objects from archive file.
 
   Args:
@@ -315,7 +340,6 @@
     xcrunwrapper: A wrapper script over xcrun.
     file_open: Reference to the builtin open function so it may be
         overridden for testing.
-    proc_exe: Object that can execute a command line process.
   """
   reachability_file_mapping = BuildReachabilityTree(
       dependency_mapping_files, file_open)
@@ -335,7 +359,7 @@
     for source_file in source_files:
       if os.path.splitext(source_file)[0] not in reachable_files_set:
         unreachable_object_names.append(
-            os.path.basename(os.path.splitext(source_file)[0]) + '.o')
+            os.path.basename(os.path.splitext(source_file)[0]))
 
     # There are unreachable objects in the archive to prune
     if unreachable_object_names:
@@ -350,6 +374,8 @@
         # Make the output archive editable
         j2objc_cmd += 'chmod +w %s;' % (output_archive)
         # Remove the unreachable objects from the archive
+        unreachable_object_names = MatchObjectNamesInArchive(
+            xcrunwrapper, input_archive, unreachable_object_names)
         j2objc_cmd += '%s ar -d -s %s %s;' % (
             xcrunwrapper, output_archive, ' '.join(unreachable_object_names))
         # Update the table of content of the archive file
@@ -362,7 +388,7 @@
   else:
     j2objc_cmd = 'cp %s %s' % (input_archive, output_archive)
 
-  proc_exe(j2objc_cmd, stderr=subprocess.STDOUT, shell=True)
+  subprocess.check_output(j2objc_cmd, stderr=subprocess.STDOUT, shell=True)
 
 
 if __name__ == '__main__':