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

4
.gitignore vendored
View File

@@ -1,4 +1,8 @@
/target
/.build
/build
/apps/ios/TeleTuiIOS/BinaryArtifacts
/apps/ios/TeleTuiIOS/Generated
# TDLib session data (contains auth tokens - NEVER commit!)
/tdlib_data/

2
Cargo.lock generated
View File

@@ -4097,8 +4097,6 @@ dependencies = [
[[package]]
name = "tdlib-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c309480dcdd6d5dc2f37866d9063fed280780ddfeb51ae3a0adc2b52b0c0bc3"
dependencies = [
"dirs 6.0.0",
"futures-channel",

View File

@@ -7,3 +7,6 @@ members = [
]
default-members = ["crates/tele-tui"]
resolver = "2"
[patch.crates-io]
tdlib-rs = { path = "crates/vendor/tdlib-rs" }

View File

@@ -1,7 +1,29 @@
// swift-tools-version: 6.0
import Foundation
import PackageDescription
let useLocalFfi = ProcessInfo.processInfo.environment["TELE_IOS_USE_LOCAL_FFI"] == "1"
let localFfiTargets: [Target] = useLocalFfi ? [
.binaryTarget(
name: "tele_ios_ffiFFI",
path: "BinaryArtifacts/tele_ios_ffi.xcframework"
),
.binaryTarget(
name: "tdjson",
path: "BinaryArtifacts/tdjson.xcframework"
),
.target(
name: "tele_ios_ffi",
dependencies: ["tele_ios_ffiFFI", "tdjson"],
path: "Generated/tele_ios_ffi/Sources/tele_ios_ffi"
),
] : []
let coreDependencies: [Target.Dependency] = useLocalFfi ? [
"tele_ios_ffi",
] : []
let package = Package(
name: "TeleTuiIOS",
platforms: [
@@ -14,7 +36,10 @@ let package = Package(
.executable(name: "TeleTuiIOSSmokeTests", targets: ["TeleTuiIOSSmokeTests"]),
],
targets: [
.target(name: "TeleTuiIOSCore"),
.target(
name: "TeleTuiIOSCore",
dependencies: coreDependencies
),
.executableTarget(
name: "TeleTuiIOSApp",
dependencies: ["TeleTuiIOSCore"]
@@ -23,5 +48,5 @@ let package = Package(
name: "TeleTuiIOSSmokeTests",
dependencies: ["TeleTuiIOSCore"]
),
]
] + localFfiTargets
)

View File

@@ -38,3 +38,10 @@ Run the simulator launch plus screenshot sanity check:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/smoke-ios-simulator-ui.sh
```
Build the app against the local real Rust/TDLib FFI artifacts:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-ios-real-ffi-xcframework.sh
TELE_IOS_USE_LOCAL_FFI=1 DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-ios-simulator-app.sh
```

View File

@@ -5,7 +5,7 @@ public enum SessionBridgeFactory {
account: Account,
useFakeTdlib: Bool = true
) -> SessionBridge {
#if canImport(tele_ios_ffiFFI)
#if canImport(tele_ios_ffi) || canImport(tele_ios_ffiFFI)
do {
return try UniFfiSessionBridge(account: account, useFakeTdlib: useFakeTdlib)
} catch {

View File

@@ -1,8 +1,12 @@
import Foundation
#if canImport(tele_ios_ffiFFI)
#if canImport(tele_ios_ffi)
import tele_ios_ffi
#elseif canImport(tele_ios_ffiFFI)
import tele_ios_ffiFFI
#endif
#if canImport(tele_ios_ffi) || canImport(tele_ios_ffiFFI)
public actor UniFfiSessionBridge: SessionBridge {
private let handle: SessionHandle
private let defaultLimit: Int32

View File

@@ -10,12 +10,14 @@ keywords = ["telegram", "tdlib"]
categories = ["api-bindings"]
[features]
default = []
default = ["tdlib-download"]
images = []
test-support = []
tdlib-download = ["tdlib-rs/download-tdlib"]
tdlib-local = ["tdlib-rs/local-tdlib"]
[dependencies]
tdlib-rs = { version = "1.2.0", features = ["download-tdlib"] }
tdlib-rs = { version = "1.2.0", default-features = false }
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -11,15 +11,17 @@ repository = "https://github.com/your-username/tele-tui"
crate-type = ["cdylib", "staticlib", "rlib"]
[features]
default = ["core-session"]
default = ["core-session-download"]
core-session = ["dep:tele-core"]
core-session-download = ["core-session", "tele-core/tdlib-download"]
core-session-local-tdlib = ["core-session", "tele-core/tdlib-local"]
standalone-fake = []
[dependencies]
tele-core = { path = "../tele-core", features = ["test-support"], optional = true }
tele-core = { path = "../tele-core", default-features = false, features = ["test-support"], optional = true }
tokio = { version = "1", features = ["rt-multi-thread"] }
thiserror = "1.0"
uniffi = { version = "0.31.1", features = ["tokio"] }
[dev-dependencies]
tele-core = { path = "../tele-core", features = ["test-support"] }
tele-core = { path = "../tele-core", default-features = false, features = ["test-support", "tdlib-download"] }

View File

@@ -6,7 +6,7 @@ Current scope:
- Exposes a fake-backed `SessionHandle` for Swift integration tests and app shell work.
- Mirrors the `tele-core::session` DTO/event model with UniFFI-compatible records and enums.
- Keeps real TDLib session creation out of this crate until iOS simulator/device linking is validated.
- Supports a fake-only build for UI work and a real TDLib build path using local iOS TDLib artifacts.
Generate Swift bindings and headers:
@@ -40,5 +40,7 @@ Current linking status:
- Xcode is installed at `/Applications/Xcode.app`, and `DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -version` reports Xcode 26.5.
- The iOS 26.5 simulator runtime is installed and `scripts/check-ios-prereqs.sh` passes with available iPhone/iPad simulators.
- The current app shell uses the fake Swift bridge. Real TDLib iOS simulator/device linking is still pending until TDLib is built for `iphonesimulator` and `iphoneos` and wired into the UniFFI target.
- Run `scripts/check-ios-tdlib-linking.sh` to reproduce the current TDLib iOS blocker documented in `docs/ios/tdlib-linking.md`.
- The current app shell uses the fake Swift bridge.
- `tdlib-rs` does not publish iOS `download-tdlib` archives, so real iOS linking uses `tele-core/tdlib-local` and `LOCAL_TDLIB_PATH`.
- Local TDLib linking is validated for `aarch64-apple-ios-sim` via `scripts/check-ios-tdlib-linking.sh` and for `aarch64-apple-ios` via `IOS_RUST_TARGET=aarch64-apple-ios scripts/build-ios-ffi-with-local-tdlib.sh`.
- `scripts/build-ios-real-ffi-xcframework.sh` packages local simulator Rust slices plus local `libtdjson` into app-local XCFrameworks, generates Swift bindings, and enables Xcode builds with `TELE_IOS_USE_LOCAL_FFI=1`.

View File

@@ -19,7 +19,7 @@ images = ["dep:ratatui-image", "dep:image", "tele-core/images"]
test-support = ["tele-core/test-support"]
[dependencies]
tele-core = { path = "../tele-core" }
tele-core = { path = "../tele-core", default-features = false, features = ["tdlib-download"] }
ratatui = "0.29"
crossterm = "0.28"
tdlib-rs = { version = "1.2.0", features = ["download-tdlib"] }

1
crates/vendor/tdlib-rs/.cargo-ok vendored Normal file
View File

@@ -0,0 +1 @@
{"v":1}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "b60e42456ddbcdcef994f9ffd07c92f1e94dc543"
},
"path_in_vcs": "tdlib-rs"
}

2407
crates/vendor/tdlib-rs/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

133
crates/vendor/tdlib-rs/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,133 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "tdlib-rs"
version = "1.2.0"
authors = [
"Federico Bruzzone <federico.bruzzone.i@gmail.com>",
"Andrea Longoni",
]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Rust wrapper around the Telegram Database Library."
homepage = "https://github.com/FedericoBruzzone/tdlib-rs"
documentation = "https://docs.rs/tdlib-rs"
readme = "README.md"
keywords = [
"telegram",
"tdlib",
"tdjson",
"tdlib-rs",
"telegram-api",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/FedericoBruzzone/tdlib-rs"
[package.metadata.docs.rs]
features = [
"docs",
"bots-only-api",
]
[package.metadata.system-deps]
tdjson = "1.8.29"
[features]
bots-only-api = []
default = []
docs = []
download-tdlib = [
"dep:reqwest",
"dep:zip",
]
local-tdlib = []
pkg-config = ["dep:system-deps"]
[lib]
name = "tdlib_rs"
path = "src/lib.rs"
[[example]]
name = "get_me"
path = "examples/get_me.rs"
[[example]]
name = "test_ci"
path = "examples/test_ci.rs"
[dependencies.dirs]
version = "6.0.0"
[dependencies.futures-channel]
version = "0.3"
[dependencies.log]
version = "0.4"
[dependencies.once_cell]
version = "1.18"
[dependencies.reqwest]
version = "0.12.4"
features = ["blocking"]
optional = true
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.serde_json]
version = "1.0"
[dependencies.serde_with]
version = "3.2"
[dependencies.system-deps]
version = "7"
optional = true
[dependencies.zip]
version = "2.0.0"
optional = true
[dev-dependencies.tokio]
version = "1"
features = [
"macros",
"rt-multi-thread",
"sync",
"time",
]
[build-dependencies.reqwest]
version = "0.12.4"
features = ["blocking"]
optional = true
[build-dependencies.system-deps]
version = "7"
optional = true
[build-dependencies.tdlib-rs-gen]
version = "1.2.0"
[build-dependencies.tdlib-rs-parser]
version = "1.2.0"
[build-dependencies.zip]
version = "2.0.0"
optional = true

57
crates/vendor/tdlib-rs/Cargo.toml.orig generated vendored Normal file
View File

@@ -0,0 +1,57 @@
[package]
name = "tdlib-rs"
version = "1.2.0"
authors = [
"Federico Bruzzone <federico.bruzzone.i@gmail.com>",
"Andrea Longoni",
]
edition = "2021"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/FedericoBruzzone/tdlib-rs"
repository = "https://github.com/FedericoBruzzone/tdlib-rs"
documentation = "https://docs.rs/tdlib-rs"
keywords = ["telegram", "tdlib", "tdjson", "tdlib-rs", "telegram-api"]
description = "Rust wrapper around the Telegram Database Library."
readme = "README.md"
[package.metadata.docs.rs]
features = ["docs", "bots-only-api"]
[package.metadata.system-deps]
tdjson = "1.8.29"
[features]
# The default feature build the library using the local tdlib library
default = []
# This feature is used to enable the functions only available to the Telegram bots
bots-only-api = []
# This feature is used to build the documentation preventing linking to the tdjson library
docs = []
# This feature is used to build the library using the tdlib library installed in the system
local-tdlib = []
# This feature is used to build the library using pkg-config
pkg-config = ["dep:system-deps"]
# This feature is used to build the library using the tdlib library downloaded from github
download-tdlib = ["dep:reqwest", "dep:zip"]
[dependencies]
log = "0.4"
futures-channel = "0.3"
once_cell = "1.18"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "3.2"
system-deps = { version = "7", optional = true }
reqwest = { version = "0.12.4", features = ["blocking"], optional = true }
zip = { version = "2.0.0", optional = true }
dirs = "6.0.0"
[build-dependencies]
tdlib-rs-gen = { path = "../tdlib-rs-gen", version = "1.2.0" }
tdlib-rs-parser = { path = "../tdlib-rs-parser", version = "1.2.0" }
system-deps = { version = "7", optional = true }
reqwest = { version = "0.12.4", features = ["blocking"], optional = true }
zip = { version = "2.0.0", optional = true }
[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"] }

158
crates/vendor/tdlib-rs/README.md vendored Normal file
View File

@@ -0,0 +1,158 @@
[github-license-mit]: https://github.com/FedericoBruzzone/tdlib-rs/blob/main/LICENSE-MIT
[github-license-apache]: https://github.com/FedericoBruzzone/tdlib-rs/blob/main/LICENSE-APACHE
# tdlib-rs
[![Latest version](https://img.shields.io/crates/v/tdlib_rs.svg)](https://crates.io/crates/tdlib_rs)
[![Documentation](https://docs.rs/tdlib-rs/badge.svg)](https://docs.rs/tdlib-rs/latest/tdlib_rs/)
[![CI Linux](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-linux.yml/badge.svg)](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-linux.yml)
[![CI Windows](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-windows.yml/badge.svg)](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-windows.yml)
[![CI macOS](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/FedericoBruzzone/tdlib-rs/actions/workflows/ci-macos.yml)
[![downloads](https://img.shields.io/crates/d/tdlib_rs)](https://crates.io/crates/tdlib_rs)
![license](https://img.shields.io/crates/l/tdlib_rs)
A Rust wrapper around the Telegram Database library. It includes a generator to automatically generate the types and functions from the TDLib's [Type Language](https://core.telegram.org/mtproto/TL) file.
## Why this fork?
This is an improved version of the [tdlib-rs](https://github.com/paper-plane-developers/tdlib-rs) library, with the following additional features:
1. It is cross-platform, it works on Windows, Linux and MacOS.
2. Not required `tdlib` to be compiled and installed on the system.
3. Not required `pkg-config` to build the library and associated exported variables.
4. Three different ways to build the library:
- `download-tdlib`: download the precompiled library from the GitHub releases.
- `local-tdlib`: use the `tdlib` installed on the system.
- `pkg-config`: use the `pkg-config` to build the library.
5. It is possible to download the `tdlib` library from the GitHub releases.
## Information
We provide a precompiled version of the library for the supported platforms:
- Linux (x86_64)
- Linux (arm64)
- macOS Intel (x86_64)
- macOS Apple Silicon (arm64)
- Windows (x86_64)
- Windows (arm64)
We compile it in the CI and we upload the artifacts to the GitHub releases, so we can download it and use to build this library.
It's mainly created for using it in the [tgt](https://github.com/FedericoBruzzone/tgt) client, but it should work also for any other Rust project.
Current supported TDLib version: [1.8.29](https://github.com/tdlib/td/commit/af69dd4397b6dc1bf23ba0fd0bf429fcba6454f6).
## Cargo features
Please see the documentation of the module `build` for more information about the features [here](https://docs.rs/tdlib-rs/latest/tdlib_rs/build/index.html).
It functions that you can use to build the library in different ways.
### download-tdlib
If you don't want to compile and intall the `tdlib` on your system manually, you should enable the `download-tdlib` feature in the `Cargo.toml` file:
```toml
[dependencies]
tdlib = { version = "...", features = [ "download-tdlib" ] }
[build-dependencies]
tdlib = { version = "...", features = [ "download-tdlib" ] }
```
```rust
// build.rs
fn main() {
tdlib_rs::build::build(None);
}
```
### local-tdlib
`local-tdlib` require you to have the `tdlib` (version 1.8.29) compiled and installed on your system, and the following variables exported, for example in the `.bashrc` file:
```sh
# The path to the tdlib folder
export LOCAL_TDLIB_PATH=$HOME/lib/tdlib
```
Then you can enable the `local-tdlib` feature in the `Cargo.toml` file:
```toml
[dependencies]
tdlib = { version = "...", features = [ "local-tdlib" ] }
[build-dependencies]
tdlib = { version = "...", features = [ "local-tdlib" ] }
```
```rust
// build.rs
fn main() {
tdlib_rs::build::build(None);
}
```
### pkg-config
If you want to use the `pkg-config` to build this library, you should enable the `pkg-config` feature in the `Cargo.toml` file:
```toml
[dependencies]
tdlib = { version = "...", features = [ "pkg-config" ] }
[build-dependencies]
tdlib = { version = "...", features = [ "pkg-config" ] }
```
```rust
// build.rs
fn main() {
tdlib_rs::build::build(None);
}
```
remember to have the `tdlib` (version 1.8.29) compiled on your system, and the following variables exported, for example in the `.bashrc` file:
```sh
# pkg-config configuration
export PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH
# dynmic linker configuration
export LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH
```
### docs
This feature skip the linking of the library and only generate the code of `generated.rs`.
Is used only for testing.
### bots-only-api
This feature enable the generation of the functions only used by Telegram bots.
## License
This repository are licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE][github-license-apache] or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT][github-license-mit] or <http://opensource.org/licenses/MIT>)
at your option.
Please review the license file provided in the repository for more information regarding the terms and conditions of the license.
## Contact
If you have any questions, suggestions, or feedback, do not hesitate to [contact me](https://federicobruzzone.github.io/).
Mantainers:
- [FedericoBruzzone](https://github.com/FedericoBruzzone)
- [Andreal2000](https://github.com/Andreal2000)
## Credits
- [grammers](https://github.com/Lonami/grammers): the `tdlib-tl-gen` and `tdlib-tl-parser` projects are forks of the `grammers-tl-gen` and `grammers-tl-parser` projects.
- [rust-tdlib](https://github.com/aCLr/rust-tdlib): for inspiration about some client code.
- [tdlib-rs](https://github.com/paper-plane-developers/tdlib-rs): for inspiration about the generator code.

270
crates/vendor/tdlib-rs/build.rs vendored Normal file
View File

@@ -0,0 +1,270 @@
// Copyright 2020 - developers of the `grammers` project.
// Copyright 2021 - developers of the `tdlib-rs` project.
// Copyright 2024 - developers of the `tgt` and `tdlib-rs` projects.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::env;
use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::path::Path;
use tdlib_rs_gen::generate_rust_code;
use tdlib_rs_parser::parse_tl_file;
use tdlib_rs_parser::tl::Definition;
#[allow(dead_code)]
#[cfg(not(any(feature = "docs", feature = "pkg-config")))]
/// The version of the TDLib library.
const TDLIB_VERSION: &str = "1.8.29";
/// Load the type language definitions from a certain file.
/// Parse errors will be printed to `stderr`, and only the
/// valid results will be returned.
fn load_tl(file: &str) -> std::io::Result<Vec<Definition>> {
let mut file = File::open(file)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(parse_tl_file(contents)
.filter_map(|d| match d {
Ok(d) => Some(d),
Err(e) => {
eprintln!("TL: parse error: {e:?}");
None
}
})
.collect())
}
#[cfg(feature = "local-tdlib")]
/// Copy all files from a directory to another.
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
#[cfg(feature = "local-tdlib")]
/// Copy all the tdlib folder find in the LOCAL_TDLIB_PATH environment variable to the OUT_DIR/tdlib folder
fn copy_local_tdlib() {
match env::var("LOCAL_TDLIB_PATH") {
Ok(tdlib_path) => {
let out_dir = env::var("OUT_DIR").unwrap();
let prefix = format!("{out_dir}/tdlib");
copy_dir_all(Path::new(&tdlib_path), Path::new(&prefix)).unwrap();
}
Err(_) => {
panic!("The LOCAL_TDLIB_PATH env variable must be set to the path of the tdlib folder");
}
};
}
#[cfg(any(feature = "download-tdlib", feature = "local-tdlib"))]
/// Build the project using the generic build configuration.
/// The current supported platforms are:
/// - Linux x86_64
/// - Linux aarch64
/// - Windows x86_64
/// - Windows aarch64
/// - MacOS x86_64
/// - MacOS aarch64
fn generic_build() {
let out_dir = env::var("OUT_DIR").unwrap();
let prefix = format!("{out_dir}/tdlib");
let include_dir = format!("{prefix}/include");
let lib_dir = format!("{prefix}/lib");
let lib_path = {
#[cfg(any(
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64")
))]
{
format!("{lib_dir}/libtdjson.so.{TDLIB_VERSION}")
}
#[cfg(any(
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "macos", target_arch = "aarch64"),
all(target_os = "ios", target_arch = "x86_64"),
all(target_os = "ios", target_arch = "aarch64")
))]
{
format!("{lib_dir}/libtdjson.{TDLIB_VERSION}.dylib")
}
#[cfg(any(
all(target_os = "windows", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "aarch64")
))]
{
format!(r"{lib_dir}\tdjson.lib")
}
};
if !std::path::PathBuf::from(lib_path.clone()).exists() {
panic!("tdjson shared library not found at {lib_path}");
}
#[cfg(any(
all(target_os = "windows", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "aarch64")
))]
{
let bin_dir = format!(r"{prefix}\bin");
println!("cargo:rustc-link-search=native={bin_dir}");
}
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:include={include_dir}");
println!("cargo:rustc-link-lib=dylib=tdjson");
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}");
}
#[cfg(feature = "download-tdlib")]
fn download_tdlib() {
let base_url = "https://github.com/FedericoBruzzone/tdlib-rs/releases/download";
let url = format!(
"{}/v{}/tdlib-{}-{}-{}.zip",
base_url,
env!("CARGO_PKG_VERSION"),
TDLIB_VERSION,
std::env::var("CARGO_CFG_TARGET_OS").unwrap(),
std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
);
// let target_os = if cfg!(target_os = "windows") {
// "Windows"
// } else if cfg!(target_os = "linux") {
// "Linux"
// } else if cfg!(target_os = "macos") {
// "macOS"
// } else {
// ""
// };
// let target_arch = if cfg!(target_arch = "x86_64") {
// "X64"
// } else if cfg!(target_arch = "aarch64") {
// "ARM64"
// } else {
// ""
// };
// let url = format!(
// "{}/test/{}-{}-TDLib-{}.zip",
// base_url, target_os, target_arch, "2589c3fd46925f5d57e4ec79233cd1bd0f5d0c09"
// );
let out_dir = env::var("OUT_DIR").unwrap();
let tdlib_dir = format!("{}/tdlib", &out_dir);
let zip_path = format!("{}.zip", &tdlib_dir);
// Create the request
let response = reqwest::blocking::get(&url).unwrap();
// Check if the response status is successful
if response.status().is_success() {
// Create a file to write to
let mut dest = File::create(&zip_path).unwrap();
// Get the response bytes and write to the file
let content = response.bytes().unwrap();
std::io::copy(&mut content.as_ref(), &mut dest).unwrap();
} else {
panic!(
"[{}] Failed to download file: {}\n{}\n{}",
"Your OS or architecture may be unsupported.",
"Please try using the `pkg-config` or `local-tdlib` features.",
response.status(),
&url
)
}
let mut archive = zip::ZipArchive::new(File::open(&zip_path).unwrap()).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = Path::new(&out_dir).join(file.name());
if (*file.name()).ends_with('/') {
std::fs::create_dir_all(&outpath).unwrap();
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
std::fs::create_dir_all(p).unwrap();
}
}
let mut outfile = File::create(&outpath).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
// Get and set permissions
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).unwrap();
}
}
}
let _ = std::fs::remove_file(&zip_path);
}
fn main() -> std::io::Result<()> {
#[cfg(all(feature = "docs", feature = "pkg-config"))]
compile_error!(
"feature \"docs\" and feature \"pkg-config\" cannot be enabled at the same time"
);
#[cfg(all(feature = "docs", feature = "download-tdlib"))]
compile_error!(
"feature \"docs\" and feature \"download-tdlib\" cannot be enabled at the same time"
);
#[cfg(all(feature = "pkg-config", feature = "download-tdlib"))]
compile_error!(
"feature \"pkg-config\" and feature \"download-tdlib\" cannot be enabled at the same time"
);
println!("cargo:rerun-if-changed=build.rs");
#[cfg(feature = "local-tdlib")]
println!("cargo:rerun-if-env-changed=LOCAL_TDLIB_PATH");
// Prevent linking libraries to avoid documentation failure
#[cfg(not(feature = "docs"))]
{
// It requires the following variables to be set:
// - export PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH
// - export LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH
#[cfg(feature = "pkg-config")]
system_deps::Config::new().probe().unwrap();
#[cfg(feature = "download-tdlib")]
download_tdlib();
// It requires the following variable to be set:
// - export LOCAL_TDLIB_PATH=$HOME/lib/tdlib
#[cfg(feature = "local-tdlib")]
copy_local_tdlib();
#[cfg(any(feature = "download-tdlib", feature = "local-tdlib"))]
generic_build();
}
let out_dir = env::var("OUT_DIR").unwrap();
let definitions = load_tl("tl/api.tl")?;
let mut file = BufWriter::new(File::create(Path::new(&out_dir).join("generated.rs"))?);
generate_rust_code(&mut file, &definitions, cfg!(feature = "bots-only-api"))?;
file.flush()?;
Ok(())
}

View File

@@ -0,0 +1,193 @@
// cargo run -p tdlib-rs --example get_me --features default
// cargo run -p tdlib-rs --example get_me --features download-tdlib
// cargo run -p tdlib-rs --example get_me --features pkg-config
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use tdlib_rs::{
enums::{self, AuthorizationState, Update, User},
functions,
};
use tokio::sync::mpsc::{self, Receiver, Sender};
fn ask_user(string: &str) -> String {
println!("{string}");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
async fn handle_update(update: Update, auth_tx: &Sender<AuthorizationState>) {
if let Update::AuthorizationState(update) = update {
auth_tx.send(update.authorization_state).await.unwrap();
}
}
async fn handle_authorization_state(
client_id: i32,
mut auth_rx: Receiver<AuthorizationState>,
run_flag: Arc<AtomicBool>,
) -> Receiver<AuthorizationState> {
while let Some(state) = auth_rx.recv().await {
match state {
AuthorizationState::WaitTdlibParameters => {
let response = functions::set_tdlib_parameters(
false,
"get_me_db".into(),
String::new(),
String::new(),
false,
false,
false,
false,
env!("API_ID").parse().unwrap(),
env!("API_HASH").into(),
"en".into(),
"Desktop".into(),
String::new(),
env!("CARGO_PKG_VERSION").into(),
client_id,
)
.await;
if let Err(error) = response {
println!("{}", error.message);
}
}
AuthorizationState::WaitPhoneNumber => loop {
let input = ask_user("Enter your phone number (include the country calling code):");
let response =
functions::set_authentication_phone_number(input, None, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
},
AuthorizationState::WaitOtherDeviceConfirmation(x) => {
println!(
"Please confirm this login link on another device: {}",
x.link
);
}
AuthorizationState::WaitEmailAddress(_x) => {
let email_address = ask_user("Please enter email address: ");
let response =
functions::set_authentication_email_address(email_address, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
}
AuthorizationState::WaitEmailCode(_x) => {
let code = ask_user("Please enter email authentication code: ");
let response = functions::check_authentication_email_code(
enums::EmailAddressAuthentication::Code(
tdlib_rs::types::EmailAddressAuthenticationCode { code },
),
client_id,
)
.await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
}
AuthorizationState::WaitCode(_) => loop {
let input = ask_user("Enter the verification code:");
let response = functions::check_authentication_code(input, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
},
AuthorizationState::WaitRegistration(_x) => {
// x useless but contains the TOS if we want to show it
let first_name = ask_user("Please enter your first name: ");
let last_name = ask_user("Please enter your last name: ");
functions::register_user(first_name, last_name, false, client_id)
.await
.unwrap();
}
AuthorizationState::WaitPassword(_x) => {
let password = ask_user("Please enter password: ");
functions::check_authentication_password(password, client_id)
.await
.unwrap();
}
AuthorizationState::Ready => {
break;
}
AuthorizationState::Closed => {
// Set the flag to false to stop receiving updates from the
// spawned task
run_flag.store(false, Ordering::Release);
break;
}
_ => (),
}
}
auth_rx
}
#[tokio::main]
async fn main() {
// Create the client object
let client_id = tdlib_rs::create_client();
// Create a mpsc channel for handling AuthorizationState updates separately
// from the task
let (auth_tx, auth_rx) = mpsc::channel(5);
// Create a flag to make it possible to stop receiving updates
let run_flag = Arc::new(AtomicBool::new(true));
let run_flag_clone = run_flag.clone();
// Spawn a task to receive updates/responses
let handle = tokio::spawn(async move {
while run_flag_clone.load(Ordering::Acquire) {
let result = tokio::task::spawn_blocking(tdlib_rs::receive)
.await
.unwrap();
if let Some((update, _client_id)) = result {
handle_update(update, &auth_tx).await;
} else {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
});
// tokio::spawn(async move {
// while run_flag_clone.load(Ordering::Acquire) {
// if let Some((update, _client_id)) = tdlib_rs::receive() {
// handle_update(update, &auth_tx).await;
// }
// }
// });
// Set a fairly low verbosity level. We mainly do this because tdlib
// requires to perform a random request with the client to start receiving
// updates for it.
functions::set_log_verbosity_level(2, client_id)
.await
.unwrap();
// Handle the authorization state to authenticate the client
let auth_rx = handle_authorization_state(client_id, auth_rx, run_flag.clone()).await;
// Run the get_me() method to get user information
let User::User(me) = functions::get_me(client_id).await.unwrap();
println!("Hi, I'm {}", me.first_name);
// Tell the client to close
functions::close(client_id).await.unwrap();
// Handle the authorization state to wait for the "Closed" state
handle_authorization_state(client_id, auth_rx, run_flag.clone()).await;
// Wait for the previously spawned task to end the execution
handle.await.unwrap();
}

View File

@@ -0,0 +1,12 @@
// cargo run -p tdlib-rs --example test_ci --features default
// cargo run -p tdlib-rs --example test_ci --features download-tdlib
// cargo run -p tdlib-rs --example test_ci --features pkg-config
#[tokio::main]
async fn main() {
// Create the client object for testing
let _client_id = tdlib_rs::create_client();
// Exit 0
std::process::exit(0);
}

493
crates/vendor/tdlib-rs/src/build.rs vendored Normal file
View File

@@ -0,0 +1,493 @@
//! The build module is used to build the project using the enabled features.
//! The features are correctly set when exactly one of the following features is enabled:
//! - `local-tdlib`
//! - `pkg-config`
//! - `download-tdlib`
#[allow(dead_code)]
#[cfg(not(any(feature = "docs", feature = "pkg-config")))]
const TDLIB_VERSION: &str = "1.8.29";
#[cfg(feature = "download-tdlib")]
const TDLIB_CARGO_PKG_VERSION: &str = "1.2.0";
// WARNING: This function is not used in the current version of the library.
// #[cfg(not(any(feature = "docs", feature = "pkg-config", feature = "download-tdlib")))]
// fn copy_local_tdlib() {
// match std::env::var("LOCAL_TDLIB_PATH") {
// Ok(tdlib_path) => {
// let out_dir = std::env::var("OUT_DIR").unwrap();
// let prefix = format!("{}/tdlib", out_dir);
// copy_dir_all(std::path::Path::new(&tdlib_path), std::path::Path::new(&prefix)).unwrap();
// }
// Err(_) => {
// panic!("The LOCAL_TDLIB_PATH env variable must be set to the path of the tdlib folder");
// }
// };
// }
#[cfg(feature = "download-tdlib")]
/// Copy all files from a directory to another.
/// It assumes that the source directory exists.
/// If the destination directory does not exist, it will be created.
fn copy_dir_all(
src: impl AsRef<std::path::Path>,
dst: impl AsRef<std::path::Path>,
) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
#[cfg(feature = "download-tdlib")]
/// Download the tdlib library from the GitHub release page.
/// The function will download the tdlib library from the GitHub release page, and extract the
/// files in the OUT_DIR/tdlib folder.
/// The OUT_DIR environment variable is set by Cargo and points to the target directory.
/// The OS and architecture currently supported are:
/// - Linux x86_64
/// - Linux aarch64
/// - Windows x86_64
/// - Windows aarch64
/// - MacOS x86_64
/// - MacOS aarch64
///
/// If the OS or architecture is not supported, the function will panic.
fn download_tdlib() {
let base_url = "https://github.com/FedericoBruzzone/tdlib-rs/releases/download";
let url = format!(
"{}/v{}/tdlib-{}-{}-{}.zip",
base_url,
TDLIB_CARGO_PKG_VERSION,
TDLIB_VERSION,
std::env::var("CARGO_CFG_TARGET_OS").unwrap(),
std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
);
let out_dir = std::env::var("OUT_DIR").unwrap();
let tdlib_dir = format!("{}/tdlib", &out_dir);
let zip_path = format!("{}.zip", &tdlib_dir);
// Create the request
let response = reqwest::blocking::get(&url).unwrap();
// Check if the response status is successful
if response.status().is_success() {
// Create a file to write to
let mut dest = std::fs::File::create(&zip_path).unwrap();
// Get the response bytes and write to the file
let content = response.bytes().unwrap();
std::io::copy(&mut content.as_ref(), &mut dest).unwrap();
} else {
panic!(
"[{}] Failed to download file: {}\n{}\n{}",
"Your OS or architecture may be unsupported.",
"Please try using the `pkg-config` or `local-tdlib` features.",
response.status(),
&url
)
}
let mut archive = zip::ZipArchive::new(std::fs::File::open(&zip_path).unwrap()).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = std::path::Path::new(&out_dir).join(file.name());
if (*file.name()).ends_with('/') {
std::fs::create_dir_all(&outpath).unwrap();
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
std::fs::create_dir_all(p).unwrap();
}
}
let mut outfile = std::fs::File::create(&outpath).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
// Get and set permissions
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).unwrap();
}
}
}
let _ = std::fs::remove_file(&zip_path);
}
#[cfg(any(feature = "download-tdlib", feature = "local-tdlib"))]
/// Build the project using the `download-tdlib` or `local-tdlib` feature.
/// # Arguments
/// - `lib_path`: The path where the tdlib library is located. If `None`, the path will be the `OUT_DIR` environment variable.
///
/// The function will pass to the `rustc` the following flags:
/// - `cargo:rustc-link-search=native=.../tdlib/lib`
/// - `cargo:include=.../tdlib/include`
/// - `cargo:rustc-link-lib=dylib=tdjson`
/// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib`
/// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows)
///
/// The `...` represents the `dest_path` or the `OUT_DIR` environment variable.
///
/// If the tdlib library is not found at the specified path, the function will panic.
///
/// The function will panic if the tdlib library is not found at the specified path.
fn generic_build(lib_path: Option<String>) {
let correct_lib_path: String;
match lib_path {
Some(lib_path) => {
if lib_path.ends_with('/') || lib_path.ends_with('\\') {
correct_lib_path = lib_path[..lib_path.len() - 1].to_string();
} else {
correct_lib_path = lib_path.to_string();
}
}
None => {
correct_lib_path = format!("{}/tdlib", std::env::var("OUT_DIR").unwrap());
}
}
let prefix = correct_lib_path.to_string();
let include_dir = format!("{prefix}/include");
let lib_dir = format!("{prefix}/lib");
let mut_lib_path = {
#[cfg(any(
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64")
))]
{
format!("{lib_dir}/libtdjson.so.{TDLIB_VERSION}")
}
#[cfg(any(
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "macos", target_arch = "aarch64"),
all(target_os = "ios", target_arch = "x86_64"),
all(target_os = "ios", target_arch = "aarch64")
))]
{
format!("{lib_dir}/libtdjson.{TDLIB_VERSION}.dylib")
}
#[cfg(any(
all(target_os = "windows", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "aarch64")
))]
{
format!(r"{lib_dir}\tdjson.lib")
}
};
if !std::path::PathBuf::from(mut_lib_path.clone()).exists() {
panic!("tdjson shared library not found at {mut_lib_path}");
}
// This should be not necessary, but it is a workaround because windows does not find the
// tdjson.dll using the commands below.
// TODO: investigate and if it is a bug in `cargo` or `rustc`, open an issue to `cargo` to fix
// this.
#[cfg(any(
all(target_os = "windows", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "aarch64")
))]
{
let bin_dir = format!(r"{prefix}\bin");
let cargo_bin = format!("{}/.cargo/bin", dirs::home_dir().unwrap().to_str().unwrap());
let libcrypto3x64 = format!(r"{bin_dir}\libcrypto-3-x64.dll");
let libssl3x64 = format!(r"{bin_dir}\libssl-3-x64.dll");
let tdjson = format!(r"{bin_dir}\tdjson.dll");
let zlib1 = format!(r"{bin_dir}\zlib1.dll");
let cargo_libcrypto3x64 = format!(r"{cargo_bin}\libcrypto-3-x64.dll");
let cargo_libssl3x64 = format!(r"{cargo_bin}\libssl-3-x64.dll");
let cargo_tdjson = format!(r"{cargo_bin}\tdjson.dll");
let cargo_zlib1 = format!(r"{cargo_bin}\zlib1.dll");
// Delete the files if they exist
let _ = std::fs::remove_file(&cargo_libcrypto3x64);
let _ = std::fs::remove_file(&cargo_libssl3x64);
let _ = std::fs::remove_file(&cargo_tdjson);
let _ = std::fs::remove_file(&cargo_zlib1);
// Move all files to cargo_bin
let _ = std::fs::copy(libcrypto3x64.clone(), cargo_libcrypto3x64.clone());
let _ = std::fs::copy(libssl3x64.clone(), cargo_libssl3x64.clone());
let _ = std::fs::copy(tdjson.clone(), cargo_tdjson.clone());
let _ = std::fs::copy(zlib1.clone(), cargo_zlib1.clone());
}
#[cfg(any(
all(target_os = "windows", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "aarch64")
))]
{
let bin_dir = format!(r"{prefix}\bin");
println!("cargo:rustc-link-search=native={bin_dir}");
}
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:include={include_dir}");
println!("cargo:rustc-link-lib=dylib=tdjson");
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}");
}
/// Check if the features are correctly set.
/// The features are correctly set when exactly one of the following features is enabled:
/// - `local-tdlib`
/// - `pkg-config`
/// - `download-tdlib`
/// - `docs` (only for tdlib documentation)
///
/// The following features cannot be enabled at the same time:
/// - `docs` and `pkg-config`
/// - `docs` and `download-tdlib`
/// - `docs` and `local-tdlib`
/// - `pkg-config` and `local-tdlib`
/// - `pkg-config` and `download-tdlib`
/// - `local-tdlib` and `download-tdlib`
///
/// If the features are not correctly set, the function will generate a compile error
pub fn check_features() {
// #[cfg(not(any(feature = "docs", feature = "local-tdlib", feature = "pkg-config", feature = "download-tdlib")))]
// println!("cargo:warning=No features enabled, you must enable at least one of the following features: docs, local-tdlib, pkg-config, download-tdlib");
// compile_error!("You must enable at least one of the following features: docs, local-tdlib, pkg-config, download-tdlib");
#[cfg(all(feature = "docs", feature = "pkg-config"))]
compile_error!(
"feature \"docs\" and feature \"pkg-config\" cannot be enabled at the same time"
);
#[cfg(all(feature = "docs", feature = "download-tdlib"))]
compile_error!(
"feature \"docs\" and feature \"download-tdlib\" cannot be enabled at the same time"
);
#[cfg(all(feature = "docs", feature = "local-tdlib"))]
compile_error!(
"feature \"docs\" and feature \"local-tdlib\" cannot be enabled at the same time"
);
#[cfg(all(feature = "pkg-config", feature = "local-tdlib"))]
compile_error!(
"feature \"pkg-config\" and feature \"local-tdlib\" cannot be enabled at the same time"
);
#[cfg(all(feature = "pkg-config", feature = "download-tdlib"))]
compile_error!(
"feature \"pkg-config\" and feature \"download-tdlib\" cannot be enabled at the same time"
);
#[cfg(all(feature = "local-tdlib", feature = "download-tdlib"))]
compile_error!(
"feature \"local-tdlib\" and feature \"download-tdlib\" cannot be enabled at the same time"
);
}
/// Set the `rerun-if-changed` and `rerun-if-env-changed` flags for the build script.
/// The `rerun-if-changed` flag is set for the `build.rs` file.
/// The `rerun-if-env-changed` flag is set for the `LOCAL_TDLIB_PATH` environment variable.
pub fn set_rerun_if() {
#[cfg(feature = "local-tdlib")]
println!("cargo:rerun-if-env-changed=LOCAL_TDLIB_PATH");
println!("cargo:rerun-if-changed=build.rs");
}
#[cfg(any(feature = "pkg-config", feature = "docs"))]
#[allow(clippy::needless_doctest_main)]
/// Build the project using the `pkg-config` feature.
/// Using the `pkg-config` feature, the function will probe the system dependencies.
/// It means that the function assumes that the tdlib library is compiled in the system.
/// It requires the following variables to be set:
/// - `PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH`
/// - `LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH`
///
/// If the variables are not set, the function will panic.
///
/// # Example
/// Cargo.toml:
/// ```toml
/// [dependencies]
/// tdlib = { version = "...", features = ["pkg-config"] }
/// ```
///
/// build.rs:
/// ```rust
/// fn main() {
/// tdlib_rs::build::check_features();
/// tdlib_rs::build::set_rerun_if();
/// tdlib_rs::build::build_pkg_config();
/// // Other build configurations
/// // ...
/// }
/// ```
pub fn build_pkg_config() {
#[cfg(not(feature = "docs"))]
{
system_deps::Config::new().probe().unwrap();
}
}
#[cfg(any(feature = "download-tdlib", feature = "docs"))]
#[allow(clippy::needless_doctest_main)]
#[allow(unused_variables)]
/// Build the project using the `download-tdlib` feature.
///
/// # Arguments
/// - `dest_path`: The destination path where the tdlib library will be copied. If `None`, the path will be the `OUT_DIR` environment variable.
///
/// Note that this function will pass to the `rustc` the following flags:
/// - `cargo:rustc-link-search=native=.../tdlib/lib`
/// - `cargo:include=.../tdlib/include`
/// - `cargo:rustc-link-lib=dylib=tdjson`
/// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib`
/// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows)
///
/// The `...` represents the `dest_path` or the `OUT_DIR` environment variable.
///
/// The function will download the tdlib library from the GitHub release page.
/// Using the `download-tdlib` feature, no system dependencies are required.
/// The OS and architecture currently supported are:
/// - Linux x86_64
/// - Linux aarch64
/// - Windows x86_64
/// - Windows aarch64
/// - MacOS x86_64
/// - MacOS aarch64
///
/// If the OS or architecture is not supported, the function will panic.
///
/// # Example
/// Cargo.toml:
/// ```toml
/// [dependencies]
/// tdlib = { version = "...", features = ["download-tdlib"] }
///
/// [build-dependencies]
/// tdlib = { version = "...", features = [ "download-tdlib" ] }
/// ```
///
/// build.rs:
/// ```rust
/// fn main() {
/// tdlib_rs::build::check_features();
/// tdlib_rs::build::set_rerun_if();
/// tdlib_rs::build::build_download_tdlib(None);
/// // Other build configurations
/// // ...
/// }
/// ```
pub fn build_download_tdlib(dest_path: Option<String>) {
#[cfg(not(feature = "docs"))]
{
download_tdlib();
if let Some(dest_path) = &dest_path {
let out_dir = std::env::var("OUT_DIR").unwrap();
let tdlib_dir = format!("{}/tdlib", &out_dir);
copy_dir_all(
std::path::Path::new(&tdlib_dir),
std::path::Path::new(&dest_path),
)
.unwrap();
}
generic_build(dest_path);
}
}
#[cfg(any(feature = "local-tdlib", feature = "docs"))]
#[allow(clippy::needless_doctest_main)]
/// Build the project using the `local-tdlib` feature.
/// Using the `local-tdlib` feature, the function will copy the tdlib library from the
/// `LOCAL_TDLIB_PATH` environment variable.
/// The tdlib folder must contain the `lib` and `include` folders.
/// You can directly download the tdlib library from the [TDLib Release GitHub page](https://github.com/FedericoBruzzone/tdlib-rs/releases).
///
/// The `LOCAL_TDLIB_PATH` environment variable must be set to the path of the tdlib folder.
///
/// The function will pass to the `rustc` the following flags:
/// - `cargo:rustc-link-search=native=.../tdlib/lib`
/// - `cargo:include=.../tdlib/include`
/// - `cargo:rustc-link-lib=dylib=tdjson`
/// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib`
/// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows)
///
/// The `...` represents the `LOCAL_TDLIB_PATH` environment variable.
///
/// If the `LOCAL_TDLIB_PATH` environment variable is not set, the function will panic.
///
/// # Example
/// Cargo.toml:
/// ```toml
/// [dependencies]
/// tdlib = { version = "...", features = ["local-tdlib"] }
///
/// [build-dependencies]
/// tdlib = { version = "...", features = [ "download-tdlib" ] }
/// ```
///
/// build.rs:
/// ```rust
/// fn main() {
/// tdlib_rs::build::check_features();
/// tdlib_rs::build::set_rerun_if();
/// tdlib_rs::build::build_local_tdlib();
/// // Other build configurations
/// // ...
/// }
/// ```
pub fn build_local_tdlib() {
#[cfg(not(feature = "docs"))]
{
// copy_local_tdlib();
let path = std::env::var("LOCAL_TDLIB_PATH").unwrap();
generic_build(Some(path));
}
}
#[allow(clippy::needless_doctest_main)]
/// Build the project using the enabled features.
///
/// # Arguments
/// - `dest_path`: The destination path where the tdlib library will be copied. If `None`, the path
/// will be the `OUT_DIR` environment variable. This argument is used only when the
/// `download-tdlib` feature is enabled.
///
/// The function will check if the features are correctly set.
/// The function will set the `rerun-if-changed` and `rerun-if-env-changed` flags for the build
/// script.
/// The function will build the project using the enabled feature.
///
/// # Example
/// Cargo.toml:
/// ```toml
/// [dependencies]
/// tdlib = { version = "...", features = ["download-tdlib"] }
///
/// [build-dependencies]
/// tdlib = { version = "...", features = [ "download-tdlib" ] }
/// ```
///
/// build.rs:
/// ```rust
/// fn main() {
/// tdlib_rs::build::build(None);
/// // Other build configurations
/// // ...
/// }
/// ```
pub fn build(_dest_path: Option<String>) {
check_features();
set_rerun_if();
#[cfg(feature = "pkg-config")]
build_pkg_config();
#[cfg(feature = "download-tdlib")]
build_download_tdlib(_dest_path);
#[cfg(feature = "local-tdlib")]
build_local_tdlib();
}

10
crates/vendor/tdlib-rs/src/generated.rs vendored Normal file
View File

@@ -0,0 +1,10 @@
// Copyright 2020 - developers of the `grammers` project.
// Copyright 2021 - developers of the `tdlib-rs` project.
// Copyright 2024 - developers of the `tgt` and `tdlib-rs` projects.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
include!(concat!(env!("OUT_DIR"), "/generated.rs"));

69
crates/vendor/tdlib-rs/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2020 - developers of the `grammers` project.
// Copyright 2021 - developers of the `tdlib-rs` project.
// Copyright 2024 - developers of the `tgt` and `tdlib-rs` projects.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
pub mod build;
mod generated;
mod observer;
mod tdjson;
pub use generated::{enums, functions, types};
use enums::Update;
use once_cell::sync::Lazy;
use serde_json::Value;
use std::sync::atomic::{AtomicU32, Ordering};
static EXTRA_COUNTER: AtomicU32 = AtomicU32::new(0);
static OBSERVER: Lazy<observer::Observer> = Lazy::new(observer::Observer::new);
/// Create a TdLib client returning its id. Note that to start receiving
/// updates for a client you need to send at least a request with it first.
pub fn create_client() -> i32 {
tdjson::create_client()
}
/// Receive a single update or response from TdLib. If it's an update, it
/// returns a tuple with the `Update` and the associated `client_id`.
/// Note that to start receiving updates for a client you need to send
/// at least a request with it first.
pub fn receive() -> Option<(Update, i32)> {
let response = tdjson::receive(2.0);
if let Some(response_str) = response {
let response: Value = serde_json::from_str(&response_str).unwrap();
match response.get("@extra") {
Some(_) => {
OBSERVER.notify(response);
}
None => {
let client_id = response["@client_id"].as_i64().unwrap() as i32;
match serde_json::from_value(response) {
Ok(update) => {
return Some((update, client_id));
}
Err(e) => {
log::warn!("Received an unknown response: {response_str}\nReason: {e}");
}
}
}
}
}
None
}
pub(crate) async fn send_request(client_id: i32, mut request: Value) -> Value {
let extra = EXTRA_COUNTER.fetch_add(1, Ordering::Relaxed);
request["@extra"] = serde_json::to_value(extra).unwrap();
let receiver = OBSERVER.subscribe(extra);
tdjson::send(client_id, request.to_string());
receiver.await.unwrap()
}

45
crates/vendor/tdlib-rs/src/observer.rs vendored Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 2020 - developers of the `grammers` project.
// Copyright 2021 - developers of the `tdlib-rs` project.
// Copyright 2024 - developers of the `tgt` project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use futures_channel::oneshot;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::RwLock;
pub(super) struct Observer {
requests: RwLock<HashMap<u32, oneshot::Sender<Value>>>,
}
impl Observer {
pub fn new() -> Self {
Observer {
requests: RwLock::default(),
}
}
pub fn subscribe(&self, extra: u32) -> oneshot::Receiver<Value> {
let (sender, receiver) = oneshot::channel();
self.requests.write().unwrap().insert(extra, sender);
receiver
}
pub fn notify(&self, response: Value) {
let extra = response["@extra"].as_u64().unwrap() as u32;
match self.requests.write().unwrap().remove(&extra) {
Some(sender) => {
if sender.send(response).is_err() {
log::warn!("Got a response of an unaccessible request");
}
}
None => {
log::warn!("Got a response of an unknown request");
}
}
}
}

35
crates/vendor/tdlib-rs/src/tdjson.rs vendored Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2020 - developers of the `grammers` project.
// Copyright 2021 - developers of the `tdlib-rs` project.
// Copyright 2024 - developers of the `tgt` project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_double, c_int};
#[link(name = "tdjson")]
extern "C" {
fn td_create_client_id() -> c_int;
fn td_send(client_id: c_int, request: *const c_char);
fn td_receive(timeout: c_double) -> *const c_char;
}
pub(crate) fn create_client() -> i32 {
unsafe { td_create_client_id() }
}
pub(crate) fn send(client_id: i32, request: String) {
let cstring = CString::new(request).unwrap();
unsafe { td_send(client_id, cstring.as_ptr()) }
}
pub(crate) fn receive(timeout: f64) -> Option<String> {
unsafe {
td_receive(timeout)
.as_ref()
.map(|response| CStr::from_ptr(response).to_string_lossy().into_owned())
}
}

10841
crates/vendor/tdlib-rs/tl/api.tl vendored Normal file

File diff suppressed because it is too large Load Diff

9177
crates/vendor/tdlib-rs/tl/api_1.8.19.tl vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ Local toolchain status:
- `scripts/check-ios-prereqs.sh` passes.
- `scripts/run-ios-simulator-app.sh` launches the fake-backed SwiftUI shell in iOS Simulator.
Current real TDLib blocker:
Previous real TDLib blocker:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer cargo build -p tele-ios-ffi --target aarch64-apple-ios-sim --release
@@ -25,10 +25,83 @@ Interpretation:
- Rust, Swift, Xcode, and the iOS simulator runtime are working.
- The fake-backed iOS app shell can be built, installed, launched, and rendered.
- Real TDLib iOS linking is blocked because `tdlib-rs` 1.2.0 does not publish the iOS static library artifact requested by its `download-tdlib` build script.
- `download-tdlib` cannot be used for iOS because `tdlib-rs` 1.2.0 does not publish the iOS artifact requested by its build script.
Next viable paths:
Current real TDLib status:
1. Build TDLib for `iphoneos` and `iphonesimulator` locally and switch the Rust dependency path to `local-tdlib` or `pkg-config`.
2. Add a fake-only `tele-ios-ffi` build feature that avoids linking TDLib for simulator UI work, while keeping real TDLib behind a separate feature.
3. Replace the `tdlib-rs` packaging path for iOS with a lower-level C ABI/XCFramework if UniFFI plus `tdlib-rs` cannot link cleanly on device.
- `scripts/build-tdlib-ios.sh` builds TDLib `1.8.29` locally for iOS device and simulator.
- `.build/tdlib-ios/iphoneos/lib/libtdjson.1.8.29.dylib` is `arm64`.
- `.build/tdlib-ios/iphonesimulator/lib/libtdjson.1.8.29.dylib` is a universal `x86_64` + `arm64` simulator dylib.
- `scripts/check-ios-tdlib-linking.sh` builds `tele-ios-ffi` for `aarch64-apple-ios-sim` with local TDLib.
- `IOS_RUST_TARGET=aarch64-apple-ios scripts/build-ios-ffi-with-local-tdlib.sh` builds `tele-ios-ffi` for device with local TDLib.
- `scripts/build-ios-real-ffi-xcframework.sh` packages simulator Rust slices into `tele_ios_ffi.xcframework`, packages local `libtdjson` into `tdjson.xcframework`, and writes generated Swift bindings into the Swift package's local `Generated` path.
- `TELE_IOS_USE_LOCAL_FFI=1 scripts/build-ios-simulator-app.sh` builds and packages the SwiftUI simulator app against the real Rust/TDLib FFI path, including `Frameworks/libtdjson.dylib` in the `.app` bundle.
- The repo patches `tdlib-rs` through `crates/vendor/tdlib-rs` because upstream `src/build.rs` does not handle `target_os = "ios"` for `local-tdlib`.
Selected path:
1. Keep `download-tdlib` for the terminal/macOS build path.
2. Build TDLib locally for iOS and iOS Simulator from the TDLib source commit matching TDLib `1.8.29`.
3. Build `tele-ios-ffi` with `tele-core/tdlib-local` and `LOCAL_TDLIB_PATH`.
4. Package the resulting Rust FFI library and TDLib dylib into the iOS app/XCFramework path.
The TDLib source commit used for `1.8.29` is:
```text
af69dd4397b6dc1bf23ba0fd0bf429fcba6454f6
```
This can be checked with:
```bash
npm info prebuilt-tdlib@td-1.8.29 tdlib --json
```
Build local TDLib artifacts:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-tdlib-ios.sh
```
This writes local, untracked outputs to:
```text
.build/tdlib-ios/iphoneos/
.build/tdlib-ios/iphonesimulator/
```
Build the real iOS FFI path against local TDLib:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-ios-ffi-with-local-tdlib.sh
```
or for a device target:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer IOS_RUST_TARGET=aarch64-apple-ios scripts/build-ios-ffi-with-local-tdlib.sh
```
Package the real FFI path for the Swift app and build the simulator `.app`:
```bash
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-ios-real-ffi-xcframework.sh
TELE_IOS_USE_LOCAL_FFI=1 DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer scripts/build-ios-simulator-app.sh
```
Known local prerequisites:
- Xcode with the iOS SDK installed.
- Homebrew build tools: `brew install cmake gperf coreutils`.
- Rust iOS targets: `rustup target add aarch64-apple-ios-sim aarch64-apple-ios`.
- `scripts/build-tdlib-ios.sh` builds only the required OpenSSL platforms through TDLib's vendored `Python-Apple-support` flow, with a local patch for the simulator target triple under Xcode 26.
- TDLib CMake uses `TDLIB_IOS_DEPLOYMENT_TARGET=17.0` by default to match the Swift package minimum iOS version.
- TDLib's `prepare_cross_compiling` target is run as a host-side generation step before cross-compiling, so generated TL/MIME sources exist for the iOS build.
Example local blocker if these tools are missing:
```text
Required tool not found: cmake
```
CMake 4.x compatibility is handled by passing `-DCMAKE_POLICY_VERSION_MINIMUM=3.5` to TDLib's configure step.

View File

@@ -45,6 +45,33 @@ Architecture default: Rust `tele-core` remains the source of Telegram state and
- Swift sample test can create a fake session, receive events, load chats, open a chat, send a message, react, and search.
- Real TDLib linking is validated for iOS simulator and device or documented as blocked with exact linker error.
#### Phase 2A: Local TDLib for iOS
`tdlib-rs` 1.2.0 with `download-tdlib` downloads prebuilt TDLib archives from `FedericoBruzzone/tdlib-rs` releases using the pattern `tdlib-{tdlib_version}-{target_os}-{target_arch}.zip`. The terminal/macOS build works because the macOS archive exists. The iOS archives, for example `tdlib-1.8.29-ios-aarch64.zip`, are not published, so iOS real linking must not depend on `download-tdlib`.
- Keep `download-tdlib` for the terminal/macOS build path while it remains useful there.
- Switch the iOS FFI build path to `tdlib-rs` `local-tdlib` support.
- Pin the local TDLib build to the TDLib version expected by `tdlib-rs` 1.2.0: `1.8.29`.
- Add `scripts/build-tdlib-ios.sh` to build TDLib locally for:
- `arm64-apple-ios`
- `arm64-apple-ios-sim`
- optionally `x86_64-apple-ios-sim` if Intel Mac simulator support is needed.
- Store generated TDLib artifacts outside git, for example:
- `.build/tdlib-ios/iphoneos/include`
- `.build/tdlib-ios/iphoneos/lib`
- `.build/tdlib-ios/iphonesimulator/include`
- `.build/tdlib-ios/iphonesimulator/lib`
- Add `scripts/build-ios-ffi-with-local-tdlib.sh` to set `LOCAL_TDLIB_PATH` and build `tele-ios-ffi` for simulator/device targets.
- Patch `tdlib-rs` through `crates/vendor/tdlib-rs` until upstream handles `target_os = "ios"` in its `local-tdlib` build helper.
- Run TDLib's host-side `prepare_cross_compiling` target before the iOS cross-build, because TDLib 1.8.29 expects generated TL/MIME sources to exist during cross-compilation.
- After simulator and device Rust builds link, package the Rust FFI output and TDLib dependency into an iOS-consumable XCFramework or documented adjacent native dependency.
- Acceptance:
- `scripts/check-ios-tdlib-linking.sh` no longer fails because of a missing GitHub release archive. Completed on 2026-05-21.
- `cargo build -p tele-ios-ffi --target aarch64-apple-ios-sim --release` links with local TDLib. Completed on 2026-05-21 through `scripts/check-ios-tdlib-linking.sh`.
- `cargo build -p tele-ios-ffi --target aarch64-apple-ios --release` links with local TDLib. Completed on 2026-05-21 through `IOS_RUST_TARGET=aarch64-apple-ios scripts/build-ios-ffi-with-local-tdlib.sh`.
- The Xcode app can be built against the real FFI path, not only the fake bridge. Completed on 2026-05-21 through `scripts/build-ios-real-ffi-xcframework.sh` and `TELE_IOS_USE_LOCAL_FFI=1 scripts/build-ios-simulator-app.sh`.
- Any remaining CMake/OpenSSL/zlib/linker blockers are documented with the exact command and error text.
### Phase 3: iOS App Shell
- Add native app under `apps/ios/TeleTuiIOS`.

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"