diff --git a/tensorflow/lite/ios/BUILD.apple b/tensorflow/lite/ios/BUILD.apple index 0aaed0fde68..fd5bdd9e3e4 100644 --- a/tensorflow/lite/ios/BUILD.apple +++ b/tensorflow/lite/ios/BUILD.apple @@ -6,6 +6,7 @@ load( "TFL_MINIMUM_OS_VERSION", "strip_common_include_path_prefix", "tflite_ios_framework", + "tflite_ios_xcframework", ) load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") load("//tensorflow:pytype.default.bzl", "pytype_strict_binary", "pytype_strict_library") @@ -29,6 +30,17 @@ sh_binary( ], ) +sh_binary( + name = "hide_xcframework_symbols_with_allowlist", + srcs = [ + "hide_xcframework_symbols_with_allowlist.sh", + ], + visibility = [ + "//tensorflow/lite:__subpackages__", + "@org_tensorflow_lite_support//tensorflow_lite_support:__subpackages__", + ], +) + pytype_strict_library( name = "extract_object_files", srcs = [ @@ -126,6 +138,35 @@ tflite_ios_framework( ], ) +# bazel build -c opt --config=ios //tensorflow/lite/ios:TensorFlowLiteC_xcframework +tflite_ios_xcframework( + name = "TensorFlowLiteC_xcframework", + allowlist_symbols_file = ":allowlist_TensorFlowLiteC.txt", + bundle_name = "TensorFlowLiteC", + ios = { + "simulator": [ + "x86_64", + "arm64", + ], + "device": ["arm64"], + }, + minimum_os_versions = {"ios": TFL_MINIMUM_OS_VERSION}, + public_hdrs = [ + ":builtin_ops.h", + ":c_api.h", + ":c_api_experimental.h", + ":c_api_types.h", + ":common.h", + ":profiler.h", + ":telemetry_setting.h", + ":xnnpack_delegate.h", + ], + tags = ["manual"], # TODO: Remove once tf is on bazel 6.x+ + deps = [ + ":tensorflow_lite_c", + ], +) + # Similar to TensorFlowLiteC_framework but this is a static framework and symbol # hiding is not applied. Note both have the same bundle name. ios_static_framework( diff --git a/tensorflow/lite/ios/hide_xcframework_symbols_with_allowlist.sh b/tensorflow/lite/ios/hide_xcframework_symbols_with_allowlist.sh new file mode 100755 index 00000000000..8c0b894ab15 --- /dev/null +++ b/tensorflow/lite/ios/hide_xcframework_symbols_with_allowlist.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# Copyright 2023 The TensorFlow 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 script to merge Mach-O object files into a single object file and hide +# their internal symbols. Only allowed symbols will be visible in the +# symbol table after this script. + +# To run this script, you must set several variables: +# INPUT_FRAMEWORK: a zip file containing the iOS static framework. +# BUNDLE_NAME: the pod/bundle name of the iOS static framework. +# ALLOWLIST_FILE_PATH: contains the allowed symbols. +# EXTRACT_SCRIPT_PATH: path to the extract_object_files script. +# OUTPUT: the output zip file. + +# Halt on any error or any unknown variable. +set -ue + +# mktemp from coreutils has different flags. Make sure we get the iOS one. +MKTEMP=/usr/bin/mktemp + +LD_DEBUGGABLE_FLAGS="-x" +# Uncomment the below to get debuggable output. This can only be done for one +# library at a time. +# LD_DEBUGGABLE_FLAGS="-d" + +# Exits if C++ symbols are found in the allowlist. +if grep -q "^__Z" "${ALLOWLIST_FILE_PATH}"; then + echo "ERROR: Failed in symbol hiding. This rule does not permit hiding of" \ + "C++ symbols due to possible serious problems mixing symbol hiding," \ + "shared libraries and the C++ runtime." \ + "More info can be found in go/ios-symbols-hiding." \ + "Please recheck the allowlist and remove C++ symbols:" + echo "$(grep "^__Z" "${ALLOWLIST_FILE_PATH}")" + exit 1 # terminate and indicate error +fi +# Unzips the framework zip file into a temp workspace. +xcframework=$($MKTEMP -t framework -d) +unzip "${INPUT_FRAMEWORK}" -d "${xcframework}"/ +unzip -oqq "${INPUT_FRAMEWORK}" + +for framework in "$xcframework"/*.xcframework/ios-*; do + echo "fw is $framework" + # Executable file in the framework. + executable_file="${BUNDLE_NAME}.framework/${BUNDLE_NAME}" + + # Extracts architectures from the framework binary. + archs_str=$(xcrun lipo -info "${framework}/${executable_file}" | + sed -En -e 's/^(Non-|Architectures in the )fat file: .+( is architecture| are): (.*)$/\3/p') + + IFS=' ' read -r -a archs <<< "${archs_str}" + + merge_cmd=(xcrun lipo) + + # Merges object files and hide symbols for each architecture. + for arch in "${archs[@]}"; do + archdir=$($MKTEMP -t "${arch}" -d) + arch_file="${archdir}/${arch}" + + # Handles the binary differently if they are fat or thin. + if [[ "${#archs[@]}" -gt 1 ]]; then + xcrun lipo "${framework}/${executable_file}" -thin "${arch}" -output "${arch_file}" + else + mv "${framework}/${executable_file}" "${arch_file}" + fi + if [[ "$arch" == "armv7" ]]; then + # Check that there are no thread local variables in the input, as they get broken. + # See b/124533863. + thread_locals=$(xcrun nm -m -g "${arch_file}" | awk '/__DATA,__thread_vars/ { print $5 }' | c++filt) + if [[ -n "${thread_locals}" ]]; then + echo + echo "WARNING: This symbol hiding script breaks thread local variables on 32-bit arm, you had:" + echo "${thread_locals}" + echo + echo "Your build will crash if these variables are actually used at runtime." + echo + fi + fi + if [[ ! -z "${EXTRACT_SCRIPT_PATH}" ]]; then + "${EXTRACT_SCRIPT_PATH}" "${arch_file}" "${archdir}" + else + # ar tool extracts the objects in the current working directory. Since the + # default working directory for a genrule is always the same, there can be + # a race condition when this script is called for multiple targets + # simultaneously. + pushd "${archdir}" > /dev/null + xcrun ar -x "${arch_file}" + popd > /dev/null + fi + + objects_file_list=$($MKTEMP) + # Hides the symbols except the allowed ones. + find "${archdir}" -name "*.o" >> "${objects_file_list}" + + # Checks whether bitcode is enabled in the framework. + all_objects_have_bitcode=true + for object_file in $(cat "$objects_file_list"); do + if otool -arch "${arch}" -l "${object_file}" | grep -q __LLVM; then + : # Do nothing + else + echo "The ${arch} in ${object_file} is NOT bitcode-enabled." + all_objects_have_bitcode=false + break + fi + done + if [[ "$all_objects_have_bitcode" = "true" ]]; then + echo "The ${arch} in ${executable_file} is fully bitcode-enabled." + xcrun ld -r -bitcode_bundle -exported_symbols_list \ + "${ALLOWLIST_FILE_PATH}" \ + $LD_DEBUGGABLE_FLAGS \ + -filelist "${objects_file_list}" -o "${arch_file}_processed.o" + else + echo "The ${arch} in ${executable_file} is NOT fully bitcode-enabled." + xcrun ld -r -exported_symbols_list \ + "${ALLOWLIST_FILE_PATH}" \ + $LD_DEBUGGABLE_FLAGS \ + -filelist "${objects_file_list}" -o "${arch_file}_processed.o" + fi + + output_object="${framework}/${arch}" + + mv "${arch_file}_processed.o" "${output_object}" + rm -rf "${archdir}" + rm "${objects_file_list}" + merge_cmd+=(-arch "${arch}" "${output_object}") + done + + merge_cmd+=(-create -output "${BUNDLE_NAME}") + "${merge_cmd[@]}" + + chmod +x "${BUNDLE_NAME}" + executable_slice="${BUNDLE_NAME}.xcframework/$(basename "${framework}")/${BUNDLE_NAME}.framework/${BUNDLE_NAME}" + rm "$executable_slice" + mv "${BUNDLE_NAME}" "${executable_slice}" +done + +( TZ=UTC find "${BUNDLE_NAME}.xcframework/" -exec touch -h -t 198001010000 {} \+ ) +zip --compression-method store --symlinks --recurse-paths --quiet "${OUTPUT}" "${BUNDLE_NAME}.xcframework/" diff --git a/tensorflow/lite/ios/ios.bzl b/tensorflow/lite/ios/ios.bzl index c2bf592207c..a123f2d9982 100644 --- a/tensorflow/lite/ios/ios.bzl +++ b/tensorflow/lite/ios/ios.bzl @@ -1,6 +1,7 @@ """TensorFlow Lite Build Configurations for iOS""" load("//tensorflow:tensorflow.bzl", "clean_dep") +load("@build_bazel_rules_apple//apple:apple.bzl", "apple_static_xcframework") # Placeholder for Google-internal load statements. load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") @@ -85,6 +86,59 @@ def tflite_ios_framework( ], ) +# iOS xcframework with symbol allowlist. Exported C++ symbols might cause symbol +# collision with other libraries. List of symbols to allowlist can be +# generated by running `nm -m -g FRAMEWORK_LIBRARY | grep _TfLite` for framework +# built with `apple_static_xcframework` rule. +def tflite_ios_xcframework( + name, + bundle_name, + allowlist_symbols_file, + **kwargs): + """Apply symbol hiding to the output of apple_static_framework. + + Args: + name: The name of the target. + bundle_name: The name to give to the xcframework bundle, without the + ".xcframework" extension. If omitted, the target's name will be used. + allowlist_symbols_file: a file including a list of allowed symbols, + one symbol per line. + **kwargs: Pass-through arguments. + """ + + preprocessed_name = "Preprocessed_" + name + apple_static_xcframework( + name = preprocessed_name, + bundle_name = bundle_name, + **kwargs + ) + + xcframework_target = ":{}.xcframework.zip".format(preprocessed_name) + + srcs = [ + xcframework_target, + allowlist_symbols_file, + ] + clean_dep_extract_object_files_main = clean_dep("//tensorflow/lite/ios:extract_object_files_main") + clean_dep_hide_symbols_with_allowlist = clean_dep("//tensorflow/lite/ios:hide_xcframework_symbols_with_allowlist") + cmd = ("INPUT_FRAMEWORK=\"$(location " + xcframework_target + ")\" " + + "BUNDLE_NAME=\"" + bundle_name + "\" " + + "ALLOWLIST_FILE_PATH=\"$(location " + allowlist_symbols_file + ")\" " + + "EXTRACT_SCRIPT_PATH=\"$(location " + clean_dep_extract_object_files_main + ")\" " + + "OUTPUT=\"$(OUTS)\" " + + "\"$(location " + clean_dep_hide_symbols_with_allowlist + ")\"") + + native.genrule( + name = name, + srcs = srcs, + outs = [name + ".zip"], + cmd = cmd, + tools = [ + clean_dep_extract_object_files_main, + clean_dep_hide_symbols_with_allowlist, + ], + ) + # When the static framework is built with bazel, the all header files are moved # to the "Headers" directory with no header path prefixes. This auxiliary rule # is used for stripping the path prefix of header inclusions paths from the diff --git a/tensorflow/lite/swift/BUILD.apple b/tensorflow/lite/swift/BUILD.apple index e137c4ce772..b5c16d15ab4 100644 --- a/tensorflow/lite/swift/BUILD.apple +++ b/tensorflow/lite/swift/BUILD.apple @@ -2,6 +2,7 @@ load("//tensorflow/lite:special_rules.bzl", "ios_visibility_allowlist", "tflite_ios_lab_runner") load("//tensorflow/lite/ios:ios.bzl", "TFL_DEFAULT_TAGS", "TFL_DISABLED_SANITIZER_TAGS", "TFL_MINIMUM_OS_VERSION") +load("//tools/build_defs/apple:apple.bzl", "apple_static_xcframework") load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework", "ios_unit_test") load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") @@ -45,6 +46,7 @@ swift_library( ], "//conditions:default": [], }), + copts = ["-no-verify-emitted-module-interface"], linkopts = select({ ":use_coreml_delegate": [ "-Wl,-weak_framework,CoreML", @@ -103,6 +105,27 @@ ios_static_framework( ], ) +# bazel build -c opt --config=ios //tensorflow/lite/swift:TensorFlowLite_xcframework +apple_static_xcframework( + name = "TensorFlowLite_xcframework", + avoid_deps = [ + "//tensorflow/lite/ios:tensorflow_lite_c", + ], + bundle_name = "TensorFlowLite", + ios = { + "simulator": [ + "x86_64", + "arm64", + ], + "device": ["arm64"], + }, + minimum_os_versions = {"ios": TFL_MINIMUM_OS_VERSION}, + tags = ["manual"], # TODO: Remove once tf is on bazel 6.x+ + deps = [ + ":TensorFlowLite", + ], +) + ios_unit_test( name = "Tests", size = "small", diff --git a/tensorflow/workspace2.bzl b/tensorflow/workspace2.bzl index e52b690bdd9..f83f3973c84 100644 --- a/tensorflow/workspace2.bzl +++ b/tensorflow/workspace2.bzl @@ -731,8 +731,8 @@ def _tf_repositories(): # https://github.com/bazelbuild/rules_swift/releases tf_http_archive( name = "build_bazel_rules_swift", - sha256 = "12057b7aa904467284eee640de5e33853e51d8e31aae50b3fb25d2823d51c6b8", - urls = tf_mirror_urls("https://github.com/bazelbuild/rules_swift/releases/download/1.0.0/rules_swift.1.0.0.tar.gz"), + sha256 = "32f95dbe6a88eb298aaa790f05065434f32a662c65ec0a6aabdaf6881e4f169f", + urls = tf_mirror_urls("https://github.com/bazelbuild/rules_swift/releases/download/1.5.0/rules_swift.1.5.0.tar.gz"), ) # https://github.com/bazelbuild/apple_support/releases