# How Telegram-iOS Uses Bazel for Build System Architecture and Dependency Management

> Discover how Telegram-iOS leverages Bazel for a reproducible build system. Learn about its dependency management across iOS, macOS, and Apple simulator targets using declarative BUILD files.

- Repository: [TelegramMessenger/Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS)
- Tags: architecture
- Published: 2026-04-07

---

**Telegram-iOS employs Bazel to create a fully reproducible, incremental build system that orchestrates hundreds of third-party libraries and internal modules across iOS, macOS, and Apple simulator targets through declarative BUILD files and external dependency resolution.**

The TelegramMessenger/Telegram-iOS repository is built entirely with **Bazel**, replacing traditional Xcode project management with a hermetic, scalable build architecture. This approach enables deterministic builds across different development environments while supporting complex dependency graphs spanning native C libraries, Swift components, and Apple-specific resource bundles.

## Core Bazel Configuration Files

The root of the repository contains several critical files that define the build environment and external dependencies.

### MODULE.bazel and External Rule Dependencies

The [`MODULE.bazel`](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/MODULE.bazel) file serves as the central dependency declaration, importing essential rulesets including `rules_apple`, `rules_swift`, `bazel_features`, and the Apple provisioning-profile extension. It also fetches versioned binary archives for **CMake**, **Meson**, **Ninja**, and **FlatBuffers** via `http_file` rules with SHA-256 checksums to guarantee reproducibility.

### Global Build Flags in .bazelrc

The [`.bazelrc`](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/.bazelrc) file configures global Bazel behavior for Apple platforms. It selects the Apple crosstool, enables per-file compiler options for Objective-C (`-fno-objc-msgsend-selector-stubs`), activates Swift caching mechanisms (`swift.cacheable_swiftmodules`), and forces standalone workers for genrules and Swift compilation to minimize contention during parallel builds.

### Root BUILD.bazel and SourceKit Support

The [`BUILD.bazel`](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/BUILD.bazel) at the repository root exposes a `setup_sourcekit_bsp` target that enables IDE indexing across Swift, Objective-C, and C++ sources. It also exports the [`versions.json`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/versions.json) file consumed by CI pipelines.

### Bootstrap Scripting with prepare-build.sh

Before any Bazel command runs, the [`build-system/prepare-build.sh`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/build-system/prepare-build.sh) script orchestrates the build environment. It creates the `build-input/data` package, copies provisioning profiles via `copy-provisioning-profiles-*.sh` scripts, and generates build variables from [`template_minimal_development_configuration.json`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/template_minimal_development_configuration.json). This JSON template supplies app-specific values including bundle identifiers and API keys required by the `prepare-build-variables-*.sh` scripts.

## Dependency Management Architecture

Telegram-iOS organizes its massive dependency graph into distinct categories, each handled through specific Bazel patterns.

### Third-Party Libraries in third-party/

External dependencies reside under `third-party/`, where each library contains a **`BUILD`** file declaring `cc_library`, `objc_library`, or `swift_library` targets. These files leverage `@build_bazel_rules_apple` macros to apply platform-specific flags.

For example, `third-party/libyuv/BUILD` uses `select()` statements to choose architecture-specific compiler flags:

```python
"@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags,
"@build_bazel_rules_apple//apple:ios_x86_64": common_flags + x86_64_specific_flags,

```

Similarly, `third-party/openh264/BUILD` demonstrates per-architecture source list selection:

```python
"@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_sources,
"@build_bazel_rules_apple//apple:ios_x86_64": [],

```

### Internal Submodules Structure

Internal code lives in `submodules/`, with each module containing its own `BUILD` file that wires the component into the global dependency graph. These modules often reference resources through `@build_bazel_rules_apple//apple:resources.bzl` and declare visibility constraints controlling which top-level targets can link against them.

### Versioned External Binaries

Build-time tools including CMake, Meson, Ninja, and FlatBuffers are fetched via `http_file` rules defined in `MODULE.bazel`. Their SHA-256 checksums ensure that every build uses identical binary versions, preventing "works on my machine" discrepancies.

### Apple Provisioning Profile Handling

Rather than committing provisioning profiles to version control, Telegram-iOS uses the `provisioning_profile_repository` extension from `rules_apple`. The [`prepare-build.sh`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/prepare-build.sh) script injects the appropriate development or distribution profile based on script arguments, enabling seamless certificate management without repository bloat.

## Build Flow and Execution

The standard development workflow follows a strict sequence:

1. Developer executes `./build-system/prepare-build.sh Telegram development`
2. The script populates `build-input/data` with provisioning profiles and generates JSON configuration
3. Bazel resolves the dependency graph, pulling external archives and compiling submodules
4. The Apple toolchain (`@build_bazel_rules_apple//apple:ios.bzl`) links the final `//Telegram:Telegram` target

This flow ensures that build variables and signing assets are validated before compilation begins, preventing late-stage failures during code signing.

## Performance Optimization and Caching

The `.bazelrc` configuration forces **standalone** execution strategies for genrules and Swift compilation (`--strategy=Genrule=standalone`, `--strategy=SwiftCompile=worker`). This reduces worker contention and improves incremental build performance on macOS hosts.

Additionally, the `swift.debug_prefix_map` parameter ensures that debug symbols remain deterministic across different checkout paths, which is essential for CI reproducibility and remote caching.

## IDE Support and SourceKit Integration

The `setup_sourcekit_bsp` rule in `BUILD.bazel` creates a **SourceKit-BSP** server that watches all source files in the repository. This provides real-time diagnostics and indexing to Xcode or other LSP-compatible editors, compensating for the fact that Telegram-iOS does not rely on checked-in `.xcodeproj` files.

## Practical Implementation Examples

### Integrating a New Third-Party C Library

To add a library like **libfoo**, declare the archive in `MODULE.bazel`:

```python
http_file(
    name = "libfoo_tar",
    urls = ["https://example.com/libfoo-1.2.3.tar.gz"],
    sha256 = "e3b0c44298fc1c149afbf4c8996fb924...",
)

```

Then create `third-party/libfoo/BUILD` with architecture-specific optimizations:

```python
load("@rules_cc//cc:defs.bzl", "cc_library")

cc_library(
    name = "foo",
    srcs = glob(["src/**/*.c"]),
    hdrs = glob(["include/**/*.h"]),
    includes = ["include"],
    visibility = ["//visibility:public"],
    copts = select({
        "@build_bazel_rules_apple//apple:ios_arm64": ["-march=armv8.2-a"],
        "@build_bazel_rules_apple//apple:ios_x86_64": ["-march=x86-64"],
        "//conditions:default": [],
    }),
)

```

Other modules can then reference this library via `deps = ["@libfoo//:foo"]`.

### Creating an Internal Submodule

The `submodules/ChatUI/BUILD` file demonstrates how internal components declare dependencies:

```python
objc_library(
    name = "ChatUI",
    srcs = glob(["**/*.m", "**/*.mm"]),
    hdrs = glob(["**/*.h"]),
    resources = glob(["Resources/**"]),
    deps = [
        "//submodules/AvatarNode:AvatarNode",
        "//third-party/libyuv:libyuv",
    ],
    visibility = ["//Telegram:Telegram"],
)

```

This configuration bundles Objective-C implementation, resources, and dependencies on both internal modules (`AvatarNode`) and third-party libraries (`libyuv`), with visibility restricted to the top-level Telegram target.

## Summary

- **Telegram-iOS** uses **Bazel** as its exclusive build system, replacing Xcode project files with declarative `BUILD` configurations.
- The [`MODULE.bazel`](https://github.com/TelegramMessenger/Telegram-iOS/blob/master/MODULE.bazel) file manages external rulesets and binary dependencies via `http_file` with SHA-256 verification.
- Platform-specific compilation flags are selected using `@build_bazel_rules_apple` `select()` patterns in `third-party/` BUILD files.
- The [`build-system/prepare-build.sh`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/build-system/prepare-build.sh) script bootstraps the environment by provisioning profiles and generating build variables before Bazel execution.
- Performance optimizations in `.bazelrc` include standalone workers, Swift module caching, and deterministic debug prefix mapping.
- IDE support is provided through a **SourceKit-BSP** target defined in the root `BUILD.bazel` file.

## Frequently Asked Questions

### Why did Telegram-iOS choose Bazel over native Xcode builds?

According to the Telegram-iOS source code, Bazel provides **reproducible, incremental builds** across iOS, macOS, and simulator targets while managing hundreds of dependencies. Unlike Xcode projects, which require manual configuration of each target's build settings, Bazel's declarative approach ensures consistent compiler flags and dependency resolution across different developer machines and CI environments.

### How are iOS provisioning profiles managed in the Bazel build?

Provisioning profiles are handled through the `provisioning_profile_repository` extension from `rules_apple`, as configured in `MODULE.bazel`. The [`build-system/prepare-build.sh`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/build-system/prepare-build.sh) script calls `copy-provisioning-profiles-*.sh` helpers to inject the correct development or distribution certificates into the `build-input/data` directory before compilation, keeping sensitive credentials out of version control.

### Can I build the Telegram-iOS app without running prepare-build.sh?

No, the build requires the bootstrap script. The [`prepare-build.sh`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/prepare-build.sh) script generates essential configuration files including the JSON build variables derived from [`template_minimal_development_configuration.json`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/template_minimal_development_configuration.json) and organizes provisioning profiles. Attempting to run `bazel build` directly will fail because these generated inputs are required by targets in the `Telegram/BUILD` file.

### How does the build system handle architecture-specific compiler flags?

The repository uses Bazel's `select()` mechanism with conditions from `@build_bazel_rules_apple`, such as `@build_bazel_rules_apple//apple:ios_arm64` and `@build_bazel_rules_apple//apple:ios_x86_64`. Libraries like **libyuv** and **OpenH264** in the `third-party/` directory demonstrate this pattern by applying different `copts` or source lists based on the target architecture, ensuring optimized binaries for each platform.