Azimjon Abdurasulov

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

📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot

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?
  2. Uchlik: apple_support, rules_swift, rules_apple
  3. swift_library — Swift kodini modulga aylantirish
  4. ios_application — .app va .ipa yasash
  5. Info.plist: birlashtirish va o'zgaruvchilar
  6. "Kitoblar" ilovasini yig'amiz
  7. .ipa ichida nima bor?
  8. [Nega tags = ["manual"]? — birinchi haqiqiy xato](#manual)
  9. Simulyatorda ishga tushirish: bazel run
  10. Qaysi simulyator? device va version tanlash
  11. Testlar simulyatorda
  12. Debug, release va arxitektura
  13. Xcode va SDK qayerdan keladi
  14. Xcode loyihasi vs Bazel: fayllar
  15. O'lchovlar — haqiqiy raqamlar
  16. Amaliy mashqlar
  17. O'zingizni tekshiring
  18. Xulosa
  19. 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, workspace, target, label, build graph 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:

ModulVersiya (BCR, 2026-10)Nima qiladi
apple_support2.10.1Xcode 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_swift4.2.1Swift kodini kompilyatsiya qiladi (swift_library, swift_binary, swift_test). Doimiy ishlaydigan persistent worker bilan tezlashtiradi.
rules_apple5.3.0Apple 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:

# 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 modulga 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:

// 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)"
    }
}
// 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_libraryga aylantiramiz. Core/BUILD.bazel:

# 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_libraryning muhim atributlari (manba: rules_swift doc/rules.md, 4.2.1):

AtributVazifasi
nameTarget nomi (majburiy). Label //Core:Core bo'ladi.
srcsKompilyatsiya qilinadigan .swift fayllar. glob() bilan papkadagi hammasini olamiz.
module_nameSwift modulining nomi — bu import Core da yoziladigan nom. Berilmasa, Bazel label'dan avtomatik yasaydi (//Core:Core → Core), lekin aniq yozgan ma'qul.
depsBu kutubxona bog'liq bo'lgan boshqa target'lar. Coreda hozircha yo'q.
visibilityKim bu target'ga ulana oladi. //visibility:public — hamma. (Visibility haqida 3-darsda 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 ilovaga 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:

# 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 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:

AtributMajburiymiVazifasi
name✅ haTarget nomi.
families✅ haQaysi qurilma oilalari: ["iphone"], ["ipad"] yoki ["iphone", "ipad"]. Kamida bittasi kerak.
minimum_os_version✅ haEng 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'qInfo.plist fayllari ro'yxati. Berilmasa, rules_apple standart Info.plist ishlatadi.
deps⛔ yo'qIlovaga linklanadigan kutubxonalar.

Keyin tez-tez ishlatiladigan ixtiyoriylar:

AtributVazifasi
resourcesBundle'ga qo'shiladigan resurslar (JSON, rasm, .strings...).
app_iconsIlova ikonkalari. Fayllar bitta *.xcassets/*.appiconset papkada bo'lishi kerak. (Xcode 26+'da Icon Composer'ning *.icon bundle'i ham.)
launch_storyboardIshga tushish ekrani (.storyboard/.xib).
entitlementsQurilma build'lari uchun entitlement fayli (App Groups, Push...). Simulyator uchun ko'pincha kerak emas.
provisioning_profileQurilmaga chiqarish uchun provisioning profil. Simulyator uchun ixtiyoriy.
versionapple_bundle_version target'i (CFBundleVersion'ni boshqarish uchun).
frameworksIlova bilan birga joylanadigan ios_framework target'lari.
extensionsApp 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 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:

<?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.plistga 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'zgaruvchiQiymati
PRODUCT_BUNDLE_IDENTIFIERios_application'ning bundle_id atributi.
PRODUCT_NAME / TARGET_NAMEbundle_name (agar berilgan bo'lsa) yoki target nomi.
EXECUTABLE_NAMEexecutable_name → bundle_name → target nomi.
BUNDLE_NAMEPRODUCT_NAME + kengaytma (.app).
DEVELOPMENT_LANGUAGEHozircha 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 ichini ochganimizda o'z ko'zimiz bilan ko'ramiz.

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

Hamma qismlarni birlashtiramiz. Mana to'liq workspace tuzilishi:

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 tanish. Bu safar .bazelrc'ga iOS uchun bir nechta flag qo'shamiz:

# .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 batafsil tushuntiramiz.)

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

// 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):

// 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)
                }
            }
        }
    }
}
// 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:

bazel build //App:Kitoblar

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

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 raqamlar bilan ko'rsatamiz.

🧠 Esda tuting: Build chiqishidagi action nomlari — Compiling Swift module, Linking, SwiftStdlibCopy, Bundling — bu 1-bo'limdagi "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

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:

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

Natija:

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:

FaylNima 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/CodeResourcesCode 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:

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

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

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

Natija (qisqartirildi):

{
  "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 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:

bazel build //...

Natija — XATO:

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:

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:

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:

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):

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):

QiymatNima qiladi
install_and_run (standart)Eski nusxani to'xtatadi, o'rnatadi va ishga tushiradi (log'larga ulanadi).
install_without_runningO'rnatadi, lekin ishga tushirmaydi — terminal darhol bo'shaydi.
run_without_installingQayta o'rnatmay, mavjud o'rnatilgan ilovani ishga tushiradi.

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

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:

--@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

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:

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

Natija — XATO (1.1 soniyada):

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:

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 o'rganamiz — bu yerda faqat birinchi tanishuv).

Core/Tests/BookTests.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:

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:

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

Natija:

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:

//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 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:

FlagRejimQachon
-c fastbuildStandart. Minimal optimizatsiya, tez kompilyatsiya.Kundalik ish, CI testlari.
-c dbgDebug — to'liq debug simvollari (.dSYM).Debugger bilan ishlaganda.
-c optOptimizatsiya qilingan.Release, App Store, performance o'lchash.

Masalan, release build:

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):

MaqsadFlagNatija
Simulyator (Apple Silicon Mac)standart (flag kerak emas)ios_sim_arm64
Haqiqiy qurilma--ios_multi_cpus=arm64qurilma arm64
# 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:

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:
bazel clean --expunge

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

  1. "Xcode versiyasi topilmadi" xatosi. Agar --xcode_version=X bersangiz, lekin Bazel'ning keshlangan ro'yxatida u yo'q bo'lsa:
--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

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 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.

StsenariyVaqtNima bo'ldi
Sovuq build (clean --expunge + disk cache o'chirilgan)94.6 s350 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 s186 disk cache hit. Hech narsa qaytadan kompilyatsiya qilinmadi.
Incremental (bitta "barg" Swift faylni tahrirlash)4.9 sFaqat AppLib moduli qaytadan kompilyatsiya + bundle + imzo (2 ta process).
No-op (hech narsa o'zgarmadi)0.2 sServer grafni xotirada saqlaydi — hech narsa qilinmadi.
Release (-c opt)16.9 sopt 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 gaplashgan edik).

📊 Real raqamlar: Bitta barg faylni o'zgartirganda incremental build 4.9 s edi — bu AppLib modulining butuni 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 UIDeviceFamilyni 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 .ipasini 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 running nega "osilib" qolishini bilaman.
  • [ ] To'g'ri simulyator device + versionni .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.pbxprojidan 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


Manbalar {#manbalar}

  1. rules_apple — iOS rule'lari (rules-ios.md) — ios_application, ios_unit_test atributlari.
  2. rules_apple — Info.plist va simulyator (common_info.md) — Info.plist birlashtirish, o'zgaruvchilar, BAZEL_APPLE_RUN_MODE, Xcode qulflash.
  3. rules_apple — iOS ilova qurish bo'yicha rasmiy tutorial — minimal workspace, bazel run, --ios_multi_cpus.
  4. rules_swift — swift_library (rules.md) — swift_library atributlari.
  5. apple_support — README va xcode_config — Xcode toolchain, apple_support tartibi, Xcode versiya xatolari.
  6. Bazel Central Registry — modul versiyalari (rules_apple 5.3.0, rules_swift 4.2.1, apple_support 2.10.1).
  7. Bazel 9 e'loni (blog) — WORKSPACE olib tashlandi, objc_library @rules_cc'dan.
  8. Spotify — iOS build tizimini Bazel'ga o'tkazish — 2000+ BUILD fayl, p75 ~80 → ~20 daqiqa.
  9. InfoQ — Spotify Bazel iOS migratsiyasi — raqamlar tasdiqi.
  10. Allegro — iOS build'larini Bazel bilan tezlashtirish — 260 → 85 soniya, 25 muhandis.
  11. Google tadqiqoti — build vaqti va samaradorlik (DX) — tezroq build → o'lchanadigan samaradorlik.
  12. Telegram iOS — ochiq kodli Bazel iOS loyihasi — rules_apple/rules_swift bilan real ilova.
  13. Square — Apple Silicon'ga Bazel bilan tayyorlanish — simulyator vs qurilma arm64 muammosi.

Markdown manba

iOS Lessons