# Bazel 5: Birinchi iOS ilova — rules_apple, rules_swift, SwiftUI, Info.plist va simulator

> **Holat:** 2026-yil oktabr. Bu darsda noldan to'liq iOS SwiftUI ilovasini Bazel bilan quramiz, simulyatorda ishga tushiramiz va `.ipa` ichini ochib ko'ramiz. Kod misollari Bazel 9.3.0 (Bazelisk 1.29.0), rules_apple 5.3.0, rules_swift 4.2.1, apple_support 2.10.1 va Xcode 27.1 bilan Apple M4 Pro'da tekshirilgan.

> **Bu darsdan keyin siz:** bitta ekranli SwiftUI ilovasini Bazel bilan `swift_library` + `ios_application` orqali qurib, simulyatorda `bazel run` bilan ochib, `.ipa` faylining har bir qismini tushunadigan bo'lasiz.

- `apple_support`, `rules_swift`, `rules_apple` uchligi nima qilishini va MODULE.bazel'da qanday ulanishini bilasiz.
- `swift_library` va `ios_application` rule'larining muhim atributlarini (va qaysilari majburiy ekanini) aytib bera olasiz.
- Info.plist'ning birlashtirilishini va `$(PRODUCT_NAME)` kabi o'zgaruvchilarni tushunasiz.
- `bazel build`, `bazel run` va `bazel test` ni iOS uchun to'g'ri simulyator flaglari bilan ishlatasiz.
- `.ipa` ichini `unzip -l` bilan ochib, har bir faylning vazifasini bilasiz.
- Debug/release (`-c dbg` / `-c opt`), arxitektura (`--ios_multi_cpus`) va Xcode qayerdan kelishini sozlay olasiz.

## Mundarija

1. [Nega iOS uchun maxsus rule'lar kerak?](#nega)
2. [Uchlik: apple_support, rules_swift, rules_apple](#uchlik)
3. [swift_library — Swift kodini modulga aylantirish](#swift-library)
4. [ios_application — .app va .ipa yasash](#ios-application)
5. [Info.plist: birlashtirish va o'zgaruvchilar](#infoplist)
6. ["Kitoblar" ilovasini yig'amiz](#kitoblar)
7. [.ipa ichida nima bor?](#ipa)
8. [Nega `tags = ["manual"]`? — birinchi haqiqiy xato](#manual)
9. [Simulyatorda ishga tushirish: bazel run](#run)
10. [Qaysi simulyator? device va version tanlash](#simulyator)
11. [Testlar simulyatorda](#testlar)
12. [Debug, release va arxitektura](#debug-release)
13. [Xcode va SDK qayerdan keladi](#xcode-sdk)
14. [Xcode loyihasi vs Bazel: fayllar](#xcode-vs-bazel)
15. [O'lchovlar — haqiqiy raqamlar](#olchovlar)
16. [Amaliy mashqlar](#mashqlar)
17. [O'zingizni tekshiring](#checklist)
18. [Xulosa](#xulosa)
19. [Manbalar](#manbalar)

---

## 1. Nega iOS uchun maxsus rule'lar kerak? {#nega}

Oldingi to'rtta darsda biz Bazel'ning asoslarini o'rgandik: [o'rnatish va birinchi build](/ios-lessons/bazel-02-ornatish), [workspace, target, label, build graph](/ios-lessons/bazel-03-tushunchalar) va [Starlark tili, macro'lar hamda `select()`](/ios-lessons/bazel-04-starlark). U yerdagi barcha misollar **macOS buyruq qatori** dasturlari edi — ular `swift_binary` bilan quriladi, natijasi esa oddiy ishga tushadigan fayl.

iOS ilovasi butunlay boshqacha narsa. U shunchaki kompilyatsiya qilingan binary emas — u **bundle**: ichida binary, `Info.plist`, rasmlar, imzo (code signature) va boshqa resurslar bo'lgan `.app` papka. Uni simulyator yoki qurilma tushunadigan `.ipa` ichiga joylash kerak. `swift_binary` buni bilmaydi.

Shuning uchun Apple platformalari uchun alohida rule'lar to'plami bor. Bu darsda ularni ishga solamiz va birinchi haqiqiy iOS ilovamizni — oddiy "Kitoblar" ro'yxatini — Bazel bilan quramiz.

> **🚀 Motivatsiya:** Bu "o'yinchoq" misol emas. Xuddi shu uchta rule — `apple_support`, `rules_swift`, `rules_apple` — bilan haqiqiy, millionlab odam ishlatadigan ilovalar quriladi. **Telegram iOS** (ochiq kodli, `github.com/TelegramMessenger/Telegram-iOS`) to'liq Bazel bilan quriladi: aynan shu rule'lar, `minimum_os_version = 15.0`, 6 ta app extension va 5 ta ichki `ios_framework`. Ya'ni bugun o'rganadigan `ios_application` va `swift_library` — bu real sanoat vositalari (manba: Telegram-iOS ochiq `MODULE.bazel` va `.bazelrc`).

Nega jamoalar umuman Xcode'dan Bazel'ga o'tadi? **Spotify** muhandislik blogida (2023) yozishicha, ularda 200+ iOS muhandisi bor va Xcode loyihasini qo'lda boshqarish imkonsiz bo'lib qolgan edi: generator **2000+ `BUILD.bazel`** faylini avtomatik yaratgan, faqat 30–50 tasi qo'lda yozilgan. Natija: merge oldidan kutish vaqti (p75) **~80 daqiqadan ~20 daqiqaga** tushgan ("factor of four"), eng og'ir konfiguratsiya (800+ test target, ~3M qator kod) **45+ daqiqadan 10 daqiqadan kamga** (manba: engineering.atspotify.com, 2023-10). Kichikroq jamoada ham foyda bor: **Allegro** (2020) 25 kishilik jamoada clean build'ni **260 soniyadan 85 soniyaga** qisqartirgan (manba: blog.allegro.tech, 2020-12).

Bu raqamlar nega muhim? Google'ning nazorat ostida o'tkazgan tajribasiga ko'ra, tezroq build'lar kichik/o'rta o'zgarishlarda **11–14% tezroq** ishlashga va haftasiga qo'shimcha bitta build'ga olib keladi — kichik ko'rinsa ham, statistik jihatdan ahamiyatli (manba: DX newsletter, getdx.com). Build tezligi — bu to'g'ridan-to'g'ri rivojlanish tezligi.

Keling, uchlikdan boshlaymiz.

---

## 2. Uchlik: apple_support, rules_swift, rules_apple {#uchlik}

Bitta iOS ilovasini qurish uchun bizga uchta tashqi Bazel moduli kerak. Ularning har biri aniq bir vazifani bajaradi:

| Modul           | Versiya (BCR, 2026-10) | Nima qiladi                                                                                                                                                                           |
| :-------------- | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `apple_support` | 2.10.1                 | Xcode va platforma SDK'larini Bazel **toolchain** sifatida ro'yxatdan o'tkazadi. Ya'ni Bazel qaysi `swiftc`, `clang`, `ld` va SDK'ni ishlatishini shu modul aytib beradi.             |
| `rules_swift`   | 4.2.1                  | Swift kodini kompilyatsiya qiladi (`swift_library`, `swift_binary`, `swift_test`). Doimiy ishlaydigan **persistent worker** bilan tezlashtiradi.                                      |
| `rules_apple`   | 5.3.0                  | Apple bundle'larini yasaydi va ishga tushiradi (`ios_application`, `ios_unit_test`, `ios_framework`...). Linking, bundling, codesigning va simulyatorga o'rnatish shu modulning ishi. |

Bu uchalasini `MODULE.bazel` faylida e'lon qilamiz:

```starlark
# MODULE.bazel — "Kitoblar" iOS ilovasi uchun Bazel modullari
module(
    name = "kitoblar",
    version = "1.0.0",
)

# apple_support rules_cc'dan YUQORIDA turishi shart: birinchi ro'yxatdan
# o'tgan cc toolchain yutadi. apple_support Xcode va SDK'ni toolchain qiladi.
bazel_dep(name = "apple_support", version = "2.10.1")
bazel_dep(name = "rules_swift", version = "4.2.1")
bazel_dep(name = "rules_apple", version = "5.3.0")
```

> **🧠 Esda tuting:** `apple_support` boshqa rule'lardan **yuqorida** turishi kerak. Modulning o'z README'sida yozilgani kabi, birinchi ro'yxatdan o'tgan cc toolchain g'olib chiqadi — agar `rules_cc` yoki boshqa narsa oldinroq kelsa, noto'g'ri (Apple bo'lmagan) toolchain tanlanib qolishi mumkin (manba: apple_support README, 2.10.1).

Uchta narsaga e'tibor bering:

1. **`rules_cc` ko'rinmaydi.** Bazel 9'da barcha C/C++/Objective-C rule'lari (`cc_library`, `objc_library`...) Bazel yadrosidan chiqarib tashlangan — ular endi `@rules_cc`'dan `load()` qilinishi kerak (manba: Bazel 9.0 release notes). Bizning ilovamiz **sof Swift** bo'lgani uchun `rules_cc` kerak emas. Agar siz o'zingiz `objc_library` yozsangiz, u holda `bazel_dep(name = "rules_cc", version = "0.2.26")` qo'shasiz.

2. **`repo_name` yo'q.** Eski (rules_apple 4.x va undan oldingi) qo'llanmalar `bazel_dep(..., repo_name = "build_bazel_rules_apple")` ishlatardi va label'lar `@build_bazel_rules_apple//...` ko'rinishida edi. rules_apple 5.x'da bu kerak emas — oddiy `@rules_apple//...` va `@rules_swift//...` ishlatamiz (manba: rules_apple 5.0.0 release notes).

3. **Versiyalar mosligi.** rules_apple 5.3.0 `bazel_compatibility = [">=8.0.0"]` deb e'lon qiladi va rules_swift 4.0.1+ talab qiladi (manba: FACTS, tekshirilgan). Ya'ni bu to'plam Bazel 8.x LTS va 9.x bilan ishlaydi.

Birinchi build'da Bazel bu modullarni Bazel Central Registry'dan (BCR) oladi va `MODULE.bazel.lock` faylini (lockFileVersion 28) avtomatik yaratadi — uni **git'ga qo'shing**, shunda butun jamoa aynan bir xil versiyalarni oladi.

> **⚠️ Diqqat:** Versiyalarni aniq yozib qo'ying (`5.3.0`, `9.0` emas). "Eng so'nggisi" degan narsa vaqt o'tishi bilan o'zgaradi; aniq versiya esa bir yildan keyin ham bir xil natija beradi. Bu — Bazel'ning asosiy va'dasi bo'lgan **takrorlanuvchanlik** (reproducibility)ning birinchi qadami.

---

## 3. swift_library — Swift kodini modulga aylantirish {#swift-library}

Bazel'da hech qachon Swift fayllarini to'g'ridan-to'g'ri `ios_application`'ga bermaymiz. Avval ularni `swift_library` bilan bitta **modul**ga aylantiramiz, keyin ilovaga **bog'liqlik** (dependency) sifatida ulaymiz. Bu bejiz emas: har bir `swift_library` — bu alohida keshlanadi va parallel quriladi. Kodni modullarga bo'lganingizda, bitta faylni o'zgartirsangiz faqat o'sha modul qayta quriladi (buni darsning oxirida o'lchaymiz).

"Kitoblar" ilovamizning yuragi — `Core` moduli. Unda domen modeli (`Book`) va offline ma'lumot (`BookLibrary`) yashaydi. Mana modelni:

```swift
// Core/Sources/Book.swift
import Foundation

/// Bitta kitob. Offline ro'yxat uchun eng sodda model.
public struct Book: Identifiable, Hashable, Sendable {
    public let id: UUID
    public let title: String
    public let author: String
    public let year: Int
    public let pages: Int

    public init(id: UUID = UUID(), title: String, author: String, year: Int, pages: Int) {
        self.id = id
        self.title = title
        self.author = author
        self.year = year
        self.pages = pages
    }

    /// Ro'yxatda ikkinchi qatorda ko'rsatiladigan qisqa tavsif.
    public var subtitle: String {
        "\(author) · \(year)"
    }
}
```

```swift
// Core/Sources/BookLibrary.swift
import Foundation

/// Internetsiz ishlaydigan kutubxona: namuna ma'lumotlar shu yerda.
public enum BookLibrary {
    public static let all: [Book] = [
        Book(title: "O'tgan kunlar", author: "Abdulla Qodiriy", year: 1926, pages: 416),
        Book(title: "Mehrobdan chayon", author: "Abdulla Qodiriy", year: 1929, pages: 352),
        Book(title: "Sariq devni minib", author: "Xudoyberdi To'xtaboyev", year: 1969, pages: 288),
        Book(title: "Kecha va kunduz", author: "Cho'lpon", year: 1936, pages: 232),
        Book(title: "Ikki eshik orasi", author: "O'tkir Hoshimov", year: 1986, pages: 592),
    ]

    /// Sarlavha bo'yicha qidiruv (katta-kichik harf farqsiz).
    public static func search(_ query: String) -> [Book] {
        let q = query.trimmingCharacters(in: .whitespaces).lowercased()
        guard !q.isEmpty else { return all }
        return all.filter { $0.title.lowercased().contains(q) || $0.author.lowercased().contains(q) }
    }
}
```

Endi bu ikki faylni `swift_library`ga aylantiramiz. `Core/BUILD.bazel`:

```starlark
# Core/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:ios.bzl", "ios_unit_test")

swift_library(
    name = "Core",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "Core",
    visibility = ["//visibility:public"],
)
```

Bu yettita qator nimani anglatadi? `swift_library`ning muhim atributlari (manba: rules_swift `doc/rules.md`, 4.2.1):

| Atribut       | Vazifasi                                                                                                                                                      |
| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`        | Target nomi (majburiy). Label `//Core:Core` bo'ladi.                                                                                                          |
| `srcs`        | Kompilyatsiya qilinadigan `.swift` fayllar. `glob()` bilan papkadagi hammasini olamiz.                                                                        |
| `module_name` | Swift modulining nomi — bu `import Core` da yoziladigan nom. Berilmasa, Bazel label'dan avtomatik yasaydi (`//Core:Core` → `Core`), lekin aniq yozgan ma'qul. |
| `deps`        | Bu kutubxona bog'liq bo'lgan boshqa target'lar. `Core`da hozircha yo'q.                                                                                       |
| `visibility`  | Kim bu target'ga ulana oladi. `//visibility:public` — hamma. (Visibility haqida [3-darsda](/ios-lessons/bazel-03-tushunchalar) gaplashgan edik.)              |

Yana bir nechta tez-tez ishlatiladigan atributlar bor (hozir kerak emas, lekin bilib qo'ying):

- `copts` — `swiftc`'ga qo'shimcha flaglar (`["-warnings-as-errors"]` kabi).
- `defines` — Swift uchun shartli kompilyatsiya belgilari (C'dagidek qiymatsiz, faqat nom: `["DEBUG_LOGGING"]`). Bu belgi barcha bog'liq target'larga ham o'tadi — ehtiyot bo'ling, odatda `local_defines` yaxshiroq.
- `data` — ishga tushishda kerak bo'ladigan fayllar (resurslar).
- `private_deps` — faqat shu kutubxonaning ichida ishlatiladigan, lekin tashqariga "ko'rsatilmaydigan" bog'liqliklar (implementation-only import).
- `testonly = True` — bu target faqat testlarda ishlatilishi mumkin degani.

> **💡 Pro tip:** Swift'ni Bazel bir **modulni to'liq** (whole module) kompilyatsiya qiladi, fayl-ba-fayl emas. Ya'ni `Core` ichidagi bitta faylni o'zgartirsangiz, butun `Core` moduli qayta kompilyatsiya bo'ladi. Shuning uchun katta loyihalarni ko'p kichik `swift_library`'larga bo'lish — bu nafaqat toza arxitektura, balki **tezroq incremental build** ham demakdir. Buni 13-darsda ("Vazifalar" ilovasi) raqamlar bilan ko'rsatamiz.

---

## 4. ios_application — .app va .ipa yasash {#ios-application}

Endi Swift modulini **haqiqiy ilova**ga aylantiramiz. Bu `rules_apple`'ning `ios_application` rule'i ishi. U kutubxonani oladi, linklaydi, `Info.plist` bilan birlashtiradi, resurslarni joylaydi, imzolaydi va `.app` + `.ipa` yasaydi.

"Kitoblar" ilovamizning `App/BUILD.bazel` faylida avval UI kutubxonasini, keyin ilovani e'lon qilamiz:

```starlark
# App/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:ios.bzl", "ios_application")

swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "AppLib",
    # Bu kutubxona faqat iOS uchun (SwiftUI'ning iOS-only API'lari bor), shuning
    # uchun "manual" — "bazel build //..." uni macOS host uchun qurishga urinmaydi.
    tags = ["manual"],
    deps = ["//Core"],
)

ios_application(
    name = "Kitoblar",
    bundle_id = "uz.azimjondev.kitoblar",
    families = ["iphone"],
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    deps = [":AppLib"],
)
```

(Haligi `tags = ["manual"]`'ning nega kerakligini [8-bo'limda](#manual) aniq xato bilan ko'rsatamiz — hozircha shunchaki qoldiring.)

`ios_application`'ning atributlari (manba: rules_apple `doc/rules-ios.md`, 5.3.0). Avval **majburiylari** — rules_apple 5.3.0'da faqat uchtasi haqiqatan majburiy:

| Atribut              | Majburiymi             | Vazifasi                                                                                                   |
| :------------------- | :--------------------- | :--------------------------------------------------------------------------------------------------------- |
| `name`               | ✅ ha                  | Target nomi.                                                                                               |
| `families`           | ✅ ha                  | Qaysi qurilma oilalari: `["iphone"]`, `["ipad"]` yoki `["iphone", "ipad"]`. Kamida bittasi kerak.          |
| `minimum_os_version` | ✅ ha                  | Eng past qo'llab-quvvatlanadigan iOS versiyasi, masalan `"17.0"`.                                          |
| `bundle_id`          | ⛔ yo'q (lekin yozing) | Teskari-DNS ko'rinishidagi bundle ID. Texnik jihatdan ixtiyoriy (standart `""`), lekin amalda doim bering. |
| `infoplists`         | ⛔ yo'q                | Info.plist fayllari ro'yxati. Berilmasa, rules_apple standart Info.plist ishlatadi.                        |
| `deps`               | ⛔ yo'q                | Ilovaga linklanadigan kutubxonalar.                                                                        |

Keyin **tez-tez ishlatiladigan ixtiyoriylar**:

| Atribut                | Vazifasi                                                                                                                                   |
| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
| `resources`            | Bundle'ga qo'shiladigan resurslar (JSON, rasm, `.strings`...).                                                                             |
| `app_icons`            | Ilova ikonkalari. Fayllar bitta `*.xcassets/*.appiconset` papkada bo'lishi kerak. (Xcode 26+'da Icon Composer'ning `*.icon` bundle'i ham.) |
| `launch_storyboard`    | Ishga tushish ekrani (`.storyboard`/`.xib`).                                                                                               |
| `entitlements`         | Qurilma build'lari uchun entitlement fayli (App Groups, Push...). Simulyator uchun ko'pincha kerak emas.                                   |
| `provisioning_profile` | Qurilmaga chiqarish uchun provisioning profil. Simulyator uchun ixtiyoriy.                                                                 |
| `version`              | `apple_bundle_version` target'i (CFBundleVersion'ni boshqarish uchun).                                                                     |
| `frameworks`           | Ilova bilan birga joylanadigan `ios_framework` target'lari.                                                                                |
| `extensions`           | App extension'lar (Widget, Share...). Buni 8-darsda ko'ramiz.                                                                              |

Hozircha bizga faqat 5 ta atribut kerak: `name`, `bundle_id`, `families`, `infoplists`, `minimum_os_version`, `deps`. Soddalik — kuch.

> **🧠 Esda tuting:** `deps = [":AppLib"]` — bu ikki nuqta bilan boshlangan label **shu BUILD faylidagi** target degani (`//App:AppLib`). `deps = ["//Core"]` esa boshqa paketdagi target (`//Core:Core`'ning qisqartmasi). Label sintaksisi — [3-darsning](/ios-lessons/bazel-03-tushunchalar) mavzusi edi.

---

## 5. Info.plist: birlashtirish va o'zgaruvchilar {#infoplist}

Har bir iOS ilovasida `Info.plist` bo'ladi — bu ilovaning "pasporti": uning nomi, versiyasi, bundle ID'si va ishga tushish sozlamalari. Bazel'da uni odatda alohida fayl sifatida yozamiz va `ios_application`'ning `infoplists` atributiga beramiz.

Mana bizning minimal `App/Info.plist`:

```json
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key><string>en</string>
  <key>CFBundleExecutable</key><string>$(EXECUTABLE_NAME)</string>
  <key>CFBundleIdentifier</key><string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
  <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
  <key>CFBundleName</key><string>$(PRODUCT_NAME)</string>
  <key>CFBundleShortVersionString</key><string>1.0</string>
  <key>CFBundleVersion</key><string>1</string>
  <key>LSRequiresIPhoneOS</key><true/>
  <key>UILaunchScreen</key><dict/>
</dict>
</plist>
```

(Fayl bu yerda `json` deb belgilangan, lekin aslida bu XML plist — sayt uni shunday rang beradi.)

Bu yerda ikkita muhim mexanizm bor.

**Birlashtirish (merging).** `infoplists` atributi — bu **ro'yxat**. Siz bir nechta `.plist` bo'lagini berishingiz mumkin, Bazel ularni bitta `Info.plist`ga qo'shadi. Bu `select()` bilan juda foydali: masalan, TestFlight build'i uchun boshqa `CFBundleDisplayName` beradigan faylni tanlashingiz mumkin. Qoida: agar ikki faylda bir xil kalit bo'lsa, qiymatlari **bir xil** bo'lishi shart, aks holda build yiqiladi (manba: rules_apple `common_info.md`).

**O'zgaruvchilar (variable substitution).** Yuqoridagi `$(EXECUTABLE_NAME)`, `$(PRODUCT_BUNDLE_IDENTIFIER)`, `$(PRODUCT_NAME)` — bular Xcode uslubidagi o'zgaruvchilar. Bazel ularni build paytida haqiqiy qiymatlarga almashtiradi. `$(NAME)` yoki `${NAME}` ikkala shakl ham ishlaydi. Qo'llab-quvvatlanadigan o'zgaruvchilar (manba: rules_apple `common_info.md`, "Info.plist Handling"):

| O'zgaruvchi                    | Qiymati                                                |
| :----------------------------- | :----------------------------------------------------- |
| `PRODUCT_BUNDLE_IDENTIFIER`    | `ios_application`'ning `bundle_id` atributi.           |
| `PRODUCT_NAME` / `TARGET_NAME` | `bundle_name` (agar berilgan bo'lsa) yoki target nomi. |
| `EXECUTABLE_NAME`              | `executable_name` → `bundle_name` → target nomi.       |
| `BUNDLE_NAME`                  | `PRODUCT_NAME` + kengaytma (`.app`).                   |
| `DEVELOPMENT_LANGUAGE`         | Hozircha `en` ga qotirilgan.                           |

> **⚠️ Diqqat:** `$(PRODUCT_MODULE_NAME)` o'zgaruvchisi **qo'llab-quvvatlanmaydi**. Xcode'da u avtomatik to'ldiriladi, lekin Bazel'da har bir `swift_library` alohida modul bo'lgani uchun printsipial klass qaysi modulda ekanini avtomatik aniqlab bo'lmaydi. Agar extension'da `NSExtensionPrincipalClass` kerak bo'lsa, modul nomini `Info.plist`'ingizda **qo'lda yozing** (manba: rules_apple `common_info.md`).

## Build paytida bu fayl boshqa kalitlar bilan ham to'ldiriladi (Xcode versiyasi, SDK, platforma) — buni [7-bo'limda](#ipa) `.ipa` ichini ochganimizda o'z ko'zimiz bilan ko'ramiz.

## 6. "Kitoblar" ilovasini yig'amiz {#kitoblar}

Hamma qismlarni birlashtiramiz. Mana to'liq workspace tuzilishi:

```text
Kitoblar/
├── .bazelversion            # 9.3.0
├── .bazelrc                 # umumiy flaglar (pastda)
├── MODULE.bazel             # apple_support, rules_swift, rules_apple
├── Core/
│   ├── BUILD.bazel          # swift_library Core + testlar
│   ├── Sources/
│   │   ├── Book.swift
│   │   └── BookLibrary.swift
│   └── Tests/
│       └── BookTests.swift
└── App/
    ├── BUILD.bazel          # swift_library AppLib + ios_application Kitoblar
    ├── Info.plist
    └── Sources/
        ├── KitoblarApp.swift
        ├── BookListView.swift
        └── BookDetailView.swift
```

`.bazelversion` va `.bazelrc` — [2-darsdan](/ios-lessons/bazel-02-ornatish) tanish. Bu safar `.bazelrc`'ga iOS uchun bir nechta flag qo'shamiz:

```bash
# .bazelrc — "Kitoblar" ilovasi uchun asosiy sozlamalar

# Butun jamoa va CI bir xil disk cache'dan foydalansin (B10'da chuqur ko'ramiz).
build --disk_cache=~/.cache/bazel-disk

# bazel test faqat test maqsadlarini qursin, platformaga bog'liq boshqalarini emas.
test --build_tests_only

# Simulyatorni tanlaymiz: Xcode 27'da "iPhone 15" yo'q, standart runner yiqiladi.
test --ios_simulator_device="iPhone 17 Pro"
test --ios_simulator_version=27.0
```

(Oxirgi ikki qatorni [10-bo'limda](#simulyator) batafsil tushuntiramiz.)

Endi UI kodini yozamiz. Ilovaning kirish nuqtasi — `@main` struct, u `swift_library` **ichida** yashaydi (Bazel'da alohida "main" fayl shart emas):

```swift
// App/Sources/KitoblarApp.swift
import SwiftUI

/// Ilovaning kirish nuqtasi. @main swift_library ichida yashaydi —
/// Bazel'da alohida "main" fayl shart emas.
@main
struct KitoblarApp: App {
    var body: some Scene {
        WindowGroup {
            BookListView()
        }
    }
}
```

Asosiy ekran — kitoblar ro'yxati va qidiruv. Ma'lumot `Core` modulidan keladi (`import Core`):

```swift
// App/Sources/BookListView.swift
import SwiftUI
import Core

/// Kitoblar ro'yxati + qidiruv. Ma'lumot Core modulidan keladi.
struct BookListView: View {
    @State private var query = ""

    private var books: [Book] {
        BookLibrary.search(query)
    }

    var body: some View {
        NavigationStack {
            List(books) { book in
                NavigationLink(value: book) {
                    VStack(alignment: .leading, spacing: 4) {
                        Text(book.title).font(.headline)
                        Text(book.subtitle).font(.subheadline).foregroundStyle(.secondary)
                    }
                }
            }
            .navigationTitle("Kitoblar")
            .navigationDestination(for: Book.self) { book in
                BookDetailView(book: book)
            }
            .searchable(text: $query, prompt: "Sarlavha yoki muallif")
            .overlay {
                if books.isEmpty {
                    ContentUnavailableView.search(text: query)
                }
            }
        }
    }
}
```

```swift
// App/Sources/BookDetailView.swift
import SwiftUI
import Core

/// Bitta kitob tafsiloti.
struct BookDetailView: View {
    let book: Book

    var body: some View {
        List {
            LabeledContent("Muallif", value: book.author)
            LabeledContent("Yil", value: String(book.year))
            LabeledContent("Sahifalar", value: String(book.pages))
        }
        .navigationTitle(book.title)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

Hammasi tayyor. Birinchi build'ni ishga tushiramiz:

```bash
bazel build //App:Kitoblar
```

**Natija** (birinchi, sovuq build — bo'sh disk cache, M4 Pro):

```text
INFO: Analyzed target //App:Kitoblar (199 packages loaded, 6819 targets configured, 7 aspect applications).
[335 / 350] Compiling Swift module //Core:Core; worker
[341 / 350] Compiling Swift module //App:AppLib; worker
[342 / 350] Linking App/libAppLib.a
[346 / 350] SwiftStdlibCopy App/Kitoblar-intermediates/swiftlibs
[347 / 350] Bundling Kitoblar
INFO: Found 1 target...
Target //App:Kitoblar up-to-date:
  bazel-bin/App/Kitoblar.ipa
INFO: Elapsed time: 94.601s, Critical Path: 50.01s
INFO: 350 processes: 164 internal, 183 darwin-sandbox, 1 local, 2 worker.
```

Ikki narsaga e'tibor bering. Birinchidan, natija — `bazel-bin/App/Kitoblar.ipa`. Ikkinchidan, birinchi build **94.6 soniya** oldi — bu ko'p ko'rinishi mumkin, lekin bu **bir martalik** narx: vaqtning katta qismi rules_swift'ning persistent worker vositasini (abseil kutubxonasidan) kompilyatsiya qilishga ketadi. Keyingi build'lar soniyalar ichida tugaydi — buni [15-bo'limda](#olchovlar) raqamlar bilan ko'rsatamiz.

> **🧠 Esda tuting:** Build chiqishidagi action nomlari — `Compiling Swift module`, `Linking`, `SwiftStdlibCopy`, `Bundling` — bu [1-bo'limdagi](#nega) "iOS ilovasi bundle" degan gapning amaliy ko'rinishi. Har bir qadam alohida **action** bo'lib, alohida keshlanadi. Quyidagi diagramma butun quvurni ko'rsatadi:

![swift_library'dan .ipa'gacha bo'lgan build quvuri: Swift fayllar kompilyatsiya qilinadi, linklanadi, bundle qilinadi va imzolanadi; o'ngda haqiqiy Bazel action nomlari](/lessons/images/bazel/bazel_05_ios_target_layers.svg)

Chapdagi oqim yuqoridan pastga: Swift manba fayllar `swift_library` orqali `.swiftmodule` va `libAppLib.a` ga aylanadi; `ios_application` esa ularni Info.plist va resurslar bilan birga linklab, bundle qilib, imzolab, `Kitoblar.app` → `Kitoblar.ipa` chiqaradi. O'ngdagi yashil/ko'k/qizil yozuvlar — `bazel build` chiqishidan olingan haqiqiy action nomlari.

---

## 7. .ipa ichida nima bor? {#ipa}

`.ipa` — bu shunchaki `.zip` fayl (simulyator uchun siqilmagan holda). Keling, ichini ochib ko'ramiz:

```bash
unzip -l bazel-bin/App/Kitoblar.ipa
```

**Natija:**

```text
Archive:  bazel-bin/App/Kitoblar.ipa
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  01-01-1980 06:00   Payload/Kitoblar.app/
      749  01-01-1980 06:00   Payload/Kitoblar.app/Info.plist
   164384  01-01-1980 06:00   Payload/Kitoblar.app/Kitoblar
        8  01-01-1980 06:00   Payload/Kitoblar.app/PkgInfo
     1869  01-01-1980 06:00   Payload/Kitoblar.app/_CodeSignature/CodeResources
---------                     -------
   167010                     5 files
```

Har bir qatorni ko'rib chiqamiz:

| Fayl                           | Nima u                                                                                                            |
| :----------------------------- | :---------------------------------------------------------------------------------------------------------------- |
| `Payload/Kitoblar.app/`        | Ilova bundle'i. Har bir `.ipa` ichida `Payload/` papka va undagi bitta `.app` bo'ladi — bu App Store'ning talabi. |
| `Info.plist` (749 bayt)        | Birlashtirilgan va to'ldirilgan "pasport". Pastda ochib ko'ramiz.                                                 |
| `Kitoblar` (164 KB)            | Haqiqiy ishga tushadigan binary — Mach-O, `arm64`. Bizning butun Swift kodimiz + SwiftUI shu yerda.               |
| `PkgInfo` (8 bayt)             | Eski Mac uslubidagi tur kodi (`APPL...`). Bazel avtomatik qo'shadi.                                               |
| `_CodeSignature/CodeResources` | Code signature — qaysi fayl o'zgarmaganini tekshiradigan imzo. Simulyator uchun "ad-hoc" (hisobsiz) imzo.         |

Sana `01-01-1980` — bu xato emas, balki **ataylab**: Bazel takrorlanuvchan (reproducible) natija uchun fayl vaqtlarini qotirib qo'yadi, shunda bir xil kirish doim bir xil `.ipa` beradi.

Binary'ning turini tasdiqlaymiz:

```bash
file Payload/Kitoblar.app/Kitoblar
# Payload/Kitoblar.app/Kitoblar: Mach-O 64-bit executable arm64
```

Endi **birlashtirilgan** `Info.plist`ga qaraymiz. Biz yozgan fayl 15 qator edi, lekin bundle'dagisida ancha ko'proq kalit bor — Bazel va Xcode qo'shgan:

```bash
plutil -p Payload/Kitoblar.app/Info.plist
```

**Natija (qisqartirildi):**

```text
{
  "CFBundleExecutable" => "Kitoblar"            # $(EXECUTABLE_NAME) almashdi
  "CFBundleIdentifier" => "uz.azimjondev.kitoblar"   # $(PRODUCT_BUNDLE_IDENTIFIER)
  "CFBundleName" => "Kitoblar"                  # $(PRODUCT_NAME)
  "CFBundleSupportedPlatforms" => [ "iPhoneSimulator" ]
  "DTPlatformName" => "iphonesimulator"
  "DTPlatformVersion" => "27.1"
  "DTSDKName" => "iphonesimulator27.1"
  "DTXcode" => "2710"
  "DTXcodeBuild" => "27A9275"
  "MinimumOSVersion" => "17.0"                  # minimum_os_version atributidan
  "UIDeviceFamily" => [ 1 ]                     # families = ["iphone"] → 1
  "UILaunchScreen" => { }
}
```

Mana o'zgaruvchilar qanday almashdi: `$(EXECUTABLE_NAME)` → `Kitoblar`, `$(PRODUCT_BUNDLE_IDENTIFIER)` → `uz.azimjondev.kitoblar`, `$(PRODUCT_NAME)` → `Kitoblar`. `DT...` kalitlari (Xcode, SDK, platforma versiyalari) va `MinimumOSVersion`, `UIDeviceFamily` — bularni Bazel build paytida avtomatik qo'shadi. `families = ["iphone"]` atributimiz `UIDeviceFamily => [1]` ga aylandi (1 = iPhone, 2 = iPad).

> **💡 Pro tip:** `.ipa` ichini ochib ko'rish — bu eng yaxshi debugging usullaridan biri. "Nega ilovam noto'g'ri nom bilan ko'rinyapti?" yoki "Nega resursim topilmayapti?" degan savollarga ko'pincha `unzip -l` va `plutil -p` darhol javob beradi. Xcode'da bu ma'lumot bir necha oyna ortida yashiringan; Bazel'da esa bitta zip fayl.

---

## 8. Nega `tags = ["manual"]`? — birinchi haqiqiy xato {#manual}

[4-bo'limda](#ios-application) `AppLib`'ga `tags = ["manual"]` qo'shgan edik va "keyin tushuntiramiz" degan edik. Mana sababini — haqiqiy xato bilan.

Faraz qilaylik, bu tegni qo'ymadik va butun workspace'ni bir buyruq bilan qurmoqchimiz:

```bash
bazel build //...
```

**Natija — XATO:**

```text
ERROR: .../App/BUILD.bazel:5:14: Compiling Swift module //App:AppLib failed:
  (Exit 1): worker failed: error executing SwiftCompile command
  ... bazel-out/darwin_arm64-fastbuild/bin/App/AppLib.swiftmodule ...
App/Sources/BookDetailView.swift:16:10: error: 'navigationBarTitleDisplayMode' is unavailable in macOS
   |          `- error: 'navigationBarTitleDisplayMode' is unavailable in macOS
SwiftUI.View.navigationBarTitleDisplayMode:2:25: note: 'navigationBarTitleDisplayMode'
  has been explicitly marked unavailable here
ERROR: Build did NOT complete successfully
```

Nima bo'ldi? **Root cause**'ni izlaymiz. Xato yo'lidagi `darwin_arm64-fastbuild` — bu kalit. `bazel build //...` **barcha** target'larni quradi, shu jumladan `//App:AppLib`'ni **o'zicha** (standart konfiguratsiyada = host platforma = **macOS**). Lekin `BookDetailView` iOS'ga xos `navigationBarTitleDisplayMode(.inline)` ishlatadi — bu API macOS'da yo'q. Shuning uchun `AppLib`'ni macOS uchun kompilyatsiya qilishga urinish yiqiladi.

`AppLib` **faqat** `ios_application` orqali qurilishi kerak — chunki aynan `ios_application` uni iOS konfiguratsiyasiga sozlaydi. `tags = ["manual"]` esa `//...` va `bazel test //...` kabi "hammasini" buyruqlarini bu target'ni **o'tkazib yuborishga** majbur qiladi:

```starlark
swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "AppLib",
    tags = ["manual"],   # <-- "bazel build //..." bu target'ni o'tkazib yuboradi
    deps = ["//Core"],
)
```

Shundan keyin `bazel build //...` muammosiz ishlaydi:

```text
INFO: Analyzed 4 targets (1 packages loaded, 6 targets configured, 3 aspect applications).
INFO: Found 4 targets...
INFO: Build completed successfully, 1 total action
```

E'tibor bering: `//Core:Core` **tegsiz** ham muammosiz quriladi — chunki unda macOS'da yo'q API yo'q (u sof domen kodi). Faqat UI kutubxonasi (iOS-only API ishlatadigani) `manual` bo'lishi kerak.

> **🧠 Esda tuting:** Qoida oddiy: **faqat iOS'da ishlaydigan `swift_library`'ga `tags = ["manual"]` qo'ying**, chunki uni host (macOS) uchun qurib bo'lmaydi. U ilova yoki test orqali, to'g'ri platforma konfiguratsiyasida quriladi. rules_apple'ning rasmiy misollari (`examples/ios/HelloWorldSwift`) ham aynan shunday qiladi — ularning `swift_library`'si `tags = ["manual"]` bilan.

> **⚠️ Diqqat:** Bu xato juda chalg'ituvchi, chunki Xcode'da bunaqa muammo yo'q (Xcode doim target'ni to'g'ri platformaga sozlaydi). Bazel esa har bir target'ni mustaqil quradigani uchun "qaysi platforma uchun?" degan savol ochiq qoladi. `manual` teg — shu savolga javob.

---

## 9. Simulyatorda ishga tushirish: bazel run {#run}

`.ipa` qurdik — endi uni **simulyatorda ochamiz**. Bazel'da bu bitta buyruq:

```bash
bazel run //App:Kitoblar \
  --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17 Pro" \
  --@rules_apple//apple/build_settings:ios_simulator_version=27.0
```

`bazel run` ilovani quradi, simulyatorni yoqadi, ilovani o'rnatadi va ishga tushiradi. **Natija (qisqartirildi):**

```text
2026-10-13 13:05:57 INFO Launching app uz.azimjondev.kitoblar in simulator D5622551-...
Kitoblar[42454:2523195] ...
uz.azimjondev.kitoblar: 42454
```

Simulyatorda "Kitoblar" ilovasi ochiladi: 5 ta kitob ro'yxati (sarlavha + "muallif · yil"), pastda qidiruv maydoni ("Sarlavha yoki muallif"). Bir kitobni bossangiz, tafsilot ekraniga o'tadi. Bizning `Core` modulidagi ma'lumot ekranga chiqdi — butun quvur ishladi: Bazel → Swift → SwiftUI → simulyator.

> **⚠️ Diqqat — `bazel run` "osilib" qoladi:** iOS ilovasi (GUI) o'z-o'zidan tugamaydi. `bazel run` esa ilovani ishga tushirib, uning console log'larini ko'rsatib **ulanib turadi** — ya'ni terminal "band" bo'lib qoladi. Buni shu darsni tayyorlashda o'z ko'zimiz bilan ko'rdik: `bazel run` 10 daqiqadan ko'proq tugamay turdi, chunki ilova yopilmadi. Terminalni bo'shatish uchun **Ctrl-C** bosing (ilova simulyatorda ochiq qoladi), yoki quyidagi rejimni ishlating.

`bazel run`'ning xatti-harakatini `BAZEL_APPLE_RUN_MODE` muhit o'zgaruvchisi bilan boshqarasiz (manba: rules_apple `common_info.md`):

| Qiymat                       | Nima qiladi                                                                    |
| :--------------------------- | :----------------------------------------------------------------------------- |
| `install_and_run` (standart) | Eski nusxani to'xtatadi, o'rnatadi va **ishga tushiradi** (log'larga ulanadi). |
| `install_without_running`    | O'rnatadi, lekin ishga tushirmaydi — terminal darhol bo'shaydi.                |
| `run_without_installing`     | Qayta o'rnatmay, mavjud o'rnatilgan ilovani ishga tushiradi.                   |

Masalan, faqat o'rnatish uchun (terminal osilib qolmaydi):

```bash
BAZEL_APPLE_RUN_MODE=install_without_running bazel run //App:Kitoblar \
  --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17 Pro" \
  --@rules_apple//apple/build_settings:ios_simulator_version=27.0
```

> **💡 Pro tip:** `bazel build` faqat `.ipa` yasaydi — simulyatorga tegmaydi. `bazel run` esa quradi **va** ishga tushiradi. Agar sizga faqat fayl kerak bo'lsa (masalan CI'da artifact sifatida), `bazel build` ishlating. Kundalik ishda esa `bazel run` qulayroq.

---

## 10. Qaysi simulyator? device va version tanlash {#simulyator}

Yuqoridagi `bazel run` buyrug'ida biz simulyatorni aniq tanladik:

```bash
--@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17 Pro"
--@rules_apple//apple/build_settings:ios_simulator_version=27.0
```

Nega buni aniq yozish kerak? Chunki Bazel qaysi simulyatorni ishlatishni bir necha manbadan izlaydi va standart holat Xcode 27'da muammoli bo'lishi mumkin. Mana to'liq tartib (manba: rules_apple `build_settings.bzl` manba kodi va skeptik tekshiruv):

![Simulyator tanlash tartibi: test atributlari, so'ng build settings (tavsiya etiladi), so'ng native flaglar, oxirida hardcoded iPhone 15 zaxirasi; o'ngda haqiqiy xato xabari](/lessons/images/bazel/bazel_05_simulator_selection.svg)

Yuqoridan pastga — birinchisi topilsa, shu ishlatiladi:

1. **Test target atributlari** — `ios_unit_test`/`ios_ui_test`'dagi `device_type` va `os_version`.
2. **Build settings** — `--@rules_apple//apple/build_settings:ios_simulator_device` / `..._version`. **Bu — tavsiya etilgan shakl**, chunki u kelajakka chidamli.
3. **Native Bazel flaglar** — `--ios_simulator_device` / `--ios_simulator_version`. Bular Bazel 9.3.0'da ishlaydi (biz `.bazelrc`'da shularni ishlatdik), lekin manba kodidagi izoh ularni "Bazel 9.x tashlanganda olib tashlanadi" deb ogohlantiradi.
4. **Zaxira** — agar hech narsa berilmasa, runner qotirilgan `"iPhone 15"`'ni so'raydi. Xcode 27'da, agar bu qurilma turi o'rnatilmagan bo'lsa, build yiqiladi.

Nega standart holatga tayanmaslik kerak? Shu darsni tayyorlashda biz **ataylab mavjud bo'lmagan** qurilmani so'rab ko'rdik:

```bash
bazel test //Core:CoreTests \
  --ios_simulator_device="iPhone 99 Ultra" --ios_simulator_version=27.0
```

**Natija — XATO (1.1 soniyada):**

```text
Selected simulator runtime com.apple.CoreSimulator.SimRuntime.iOS-27-0
Invalid device type: iPhone 99 Ultra
...
subprocess.CalledProcessError: Command '['xcrun', 'simctl', 'create',
  'BAZEL_TEST_iPhone 99 Ultra_27.0', 'iPhone 99 Ultra',
  'com.apple.CoreSimulator.SimRuntime.iOS-27-0']' returned non-zero exit status 146.
//Core:CoreTests          FAILED in 1.1s
```

`Invalid device type` — runner `simctl create` bilan simulyator yaratolmadi, chunki "iPhone 99 Ultra" degan qurilma yo'q. Shuning uchun **har doim** o'z mashinangizda bor qurilma va versiyani tanlang:

```bash
xcrun simctl list devicetypes   # qanday qurilmalar bor
xcrun simctl list runtimes      # qanday iOS versiyalari o'rnatilgan
```

So'ng topganingizni `.bazelrc`'ga yozib qo'ying (biz `iPhone 17 Pro` + `27.0` tanladik, chunki shu mashinada bor).

> **🧠 Esda tuting:** Bu mashinada (Xcode 27.1) `iPhone 15` ham, `iPhone 17 Pro` ham o'rnatilgan edi, shuning uchun qotirilgan `"iPhone 15"` zaxirasi ham ishlardi. Lekin boshqa mashinada yoki CI'da `iPhone 15` bo'lmasligi mumkin — shuning uchun **2-pog'ona (build settings)** bilan aniq qurilma + versiyani o'zingiz bering. Bu kelajakka ham, jamoadagi boshqa Mac'larga ham, CI runner'ga ham chidamli.

> **💡 Pro tip:** Testlar uchun simulyator flaglarini bir marta `.bazelrc`'ning `test` bo'limiga yozib qo'ying (biz qilgandek). `bazel run` uchun esa `build` bo'limiga `build --@rules_apple//apple/build_settings:ios_simulator_device="..."` qo'shsangiz, har safar buyruqda yozib o'tirmaysiz.

---

## 11. Testlar simulyatorda {#testlar}

Bizning `Core` modulida testlar bor edi. Ularni ko'rib chiqamiz (testlarni chuqur [6-darsda](/ios-lessons/bazel-06-testlar) o'rganamiz — bu yerda faqat birinchi tanishuv).

`Core/Tests/BookTests.swift`:

```swift
// Core/Tests/BookTests.swift
import XCTest
@testable import Core

final class BookTests: XCTestCase {
    func testSubtitleFormat() {
        let book = Book(title: "Sinov", author: "Muallif", year: 2024, pages: 100)
        XCTAssertEqual(book.subtitle, "Muallif · 2024")
    }

    func testSearchIsCaseInsensitive() {
        let hits = BookLibrary.search("qodiriy")
        XCTAssertEqual(hits.count, 2)
    }
}
```

`Core/BUILD.bazel`'ga test target'larini qo'shamiz — bitta `swift_library` (testonly) va bitta `ios_unit_test`:

```starlark
swift_library(
    name = "CoreTestsLib",
    testonly = True,
    srcs = glob(["Tests/**/*.swift"]),
    deps = [":Core"],
)

ios_unit_test(
    name = "CoreTests",
    minimum_os_version = "17.0",
    deps = [":CoreTestsLib"],
)
```

Ishga tushiramiz:

```bash
bazel test //Core:CoreTests --test_output=all
```

**Natija:**

```text
Existing simulator 'BAZEL_TEST_iPhone 17 Pro_27.0' ... state is: booted
Test Suite 'BookTests' started
Test Case '-[Core_CoreTestsLib.BookTests testSubtitleFormat]' passed (0.002 seconds).
...
Executed 4 tests, with 0 failures (0 unexpected) in 0.013 seconds
//Core:CoreTests          PASSED in 5.2s
Executed 1 out of 1 test: 1 test passes.
```

Diqqat qiling: `ios_unit_test` o'ziga kerakli simulyatorni (`BAZEL_TEST_iPhone 17 Pro_27.0`) yaratib/yoqib oladi. Testlar o'tdi.

Endi **eng muhim** qism. Hech narsani o'zgartirmay, testni **qayta** ishga tushiring:

```text
//Core:CoreTests          (cached) PASSED in 5.2s
Executed 0 out of 1 test: 1 test passes.
```

`(cached) PASSED` va `Executed 0 out of 1 test` — Bazel testni **umuman qayta ishlatmadi**. U biladi: kod ham, bog'liqliklar ham o'zgarmadi, demak natija ham o'zgarmaydi. Bu — Bazel'ning [1-darsda](/ios-lessons/bazel-01-kirish) aytilgan super-kuchlaridan biri: **test natijalarini keshlash**. Katta loyihada bu minglab testni qayta ishlatmaslikni anglatadi.

> **🚀 Motivatsiya:** Xcode odatda testlarni har safar qaytadan ishga tushiradi. Bazel esa faqat **o'zgargan** narsaga tegishli testlarni ishlatadi, qolganini keshdan oladi. Trendyol (Tuist bilan, o'xshash g'oya) UI-test muhitini tayyorlashni 15+ daqiqadan ~30 soniyaga tushirgan (manba: Tuist blogidagi mijoz case study, 2024-12). Bu — "faqat o'zgarganini tekshir" tamoyilining kuchi, buni 6-darsda chuqur ochamiz.

---

## 12. Debug, release va arxitektura {#debug-release}

Bazel'da build "rejimi" (compilation mode)ni `-c` (yoki `--compilation_mode`) bilan tanlaysiz:

| Flag           | Rejim                                                   | Qachon                                    |
| :------------- | :------------------------------------------------------ | :---------------------------------------- |
| `-c fastbuild` | **Standart.** Minimal optimizatsiya, tez kompilyatsiya. | Kundalik ish, CI testlari.                |
| `-c dbg`       | Debug — to'liq debug simvollari (`.dSYM`).              | Debugger bilan ishlaganda.                |
| `-c opt`       | Optimizatsiya qilingan.                                 | Release, App Store, performance o'lchash. |

Masalan, release build:

```bash
bazel build //App:Kitoblar -c opt
```

Biz buni o'lchadik: `-c opt` build **16.9 soniya** oldi (M4 Pro). U `fastbuild`'dan sekinroq, chunki kod boshqa (optimizatsiyalangan) konfiguratsiyada **qaytadan** kompilyatsiya qilinadi — `fastbuild` va `opt` ikki alohida kesh.

**Arxitektura.** Simulyator va qurilma uchun binary har xil protsessor arxitekturasi uchun quriladi. Buni `--ios_multi_cpus` boshqaradi (manba: rules_apple tutorial):

| Maqsad                         | Flag                       | Natija          |
| :----------------------------- | :------------------------- | :-------------- |
| Simulyator (Apple Silicon Mac) | standart (flag kerak emas) | `ios_sim_arm64` |
| Haqiqiy qurilma                | `--ios_multi_cpus=arm64`   | qurilma `arm64` |

```bash
# qurilma uchun:
bazel build //App:Kitoblar --ios_multi_cpus=arm64
```

Bizning build chiqishida yo'l `bazel-out/ios_sim_arm64-fastbuild-...` ko'rinishida edi — ya'ni standart holda Apple Silicon Mac'da simulyator `arm64` uchun quriladi.

> **⚠️ Diqqat — simulyator vs qurilma arxitekturasi tuzog'i:** Apple Silicon'da simulyator ham `arm64`, qurilma ham `arm64`. Bu eski "fat binary" (qurilma arm64 + simulyator x86_64) mantiqini buzadi. **Square** aynan shu muammoga duch kelgan: `error: Building for simulator, but linking in file built for ios` — oldindan qurilgan static arxivlarda faqat qurilma `arm64` bo'lgan. Ular object header'larini qayta yozib (arm64 → simulyator) hal qilgan (manba: Square muhandislik blogi, Apple Silicon bilan Bazel). Agar tashqi binary (xcframework) ishlatsangiz, uning ichida simulyator `arm64` slice'i borligiga ishonch hosil qiling.

---

## 13. Xcode va SDK qayerdan keladi {#xcode-sdk}

Bazel Swift'ni o'zi kompilyatsiya qilmaydi — u sizning mashinangizdagi **Xcode**'dan `swiftc`, `clang`, SDK va `ld`'ni oladi. Buni `apple_support` moduli boshqaradi. Bazel qaysi Xcode'ni ishlatishini quyidagilar belgilaydi:

```bash
xcode-select -p
# /Applications/Xcode.app/Contents/Developer
```

- **`xcode-select -p`** — tizim standart Xcode'ini ko'rsatadi. `sudo xcode-select -s /Applications/Xcode-27.app` bilan o'zgartirasiz.
- **`DEVELOPER_DIR`** muhit o'zgaruvchisi — agar o'rnatilgan bo'lsa, `xcode-select`'dan ustun turadi (bitta buyruq uchun boshqa Xcode ishlatish qulay).
- **`--xcode_version`** Bazel flagi — aniq Xcode versiyasini "qulflaydi". Bu **cache hit**'lar uchun muhim: butun jamoa bir xil Xcode ishlatsa, remote cache ishlaydi (manba: rules_apple `common_info.md`).

> **💡 Pro tip — Xcode'ni qulflash:** Katta jamoalar `tools/bazel` nomli wrapper skript yozadi. U har build'da joriy Xcode versiyasini aniqlab, `xcode.bazelrc` fayliga yozib qo'yadi (`--xcode_version`, `--repo_env=DEVELOPER_DIR=...`). Shunda har bir muhandis va CI aynan bir xil Xcode build'ini ishlatadi va cache buzilmaydi (manba: rules_apple docs). Buni 10- va 11-darslarda (cache va CI) qo'llaymiz.

**Xcode'ni yangilaganingizda nima bo'ladi?** Ikki muammo chiqishi mumkin, ikkalasini ham bilib qo'ying:

1. **Cache eskiradi.** Xcode'ni yangilagandan keyin eski build artifactlari to'g'ri kelmasligi mumkin. Yechim:

```bash
bazel clean --expunge
```

Bu butun output base'ni (shu jumladan Xcode haqidagi keshlangan ma'lumotni) tozalaydi. Keyingi build Xcode'ni qaytadan aniqlaydi.

2. **"Xcode versiyasi topilmadi" xatosi.** Agar `--xcode_version=X` bersangiz, lekin Bazel'ning keshlangan ro'yxatida u yo'q bo'lsa:

```text
--xcode_version=X specified, but 'X' is not an available Xcode version.
Locally available versions: [...]. ... try running 'bazel fetch --configure',
and then re-run your command.
```

Yechim xatoning o'zida yozilgan: `bazel fetch --configure` (Xcode'larni qaytadan aniqlaydi), so'ng qaytadan ishga tushiring; ba'zan `bazel shutdown` ham kerak (manba: apple_support `xcode_config.bzl`). Agar Xcode `/Applications`'dan tashqarida bo'lsa, `BAZEL_ALLOW_NON_APPLICATIONS_XCODE=1` qo'shing.

> **⚠️ Diqqat:** Xcode 27 **faqat Apple Silicon** Mac'larda ishlaydi va macOS Tahoe 26.6+ talab qiladi; eski `ld64` linkeri olib tashlangan (manba: Apple Xcode 27 release notes orqali uchinchi tomon posti, 2026-09). Ya'ni Intel Mac'larda yoki Intel CI runner'larda Xcode 27 bilan Bazel iOS build'i ishlamaydi.

---

## 14. Xcode loyihasi vs Bazel: fayllar {#xcode-vs-bazel}

Oxirida bir qadam orqaga chekinib, nima qilganimizni Xcode bilan solishtiramiz. Xuddi shu ilovani Xcode'da yaratganingizda, hamma narsa bitta faylda yashaydi: `Kitoblar.xcodeproj/project.pbxproj`.

![Chapda Xcode'ning bitta ulkan project.pbxproj fayli (UUID'lar, merge konflikt), o'ngda Bazel'ning uchta kichik matn fayli](/lessons/images/bazel/bazel_05_xcode_vs_bazel_files.svg)

`project.pbxproj` — bu mashina yaratadigan, yuzlab–minglab qatorli fayl. Unda UUID'lar, build settings, build phases va fayl ro'yxati hammasi aralash. Natijada:

- **Merge konflikt do'zaxi.** Ikki muhandis bir vaqtda fayl qo'shsa, `project.pbxproj`'da konflikt chiqadi va uni qo'lda hal qilish deyarli imkonsiz. Bu — iOS jamoalarining eng mashhur dardlaridan biri.
- **Yashirin bog'liqliklar.** Target'lar orasidagi aloqa oshkora emas; "nega bu modul qayta quriladi?" degan savolga javob yo'q.
- **"Clean build tuzatadi"** — sababi noma'lum muammolar, chunki Xcode qaysi inputdan qaysi output chiqqanini aniq kuzatmaydi.

Bazel'da esa — xuddi shu ilova uchun — **uchta kichik matn fayl**: `MODULE.bazel` (11 qator), `Core/BUILD.bazel` (23 qator), `App/BUILD.bazel` (19 qator). Jami ~50 qator, hammasini odam o'qiy oladi. Bog'liqliklar oshkora (`deps = ["//Core"]`), diff/merge oddiy kod kabi, va `Info.plist` alohida fayl bo'lib qoladi.

> **🏢 Real-world:** **Spotify** aynan shu sababdan `project.pbxproj`'ni git'da saqlashni tashlagan. Ularda generator **2000+ `BUILD.bazel`** faylini avtomatik yaratadi, faqat 30–50 tasi qo'lda yoziladi. Natijada merge konfliktlar yo'qoldi va 200+ muhandis bir monorepoda to'qnashuvsiz ishlay oladi (manba: engineering.atspotify.com, 2023-10). Xuddi shunday, **Telegram iOS** ham to'liq Bazel'da quriladi — `pbxproj` git'da yo'q, kerak bo'lsa rules_xcodeproj bilan generatsiya qilinadi (manba: Telegram-iOS ochiq repo). Xcode loyihasini Bazel'dan qanday yaratishni [9-darsda](/ios-lessons/bazel-09-xcode) ko'ramiz.

---

## 15. O'lchovlar — haqiqiy raqamlar {#olchovlar}

Hamma raqamlar shu darsda, bitta mashinada o'lchandi: **Apple M4 Pro (12 yadro, 24 GB), macOS 27, Xcode 27.1, Bazel 9.3.0, rules_apple 5.3.0**. "Kitoblar" ilovasi (2 modul, ~8 kichik Swift fayl). Har bir o'lchov `heavy.sh --exclusive` bilan, boshqa build'lardan izolyatsiyada bajarildi.

| Stsenariy                                                               |       Vaqt | Nima bo'ldi                                                                                                     |
| :---------------------------------------------------------------------- | ---------: | :-------------------------------------------------------------------------------------------------------------- |
| **Sovuq build** (`clean --expunge` + disk cache o'chirilgan)            | **94.6 s** | 350 action. Vaqtning katta qismi rules_swift worker'ini (abseil'dan) kompilyatsiya qilish — bir martalik narx.  |
| **To'liq keshlangan qayta build** (`bazel clean` + disk cache yoqilgan) |  **2.6 s** | 186 disk cache hit. Hech narsa qaytadan kompilyatsiya qilinmadi.                                                |
| **Incremental** (bitta "barg" Swift faylni tahrirlash)                  |  **4.9 s** | Faqat `AppLib` moduli qaytadan kompilyatsiya + bundle + imzo (2 ta process).                                    |
| **No-op** (hech narsa o'zgarmadi)                                       |  **0.2 s** | Server grafni xotirada saqlaydi — hech narsa qilinmadi.                                                         |
| **Release** (`-c opt`)                                                  | **16.9 s** | `opt` konfiguratsiyasi `fastbuild`'dan alohida kesh — kod qaytadan, optimizatsiya bilan kompilyatsiya qilinadi. |

Bu jadval Bazel'ning asosiy g'oyasini bitta joyda ko'rsatadi:

- **Birinchi build sekin** (94.6 s) — lekin bu bir marta. Worker vositasi bir marta quriladi va keyin disk cache'da qoladi.
- **Keyingi build'lar — soniyalar** (2.6 s to'liq, 4.9 s incremental). Bu — Xcode "clean build"idan tubdan farq: Bazel faqat o'zgargan narsani qayta quradi.
- **No-op deyarli bepul** (0.2 s), chunki Bazel server grafni xotirada ushlab turadi (bu haqda [2-darsda](/ios-lessons/bazel-02-ornatish) gaplashgan edik).

> **📊 Real raqamlar:** Bitta barg faylni o'zgartirganda incremental build 4.9 s edi — bu `AppLib` modulining **butun**i qayta kompilyatsiya bo'lgani uchun (Swift modulni fayl-ba-fayl emas, to'liq quradi). Agar kodni ko'proq kichik modullarga bo'lsangiz, bitta o'zgarishdan qayta quriladigan hajm kamayadi. Katta loyihalarda aynan shu — modullashtirish — incremental build'ni bir necha barobar tezlashtiradi (buni 13- va 15-loyihalarda o'lchaymiz).

> **⚠️ Diqqat — raqamlar nisbiy:** Bu raqamlar kichik, 2 modulli ilovaga tegishli. Haqiqiy, yuzlab modulli ilovada sovuq build daqiqalab davom etadi, lekin incremental va keshlangan build'lardan yutuq ham ancha kattaroq bo'ladi. Shuningdek, bu Bazel'ni Xcode bilan to'g'ridan-to'g'ri solishtirish emas — neytral benchmark mavjud emas; har bir kompaniya o'z eski tizimini o'z yangi tizimi bilan solishtiradi (ko'pincha vendor e'lon qilgan raqamlar).

---

## Amaliy mashqlar {#mashqlar}

1. **iPad qo'shing.** `ios_application`'da `families = ["iphone", "ipad"]` qiling, qayta quring, `.ipa` ichidagi `Info.plist`'da `UIDeviceFamily`ni tekshiring (`plutil -p`). Endi `[1, 2]` bo'lishi kerak.

2. **Ko'rinadigan nom qo'shing.** `Info.plist`'ga `CFBundleDisplayName` kalitini qo'shing (masalan `"Mening kutubxonam"`), qayta quring va `plutil -p Payload/Kitoblar.app/Info.plist` bilan tasdiqlang. Simulyatorda ilova nomi o'zgarganini ko'ring.

3. **Incremental'ni o'lchang.** `BookLibrary.all`'ga yangi kitob qo'shing va `bazel build //App:Kitoblar` vaqtini o'lchang. Keyin `BookListView.swift`'dagi bitta matnni o'zgartirib, yana o'lchang. Qaysi biri tezroq va nega? (Maslahat: `Core` o'zgarsa, unga bog'liq `AppLib` ham qayta quriladi.)

4. ⭐ **Debug vs release hajmi.** `bazel build //App:Kitoblar -c opt` va `-c fastbuild` build'larini qiling, har birining `.ipa`sini `unzip -l` bilan oching va binary (`Kitoblar`) hajmini solishtiring.

5. ⭐ **Simulyator xatosini ko'ring.** `xcrun simctl list devicetypes` dan boshqa qurilma tanlab `bazel run` qiling. Keyin ataylab mavjud bo'lmagan qurilma (`"iPhone 42"`) bering va `Invalid device type` xatosini o'qing.

6. ⭐⭐ **`manual` tegini tushuning.** `AppLib`'dan `tags = ["manual"]`ni vaqtincha olib tashlang va `bazel build //...` ishga tushiring. Xatoni (`unavailable in macOS`) o'qing, keyin tegni qaytaring. Savol: nega `//Core:Core` teg bo'lmasa ham muammosiz quriladi, `//App:AppLib` esa yo'q?

---

## O'zingizni tekshiring {#checklist}

- [ ] `MODULE.bazel`'da `apple_support`, `rules_swift`, `rules_apple` bor va `apple_support` eng yuqorida turadi.
- [ ] `swift_library` bilan modul yarata olaman va `module_name` nima uchun kerakligini bilaman.
- [ ] `ios_application`'ning 3 ta majburiy atributini ayta olaman: `name`, `families`, `minimum_os_version`.
- [ ] `Info.plist`'dagi `$(PRODUCT_NAME)` kabi o'zgaruvchilar nima bilan almashishini bilaman.
- [ ] `bazel build //App:Kitoblar` bilan `.ipa` yasadim va `unzip -l` bilan ichidagi 5 ta elementni tushunaman.
- [ ] `bazel run` bilan simulyatorda ochdim va `bazel run`ning nega "osilib" qolishini bilaman.
- [ ] To'g'ri simulyator `device` + `version`ni `.bazelrc`'ga yozdim va nega standart holatga tayanmaslik kerakligini bilaman.
- [ ] Faqat-iOS `swift_library`'ga nega `tags = ["manual"]` kerakligini tushunaman.

---

## Xulosa {#xulosa}

- **iOS ilovasi — bu bundle, oddiy binary emas.** Shuning uchun `swift_binary` emas, `ios_application` kerak: u linklaydi, bundle qiladi, imzolaydi va `.app`/`.ipa` yasaydi.
- **Uchlik:** `apple_support` (Xcode va SDK'ni toolchain qiladi), `rules_swift` (Swift kompilyatsiya), `rules_apple` (Apple bundle va run). `apple_support` MODULE.bazel'da yuqorida turadi.
- **Oqim:** Swift fayllar → `swift_library` (modul) → `ios_application` (.app/.ipa). `Info.plist` alohida fayl bo'lib, `$(PRODUCT_NAME)` kabi o'zgaruvchilar build paytida almashadi.
- **`bazel build` = `.ipa` yasaydi; `bazel run` = quradi va simulyatorda ishga tushiradi** (va console'ga ulanib turadi — Ctrl-C yoki `install_without_running`).
- **Simulyatorni aniq tanlang** — tavsiya etilgan `--@rules_apple//apple/build_settings:ios_simulator_device/version` shakli bilan, mashinangizda bor qurilmani (`xcrun simctl list`).
- **Faqat-iOS `swift_library`'ga `tags = ["manual"]`** qo'ying, aks holda `bazel build //...` uni macOS host uchun qurib yiqiladi.
- **Debug/release `-c` bilan**; simulyator standart `arm64` (Apple Silicon), qurilma `--ios_multi_cpus=arm64`.
- **Xcode'ni qulflang** (`--xcode_version`, `tools/bazel` wrapper) cache hit'lar uchun; Xcode'ni yangilaganda `bazel clean --expunge`.
- **Bazel'da butun ilova ~50 qator matn fayl** — Xcode'ning yuzlab–minglab qatorli `project.pbxproj`idan farqli, merge qilsa va o'qisa bo'ladi.

**Keyingi dars:** [Bazel 6: Testlar — ios_unit_test, XCTest va Swift Testing, test cache, coverage va UI testlar](/ios-lessons/bazel-06-testlar)

---

## Manbalar {#manbalar}

1. [rules_apple — iOS rule'lari (rules-ios.md)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-ios.md) — `ios_application`, `ios_unit_test` atributlari.
2. [rules_apple — Info.plist va simulyator (common_info.md)](https://github.com/bazelbuild/rules_apple/blob/main/doc/common_info.md) — Info.plist birlashtirish, o'zgaruvchilar, `BAZEL_APPLE_RUN_MODE`, Xcode qulflash.
3. [rules_apple — iOS ilova qurish bo'yicha rasmiy tutorial](https://github.com/bazelbuild/rules_apple/blob/main/doc/tutorials/ios-app.md) — minimal workspace, `bazel run`, `--ios_multi_cpus`.
4. [rules_swift — swift_library (rules.md)](https://github.com/bazelbuild/rules_swift/blob/main/doc/rules.md) — `swift_library` atributlari.
5. [apple_support — README va xcode_config](https://github.com/bazelbuild/apple_support) — Xcode toolchain, `apple_support` tartibi, Xcode versiya xatolari.
6. [Bazel Central Registry](https://registry.bazel.build) — modul versiyalari (rules_apple 5.3.0, rules_swift 4.2.1, apple_support 2.10.1).
7. [Bazel 9 e'loni (blog)](https://blog.bazel.build/2026/01/20/bazel-9.html) — WORKSPACE olib tashlandi, `objc_library` `@rules_cc`'dan.
8. [Spotify — iOS build tizimini Bazel'ga o'tkazish](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly) — 2000+ BUILD fayl, p75 ~80 → ~20 daqiqa.
9. [InfoQ — Spotify Bazel iOS migratsiyasi](https://www.infoq.com/news/2023/10/spotify-bazel-ios-transition/) — raqamlar tasdiqi.
10. [Allegro — iOS build'larini Bazel bilan tezlashtirish](https://blog.allegro.tech/2020/12/speeding-up-ios-builds-with-bazel.html) — 260 → 85 soniya, 25 muhandis.
11. [Google tadqiqoti — build vaqti va samaradorlik (DX)](https://newsletter.getdx.com/p/build-times-and-developer-productivity) — tezroq build → o'lchanadigan samaradorlik.
12. [Telegram iOS — ochiq kodli Bazel iOS loyihasi](https://github.com/TelegramMessenger/Telegram-iOS) — rules_apple/rules_swift bilan real ilova.
13. [Square — Apple Silicon'ga Bazel bilan tayyorlanish](https://developer.squareup.com/blog/getting-squares-ios-build-ready-for-apple-silicon-with-bazel/) — simulyator vs qurilma `arm64` muammosi.
