Wire local TDLib into iOS FFI build

This commit is contained in:
Mikhail Kilin
2026-05-21 15:27:59 +03:00
parent aec3678bd6
commit 217328505c
34 changed files with 24460 additions and 28 deletions

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
target="${IOS_RUST_TARGET:-aarch64-apple-ios-sim}"
case "${target}" in
aarch64-apple-ios)
tdlib_platform_dir="${TDLIB_IOS_PLATFORM_DIR:-iphoneos}"
;;
aarch64-apple-ios-sim | x86_64-apple-ios)
tdlib_platform_dir="${TDLIB_IOS_PLATFORM_DIR:-iphonesimulator}"
;;
*)
printf 'Unsupported iOS Rust target: %s\n' "${target}" >&2
printf 'Supported targets: aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios\n' >&2
exit 1
;;
esac
tdlib_root="${LOCAL_TDLIB_PATH:-${repo_root}/.build/tdlib-ios/${tdlib_platform_dir}}"
tdlib_version="${TDLIB_VERSION:-1.8.29}"
if [[ -z "${DEVELOPER_DIR:-}" && -d /Applications/Xcode.app/Contents/Developer ]]; then
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
fi
if [[ ! -d "${tdlib_root}/include" ]]; then
printf 'TDLib include directory not found: %s\n' "${tdlib_root}/include" >&2
printf 'Run scripts/build-tdlib-ios.sh first or set LOCAL_TDLIB_PATH to a TDLib install root.\n' >&2
exit 1
fi
if [[ ! -f "${tdlib_root}/lib/libtdjson.${tdlib_version}.dylib" ]]; then
printf 'TDLib dylib not found: %s\n' "${tdlib_root}/lib/libtdjson.${tdlib_version}.dylib" >&2
printf 'tdlib-rs local-tdlib expects the versioned dylib name on macOS-hosted builds.\n' >&2
exit 1
fi
cd "${repo_root}"
if ! rustup target list --installed | grep -qx "${target}"; then
rustup target add "${target}"
fi
export LOCAL_TDLIB_PATH="${tdlib_root}"
cargo build \
-p tele-ios-ffi \
--no-default-features \
--features core-session-local-tdlib \
--target "${target}" \
--release
printf 'Built tele-ios-ffi for %s with LOCAL_TDLIB_PATH=%s\n' "${target}" "${LOCAL_TDLIB_PATH}"

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
package_dir="${repo_root}/apps/ios/TeleTuiIOS"
out_dir="${1:-${repo_root}/build/ios-real-ffi-xcframework}"
artifacts_dir="${package_dir}/BinaryArtifacts"
swift_sources_dir="${package_dir}/Generated/tele_ios_ffi/Sources/tele_ios_ffi"
framework_name="${IOS_FFI_FRAMEWORK_NAME:-tele_ios_ffi}"
tdjson_framework_name="${IOS_TDJSON_FRAMEWORK_NAME:-tdjson}"
targets="${IOS_RUST_TARGETS:-}"
if [[ -z "${DEVELOPER_DIR:-}" && -d /Applications/Xcode.app/Contents/Developer ]]; then
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
fi
if [[ -z "${targets}" ]]; then
targets="x86_64-apple-ios aarch64-apple-ios-sim"
fi
cd "${repo_root}"
case "${out_dir}" in
"" | "/" | "/tmp" | "/private" | "/private/tmp")
printf 'Refusing unsafe output directory: %s\n' "${out_dir}" >&2
exit 2
;;
esac
rm -rf "${out_dir}"
mkdir -p "${out_dir}/Swift" "${out_dir}/Headers" "${out_dir}/TdjsonHeaders"
tdjson_args=()
first_lib_path=""
simulator_libs=()
device_lib=""
seen_simulator=0
seen_device=0
for target in ${targets}; do
IOS_RUST_TARGET="${target}" "${repo_root}/scripts/build-ios-ffi-with-local-tdlib.sh"
lib_path="${repo_root}/target/${target}/release/libtele_ios_ffi.a"
if [[ -z "${first_lib_path}" ]]; then
first_lib_path="${lib_path}"
fi
case "${target}" in
aarch64-apple-ios)
seen_device=1
device_lib="${lib_path}"
;;
aarch64-apple-ios-sim | x86_64-apple-ios)
seen_simulator=1
simulator_libs+=("${lib_path}")
;;
*)
printf 'Unsupported iOS Rust target for XCFramework packaging: %s\n' "${target}" >&2
exit 1
;;
esac
done
if [[ -z "${first_lib_path}" ]]; then
printf 'No Rust targets selected for iOS FFI packaging\n' >&2
exit 1
fi
cargo run -p uniffi-bindgen-swift -- "${first_lib_path}" "${out_dir}/Swift" --swift-sources
cargo run -p uniffi-bindgen-swift -- "${first_lib_path}" "${out_dir}/Headers" --headers
cargo run -p uniffi-bindgen-swift -- "${first_lib_path}" "${out_dir}/Headers" \
--modulemap \
--module-name tele_ios_ffiFFI \
--modulemap-filename module.modulemap
cat > "${out_dir}/TdjsonHeaders/tdjson.h" <<'HEADER'
void td_json_client_send(void *client, const char *request);
const char *td_json_client_receive(void *client, double timeout);
const char *td_json_client_execute(void *client, const char *request);
void *td_json_client_create(void);
void td_json_client_destroy(void *client);
HEADER
if [[ "${seen_simulator}" == "1" ]]; then
tdjson_args+=(
"-library" "${repo_root}/.build/tdlib-ios/iphonesimulator/lib/libtdjson.1.8.29.dylib"
"-headers" "${out_dir}/TdjsonHeaders"
)
fi
if [[ "${seen_device}" == "1" ]]; then
tdjson_args+=(
"-library" "${repo_root}/.build/tdlib-ios/iphoneos/lib/libtdjson.1.8.29.dylib"
"-headers" "${out_dir}/TdjsonHeaders"
)
fi
rm -rf "${artifacts_dir}/${framework_name}.xcframework" \
"${artifacts_dir}/${tdjson_framework_name}.xcframework" \
"${swift_sources_dir}"
mkdir -p "${artifacts_dir}" "${swift_sources_dir}"
ffi_args=()
if [[ "${#simulator_libs[@]}" -gt 0 ]]; then
simulator_lib="${out_dir}/libtele_ios_ffi-iphonesimulator.a"
if [[ "${#simulator_libs[@]}" -eq 1 ]]; then
cp "${simulator_libs[0]}" "${simulator_lib}"
else
xcrun lipo -create "${simulator_libs[@]}" -output "${simulator_lib}"
fi
ffi_args+=("-library" "${simulator_lib}" "-headers" "${out_dir}/Headers")
fi
if [[ -n "${device_lib}" ]]; then
ffi_args+=("-library" "${device_lib}" "-headers" "${out_dir}/Headers")
fi
xcodebuild -create-xcframework \
"${ffi_args[@]}" \
-output "${artifacts_dir}/${framework_name}.xcframework"
xcodebuild -create-xcframework \
"${tdjson_args[@]}" \
-output "${artifacts_dir}/${tdjson_framework_name}.xcframework"
cp "${out_dir}/Swift/tele_ios_ffi.swift" "${swift_sources_dir}/tele_ios_ffi.swift"
printf 'Generated real iOS FFI artifacts:\n'
printf ' %s\n' "${artifacts_dir}/${framework_name}.xcframework"
printf ' %s\n' "${artifacts_dir}/${tdjson_framework_name}.xcframework"
printf ' %s\n' "${swift_sources_dir}/tele_ios_ffi.swift"
printf 'Build Swift package with: TELE_IOS_USE_LOCAL_FFI=1 DEVELOPER_DIR=%s xcodebuild ...\n' "${DEVELOPER_DIR:-}"

View File

@@ -81,6 +81,17 @@ cat > "${app_path}/Info.plist" <<PLIST
</plist>
PLIST
tdjson_path="${product_dir}/libtdjson.1.8.29.dylib"
if [[ -f "${tdjson_path}" ]]; then
mkdir -p "${app_path}/Frameworks"
cp "${tdjson_path}" "${app_path}/Frameworks/libtdjson.dylib"
codesign --remove-signature "${app_path}/TeleTuiIOSApp" >/dev/null 2>&1 || true
if ! otool -l "${app_path}/TeleTuiIOSApp" | grep -q '@executable_path/Frameworks'; then
install_name_tool -add_rpath '@executable_path/Frameworks' "${app_path}/TeleTuiIOSApp"
fi
codesign --force --sign - --timestamp=none "${app_path}/Frameworks/libtdjson.dylib" >/dev/null
fi
codesign --force --sign - --timestamp=none "${app_path}/TeleTuiIOSApp" >/dev/null
codesign --force --sign - --timestamp=none "${app_path}" >/dev/null

187
scripts/build-tdlib-ios.sh Executable file
View File

@@ -0,0 +1,187 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
tdlib_version="${TDLIB_VERSION:-1.8.29}"
tdlib_commit="${TDLIB_COMMIT:-af69dd4397b6dc1bf23ba0fd0bf429fcba6454f6}"
tdlib_repo="${TDLIB_REPO:-https://github.com/tdlib/td.git}"
source_dir="${TDLIB_SOURCE_DIR:-${repo_root}/.build/tdlib-src}"
build_root="${TDLIB_BUILD_ROOT:-${repo_root}/.build/tdlib-ios-build}"
output_root="${TDLIB_OUTPUT_ROOT:-${repo_root}/.build/tdlib-ios}"
platforms="${TDLIB_IOS_PLATFORMS:-iOS iOS-simulator}"
openssl_platforms="${TDLIB_OPENSSL_PLATFORMS:-iOS iOS-simulator}"
ios_deployment_target="${TDLIB_IOS_DEPLOYMENT_TARGET:-17.0}"
jobs="${TDLIB_BUILD_JOBS:-3}"
if [[ -z "${DEVELOPER_DIR:-}" && -d /Applications/Xcode.app/Contents/Developer ]]; then
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
fi
require_tool() {
if ! command -v "$1" >/dev/null 2>&1; then
printf 'Required tool not found: %s\n' "$1" >&2
return 1
fi
}
missing=0
for tool in git cmake c++ gperf make perl rsync xcodebuild xcrun install_name_tool; do
require_tool "${tool}" || missing=1
done
if [[ "${missing}" -ne 0 ]]; then
printf '\nInstall missing TDLib build dependencies on macOS with:\n' >&2
printf ' brew install cmake gperf coreutils\n' >&2
exit 1
fi
if ! xcrun --sdk iphoneos --show-sdk-path >/dev/null; then
printf 'The iphoneos SDK is unavailable through xcrun.\n' >&2
printf 'Set DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer and install the iOS platform in Xcode.\n' >&2
exit 1
fi
mkdir -p "$(dirname "${source_dir}")" "${build_root}" "${output_root}"
if [[ ! -d "${source_dir}/.git" ]]; then
git clone "${tdlib_repo}" "${source_dir}"
fi
git -C "${source_dir}" fetch --depth 1 origin "${tdlib_commit}"
git -C "${source_dir}" checkout --detach "${tdlib_commit}"
openssl_root="${source_dir}/example/ios/third_party/openssl"
python_apple_support_dir="${source_dir}/example/ios/Python-Apple-support"
prepare_python_apple_support() {
if [[ ! -d "${python_apple_support_dir}/.git" ]]; then
git clone https://github.com/beeware/Python-Apple-support "${python_apple_support_dir}"
fi
git -C "${python_apple_support_dir}" checkout 6f43aba0ddd5a9f52f39775d0141bd4363614020
git -C "${python_apple_support_dir}" reset --hard
(
cd "${python_apple_support_dir}"
git apply ../Python-Apple-support.patch
# Python-Apple-support at this revision emits apple-ios-simulator-simulator
# under newer Xcode SDK naming. Use the base OS name before appending
# "-simulator" to the target triple.
perl -pi -e 's/apple-\$\$\(OS_LOWER-\$\(target\)\)-simulator/apple-\$\$\(patsubst %-simulator,%,\$\$\(OS_LOWER-\$\(target\)\)\)-simulator/' Makefile
)
}
prepare_td_auto_sources() {
local host_build_dir="${build_root}/build-host-generate"
local td_auto_dir="${source_dir}/td/generate/auto/td/telegram"
local mime_auto_dir="${source_dir}/tdutils/generate/auto"
if [[ -f "${td_auto_dir}/td_api.cpp" \
&& -f "${td_auto_dir}/td_api_json.cpp" \
&& -f "${mime_auto_dir}/mime_type_to_extension.cpp" \
&& -f "${mime_auto_dir}/extension_to_mime_type.cpp" ]]; then
return 0
fi
ZERO_AR_DATE=1 cmake -S "${source_dir}" -B "${host_build_dir}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_MAKE_PROGRAM=make
cmake --build "${host_build_dir}" --target prepare_cross_compiling -- -j"${jobs}"
}
prepare_openssl_platform() {
local platform="$1"
local output_dir="${openssl_root}/${platform}"
if [[ -f "${output_dir}/lib/libcrypto.a" && -f "${output_dir}/lib/libssl.a" ]]; then
return 0
fi
prepare_python_apple_support
(
cd "${python_apple_support_dir}"
rm -rf "build/${platform}" "install/${platform}" "merge/${platform}"
make "OpenSSL-${platform}"
)
rm -rf "${output_dir}"
mkdir -p "${output_dir}/lib"
cp "${python_apple_support_dir}/merge/${platform}/openssl/lib/libcrypto.a" "${output_dir}/lib/"
cp "${python_apple_support_dir}/merge/${platform}/openssl/lib/libssl.a" "${output_dir}/lib/"
cp -R "${python_apple_support_dir}/merge/${platform}/openssl/include" "${output_dir}/include"
}
if [[ ! -f "${openssl_root}/iOS/lib/libcrypto.a" || ! -f "${openssl_root}/iOS-simulator/lib/libcrypto.a" ]]; then
printf 'OpenSSL for iOS was not found under %s.\n' "${openssl_root}" >&2
printf 'Building only required OpenSSL platforms: %s\n' "${openssl_platforms}"
for platform in ${openssl_platforms}; do
prepare_openssl_platform "${platform}"
done
fi
prepare_td_auto_sources
build_platform() {
local td_platform="$1"
local ios_platform
local out_platform
case "${td_platform}" in
iOS)
ios_platform="OS"
out_platform="iphoneos"
;;
iOS-simulator)
ios_platform="SIMULATOR"
out_platform="iphonesimulator"
;;
*)
printf 'Unsupported TDLib iOS platform: %s\n' "${td_platform}" >&2
return 1
;;
esac
local openssl_path="${openssl_root}/${td_platform}"
local openssl_crypto_library="${openssl_path}/lib/libcrypto.a"
local openssl_ssl_library="${openssl_path}/lib/libssl.a"
local build_dir="${build_root}/build-${td_platform}"
local install_dir="${build_root}/install-${td_platform}"
local output_dir="${output_root}/${out_platform}"
if [[ ! -f "${openssl_crypto_library}" || ! -f "${openssl_ssl_library}" ]]; then
printf 'OpenSSL libraries are missing for %s under %s\n' "${td_platform}" "${openssl_path}" >&2
return 1
fi
rm -rf "${build_dir}" "${install_dir}" "${output_dir}"
mkdir -p "${build_dir}" "${install_dir}" "${output_dir}/include" "${output_dir}/lib"
ZERO_AR_DATE=1 cmake -S "${source_dir}" -B "${build_dir}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_INSTALL_PREFIX="${install_dir}" \
-DCMAKE_TOOLCHAIN_FILE="${source_dir}/CMake/iOS.cmake" \
-DIOS_PLATFORM="${ios_platform}" \
-DIOS_DEPLOYMENT_TARGET="${ios_deployment_target}" \
-DCMAKE_MAKE_PROGRAM=make \
-DOPENSSL_FOUND=1 \
-DOPENSSL_CRYPTO_LIBRARY="${openssl_crypto_library}" \
-DOPENSSL_SSL_LIBRARY="${openssl_ssl_library}" \
-DOPENSSL_INCLUDE_DIR="${openssl_path}/include" \
-DOPENSSL_LIBRARIES="${openssl_crypto_library};${openssl_ssl_library}"
cmake --build "${build_dir}" --target install -- -j"${jobs}"
install_name_tool -id @rpath/libtdjson.dylib "${install_dir}/lib/libtdjson.dylib"
rsync -a "${install_dir}/include/" "${output_dir}/include/"
cp "${install_dir}/lib/libtdjson.dylib" "${output_dir}/lib/libtdjson.dylib"
cp "${install_dir}/lib/libtdjson.dylib" "${output_dir}/lib/libtdjson.${tdlib_version}.dylib"
printf 'Installed TDLib %s for %s at %s\n' "${tdlib_version}" "${td_platform}" "${output_dir}"
}
for platform in ${platforms}; do
build_platform "${platform}"
done

View File

@@ -5,10 +5,4 @@ if [[ -z "${DEVELOPER_DIR:-}" && -d /Applications/Xcode.app/Contents/Developer ]
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
fi
target="${IOS_RUST_TARGET:-aarch64-apple-ios-sim}"
if ! rustup target list --installed | grep -qx "${target}"; then
rustup target add "${target}"
fi
cargo build -p tele-ios-ffi --target "${target}" --release
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/build-ios-ffi-with-local-tdlib.sh"