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
.ipaichini 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_applicationorqali qurib, simulyatordabazel runbilan ochib,.ipafaylining har bir qismini tushunadigan bo'lasiz.
apple_support,rules_swift,rules_appleuchligi nima qilishini va MODULE.bazel'da qanday ulanishini bilasiz.swift_libraryvaios_applicationrule'larining muhim atributlarini (va qaysilari majburiy ekanini) aytib bera olasiz.- Info.plist'ning birlashtirilishini va
$(PRODUCT_NAME)kabi o'zgaruvchilarni tushunasiz. bazel build,bazel runvabazel testni iOS uchun to'g'ri simulyator flaglari bilan ishlatasiz..ipaichiniunzip -lbilan ochib, har bir faylning vazifasini bilasiz.- Debug/release (
-c dbg/-c opt), arxitektura (--ios_multi_cpus) va Xcode qayerdan kelishini sozlay olasiz.
Mundarija
- Nega iOS uchun maxsus rule'lar kerak?
- Uchlik: apple_support, rules_swift, rules_apple
- swift_library — Swift kodini modulga aylantirish
- ios_application — .app va .ipa yasash
- Info.plist: birlashtirish va o'zgaruvchilar
- "Kitoblar" ilovasini yig'amiz
- .ipa ichida nima bor?
- [Nega
tags = ["manual"]? — birinchi haqiqiy xato](#manual) - Simulyatorda ishga tushirish: bazel run
- Qaysi simulyator? device va version tanlash
- Testlar simulyatorda
- Debug, release va arxitektura
- Xcode va SDK qayerdan keladi
- Xcode loyihasi vs Bazel: fayllar
- O'lchovlar — haqiqiy raqamlar
- Amaliy mashqlar
- O'zingizni tekshiring
- Xulosa
- 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 ichkiios_framework. Ya'ni bugun o'rganadiganios_applicationvaswift_library— bu real sanoat vositalari (manba: Telegram-iOS ochiqMODULE.bazelva.bazelrc).
Nega jamoalar umuman Xcode'dan Bazel'ga o'tadi? Spotify muhandislik blogida (2023) yozishicha, ularda 200+ iOS muhandisi bor va Xcode loyihasini qo'lda boshqarish imkonsiz bo'lib qolgan edi: generator **2000+ BUILD.bazel faylini avtomatik yaratgan, faqat 30–50 tasi qo'lda yozilgan. Natija: merge oldidan kutish vaqti (p75) ~80 daqiqadan ~20 daqiqaga tushgan ("factor of four"), eng og'ir konfiguratsiya (800+ test target, ~3M qator kod) 45+ daqiqadan 10 daqiqadan kamga (manba: engineering.atspotify.com, 2023-10). Kichikroq jamoada ham foyda bor: Allegro (2020) 25 kishilik jamoada clean build'ni 260 soniyadan 85 soniyaga** qisqartirgan (manba: blog.allegro.tech, 2020-12).
Bu raqamlar nega muhim? Google'ning nazorat ostida o'tkazgan tajribasiga ko'ra, tezroq build'lar kichik/o'rta o'zgarishlarda 11–14% tezroq ishlashga va haftasiga qo'shimcha bitta build'ga olib keladi — kichik ko'rinsa ham, statistik jihatdan ahamiyatli (manba: DX newsletter, getdx.com). Build tezligi — bu to'g'ridan-to'g'ri rivojlanish tezligi.
Keling, uchlikdan boshlaymiz.
2. Uchlik: apple_support, rules_swift, rules_apple {#uchlik}
Bitta iOS ilovasini qurish uchun bizga uchta tashqi Bazel moduli kerak. Ularning har biri aniq bir vazifani bajaradi:
| Modul | Versiya (BCR, 2026-10) | Nima qiladi |
|---|---|---|
apple_support | 2.10.1 | Xcode va platforma SDK'larini Bazel toolchain sifatida ro'yxatdan o'tkazadi. Ya'ni Bazel qaysi swiftc, clang, ld va SDK'ni ishlatishini shu modul aytib beradi. |
rules_swift | 4.2.1 | Swift kodini kompilyatsiya qiladi (swift_library, swift_binary, swift_test). Doimiy ishlaydigan persistent worker bilan tezlashtiradi. |
rules_apple | 5.3.0 | Apple bundle'larini yasaydi va ishga tushiradi (ios_application, ios_unit_test, ios_framework...). Linking, bundling, codesigning va simulyatorga o'rnatish shu modulning ishi. |
Bu uchalasini MODULE.bazel faylida e'lon qilamiz:
# 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_supportboshqa rule'lardan yuqorida turishi kerak. Modulning o'z README'sida yozilgani kabi, birinchi ro'yxatdan o'tgan cc toolchain g'olib chiqadi — agarrules_ccyoki 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:
- **
rules_ccko'rinmaydi.** Bazel 9'da barcha C/C++/Objective-C rule'lari (cc_library,objc_library...) Bazel yadrosidan chiqarib tashlangan — ular endi@rules_cc'danload()qilinishi kerak (manba: Bazel 9.0 release notes). Bizning ilovamiz sof Swift bo'lgani uchunrules_cckerak emas. Agar siz o'zingizobjc_libraryyozsangiz, u holdabazel_dep(name = "rules_cc", version = "0.2.26")qo'shasiz. - **
repo_nameyo'q.** Eski (rules_apple 4.x va undan oldingi) qo'llanmalarbazel_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). - 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.0emas). "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):
| Atribut | Vazifasi |
|---|---|
name | Target nomi (majburiy). Label //Core:Core bo'ladi. |
srcs | Kompilyatsiya qilinadigan .swift fayllar. glob() bilan papkadagi hammasini olamiz. |
module_name | Swift modulining nomi — bu import Core da yoziladigan nom. Berilmasa, Bazel label'dan avtomatik yasaydi (//Core:Core → Core), lekin aniq yozgan ma'qul. |
deps | Bu kutubxona bog'liq bo'lgan boshqa target'lar. Coreda hozircha yo'q. |
visibility | Kim 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, odatdalocal_definesyaxshiroq.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
Coreichidagi bitta faylni o'zgartirsangiz, butunCoremoduli qayta kompilyatsiya bo'ladi. Shuning uchun katta loyihalarni ko'p kichikswift_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:
| Atribut | Majburiymi | Vazifasi |
|---|---|---|
name | ✅ ha | Target nomi. |
families | ✅ ha | Qaysi qurilma oilalari: ["iphone"], ["ipad"] yoki ["iphone", "ipad"]. Kamida bittasi kerak. |
minimum_os_version | ✅ ha | Eng past qo'llab-quvvatlanadigan iOS versiyasi, masalan "17.0". |
bundle_id | ⛔ yo'q (lekin yozing) | Teskari-DNS ko'rinishidagi bundle ID. Texnik jihatdan ixtiyoriy (standart ""), lekin amalda doim bering. |
infoplists | ⛔ yo'q | Info.plist fayllari ro'yxati. Berilmasa, rules_apple standart Info.plist ishlatadi. |
deps | ⛔ yo'q | Ilovaga linklanadigan kutubxonalar. |
Keyin tez-tez ishlatiladigan ixtiyoriylar:
| Atribut | Vazifasi |
|---|---|
resources | Bundle'ga qo'shiladigan resurslar (JSON, rasm, .strings...). |
app_icons | Ilova ikonkalari. Fayllar bitta *.xcassets/*.appiconset papkada bo'lishi kerak. (Xcode 26+'da Icon Composer'ning *.icon bundle'i ham.) |
launch_storyboard | Ishga tushish ekrani (.storyboard/.xib). |
entitlements | Qurilma build'lari uchun entitlement fayli (App Groups, Push...). Simulyator uchun ko'pincha kerak emas. |
provisioning_profile | Qurilmaga chiqarish uchun provisioning profil. Simulyator uchun ixtiyoriy. |
version | apple_bundle_version target'i (CFBundleVersion'ni boshqarish uchun). |
frameworks | Ilova bilan birga joylanadigan ios_framework target'lari. |
extensions | App extension'lar (Widget, Share...). Buni 8-darsda ko'ramiz. |
Hozircha bizga faqat 5 ta atribut kerak: name, bundle_id, families, infoplists, minimum_os_version, deps. Soddalik — kuch.
🧠 Esda tuting:
deps = [":AppLib"]— bu ikki nuqta bilan boshlangan label shu BUILD faylidagi target degani (//App:AppLib).deps = ["//Core"]esa boshqa paketdagi target (//Core:Core'ning qisqartmasi). Label sintaksisi — 3-darsning 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'zgaruvchi | Qiymati |
|---|---|
PRODUCT_BUNDLE_IDENTIFIER | ios_application'ning bundle_id atributi. |
PRODUCT_NAME / TARGET_NAME | bundle_name (agar berilgan bo'lsa) yoki target nomi. |
EXECUTABLE_NAME | executable_name → bundle_name → target nomi. |
BUNDLE_NAME | PRODUCT_NAME + kengaytma (.app). |
DEVELOPMENT_LANGUAGE | Hozircha en ga qotirilgan. |
⚠️ Diqqat:
$(PRODUCT_MODULE_NAME)o'zgaruvchisi qo'llab-quvvatlanmaydi. Xcode'da u avtomatik to'ldiriladi, lekin Bazel'da har birswift_libraryalohida modul bo'lgani uchun printsipial klass qaysi modulda ekanini avtomatik aniqlab bo'lmaydi. Agar extension'daNSExtensionPrincipalClasskerak bo'lsa, modul nominiInfo.plist'ingizda qo'lda yozing (manba: rules_applecommon_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:
| Fayl | Nima u |
|---|---|
Payload/Kitoblar.app/ | Ilova bundle'i. Har bir .ipa ichida Payload/ papka va undagi bitta .app bo'ladi — bu App Store'ning talabi. |
Info.plist (749 bayt) | Birlashtirilgan va to'ldirilgan "pasport". Pastda ochib ko'ramiz. |
Kitoblar (164 KB) | Haqiqiy ishga tushadigan binary — Mach-O, arm64. Bizning butun Swift kodimiz + SwiftUI shu yerda. |
PkgInfo (8 bayt) | Eski Mac uslubidagi tur kodi (APPL...). Bazel avtomatik qo'shadi. |
_CodeSignature/CodeResources | Code signature — qaysi fayl o'zgarmaganini tekshiradigan imzo. Simulyator uchun "ad-hoc" (hisobsiz) imzo. |
Sana 01-01-1980 — bu xato emas, balki ataylab: Bazel takrorlanuvchan (reproducible) natija uchun fayl vaqtlarini qotirib qo'yadi, shunda bir xil kirish doim bir xil .ipa beradi.
Binary'ning turini tasdiqlaymiz:
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:
.ipaichini 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'pinchaunzip -lvaplutil -pdarhol 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'gatags = ["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 — ularningswift_library'sitags = ["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.
manualteg — 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 runesa 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 run10 daqiqadan ko'proq tugamay turdi, chunki ilova yopilmadi. Terminalni bo'shatish uchun Ctrl-C bosing (ilova simulyatorda ochiq qoladi), yoki quyidagi rejimni ishlating.
bazel run'ning xatti-harakatini BAZEL_APPLE_RUN_MODE muhit o'zgaruvchisi bilan boshqarasiz (manba: rules_apple common_info.md):
| Qiymat | Nima qiladi |
|---|---|
install_and_run (standart) | Eski nusxani to'xtatadi, o'rnatadi va ishga tushiradi (log'larga ulanadi). |
install_without_running | O'rnatadi, lekin ishga tushirmaydi — terminal darhol bo'shaydi. |
run_without_installing | Qayta o'rnatmay, mavjud o'rnatilgan ilovani ishga tushiradi. |
Masalan, faqat o'rnatish uchun (terminal osilib qolmaydi):
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 buildfaqat.ipayasaydi — simulyatorga tegmaydi.bazel runesa quradi va ishga tushiradi. Agar sizga faqat fayl kerak bo'lsa (masalan CI'da artifact sifatida),bazel buildishlating. Kundalik ishda esabazel runqulayroq.
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:
- Test target atributlari —
ios_unit_test/ios_ui_test'dagidevice_typevaos_version. - Build settings —
--@rules_apple//apple/build_settings:ios_simulator_device/..._version. Bu — tavsiya etilgan shakl, chunki u kelajakka chidamli. - 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. - 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 15ham,iPhone 17 Proham o'rnatilgan edi, shuning uchun qotirilgan"iPhone 15"zaxirasi ham ishlardi. Lekin boshqa mashinada yoki CI'daiPhone 15bo'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'ningtestbo'limiga yozib qo'ying (biz qilgandek).bazel runuchun esabuildbo'limigabuild --@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:
| Flag | Rejim | Qachon |
|---|---|---|
-c fastbuild | Standart. Minimal optimizatsiya, tez kompilyatsiya. | Kundalik ish, CI testlari. |
-c dbg | Debug — to'liq debug simvollari (.dSYM). | Debugger bilan ishlaganda. |
-c opt | Optimizatsiya qilingan. | Release, App Store, performance o'lchash. |
Masalan, release build:
bazel build //App:Kitoblar -c opt
Biz buni o'lchadik: -c opt build 16.9 soniya oldi (M4 Pro). U fastbuild'dan sekinroq, chunki kod boshqa (optimizatsiyalangan) konfiguratsiyada qaytadan kompilyatsiya qilinadi — fastbuild va opt ikki alohida kesh.
Arxitektura. Simulyator va qurilma uchun binary har xil protsessor arxitekturasi uchun quriladi. Buni --ios_multi_cpus boshqaradi (manba: rules_apple tutorial):
| Maqsad | Flag | Natija |
|---|---|---|
| Simulyator (Apple Silicon Mac) | standart (flag kerak emas) | ios_sim_arm64 |
| Haqiqiy qurilma | --ios_multi_cpus=arm64 | qurilma arm64 |
# 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 hamarm64. 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 qurilmaarm64bo'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 simulyatorarm64slice'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.appbilan 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_versionBazel flagi — aniq Xcode versiyasini "qulflaydi". Bu cache hit**'lar uchun muhim: butun jamoa bir xil Xcode ishlatsa, remote cache ishlaydi (manba: rules_applecommon_info.md).
💡 Pro tip — Xcode'ni qulflash: Katta jamoalar
tools/bazelnomli wrapper skript yozadi. U har build'da joriy Xcode versiyasini aniqlab,xcode.bazelrcfayliga 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:
- 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.
- "Xcode versiyasi topilmadi" xatosi. Agar
--xcode_version=Xbersangiz, 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
ld64linkeri 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.bazelfaylini 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 —pbxprojgit'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.
| Stsenariy | Vaqt | Nima bo'ldi |
|---|---|---|
Sovuq build (clean --expunge + disk cache o'chirilgan) | 94.6 s | 350 action. Vaqtning katta qismi rules_swift worker'ini (abseil'dan) kompilyatsiya qilish — bir martalik narx. |
To'liq keshlangan qayta build (bazel clean + disk cache yoqilgan) | 2.6 s | 186 disk cache hit. Hech narsa qaytadan kompilyatsiya qilinmadi. |
| Incremental (bitta "barg" Swift faylni tahrirlash) | 4.9 s | Faqat AppLib moduli qaytadan kompilyatsiya + bundle + imzo (2 ta process). |
| No-op (hech narsa o'zgarmadi) | 0.2 s | Server grafni xotirada saqlaydi — hech narsa qilinmadi. |
Release (-c opt) | 16.9 s | opt konfiguratsiyasi fastbuild'dan alohida kesh — kod qaytadan, optimizatsiya bilan kompilyatsiya qilinadi. |
Bu jadval Bazel'ning asosiy g'oyasini bitta joyda ko'rsatadi:
- Birinchi build sekin (94.6 s) — lekin bu bir marta. Worker vositasi bir marta quriladi va keyin disk cache'da qoladi.
- Keyingi build'lar — soniyalar (2.6 s to'liq, 4.9 s incremental). Bu — Xcode "clean build"idan tubdan farq: Bazel faqat o'zgargan narsani qayta quradi.
- No-op deyarli bepul (0.2 s), chunki Bazel server grafni xotirada ushlab turadi (bu haqda 2-darsda gaplashgan edik).
📊 Real raqamlar: Bitta barg faylni o'zgartirganda incremental build 4.9 s edi — bu
AppLibmodulining 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}
- iPad qo'shing.
ios_application'dafamilies = ["iphone", "ipad"]qiling, qayta quring,.ipaichidagiInfo.plist'daUIDeviceFamilyni tekshiring (plutil -p). Endi[1, 2]bo'lishi kerak. - Ko'rinadigan nom qo'shing.
Info.plist'gaCFBundleDisplayNamekalitini qo'shing (masalan"Mening kutubxonam"), qayta quring vaplutil -p Payload/Kitoblar.app/Info.plistbilan tasdiqlang. Simulyatorda ilova nomi o'zgarganini ko'ring. - Incremental'ni o'lchang.
BookLibrary.all'ga yangi kitob qo'shing vabazel build //App:Kitoblarvaqtini o'lchang. KeyinBookListView.swift'dagi bitta matnni o'zgartirib, yana o'lchang. Qaysi biri tezroq va nega? (Maslahat:Coreo'zgarsa, unga bog'liqAppLibham qayta quriladi.) - ⭐ Debug vs release hajmi.
bazel build //App:Kitoblar -c optva-c fastbuildbuild'larini qiling, har birining.ipasiniunzip -lbilan oching va binary (Kitoblar) hajmini solishtiring. - ⭐ Simulyator xatosini ko'ring.
xcrun simctl list devicetypesdan boshqa qurilma tanlabbazel runqiling. Keyin ataylab mavjud bo'lmagan qurilma ("iPhone 42") bering vaInvalid device typexatosini o'qing. - ⭐⭐ **
manualtegini tushuning.**AppLib'dantags = ["manual"]ni vaqtincha olib tashlang vabazel build //...ishga tushiring. Xatoni (unavailable in macOS) o'qing, keyin tegni qaytaring. Savol: nega//Core:Coreteg bo'lmasa ham muammosiz quriladi,//App:AppLibesa yo'q?
O'zingizni tekshiring {#checklist}
- [ ]
MODULE.bazel'daapple_support,rules_swift,rules_applebor vaapple_supporteng yuqorida turadi. - [ ]
swift_librarybilan modul yarata olaman vamodule_namenima 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:Kitoblarbilan.ipayasadim vaunzip -lbilan ichidagi 5 ta elementni tushunaman. - [ ]
bazel runbilan simulyatorda ochdim vabazel 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 negatags = ["manual"]kerakligini tushunaman.
Xulosa {#xulosa}
- iOS ilovasi — bu bundle, oddiy binary emas. Shuning uchun
swift_binaryemas,ios_applicationkerak: u linklaydi, bundle qiladi, imzolaydi va.app/.ipayasaydi. - Uchlik:
apple_support(Xcode va SDK'ni toolchain qiladi),rules_swift(Swift kompilyatsiya),rules_apple(Apple bundle va run).apple_supportMODULE.bazel'da yuqorida turadi. - Oqim: Swift fayllar →
swift_library(modul) →ios_application(.app/.ipa).Info.plistalohida fayl bo'lib,$(PRODUCT_NAME)kabi o'zgaruvchilar build paytida almashadi. - **
bazel build=.ipayasaydi;bazel run= quradi va simulyatorda ishga tushiradi** (va console'ga ulanib turadi — Ctrl-C yokiinstall_without_running). - Simulyatorni aniq tanlang — tavsiya etilgan
--@rules_apple//apple/build_settings:ios_simulator_device/versionshakli bilan, mashinangizda bor qurilmani (xcrun simctl list). - **Faqat-iOS
swift_library'gatags = ["manual"]** qo'ying, aks holdabazel build //...uni macOS host uchun qurib yiqiladi. - **Debug/release
-cbilan**; simulyator standartarm64(Apple Silicon), qurilma--ios_multi_cpus=arm64. - Xcode'ni qulflang (
--xcode_version,tools/bazelwrapper) cache hit'lar uchun; Xcode'ni yangilagandabazel 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}
- rules_apple — iOS rule'lari (rules-ios.md) —
ios_application,ios_unit_testatributlari. - rules_apple — Info.plist va simulyator (common_info.md) — Info.plist birlashtirish, o'zgaruvchilar,
BAZEL_APPLE_RUN_MODE, Xcode qulflash. - rules_apple — iOS ilova qurish bo'yicha rasmiy tutorial — minimal workspace,
bazel run,--ios_multi_cpus. - rules_swift — swift_library (rules.md) —
swift_libraryatributlari. - apple_support — README va xcode_config — Xcode toolchain,
apple_supporttartibi, Xcode versiya xatolari. - Bazel Central Registry — modul versiyalari (rules_apple 5.3.0, rules_swift 4.2.1, apple_support 2.10.1).
- Bazel 9 e'loni (blog) — WORKSPACE olib tashlandi,
objc_library@rules_cc'dan. - Spotify — iOS build tizimini Bazel'ga o'tkazish — 2000+ BUILD fayl, p75 ~80 → ~20 daqiqa.
- InfoQ — Spotify Bazel iOS migratsiyasi — raqamlar tasdiqi.
- Allegro — iOS build'larini Bazel bilan tezlashtirish — 260 → 85 soniya, 25 muhandis.
- Google tadqiqoti — build vaqti va samaradorlik (DX) — tezroq build → o'lchanadigan samaradorlik.
- Telegram iOS — ochiq kodli Bazel iOS loyihasi — rules_apple/rules_swift bilan real ilova.
- Square — Apple Silicon'ga Bazel bilan tayyorlanish — simulyator vs qurilma
arm64muammosi.