Introduce swift-stdlib-tool replacement

* swift-stdlib-tool is a utility that, given a binary, walks its dynamic library deps graph and picks everything that is used by Swift runtime. This tool is being removed from Xcode 8.3, hence the replacement.

* The new tool has a different command line interface, but keeps backwards compatibility with native Bazel code through changes in the wrapper script. The wrapper script is still needed to handle xcrun ENV stuff.

--
PiperOrigin-RevId: 149691879
MOS_MIGRATED_REVID=149691879
diff --git a/src/BUILD b/src/BUILD
index a418615..fb39187 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -105,7 +105,7 @@
         "//src/tools/xcode/ibtoolwrapper:ibtoolwrapper",
         "//src/tools/xcode/libtool:libtool_srcs",
         "//src/tools/xcode/momcwrapper:momcwrapper",
-        "//src/tools/xcode/swiftstdlibtoolwrapper:swiftstdlibtoolwrapper",
+        "//src/tools/xcode/swiftstdlibtoolwrapper:swiftstdlibtoolwrapper_srcs",
         "//src/tools/xcode/environment:environment_plist",
         "//src/tools/xcode/xcrunwrapper:xcrunwrapper",
         "//src/objc_tools/bundlemerge:bundlemerge_deploy.jar",
diff --git a/src/create_embedded_tools.sh b/src/create_embedded_tools.sh
index d814f17..a9b27f0 100755
--- a/src/create_embedded_tools.sh
+++ b/src/create_embedded_tools.sh
@@ -34,7 +34,8 @@
   # tools/objc to avoid conflict.
   if  [ "$i" = "tools/objc/xcrunwrapper.sh" ] \
     || [ "$i" = "tools/objc/libtool.sh" ] \
-    || [ "$i" = "tools/objc/make_hashed_objlist.py" ]
+    || [ "$i" = "tools/objc/make_hashed_objlist.py" ] \
+    || [ "$i" = "tools/objc/swift_stdlib_tool.py" ]
   then
     continue
   fi
@@ -57,6 +58,7 @@
     *src/objc_tools/*) OUTPUT_PATH=tools/objc/precomp_${i##*/} ;;
     *xcode*StdRedirect.dylib) OUTPUT_PATH=tools/objc/StdRedirect.dylib ;;
     *xcode*make_hashed_objlist.py) OUTPUT_PATH=tools/objc/make_hashed_objlist.py ;;
+    *xcode*swift_stdlib_tool.py) OUTPUT_PATH=tools/objc/swift_stdlib_tool.py ;;
     *xcode*realpath) OUTPUT_PATH=tools/objc/realpath ;;
     *xcode*xcode-locator) OUTPUT_PATH=tools/objc/xcode-locator ;;
     *src/tools/xcode/*.sh) OUTPUT_PATH=tools/objc/${i##*/} ;;
diff --git a/src/tools/xcode/swiftstdlibtoolwrapper/BUILD b/src/tools/xcode/swiftstdlibtoolwrapper/BUILD
index a4af593..e76fe93 100644
--- a/src/tools/xcode/swiftstdlibtoolwrapper/BUILD
+++ b/src/tools/xcode/swiftstdlibtoolwrapper/BUILD
@@ -5,10 +5,20 @@
     srcs = glob(["**"]),
 )
 
+# Sources that need to be packaged into embedded tools.
+filegroup(
+    name = "swiftstdlibtoolwrapper_srcs",
+    srcs = [
+        "swift_stdlib_tool.py",
+        "swiftstdlibtoolwrapper.sh",
+    ],
+)
+
 sh_binary(
     name = "swiftstdlibtoolwrapper",
     srcs = ["swiftstdlibtoolwrapper.sh"],
     data = [
+        ":swift_stdlib_tool.py",
         "//src/tools/xcode/realpath",
         "//src/tools/xcode/xcrunwrapper",
     ],
diff --git a/src/tools/xcode/swiftstdlibtoolwrapper/swift_stdlib_tool.py b/src/tools/xcode/swiftstdlibtoolwrapper/swift_stdlib_tool.py
new file mode 100644
index 0000000..6320d21
--- /dev/null
+++ b/src/tools/xcode/swiftstdlibtoolwrapper/swift_stdlib_tool.py
@@ -0,0 +1,72 @@
+# pylint: disable=g-bad-file-header
+# Copyright 2017 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.
+
+"""A tool to find Swift runtime libraries required by a binary.
+
+This tool is modeled after Xcode's swift-stdlib-tool. Given a binary, it
+scans its transitive dylib dependencies to figure out the full set of Swift
+runtime libraries (usually named libswift*.dylib) required to run the binary.
+The libraries are then copied into the output directory.
+
+This tool is used by the Apple packaging rules to properly construct macOS, iOS,
+watchOS and tvOS app bundles.
+
+Usage:
+  swift-stdlib-tool.py BINARY_TO_SCAN PLATFORM_DIRECTORY OUTPUT_PATH
+"""
+
+import os
+import shutil
+import sys
+from macholib.MachO import MachO
+
+
+def dylib_full_path(platform_dir, relative_path):
+  """Constructs an absolute path to a platform dylib.
+
+  Args:
+    platform_dir: A path to the platforms directory in the Swift toolchain.
+    relative_path: A path to a dylib relative to the platforms directory.
+
+  Returns:
+    A normalized, absolute path to a dylib.
+  """
+  return os.path.abspath(os.path.join(platform_dir, relative_path))
+
+
+def main():
+  binary_path = sys.argv[1]
+  platform_dir = sys.argv[2]
+  out_path = sys.argv[3]
+
+  # We want any dylib linked against which name starts with "libswift"
+  seen = set()
+  queue = [binary_path]
+  while queue:
+    path = queue.pop()
+    m = MachO(path)
+    for header in m.headers:
+      for _, _, other in header.walkRelocatables():
+        if other.startswith("@rpath/libswift"):
+          full_path = dylib_full_path(platform_dir, other.lstrip("@rpath/"))
+          if full_path not in seen:
+            queue.append(full_path)
+            seen.add(full_path)
+
+  for dylib in seen:
+    shutil.copy(dylib, out_path)
+
+if __name__ == "__main__":
+  main()
diff --git a/src/tools/xcode/swiftstdlibtoolwrapper/swiftstdlibtoolwrapper.sh b/src/tools/xcode/swiftstdlibtoolwrapper/swiftstdlibtoolwrapper.sh
index 99c9205..9bf325f 100755
--- a/src/tools/xcode/swiftstdlibtoolwrapper/swiftstdlibtoolwrapper.sh
+++ b/src/tools/xcode/swiftstdlibtoolwrapper/swiftstdlibtoolwrapper.sh
@@ -27,9 +27,6 @@
 REALPATH="${MY_LOCATION}/realpath"
 WRAPPER="${MY_LOCATION}/xcrunwrapper.sh"
 
-CMD_ARGS=("$@")
-
-TOOL_ARGS=()
 while [[ $# -gt 0 ]]; do
   ARG="$1"
   shift
@@ -49,29 +46,39 @@
       shift
       TOOLCHAIN=${ARG}
       ;;
-    # Remaining args are swift-stdlib-tool args
-    *)
-     TOOL_ARGS+=("$ARG")
-     ;;
+    --scan-executable)
+      ARG="$1"
+      shift
+      BINARY=${ARG}
+      ;;
+    --platform)
+      ARG="$1"
+      shift
+      PLATFORM=${ARG}
+      ;;
     esac
 done
 
+# Prepare destination directory
 TEMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/swiftstdlibtoolZippingOutput.XXXXXX")
 trap "rm -rf \"$TEMPDIR\"" EXIT
 
 FULLPATH="$TEMPDIR/$PATH_INSIDE_ZIP"
-
-XCRUN_ARGS=()
+mkdir -p "${FULLPATH}"
 
 if [ -n "${TOOLCHAIN:-}" ]; then
-  XCRUN_ARGS+=(--toolchain "$TOOLCHAIN")
+  readonly swiftc_dir=$(dirname "$("${WRAPPER}" -f swiftc --toolchain "${TOOLCHAIN}")")
+else
+  readonly swiftc_dir=$(dirname "$("${WRAPPER}" -f swiftc)")
 fi
 
-XCRUN_ARGS+=(swift-stdlib-tool --copy --verbose )
-XCRUN_ARGS+=(--destination "$FULLPATH")
-XCRUN_ARGS+=( "${TOOL_ARGS[@]}" )
+# Each toolchain has swift libraries directory located at
+# /path/to/swiftc/../../lib/swift/<platform>/
+# This is the same relative path that Xcode uses and is considered to be stable.
+readonly platform_dir="${swiftc_dir}/../lib/swift/${PLATFORM}"
 
-$WRAPPER "${XCRUN_ARGS[@]}"
+# Always use macOS system Python as it comes with macholib module.
+/usr/bin/python "${MY_LOCATION}/swift_stdlib_tool.py" "${BINARY}" "${platform_dir}" "${FULLPATH}"
 
 # Need to push/pop tempdir so it isn't the current working directory
 # when we remove it via the EXIT trap.
diff --git a/tools/objc/BUILD b/tools/objc/BUILD
index f1852f4..2146ca5 100644
--- a/tools/objc/BUILD
+++ b/tools/objc/BUILD
@@ -95,6 +95,7 @@
     srcs = [":swiftstdlibtoolwrapper.sh"],
     data = [
         ":realpath",
+        ":swift_stdlib_tool.py",
         ":xcrunwrapper",
     ],
 )