# Bazel 8: Haqiqiy ilova anatomiyasi — resurslar, lokalizatsiya, widget extension, framework, entitlements va signing

> **Holat:** 2026-yil oktabr. Bu darsda bitta "o'yinchoq" SwiftUI ilovadan haqiqiy App Store ilovasiga o'tamiz: resurslar (asset catalog, JSON, shriftlar), 3 tilli lokalizatsiya, `apple_resource_bundle` bilan namespaced resurslar, `ios_framework` orqali kodni app va widget orasida bo'lishish, WidgetKit widget extension, App Groups entitlements, versiyalash va code signing. Hamma kod Bazel 9.3.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 (12 core, 24 GB) qurib va `.ipa` ichi ochib tekshirilgan.

> **Bu darsdan keyin siz:** bir nechta bundle (app + framework + extension), resurs va entitlements'dan iborat haqiqiy iOS ilovani Bazel'da to'liq qura olasiz va `.app` ichida nima qayerda turishini aniq bilasiz.

- `data` (kutubxona resursi) bilan top-level atributlar (`app_icons`, `infoplists`, `strings`) orasidagi farqni va har biri bundle'da qayerga tushishini bilasiz.
- Asset catalog, JSON, `.lproj` lokalizatsiya va `apple_resource_bundle` ni to'g'ri joylashtirasiz va kod ularni runtime'da qanday topishini tushunasiz.
- `ios_framework` bilan Core kodini app va widget orasida bo'lishasiz; `ios_extension` bilan WidgetKit widget qo'shasiz.
- Entitlements (App Groups), `apple_bundle_version`, `select()` bilan muhitga qarab sozlash va simulator vs qurilma signing'ni farqlaysiz.

## Mundarija

1. [Nega "haqiqiy ilova" boshqacha](#nega)
2. [Resurslar: `data` va top-level atributlar](#resurslar)
3. [Lokalizatsiya: `.lproj` va `.strings`](#lokalizatsiya)
4. [Resource bundle: namespaced resurslar](#resource-bundle)
5. [Framework: kodni app va extension orasida bo'lishish](#framework)
6. [Widget extension: WidgetKit va `ios_extension`](#widget)
7. [Entitlements va App Groups](#entitlements)
8. [Versiyalash va build konfiguratsiyalari](#versiya)
9. [Code signing: simulator va qurilma](#signing)
10. [To'liq anatomiya va o'lchovlar](#anatomiya)
11. [Amaliy mashqlar](#mashqlar)
12. [O'zingizni tekshiring](#checklist)
13. [Xulosa](#xulosa)
14. [Manbalar](#manbalar)

---

## 1. Nega "haqiqiy ilova" boshqacha {#nega}

[Bazel 5-darsda](/ios-lessons/bazel-05-ios-ilova) biz bitta `swift_library` + bitta `ios_application` dan iborat eng kichik SwiftUI ilovani qurdik. U kompilyatsiya bo'ldi, simulatorda ishga tushdi — lekin u haqiqiy App Store ilovasi emas edi. Haqiqiy ilovada doim quyidagilar bor:

- **Resurslar**: rasm va ranglar (asset catalog), shriftlar, JSON ma'lumotlar, ovozlar.
- **Lokalizatsiya**: kamida 2-3 til — ko'pincha `en`, `ru`, `uz`.
- **Bir nechta bundle**: asosiy app `.app`, uning ichida `Frameworks/` (umumiy kod) va `PlugIns/` (extension'lar — widget, share extension, notification service...).
- **Entitlements**: App Groups, push, keychain sharing kabi imkoniyatlar.
- **Versiya** va **code signing**: `CFBundleVersion`, provisioning profile, sertifikat.

Xcode'da bularning hammasi `project.pbxproj` ichida — "Build Phases", "Copy Bundle Resources", "Embed App Extensions", "Signing & Capabilities" tablaridan tahrirlanadi. Jamoada o'sha bitta XML faylga hamma tegadi va merge conflict bo'laveradi. Bazel'da esa har bir bundle — alohida **target**, har bir resurs — target atributida aniq e'lon qilinadi. Katta iOS ilovalarida bu modullar soni yuzlab bo'lishi mumkin: Toss (Tuist case study, 2026-10-01) ~1000 ta modulga ega bo'lib, ularning aksariyati Interface/Implementation/Model/Testing target'lariga bo'lingan; Monzo esa ~50 dan ~200 modulgacha o'sgan (Tuist case study, 2026-05-26). Bunday miqyosda "qaysi resurs qaysi bundle'ga tushadi" degan savolga aniq, deklarativ javob bo'lishi shart.

> **🚀 Motivatsiya:** Shu darsning oxirida sizning bitta `bazel build //App:Vaqt` buyrug'ingiz app binary'ni, uning ichidagi `Core.framework` ni, `VaqtWidget.appex` widget'ini, lokalizatsiyani (3 til + `Base.lproj`), asset catalog'ni va imzoni — hammasini bitta harakatda yasaydi. Hech qanday "Copy Bundle Resources" tabi, hech qanday qo'lda embedding yo'q. Buni `.ipa` ni ochib, har bir faylni ko'rsatib isbotlaymiz.

Bu darsda biz **"Vaqt"** nomli namoz vaqtlari ilovasini quramiz: u bundle'dagi JSON'dan kunlik jadvalni o'qiydi, 3 tilda ishlaydi, umumiy `Core` framework'ga ega va keyingi namozni ko'rsatadigan WidgetKit widget'i bor. Loyiha tuzilmasi:

```text
VaqtApp/
├── MODULE.bazel          # bazel_dep: apple_support, rules_swift, rules_apple
├── .bazelversion         # 9.3.0
├── .bazelrc              # disk cache + simulator flaglari
├── BUILD.bazel           # apple_bundle_version (versiya)
├── Core/                 # umumiy kutubxona + framework
│   ├── BUILD.bazel
│   ├── Info.plist
│   ├── Sources/          # model, store, lokalizatsiya yordamchisi
│   └── Resources/        # prayers.json
├── App/                  # asosiy ilova
│   ├── BUILD.bazel
│   ├── Info.plist
│   ├── App.entitlements  # App Group
│   ├── Sources/          # SwiftUI
│   └── Resources/        # Assets.xcassets + en/ru/uz/Base.lproj
└── Widget/               # WidgetKit extension
    ├── BUILD.bazel
    ├── Info.plist        # NSExtensionPointIdentifier
    ├── Widget.entitlements
    ├── Sources/
    └── Resources/
```

> **🏢 Real-world:** iOS uchun ikki asosiy rule to'plami — `rules_apple` va `rules_swift`. Uchinchisi, `rules_ios` (bazel-ios tashkiloti), 2025-yil 7-noyabrda **sunset** qilindi: Square muhandisi jschear #952 muhokamada "rules_ios endi kamroq maqsadga xizmat qiladi va keraksiz murakkablik qo'shadi, chunki rules_apple va rules_swift kerakli funksiyalarni oldi" deb yozdi. Shuning uchun biz faqat `rules_apple` + `rules_swift` ishlatamiz — bu bugungi to'g'ri yo'l.

Boshlashdan oldin `MODULE.bazel`, `.bazelversion` va `.bazelrc` ni tayyorlab olamiz — bular [2-dars](/ios-lessons/bazel-02-ornatish)dan tanish:

```starlark
# MODULE.bazel
module(name = "vaqt_app", version = "1.0.0")

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")
```

```text
# .bazelrc
# Umumiy disk cache — jamoadagi barcha build'lar bitta cache'dan foydalanadi
build --disk_cache=~/.cache/bazel-disk

# Xcode 27.x: test runner default "iPhone 15" ni so'raydi va xato beradi;
# shuning uchun mavjud simulatorni ko'rsatamiz
test --ios_simulator_device="iPhone 17 Pro"
test --ios_simulator_version=27.0
```

> **🧠 Esda tuting:** `rules_apple` 5.3.0 `bazel_compatibility = [">=8.0.0"]` deb e'lon qiladi — ya'ni Bazel 8 va 9 bilan ishlaydi, Bazel 7 endi qo'llab-quvvatlanmaydi (5.0.0'da Bazel 7 olib tashlandi). Shuningdek 5.x da default iOS test runner `xctestrun` runner'iga o'tdi. Eski darsliklarda uchraydigan `@build_bazel_rules_apple//...` load yo'li endi kerak emas — oddiy `@rules_apple//` ishlaydi.

---

## 2. Resurslar: `data` va top-level atributlar {#resurslar}

Birinchi va eng muhim savol: **resurs qayerga qo'yiladi va kod uni qanday topadi?** rules_apple'ning rasmiy hujjati (`resources.md`) resurslarni ikki toifaga ajratadi:

1. **Kutubxona resurslari** — kod runtime'da o'qiydigan fayllar (JSON, rasm, CoreData model). Bular `swift_library`/`objc_library` ning **`data`** atributi orqali e'lon qilinadi.
2. **Top-level resurslar** — operatsion tizim ishlatadigan fayllar (app ikonkasi, launch screen, lokalizatsiya). Bular top-level bundle rule'ning **maxsus atributlari** orqali: `app_icons`, `launch_storyboard`, `infoplists`, `strings`.

Eng oddiy holat — JSON faylni `data` ga qo'shish. rules_apple hujjatidagi qoida: **kutubxonaga `data` orqali ulangan resurslar transitiv ravishda top-level bundle'ning ILDIZIGA qo'yiladi** (subkatalog yo'qoladi). Masalan:

```starlark
# App/BUILD.bazel (soddalashtirilgan misol)
swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    data = ["Resources/prayers.json"],   # → Vaqt.app/prayers.json
    deps = ["//Core:CoreLib"],
)
```

Bu holatda Swift kod faylni `Bundle.main` orqali topadi:

```swift
guard let url = Bundle.main.url(forResource: "prayers", withExtension: "json") else {
    throw PrayerStoreError.resourceNotFound("prayers.json")
}
let data = try Data(contentsOf: url)
```

> **⚠️ Diqqat:** rules_swift **SwiftPM'dagi `Bundle.module` ni avtomatik yaratmaydi.** SwiftPM'da `Bundle.module` kutubxonaning o'z resurs bundle'iga ishora qiladi; Bazel'da esa `data` resurslari standart holatda app ildiziga tushadi va `Bundle.main` orqali topiladi. Agar kodingizda `Bundle.module` yozsangiz, kompilyatsiya xatosi olasiz. Buning yechimi — keyingi bo'limdagi `apple_resource_bundle` (namespaced bundle, framework ichida ham ishonchli ishlaydi).

**Asset catalog** (`.xcassets`) — eng keng tarqalgan resurs. Unda rang (`colorset`), ikonka (`imageset`), app icon (`appiconset`) bo'ladi. Biz ilovaga ikkita rang qo'shamiz. Avval asset catalog fayllarini yaratamiz:

```json
// App/Resources/Assets.xcassets/Accent.colorset/Contents.json
{
  "colors": [
    {
      "color": {
        "color-space": "srgb",
        "components": {
          "red": "0.102",
          "green": "0.600",
          "blue": "0.412",
          "alpha": "1.000"
        }
      },
      "idiom": "universal"
    }
  ],
  "info": { "author": "xcode", "version": 1 }
}
```

Asset catalog `.xcassets` papkasi **butunligicha** `data` ga `glob()` bilan qo'shiladi (ichida har bir `Contents.json` bor):

```starlark
# App/BUILD.bazel
swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "VaqtApp",
    # Asset catalog va lokalizatsiya — kutubxona darajasida data orqali
    data = glob([
        "Resources/Assets.xcassets/**",
        "Resources/**/*.lproj/**",
    ]),
    deps = ["//Core:CoreLib"],
)
```

SwiftUI kodida rangni nomi bilan ishlatamiz — rules_apple build vaqtida `actool` ni chaqirib `.xcassets` ni `Assets.car` ga kompilyatsiya qiladi:

```swift
// App/Sources/ContentView.swift (qism)
.background(Color("Accent").opacity(0.15))
```

Build'dan keyin `.app` ichida `Assets.car` paydo bo'lishini `unzip -l` bilan ko'ramiz (to'liq ro'yxatni 10-bo'limda keltiramiz):

```text
Payload/Vaqt.app/Assets.car          ← .xcassets → actool → Assets.car
Payload/Vaqt.app/prayers.json        ← data (agar app darajasida bo'lsa, ildizda)
```

> **💡 Pro tip:** `app_icons` atributi `data` dan farq qiladi — unga faqat bitta `*.xcassets/*.appiconset` katalogini berasiz. Xcode 26+ uchun `app_icons` yangi Icon Composer `*.icon` bundle'ini ham qabul qiladi (26 va undan oldingi OS'lar uchun yonma-yon). App icon — top-level resurs, chunki uni OS ishlatadi, kod emas.

---

## 3. Lokalizatsiya: `.lproj` va `.strings` {#lokalizatsiya}

iOS lokalizatsiyasi `.lproj` papkalariga asoslanadi: `en.lproj/`, `ru.lproj/`, `uz.lproj/`, va `Base.lproj/` (zaxira). Har birida bir xil nomli `Localizable.strings` fayl bo'ladi — faqat qiymatlar har xil tilda. Bizning ilova uchun uchta til yozamiz:

```text
# App/Resources/en.lproj/Localizable.strings
"next_prayer" = "Next prayer";
"app_title" = "Prayer Times";
```

```text
# App/Resources/ru.lproj/Localizable.strings
"next_prayer" = "Следующая молитва";
"app_title" = "Время молитв";
```

```text
# App/Resources/uz.lproj/Localizable.strings
"next_prayer" = "Keyingi namoz";
"app_title" = "Namoz vaqtlari";
```

`.lproj` papkalari — kutubxona resurslari (OS ularni foydalanuvchi tiliga qarab o'qiydi), shuning uchun ularni `data` orqali `glob()` bilan qo'shamiz. Muhim: `glob` ichida `*.lproj/**` pattern'ini ishlating, aks holda papka ichidagi fayllar tushib qolmaydi:

```starlark
# App/BUILD.bazel (data qismi)
data = glob([
    "Resources/Assets.xcassets/**",
    "Resources/**/*.lproj/**",   # en/ru/uz/Base.lproj — hammasi
]),
```

Swift tomonda standart holat — `NSLocalizedString`, u foydalanuvchi tilini `Bundle.main` orqali tanlaydi. Lekin biz darsda **har uchala tilni bir vaqtda** ko'rsatmoqchimiz (resurslar haqiqatan ham bundle'ga tushganini isbotlash uchun), shuning uchun kerakli `.lproj` ni aniq ochamiz:

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

// Lokalizatsiya kalitini berilgan til kodida qaytaradi.
// lang berilmasa — tizim tili ishlatiladi (Bundle.main).
public func localizedTitle(_ key: String, lang: String? = nil) -> String {
    guard let lang,
          let path = Bundle.main.path(forResource: lang, ofType: "lproj"),
          let bundle = Bundle(path: path) else {
        return NSLocalizedString(key, bundle: .main, comment: "")
    }
    return bundle.localizedString(forKey: key, value: key, table: nil)
}
```

Ilovani simulatorda ishga tushirganimizda (`bazel run //App:Vaqt`), ekranning pastki qismida uchala til ham to'g'ri chiqdi — demak uch `.lproj` ham bundle'da bor va runtime'da topilmoqda:

![Vaqt ilovasi simulatorda: Toshkent shahri, 5 namoz vaqti JSON'dan o'qilgan, keyingi namoz (Asr 15:47) yashil "Accent" rangda, pastda uchala tilda "Keyingi namoz / Следующая молитва / Next prayer"](/lessons/images/bazel/bazel_08_app_run.png)

Yuqoridagi skrinshotda uchta mexanizm bir vaqtda ishlayapti: (1) `prayers.json` Core framework'dan o'qildi — shahar "Toshkent", 5 ta vaqt; (2) asset catalog'dagi "Accent" yashil rang keyingi namozni (Asr 15:47) ajratib ko'rsatyapti; (3) uchala til `.lproj` papkasidan yechildi.

Build'dan keyin `.ipa` ichida barcha `.lproj` papkalari app ildizida turadi — biz buni `unzip -l` bilan ko'rdik:

```text
Payload/Vaqt.app/Base.lproj/Localizable.strings
Payload/Vaqt.app/en.lproj/Localizable.strings
Payload/Vaqt.app/ru.lproj/Localizable.strings
Payload/Vaqt.app/uz.lproj/Localizable.strings
```

Katta ilovalarda har bir qo'shimcha til `.app` hajmini oshiradi, ayniqsa framework'lar o'z tarjimalarini olib kelsa. rules_apple buni boshqarishga ikki yo'l beradi (`common_info.md`):

- **Build vaqtida cheklash** — faqat kerakli tillarni qadoqlash (debug/test tsiklini tezlashtiradi, chunki aksariyat dasturchi bitta tilda ishlaydi):

```bash
# Faqat o'zbek tilini qadoqlash (qolgan .lproj'lar o'tkazib yuboriladi)
bazel build //App:Vaqt --define "apple.locales_to_include=uz"
```

- **Target darajasida** — `locales_to_include` atributi (bu `--define` dan ustun turadi):

```starlark
ios_application(
    name = "Vaqt",
    locales_to_include = ["uz", "ru"],
    # ...
)
```

- **Avtomatik tozalash** — `--define "apple.trim_lproj_locales=yes"` ilova qo'llab-quvvatlamaydigan, lekin framework'lar olib kelgan ortiqcha `.lproj` papkalarini olib tashlaydi.

> **🧠 Esda tuting:** `Base.lproj` **har doim** qo'shiladi, hatto `locales_to_include` da ko'rsatilmasa ham. Til nomlari aniq moslashtiriladi: `pt` yetarli emas, kerak bo'lishi mumkin `pt_BR` yoki `pt_PT` — chunki papka nomi aynan shunday. Bu `--define` da ham, `locales_to_include` atributida ham amal qiladi.

> **⚠️ Diqqat:** `apple.locales_to_include` — bu `--define`, ya'ni u action key'ga ta'sir qiladi (10-darsda cache haqida batafsil). Ya'ni `--define "apple.locales_to_include=uz"` bilan qurilgan bundle va to'liq bundle — ikki xil cache yozuvi. Development uchun qulay, lekin release build'da to'liq to'plamni ishlating.

---

## 4. Resource bundle: namespaced resurslar {#resource-bundle}

2-bo'limda ko'rdikki, `data` orqali qo'shilgan resurs app ildiziga tushadi. Bu kichik ilovaga yaxshi, lekin **umumiy framework** uchun muammo: agar `Core` framework o'zining `config.json` iga ega bo'lsa va ilovada ham `config.json` bo'lsa — ular app ildizida to'qnashadi. Yechim — resurslarni **namespaced bundle** ichiga joylash. rules_apple hujjati aynan shuni tavsiya qiladi: "umumiy kutubxonalar resurslarini bundle ichida namespace qilgani qulay, shunda client ilovasi resurslari bilan to'qnashmaydi".

Buning uchun `apple_resource_bundle` ishlatamiz. Bizning `prayers.json` — Core kutubxonasiga tegishli, shuning uchun uni `CoreResources.bundle` ga joylaymiz:

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

swift_library(
    name = "CoreLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "Core",
    data = [":CoreResources"],      # namespaced bundle'ni data orqali ulaymiz
    visibility = ["//visibility:public"],
)

# Namespaced resource bundle — prayers.json shu yerda yashaydi
apple_resource_bundle(
    name = "CoreResources",
    bundle_id = "uz.azimjondev.vaqt.core.resources",
    resources = ["Resources/prayers.json"],
)
```

Endi Swift kodda resursni topish o'zgaradi. `prayers.json` `CoreResources.bundle` ichida, u esa Core framework ichida. Kutubxona kodidan framework bundle'iga yetib olish uchun `Bundle(for:)` ga "langar" (anchor) klass kerak bo'ladi — chunki `struct` da klass yo'q:

```swift
// Core/Sources/PrayerStore.swift (qism)
import Foundation

// Core framework ichidagi bundle'ni topish uchun "langar" klass.
final class CoreBundleToken {}

public struct PrayerStore: Sendable {
    public init() {}

    public func resourcesBundle() throws -> Bundle {
        // Core framework'ning o'z bundle'i (Bundle(for:) orqali)
        let coreBundle = Bundle(for: CoreBundleToken.self)
        // CoreResources.bundle avval framework ichida, bo'lmasa app root'ida qidiramiz
        for base in [coreBundle, Bundle.main] {
            if let url = base.url(forResource: "CoreResources", withExtension: "bundle"),
               let bundle = Bundle(url: url) {
                return bundle
            }
        }
        throw PrayerStoreError.bundleNotFound
    }

    public func loadToday() throws -> DaySchedule {
        let bundle = try resourcesBundle()
        guard let url = bundle.url(forResource: "prayers", withExtension: "json") else {
            throw PrayerStoreError.resourceNotFound("prayers.json")
        }
        let data = try Data(contentsOf: url)
        return try JSONDecoder().decode(DaySchedule.self, from: data)
    }
}
```

Build qilib `.ipa` ni ochganimizda — `CoreResources.bundle` aynan Core framework ICHIDA, app ildizida emas (bu tekshirilgan haqiqiy natija):

```text
Payload/Vaqt.app/Frameworks/Core.framework/CoreResources.bundle/prayers.json
```

> **🧠 Esda tuting:** Resurs **egasi qayerda** bo'lsa — bundle o'sha yerda. `CoreResources` ni `CoreLib`ning `data` siga uladik; `CoreLib` esa `Core` framework'ga kiradi. Shuning uchun `CoreResources.bundle` framework ichida joylashdi. Agar xuddi shu bundle'ni to'g'ridan-to'g'ri app kutubxonasiga ulasangiz, u app ildizida bo'lardi. Bu — "resurs qayerga tushadi" diagrammasining asosiy g'oyasi:

![Resurs e'lon qilish usuli (chapda) va uning bundle'dagi joyi (o'ngda): data → app ildizi; apple_resource_bundle → namespaced .bundle; apple_resource_group structured_resources → katalog saqlanadi; top-level atributlar → Assets.car/Info.plist/.lproj](/lessons/images/bazel/bazel_08_resource_routing.svg)

rules_apple'da resurs bundle yaratishning uch yo'li bor (`resources.md`):

| Rule                    | Nima qiladi                                                                         | Qachon ishlatiladi                                 |
| ----------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------- |
| `apple_resource_bundle` | Bir nechta fayldan `.bundle` quradi                                                 | Umumiy framework resurslari (bizning holat)        |
| `apple_bundle_import`   | Allaqachon tayyor `.bundle` papkasini import qiladi                                 | Checked-in tayyor bundle                           |
| `apple_resource_group`  | Resurslarni guruhlaydi, `structured_resources` bilan katalog strukturasini saqlaydi | Subkatalog muhim bo'lsa (masalan `audio/azon.mp3`) |

`apple_resource_group` ning asosiy farqi — `resources` va `structured_resources` atributlari. `resources` fayllarni ildizga **tekis** yoyadi (subkatalog yo'qoladi), `structured_resources` esa katalog strukturasini **saqlaydi**. rules_apple hujjatidagi misol:

```starlark
apple_resource_group(
    name = "MyResources",
    resources = ["MyText.txt"],                    # → App.app/MyText.txt
    structured_resources = ["Subdirectory/x.plist"], # → App.app/Subdirectory/x.plist
)
```

> **💡 Pro tip:** JSON, `.strings`, `.png` kabi "qayta ishlanadigan" fayllar bundling vaqtida optimallashtiriladi (masalan `.png` siqiladi, `.strings` binary plist'ga aylanadi). `structured_resources` esa **hech qanday qayta ishlanmaydi** — fayl qanday bo'lsa, shundayligicha ko'chiriladi. Agar fayl aynan o'zgarmasligi kerak bo'lsa (masalan oldindan hisoblangan ma'lumot), `structured_resources` ishlating.

---

## 5. Framework: kodni app va extension orasida bo'lishish {#framework}

Bizning `Core` modulida `PrayerTime`, `DaySchedule`, `PrayerStore` va `localizedTitle` bor. Buni **ham** ilova, **ham** widget ishlatadi. Agar Core'ni oddiy `swift_library` sifatida qoldirsak, uning kodi ikki marta — bir marta app binary'ga, bir marta widget binary'ga — statik ravishda link qilinadi. Natijada bir xil kod ikki nusxada, bundle kattalashadi va Core resurslari (prayers.json) ham ikki joyda bo'ladi.

Yechim — `Core`ni **dinamik framework** qilish. Shunda `Core.framework` app ichidagi `Frameworks/` papkasiga bir marta joylashadi, app ham, widget ham uni `@rpath` orqali bog'laydi:

```starlark
# Core/BUILD.bazel (framework qismi)
load("@rules_apple//apple:ios.bzl", "ios_framework")

ios_framework(
    name = "Core",
    bundle_id = "uz.azimjondev.vaqt.core",
    extension_safe = True,            # faqat extension-xavfsiz API'lar
    families = ["iphone"],
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    deps = [":CoreLib"],
    visibility = ["//visibility:public"],
)
```

`ios_framework` ning muhim atributi — **`extension_safe = True`**. U framework'ni `-application-extension` flagı bilan kompilyatsiya qiladi, ya'ni faqat extension'larda ruxsat etilgan API'lardan foydalanishni majburlaydi. Agar framework'ni extension ishlatsa (bizda shunday — widget), bu bayroq shart: aks holda widget ichida `UIApplication.shared` kabi extension'da taqiqlangan API ishlatib qo'ysangiz, kompilyator ogohlantiradi.

App va widget framework'ni `frameworks` atributi orqali oladi (`deps` emas — bu muhim farq):

```starlark
# App/BUILD.bazel
ios_application(
    name = "Vaqt",
    frameworks = ["//Core"],      # Core.framework → Vaqt.app/Frameworks/
    # ...
    deps = [":AppLib"],
)
```

```starlark
# Widget/BUILD.bazel
ios_extension(
    name = "VaqtWidget",
    frameworks = ["//Core"],      # xuddi shu framework'ni ulaydi
    # ...
    deps = [":WidgetLib"],
)
```

Build'dan keyin `otool -L` bilan tekshirsak, widget binary'si aynan `@rpath/Core.framework/Core` ga bog'langanini ko'ramiz — ya'ni u app bilan bir xil framework nusxasini ishlatadi (tekshirilgan haqiqiy natija):

```bash
otool -L Payload/Vaqt.app/PlugIns/VaqtWidget.appex/VaqtWidget | grep Core
```

```text
@rpath/Core.framework/Core (compatibility version 0.0.0, current version 0.0.0)
```

> **🧠 Esda tuting:** `deps` — kodni **bu target'ning binary'siga** link qiladi (statik). `frameworks` — alohida dinamik framework bundle'ini **ichiga joylashtiradi** va unga bog'laydi. Umumiy kodni app + extension orasida bo'lishmoqchi bo'lsangiz — `ios_framework` + `frameworks`. Aks holda kod har bir binary'ga nusxalanadi.

> **💡 Pro tip:** Hamma umumiy kodni ham dinamik framework qilish shart emas. Dinamik framework ishga tushganda yuklanadi (startup vaqtiga ta'sir qiladi). Agar kod faqat bitta bundle'da ishlatilsa, oddiy `swift_library` (statik) tezroq. Dinamik framework'ni **bir nechta bundle bo'lishganda** (app + extension, yoki app + app clip) ishlating. Statik framework kerak bo'lsa — `ios_static_framework` bor.

---

## 6. Widget extension: WidgetKit va `ios_extension` {#widget}

App extension — bu ilova ichida yashaydigan alohida bajariladigan modul: widget, Share extension, Notification Service, Shortcuts... Ularning hammasi `.appex` bundle sifatida app ichidagi `PlugIns/` papkasiga joylashadi. Biz keyingi namoz vaqtini ko'rsatadigan **WidgetKit** widget quramiz.

WidgetKit widget'ining ikki o'ziga xosligi bor:

1. U `@main` orqali ishga tushadi (an'anaviy `main` kirish nuqtasi) — ko'pchilik boshqa extension'lar esa "plug-in" modelida ishlaydi, kirish nuqtasini system framework beradi.
2. Uning `Info.plist` ida `NSExtensionPointIdentifier = com.apple.widgetkit-extension` bo'lishi shart.

Avval widget kodini yozamiz. U `Core` dagi `PrayerStore` ni ishlatadi — framework widget ichida ham borligi uchun bir xil store ishlaydi:

```swift
// Widget/Sources/VaqtWidget.swift
import WidgetKit
import SwiftUI
import Core

struct PrayerEntry: TimelineEntry {
    let date: Date
    let prayerName: String
    let prayerTime: String
}

struct Provider: TimelineProvider {
    private func current() -> PrayerEntry {
        guard let schedule = try? PrayerStore().loadToday() else {
            return PrayerEntry(date: Date(), prayerName: "—", prayerTime: "--:--")
        }
        let next = PrayerStore().nextPrayer(in: schedule, nowMinutes: 13 * 60)
        return PrayerEntry(date: Date(), prayerName: next.name, prayerTime: next.time)
    }

    func placeholder(in context: Context) -> PrayerEntry {
        PrayerEntry(date: Date(), prayerName: "Peshin", prayerTime: "12:28")
    }
    func getSnapshot(in context: Context, completion: @escaping (PrayerEntry) -> Void) {
        completion(current())
    }
    func getTimeline(in context: Context, completion: @escaping (Timeline<PrayerEntry>) -> Void) {
        completion(Timeline(entries: [current()], policy: .atEnd))
    }
}

struct VaqtWidgetView: View {
    let entry: PrayerEntry
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(localizedTitle("next_prayer", lang: "uz"))
                .font(.caption2).foregroundStyle(.secondary)
            Text(entry.prayerName).font(.headline)
            Text(entry.prayerTime).font(.title2.monospacedDigit().bold())
                .foregroundStyle(Color("Accent"))
        }
        .containerBackground(.fill.tertiary, for: .widget)
    }
}

@main
struct VaqtWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "VaqtWidget", provider: Provider()) { entry in
            VaqtWidgetView(entry: entry)
        }
        .configurationDisplayName("Namoz vaqti")
        .description("Keyingi namoz vaqtini ko'rsatadi")
        .supportedFamilies([.systemSmall])
    }
}
```

Widget'ning `Info.plist` i — bu yerda `NSExtensionPointIdentifier` kalit WidgetKit'ni bildiradi:

```text
# Widget/Info.plist (asosiy qism)
<key>NSExtension</key>
<dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.widgetkit-extension</string>
</dict>
```

Endi `ios_extension` bilan widget'ni quramiz va uni app ichiga joylaymiz:

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

swift_library(
    name = "WidgetLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "VaqtWidgetExt",
    data = glob(["Resources/Assets.xcassets/**"]),   # widget ham "Accent" rangni ishlatadi
    deps = ["//Core:CoreLib"],
)

ios_extension(
    name = "VaqtWidget",
    bundle_id = "uz.azimjondev.vaqt.widget",
    families = ["iphone"],
    frameworks = ["//Core"],
    entitlements = "Widget.entitlements",
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    version = "//:AppVersion",
    visibility = ["//App:__pkg__"],   # ↙ buni qo'shmaguncha xato oldik (pastda)
    deps = [":WidgetLib"],
)
```

```starlark
# App/BUILD.bazel
ios_application(
    name = "Vaqt",
    extensions = ["//Widget:VaqtWidget"],   # widget'ni app ichiga joylaymiz
    # ...
)
```

### Haqiqiy xato: visibility

Birinchi urinishda `Widget/BUILD.bazel` da `visibility` ni yozmagan edim. `bazel build //App:Vaqt` darhol to'xtadi:

```text
ERROR: .../App/BUILD.bazel:16:16: in ios_application rule //App:Vaqt: Visibility error:
target '//Widget:VaqtWidget' is not visible from target '//App:Vaqt'
```

Sabab — [3-darsdan](/ios-lessons/bazel-03-tushunchalar) bilamiz: target'lar standart holatda **private** (faqat o'z paketiga ko'rinadi). `App` paketi `Widget` paketidagi target'ni ko'ra olmaydi. Yechim — widget'ga aniq `visibility` berish. Men `["//App:__pkg__"]` ni tanladim (faqat App paketiga ko'rinadi) — chunki widget faqat shu app'ga tegishli, uni butun repo'ga ochish shart emas. Bu — visibility'ni arxitektura chegarasi sifatida ishlatish.

Tuzatgandan so'ng build o'tdi va `.app` ichida `PlugIns/VaqtWidget.appex` paydo bo'ldi (tekshirilgan):

```text
Payload/Vaqt.app/PlugIns/VaqtWidget.appex/VaqtWidget        ← widget binary
Payload/Vaqt.app/PlugIns/VaqtWidget.appex/Assets.car        ← widget o'z asset catalog'i
Payload/Vaqt.app/PlugIns/VaqtWidget.appex/Info.plist        ← NSExtensionPointIdentifier
Payload/Vaqt.app/PlugIns/VaqtWidget.appex/_CodeSignature/   ← alohida imzolangan
```

> **🧠 Esda tuting:** WidgetKit widget `@main` ishlatgani uchun `NSExtensionPrincipalClass` kerak emas. Lekin plug-in asosidagi extension'larda (Share, Notification Service) `Info.plist` da `NSExtensionPrincipalClass = $(PRODUCT_MODULE_NAME).MyClass` yoziladi. Diqqat: rules_apple **`PRODUCT_MODULE_NAME` ni almashtirmaydi** (`common_info.md` buni aniq aytadi), chunki Bazel'da har bir target alohida modul. Shuning uchun plug-in extension'da modul nomini **qo'lda** yozishingiz kerak, masalan `VaqtWidgetExt.MyClass`.

> **⚠️ Diqqat:** `ios_extension` da `extensionkit_extension` degan atribut bor (default `False`). U ExtensionKit (iOS 16+ yangi model) extension'lari uchun. WidgetKit widget'lari **ExtensionKit emas** — ular an'anaviy `NSExtension` plug-in modelida, `@main` kirish nuqtasi bilan ishlaydi. Shuning uchun biz `extensionkit_extension` ni o'zgartirmaymiz (`False` qoldiramiz). Buni noto'g'ri `True` qilsangiz, widget ishga tushmaydi.

Build grafigi endi quyidagicha ko'rinadi — `bazel query 'rdeps(//..., //Core:CoreLib)'` natijasi Core'ga bog'liq hamma target'ni ko'rsatadi:

```text
//App:AppLib       //App:Vaqt
//Core:CoreLib     //Core:Core
//Widget:WidgetLib //Widget:VaqtWidget
```

Ya'ni `Core` — grafning markazi: uni app ham, widget ham ishlatadi. Keyingi bo'limda bu umumiylik entitlements orqali ma'lumot almashishga qanday yordam berishini ko'ramiz.

---

## 7. Entitlements va App Groups {#entitlements}

Entitlements — bu ilovaga maxsus imkoniyatlar beradigan ruxsatlar ro'yxati: App Groups, Push Notifications, Keychain Sharing, iCloud va h.k. Ular `.entitlements` (plist) faylida yoziladi va bundle rule'ning `entitlements` atributi orqali ulanadi.

Bizning widget app bilan ma'lumot almashishi kerak (masalan, foydalanuvchi tanlagan shahar). Buning standart yo'li — **App Group**: app ham, widget ham bir xil guruhga a'zo bo'ladi va umumiy `UserDefaults(suiteName:)` yoki umumiy fayl konteyneridan foydalanadi. Buning uchun ikkala bundle ham bir xil App Group entitlement'iga ega bo'lishi shart:

```text
# App/App.entitlements
<key>com.apple.security.application-groups</key>
<array>
    <string>group.uz.azimjondev.vaqt</string>
</array>
```

```text
# Widget/Widget.entitlements — AYNAN bir xil guruh
<key>com.apple.security.application-groups</key>
<array>
    <string>group.uz.azimjondev.vaqt</string>
</array>
```

Ularni app va widget rule'lariga ulaymiz:

```starlark
ios_application(
    name = "Vaqt",
    entitlements = "App.entitlements",
    # ...
)

ios_extension(
    name = "VaqtWidget",
    entitlements = "Widget.entitlements",
    # ...
)
```

Swift tomonda umumiy ma'lumot shunday yoziladi/o'qiladi (ikkala bundle'da bir xil suite nomi):

```swift
let shared = UserDefaults(suiteName: "group.uz.azimjondev.vaqt")
shared?.set("Toshkent", forKey: "selectedCity")   // app yozadi
let city = shared?.string(forKey: "selectedCity")  // widget o'qiydi
```

### Entitlement qayerga yoziladi: simulator va qurilma farqi

Bu yerda muhim va chalg'ituvchi nuqta bor. Build qilib app binary'ning imzosini `codesign` bilan ko'rsak, App Group entitlement'ini **ko'rmaymiz** — faqat `get-task-allow` chiqadi:

```bash
codesign -dv --entitlements - --xml Payload/Vaqt.app
```

```text
CodeDirectory v=20400 size=651 flags=0x2(adhoc) ...
Signature=adhoc
TeamIdentifier=not set
<plist><dict><key>com.apple.security.get-task-allow</key><true/></dict></plist>
```

App Group qayerga ketdi? `common_info.md` javob beradi: **simulator build'ida entitlement'lar Mach-O segmentiga qo'shiladi**, imzo blokiga emas. Buni `otool` bilan tekshiramiz — mana haqiqiy natija (`__TEXT,__entitlements` segmenti dekodlangan):

```bash
otool -s __TEXT __entitlements Payload/Vaqt.app/Vaqt
```

```text
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>com.apple.security.application-groups</key>
    <array>
        <string>group.uz.azimjondev.vaqt</string>
    </array>
    <key>get-task-allow</key>
    <true/>
</dict>
</plist>
```

Xuddi shu entitlement widget binary'sida ham bor — ikkalasi bir App Group'da. Demak rules_apple App Group'ni to'g'ri qo'ydi, shunchaki simulator uchun uni imzo blokiga emas, Mach-O segmentiga yozdi.

> **🧠 Esda tuting:** Simulator bundle'ida entitlement'ni tekshirish uchun `codesign -d --entitlements` emas, `otool -s __TEXT __entitlements` ishlating — birinchisi (adhoc) imzo blokini ko'rsatadi, u yerda faqat `get-task-allow` bor; haqiqiy entitlement'lar Mach-O segmentida.

> **⚠️ Diqqat:** App Group'ni simulatorda **o'qish** ishlaydi, lekin ikki process orasida (app ↔ widget) **yozib bo'lishish** ba'zan faqat bundle imzolangan bo'lsa ishlaydi. `common_info.md` aytadi: "Shared App Groups kabi ba'zi entitlement'lar uchun simulator bundle imzolangan bo'lishini talab qiladi". rules_apple default holatda simulator main bundle'ini adhoc imzolaydi, shuning uchun bu odatda ishlaydi — lekin App Group orqali ishonchli cross-process yozishni biz **jonli qurilmada** tekshirishni tavsiya qilamiz (bizda Apple hisobi/qurilma yo'q, shuning uchun bu qismni hujjat asosida aytyapmiz).

> **💡 Pro tip:** Bizning darsdagi widget App Group'ga **tayanmaydi** — u `Core.framework` ichidagi `prayers.json` ni o'qiydi. Bu ataylab: bundle'dagi resurs deterministik va simulatorda ishonchli ishlaydi, App Group esa yoziladigan umumiy holat uchun. Agar widget faqat o'qiydigan ma'lumotga muhtoj bo'lsa — umumiy framework resursidan foydalaning, App Group'ga hojat yo'q.

---

## 8. Versiyalash va build konfiguratsiyalari {#versiya}

Har bir App Store ilovasida ikki versiya raqami bor: `CFBundleShortVersionString` (foydalanuvchi ko'radigan "1.0") va `CFBundleVersion` (build raqami "1.0.3"). Bazel'da bularni `apple_bundle_version` target orqali boshqaramiz — Info.plist'da qo'lda yozish shart emas:

```starlark
# BUILD.bazel (workspace root)
load("@rules_apple//apple:versioning.bzl", "apple_bundle_version")

apple_bundle_version(
    name = "AppVersion",
    build_version = "1.0.3",          # → CFBundleVersion
    short_version_string = "1.0",     # → CFBundleShortVersionString
    visibility = ["//visibility:public"],
)
```

Uni app ham, widget ham `version` atributi orqali oladi:

```starlark
ios_application(name = "Vaqt",    version = "//:AppVersion", ...)
ios_extension(  name = "VaqtWidget", version = "//:AppVersion", ...)
```

Build'dan keyin ikkala bundle'da ham versiya bir xil bo'lishini tekshirdik (haqiqiy natija):

```text
Vaqt.app/Info.plist          → CFBundleShortVersionString=1.0, CFBundleVersion=1.0.3
PlugIns/VaqtWidget.appex     → CFBundleVersion=1.0.3
```

> **🧠 Esda tuting:** Extension versiyasi app versiyasi bilan **mos kelishi** kerak — aks holda App Store yuklashni rad etadi. Shuning uchun bitta `apple_bundle_version` target'ni ikkala bundle ham ishlatgani qulay: versiyani bir joyda o'zgartirasiz, hamma bundle yangilanadi.

### CI uchun: `--embed_label` bilan versiya

Haqiqiy loyihalarda build raqamini CI beradi (masalan git tag yoki pipeline raqami). `apple_bundle_version` buni `build_label_pattern` + `--embed_label` orqali qo'llab-quvvatlaydi (hujjatdagi misol):

```starlark
apple_bundle_version(
    name = "build_label_version",
    build_label_pattern = "Vaqt_{version}_build_{build}",
    build_version = "{version}.{build}",
    capture_groups = {
        "version": "\\d+\\.\\d+",
        "build": "\\d+",
    },
    short_version_string = "{version}",
    fallback_build_label = "Vaqt_99.99_build_99",   # dasturchi build'i uchun
)
```

```bash
# CI shunday chaqiradi:
bazel build //App:Vaqt --embed_label=Vaqt_1.2_build_345
# → CFBundleVersion = 1.2.345, CFBundleShortVersionString = 1.2
# --embed_label'siz (dasturchi) → fallback: 99.99.99
```

### `select()` bilan muhitga qarab sozlash

Haqiqiy ilovada "dev" va "prod" muhitlari bo'ladi: har xil `bundle_id`, API manzili yoki ilova nomi. [4-darsdan](/ios-lessons/bazel-04-starlark) bilgan `select()` + `config_setting` bu yerda ishga tushadi. Masalan, debug va release build'da ilova nomini farqlash — har xil Info.plist fragmentlari bilan:

```starlark
# App/BUILD.bazel
config_setting(
    name = "release_build",
    values = {"compilation_mode": "opt"},
)

ios_application(
    name = "Vaqt",
    # Info.plist'lar birlashtiriladi; select() muhitga qarab fragment tanlaydi
    infoplists = ["Info.plist"] + select({
        ":release_build": ["Info.release.plist"],   # CFBundleDisplayName = "Vaqt"
        "//conditions:default": ["Info.debug.plist"], # CFBundleDisplayName = "Vaqt DEV"
    }),
    # ...
)
```

rules_apple bir nechta Info.plist faylni **birlashtiradi** (`common_info.md`): agar ikki faylda bir xil kalit bo'lsa, qiymatlari bir xil bo'lishi kerak; aks holda build xato beradi. Shuning uchun `Info.release.plist` ichida faqat farq qiladigan kalitlarni (masalan `CFBundleDisplayName`) qo'ying.

Xuddi shunday, kodga muhit qiymatlarini `defines` orqali `select()` bilan uzatish mumkin:

```starlark
swift_library(
    name = "AppLib",
    defines = select({
        ":release_build": ["API_BASE_URL=\\\"https://api.vaqt.uz\\\""],
        "//conditions:default": ["API_BASE_URL=\\\"https://dev.vaqt.uz\\\""],
    }),
    # ...
)
```

> **🧠 Esda tuting:** Info.plist'da Xcode uslubidagi o'zgaruvchilar ishlaydi: `$(PRODUCT_NAME)`, `$(PRODUCT_BUNDLE_IDENTIFIER)`, `$(EXECUTABLE_NAME)`. Biz app Info.plist'ida `$(PRODUCT_NAME)` yozdik — build'da u `Vaqt` ga aylandi (tekshirilgan). Lekin `$(PRODUCT_MODULE_NAME)` **qo'llab-quvvatlanmaydi** — uni qo'lda yozing.

> **⚠️ Diqqat:** `select()` konfiguratsiya action key'ning bir qismi. Ya'ni `-c opt` va `-c dbg` build'lari har xil cache yozuvlariga ega — bu to'g'ri va kerakli xatti-harakat, lekin cache hajmini oshiradi. Buni 10-darsda (cache) batafsil ko'ramiz.

---

## 9. Code signing: simulator va qurilma {#signing}

Code signing — bu bundle'ni kriptografik imzolash: iOS faqat ishonchli imzoga ega ilovalarni ishga tushiradi. Bazel'da signing Xcode'ning "Automatically manage signing" dan boshqacha ishlaydi — hamma narsa aniq, deklarativ.

### Simulator (biz tekshirgan yo'l)

Simulator entitlements'ni to'liq tekshirmaydi, shuning uchun:

- `provisioning_profile` **shart emas** — Apple hisobi ham, sertifikat ham kerak emas.
- rules_apple bundle'ni **adhoc** imzolaydi (`Signature=adhoc`, `TeamIdentifier=not set`).
- `bazel run //App:Vaqt` ilovani darhol simulatorga o'rnatib ishga tushiradi.

Bizning butun darsdagi build va run aynan shu yo'ldan ketdi — hech qanday hisob talab qilinmadi. Bu — o'rganish va CI testlari uchun ideal.

### Qurilma (hujjat asosida — bizda qurilma/hisob yo'q)

Jonli qurilmaga chiqarish uchun signing majburiy. rules_apple'da (claim: rules_xcodeproj FAQ + rules-apple.md):

- Top-level target'lar `provisioning_profile` atributini oladi, odatda `local_provisioning_profile` orqali to'ldiriladi:

```starlark
load("@rules_apple//apple:apple.bzl", "local_provisioning_profile")

local_provisioning_profile(
    name = "vaqt_profile",
    profile_name = "Vaqt Development",
    team_id = "ABC123DEF4",       # bir nechta hisob orasida ajratish uchun
)

ios_application(
    name = "Vaqt",
    provisioning_profile = ":vaqt_profile",
    # ...
)
```

- `local_provisioning_profile` profilni mashinangizdagi `~/Library/MobileDevice/Provisioning Profiles` dan topadi (bir nechta mos profil bo'lsa — eng yangisini tanlaydi). Profil o'rnatilmagan bo'lsa, Xcode "couldn't find any provisioning profiles matching 'TEAM_ID/PROFILE_NAME'" deydi.
- Sertifikat (signing identity) `--@rules_apple//apple/build_settings:signing_certificate_name` bilan tanlanadi (`common_info.md`):

```text
# .bazelrc
build --@rules_apple//apple/build_settings:signing_certificate_name="Apple Development: Ism Familiya"
```

- rules_xcodeproj bilan Xcode-managed profilni ishlatish uchun uni `xcode_provisioning_profile(managed_by_xcode = True, ...)` ichiga o'rab, `team_id` ni berish kerak. Buni [9-darsda](/ios-lessons/bazel-09-xcode) ko'ramiz.

> **⚠️ Diqqat:** Yuqoridagi qurilma signing qismini biz **shu mashinada tekshira olmadik** — buning uchun Apple Developer hisobi va jonli qurilma kerak. Kod va flaglar rasmiy hujjatlardan olingan. Simulator yo'li esa to'liq tekshirilgan.

### Signing diagrammasi

![Code signing ikki yo'li: chapda simulator (tekshirilgan) — provisioning profile shart emas, adhoc imzo, entitlement Mach-O segmentida; o'ngda qurilma (hujjat asosida) — provisioning profile majburiy, signing identity, entitlement imzo blokida, CI'da profil+keychain oldindan](/lessons/images/bazel/bazel_08_signing_flow.svg)

### CI va remote cache: signing'ni lokal qoldirish

Jamoada remote cache ishlatilsa (10-11 darslar), signing va bundling action'larini **remote'da bajarmaslik** tavsiya etiladi — chunki katta bundle va bog'langan binary'larni yuklab-tushirish lokalda bajarishdan sekinroq. rules_apple buning uchun tayyor `--modify_execution_info` qatorini beradi (`common_info.md`):

```text
# .bazelrc (remote cache ishlatganda)
common --modify_execution_info=^(BundleApp|BundleTreeApp|DsymDwarf|DsymLipo|GenerateAppleSymbolsFile|ObjcBinarySymbolStrip|CppArchive|CppLink|ObjcLink|ProcessAndSign|SignBinary|SwiftArchive|SwiftStdlibCopy)$=+no-remote,^(BundleResources|ImportedDynamicFrameworkProcessor)$=+no-remote-exec
```

Sabab: `ProcessAndSign` va `SignBinary` action'lari RBE (remote execution) uchun odatda sozlanmagan; `BundleApp` esa katta bundle yaratadi — uni yuklash samarasiz.

---

## 10. To'liq anatomiya va o'lchovlar {#anatomiya}

Endi hammasini birlashtiramiz. `bazel build //App:Vaqt` bitta buyruq bilan 398 ta action bajarib, quyidagi to'liq `.app` ni yasadi (bu — `unzip -l Vaqt.ipa` ning haqiqiy natijasi):

```text
Payload/Vaqt.app/
├── Vaqt                                          # app binary (arm64)
├── Info.plist                                    # CFBundleName=Vaqt, version 1.0.3
├── Assets.car                                     # .xcassets → actool
├── Base.lproj/Localizable.strings                 # 3 til + Base
├── en.lproj/Localizable.strings
├── ru.lproj/Localizable.strings
├── uz.lproj/Localizable.strings
├── _CodeSignature/CodeResources                   # adhoc (simulator)
├── Frameworks/
│   └── Core.framework/
│       ├── Core                                   # umumiy dinamik framework
│       ├── CoreResources.bundle/prayers.json      # namespaced resurs
│       ├── Info.plist
│       └── _CodeSignature/CodeResources
└── PlugIns/
    └── VaqtWidget.appex/
        ├── VaqtWidget                             # widget binary
        ├── Assets.car
        ├── Info.plist                             # NSExtensionPointIdentifier
        └── _CodeSignature/CodeResources
```

![Vaqt.app ichidagi haqiqiy tuzilma va har bir qism qaysi BUILD rule'dan kelgani: binary, plist, resurs, struktura va imzo qismlariga ajratilgan](/lessons/images/bazel/bazel_08_app_bundle_anatomy.svg)

Diagramma har bir qismni uni yaratgan rule bilan bog'laydi. Xcode'da bu bog'lanishlar `project.pbxproj` ichida yashiringan va qo'lda sozlangan bo'lardi; Bazel'da esa har biri BUILD faylida bitta satr.

### O'lchovlar (Apple M4 Pro, 12 core, Xcode 27.1, rules_apple 5.3.0)

Build tezligini o'lchadik. Eng ishonchli ko'rsatkich — **qayta kompilyatsiya qilinadigan modullar soni** (jarayon va worker soni), chunki wall-clock vaqti shu mashinada boshqa parallel build'lar bilan bo'lishilgani uchun tebranadi:

| O'lchov                                             | Jarayonlar | SwiftCompile (worker)   | Wall-clock\* |
| --------------------------------------------------- | ---------- | ----------------------- | ------------ |
| To'liq build (toza analiz, disk cache qisman issiq) | 398 action | 3                       | ~8 s         |
| No-op rebuild (hech narsa o'zgarmagan)              | 0 action   | 0                       | 0.2–0.4 s    |
| Inkremental: App leaf (`ContentView.swift`)         | 10 process | **1** (AppLib)          | ~2–4 s       |
| Inkremental: Core framework interfeysi              | 17 process | **3** (Core+App+Widget) | ~2–11 s      |

\*Wall-clock vaqti parallel agent'lar yukiga qarab tebranadi; barqaror signal — worker/action soni.

Eng muhim xulosa bu yerda: **leaf o'zgarishi faqat 1 ta Swift modulni** (AppLib) qayta kompilyatsiya qiladi; **Core framework interfeysi o'zgarsa esa 3 ta modul** (Core + AppLib + VaqtWidget) qayta kompilyatsiya qilinadi — chunki ikkalasi ham Core'ga bog'langan. Bu — dependency graph'ning kuchi: Bazel aynan nima o'zgarganini va unga nima bog'liqligini biladi, faqat o'shalarni qayta quradi. No-op rebuild esa 0.2 soniyada tugaydi, chunki Bazel hech narsa o'zgarmaganini biladi.

> **📊 Real raqamlar:** Core framework interfeysini o'zgartirish `bazel query 'rdeps(//..., //Core:CoreLib)'` ko'rsatgan 6 ta target'ga ta'sir qiladi: `//Core:CoreLib`, `//Core:Core`, `//App:AppLib`, `//App:Vaqt`, `//Widget:WidgetLib`, `//Widget:VaqtWidget`. Lekin interfeysi o'zgarmagan (masalan faqat implementatsiya ichidagi) o'zgarish downstream modullarga **action cache hit** beradi — ular qayta kompilyatsiya qilinmaydi. Buni 10-darsda (action key) chuqurroq ko'ramiz.

> **🚀 Motivatsiya:** Siz endi haqiqiy iOS ilova anatomiyasini to'liq egallab oldingiz: app + framework + widget extension, 3 tilli lokalizatsiya, asset catalog, namespaced resurslar, App Group entitlements, versiyalash va signing — hammasi bitta `bazel build` bilan, deklarativ, qayta-ishlab chiqariladigan (reproducible) tarzda. Bu — yuzlab modulli loyihalarda ham xuddi shunday ishlaydigan poydevor.

---

## Amaliy mashqlar {#mashqlar}

Quyidagi mashqlarni o'z `VaqtApp` nusxangizda bajaring. Har birida `.ipa` ni `unzip -l` bilan ochib, natijani tekshiring.

1. **Yangi resurs qo'shing.** Core framework'ga ikkinchi JSON fayl (`cities.json`) qo'shing: `Core/Resources/cities.json` yarating, uni `apple_resource_bundle` ning `resources` ro'yxatiga qo'shing. Build qiling va `CoreResources.bundle` ichida ikkala JSON ham borligini tasdiqlang. `PrayerStore` ga `loadCities()` metodini yozib, uni o'qing.

2. **To'rtinchi til.** `tr.lproj/Localizable.strings` (turkcha) qo'shing va `ContentView` ga `localizedTitle("next_prayer", lang: "tr")` qatorini qo'shing. Build qilib, `.app` ichida `tr.lproj` paydo bo'lganini ko'ring. Keyin `bazel build //App:Vaqt --define "apple.locales_to_include=uz"` bilan quring va faqat `uz.lproj` + `Base.lproj` qolganini tasdiqlang.

3. **Versiyani o'zgartiring.** `apple_bundle_version` da `build_version` ni `1.0.4` ga o'zgartiring, build qiling va `PlistBuddy -c "Print :CFBundleVersion"` bilan app ham, widget ham `1.0.4` ekanini tekshiring. So'ng `build_label_pattern` variantiga o'tib, `--embed_label=Vaqt_2.0_build_50` bilan quring va `CFBundleVersion=2.0.50` chiqishini tasdiqlang.

4. **⭐ Framework'ni statik qiling.** `Core` ni `ios_framework` o'rniga `swift_library` (ya'ni faqat `CoreLib`) sifatida app va widget'ga to'g'ridan-to'g'ri `deps` orqali ulang (framework'ni olib tashlang). Build qilib, bundle'da nima o'zgarganini kuzating: `Frameworks/Core.framework` yo'qoladi, `CoreResources.bundle` endi qayerga tushadi? App va widget hajmi qanday o'zgardi (`ls -la`)? Nega?

5. **⭐ `select()` bilan dev build.** `config_setting` va `select()` ishlatib, `-c dbg` da `bundle_id` ga `.dev` qo'shiladigan va `CFBundleDisplayName` "Vaqt DEV" bo'ladigan build yarating (`Info.debug.plist` fragmenti bilan). `bazel build //App:Vaqt -c dbg` va `-c opt` build'larini solishtiring.

6. **⭐ Ikkinchi extension.** Share extension (`ios_extension`, plug-in modeli) qo'shing. Diqqat: u `@main` emas, `NSExtensionPrincipalClass` ishlatadi — va `PRODUCT_MODULE_NAME` almashtirilmagani uchun modul nomini (`ShareExt.ShareViewController` kabi) qo'lda yozishingiz kerak. `PlugIns/` da endi ikkita `.appex` borligini tasdiqlang.

---

## O'zingizni tekshiring {#checklist}

- [ ] `data` (kutubxona resursi, app ildiziga tushadi) va top-level atributlar (`app_icons`, `infoplists`, `strings`) farqini tushunaman.
- [ ] `apple_resource_bundle` bilan namespaced bundle yarata olaman va u resurs egasi joylashgan bundle (framework yoki app) ichiga tushishini bilaman.
- [ ] `.lproj` lokalizatsiya qo'shib, `locales_to_include` / `apple.trim_lproj_locales` bilan tillarni cheklay olaman; `Base.lproj` har doim qo'shilishini bilaman.
- [ ] `ios_framework` (+`extension_safe = True`) bilan umumiy kodni app va extension orasida bo'lisha olaman; `deps` va `frameworks` farqini bilaman.
- [ ] `ios_extension` bilan WidgetKit widget quraman, `NSExtensionPointIdentifier` ni to'g'ri yozaman va `.app/PlugIns/*.appex` ni tekshiraman.
- [ ] App Group entitlements'ni app va widget'ga ulab, simulatorda uning Mach-O `__entitlements` segmentida ekanini `otool` bilan tasdiqlay olaman.
- [ ] `apple_bundle_version` bilan versiyani (hard-coded yoki `--embed_label`) boshqara olaman va extension versiyasi app bilan mos kelishini bilaman.
- [ ] Simulator (adhoc, hisob kerak emas) va qurilma (`provisioning_profile` + signing identity) signing farqini tushunaman.

---

## Xulosa {#xulosa}

- **Resurs qayerga tushishi uni qanday e'lon qilishingizga bog'liq**: `data` → app ildizi (`Bundle.main`); `apple_resource_bundle` → namespaced `.bundle` (umumiy framework uchun eng yaxshi); `apple_resource_group`'ning `structured_resources` → katalog saqlanadi; `app_icons`/`infoplists`/`strings` → OS resurslari sifatida alohida ishlanadi.
- **rules_swift `Bundle.module` ni yaratmaydi** — bu SwiftPM'ga xos. Bazel'da `Bundle.main` yoki `apple_resource_bundle` + `Bundle(for:)`/`Bundle(url:)` ishlating.
- **Lokalizatsiya** `.lproj` papkalari orqali; `Base.lproj` har doim qo'shiladi; `locales_to_include` bilan debug tezligi uchun tillarni cheklash mumkin.
- **`ios_framework` (+`extension_safe`)** umumiy kodni bir marta qadoqlaydi — app ham, widget ham `frameworks` orqali bir nusxani ishlatadi (`@rpath/Core.framework/Core`). `deps` statik link, `frameworks` dinamik embedding.
- **WidgetKit widget = `ios_extension`** `@main` + `NSExtensionPointIdentifier = com.apple.widgetkit-extension` bilan; `.app/PlugIns/*.appex` ga tushadi. `extensionkit_extension` ni WidgetKit uchun `False` qoldiring.
- **Entitlements** (App Groups) app va widget'da bir xil bo'lishi kerak; simulatorda ular imzo blokiga emas, Mach-O `__entitlements` segmentiga yoziladi (`otool` bilan tekshiriladi).
- **Versiya** `apple_bundle_version` orqali bir joyda; **signing** simulatorda adhoc (hisob kerak emas), qurilmada `provisioning_profile` + sertifikat.
- **Dependency graph tejaydi**: leaf o'zgarishi 1 modulni, framework interfeysi o'zgarishi 3 modulni qayta kompilyatsiya qildi; no-op rebuild 0.2 soniya. Bitta `bazel build` 398 action bilan to'liq ilovani (app + framework + widget + resurs + imzo) yasadi.
- **Keyingi dars:** [Bazel 9: Xcode bilan ishlash — rules_xcodeproj, indexing, debugging, SwiftUI Preview va VS Code](/ios-lessons/bazel-09-xcode)

---

## Manbalar {#manbalar}

1. [rules_apple — rules-ios.md (`ios_application`, `ios_extension`, `ios_framework`)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-ios.md)
2. [rules_apple — resources.md (resurslar falsafasi: `data` vs top-level, resource bundle'lar)](https://github.com/bazelbuild/rules_apple/blob/main/doc/resources.md)
3. [rules_apple — rules-resources.md (`apple_resource_bundle`, `apple_resource_group`, `apple_bundle_import`)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-resources.md)
4. [rules_apple — common_info.md (Info.plist merging va o'zgaruvchilar, lokalizatsiya flaglari, codesigning, `--modify_execution_info`)](https://github.com/bazelbuild/rules_apple/blob/main/doc/common_info.md)
5. [rules_apple — rules-general.md (`apple_bundle_version`)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-general.md)
6. [rules_apple — rules-apple.md (`local_provisioning_profile`, `provisioning_profile_repository`)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-apple.md)
7. [rules_xcodeproj — FAQ (code signing: `xcode_provisioning_profile`, `managed_by_xcode`)](https://github.com/MobileNativeFoundation/rules_xcodeproj/blob/main/docs/faq.md)
8. [rules_ios sunset — Discussion #952 (Square, 2025-11-07): rules_apple + rules_swift yetarli](https://github.com/bazel-ios/rules_ios/discussions/952)
9. [Tuist case study — Toss (~1000 modul, graph hisoblash narxi)](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/toss.md)
10. [Tuist case study — Monzo (~50 → ~200 modul)](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/monzo.md)
