PR #56789: [iOS] Add initial xcframework bazel support

Imported from GitHub PR https://github.com/tensorflow/tensorflow/pull/56789

This adds xcframework support for 2 of the core libraries now that the
public bazel rules support producing static framework based
xcframeworks. This forks the hide symbols script to be compatible with
operating on multiple frameworks inside a given xcframework. Currently
this rules feature requires bazel 6.x rolling releases because it uses a
new API from the C++ starlark migration. That release should be public
in the next few months, for now this doesn't affect 5.x because the new
targets are manual.
Copybara import of the project:

--
5e90cde3004bd8bc38ab8853f01dc4b2fbe91164 by Keith Smiley <keithbsmiley@gmail.com>:

[iOS] Add initial xcframework bazel support

This adds xcframework support for 2 of the core libraries now that the
public bazel rules support producing static framework based
xcframeworks. This forks the hide symbols script to be compatible with
operating on multiple frameworks inside a given xcframework. Currently
this rules feature requires bazel 6.x rolling releases because it uses a
new API from the C++ starlark migration. That release should be public
in the next few months, for now this doesn't affect 5.x because the new
targets are manual.

Merging this change closes #56789

COPYBARA_INTEGRATE_REVIEW=https://github.com/tensorflow/tensorflow/pull/56789 from keith:ks/ios-add-initial-xcframework-bazel-support 5e90cde3004bd8bc38ab8853f01dc4b2fbe91164
PiperOrigin-RevId: 571405194
This commit is contained in:
Keith Smiley
2023-10-06 12:26:19 -07:00
committed by TensorFlower Gardener
parent dbcfdeb64c
commit 45003ffedf
5 changed files with 270 additions and 2 deletions

View File

@@ -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(

View File

@@ -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/"

View File

@@ -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

View File

@@ -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",

View File

@@ -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