Bazel 4: Starlark tili — BUILD va .bzl fayllar, macro'lar, select() va konfiguratsiya
📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot
Bazel 4: Starlark tili — BUILD va .bzl fayllar, macro'lar, select() va konfiguratsiya
Holat: 2026-yil oktabr. Bu darsda Bazel'ning tili — Starlark'ni noldan o'rganamiz: BUILD va
.bzlfayllar, konstantalar, funksiyalar, macro'lar,select()va build setting'lar bilan bitta kod bazasini turli muhitlar (dev/staging/prod) uchun quramiz. Kod misollari Bazel 9.3.0, rules_apple 5.3.0, rules_swift 4.2.1, bazel_skylib 1.9.2 va Xcode 27.1 bilan Apple M4 Pro'da tekshirilgan.
Bu darsdan keyin siz: BUILD fayllaringizdagi takrorlanishni macro bilan yo'qotasiz, bitta kodni
select()orqali bir nechta muhit uchun quradigan qilib sozlaysiz va.bzlfayllarini xotirjam yoza olasiz.
- Starlark'ning Python'dan qaysi 10 ta joyda farq qilishini va nega shunday qilinganini tushunasiz.
- BUILD faylida nima qilsa bo'ladi, nima qilib bo'lmaydi — va nega funksiya
.bzl'ga ketadi. ios_module(name, srcs, test_srcs)— bitta chaqiriqdanswift_library+ test kutubxona +ios_unit_testyasaydigan symbolic macro yozasiz.select()+config_setting+string_flagbilanAPI_BASE_URL'ni build vaqtida tanlaysiz (dev → staging → prod), bironta#if'ni qo'lda o'zgartirmasdan.print(),fail()vabuildifierbilan Starlark kodini debug va lint qilasiz.
🚀 Motivatsiya: Xcode loyihasida "Debug" va "Release" uchun ikki xil
API_BASE_URLkerak bo'lsa, odatda#if DEBUGyoki.xcconfigfayllari bilan ovora bo'lasiz — va qaysi target qaysi qiymatni olishiniproject.pbxprojichida qidirasiz. Bazel'da bu bittaselect()— va biz uni shu darsda haqiqiy CLI'da ishga tushirib,dev → staging → prodo'tishini o'z ko'zimiz bilan ko'ramiz.
Mundarija
- Starlark nima va nega Python emas
- [BUILD fayl vs
.bzlfayl — mehnat taqsimoti](#build-vs-bzl) - [Starlark tili: tiplar, funksiya, comprehension,
load()](#til) - Macro nima va nega kerak
- Legacy macro — eski uslub
- [Symbolic macro va
ios_module](#symbolic-macro) - [Macro'ni "ochib ko'rish":
query --output=build](#macro-expansion) - [
select()— build vaqtidagi tanlov](#select) - [
config_settingva o'z build setting'laringiz](#build-settings) - [
--define,-c opt, platforms — konfiguratsiya xaritasi](#other-config) - Konfiguratsiya action key'ga qanday ta'sir qiladi
- [Starlark'ni debug qilish:
print(),fail(),buildifier](#debugging) - Amaliy mashqlar
- O'zingizni tekshiring
- Xulosa
- Manbalar
1. Starlark nima va nega Python emas {#starlark-nima}
Oldingi darsda (Bazel 3: Asosiy tushunchalar) biz BUILD.bazel fayllariga swift_library, ios_application kabi rule'larni yozdik. Lekin aslida o'sha fayllar oddiy matn emas — ular kichik dastur. Bazel ularni o'qiganda bir tilni ishga tushiradi: Starlark.
Starlark — Google ichida Blaze (Bazel'ning ichki ajdodi) uchun yozilgan, Python3'dan ilhomlangan til. Ilgari u "Skylark" deb atalgan. Bazel'ning rasmiy hujjatlarida u shunday ta'riflanadi: Starlark sintaksisi Python3'dan ilhomlangan va bir xil ko'rinadigan kod ko'pincha bir xil ishlaydi — lekin ataylab ba'zi joylarda cheklangan (manba: Bazel docs, rules/language).
Nega Python'ning o'zini ishlatmagan? Chunki build tizimi uchta narsani kafolatlashi kerak:
- Deterministik (aniq natija). Bir xil kirish → har doim bir xil chiqish. Agar
BUILDfayli tasodifiy son, hozirgi vaqt yoki diskdagi faylga qarab har safar boshqacha target yasasa, cache ishonchsiz bo'lib qoladi. - Hermetik (yopiq).
BUILD/.bzlkodi ixtiyoriy I/O qila olmaydi — tarmoqqa chiqa olmaydi, faylni o'qiy olmaydi, env o'zgaruvchisini "yashirincha" ololmaydi. Shu sababli build qayta-qayta takrorlanadigan bo'ladi (manba: Bazel docs,concepts/build-files→ Hermeticity). - Parallel va xavfsiz. Bazel minglab
.bzlvaBUILDfayllarini bir vaqtda o'qiydi. Agar bir fayl ikkinchisining global o'zgaruvchisini o'zgartira olsa, bu poyga (race) bo'lardi.
Shuning uchun Starlark Python'dan quyidagi joylarda qasddan farq qiladi (hammasi rules/language hujjatidan):
| Python'da bor | Starlark'da | Nega |
|---|---|---|
while, generator, yield | yo'q | cheksiz / noaniq ish vaqtining oldini olish |
| Rekursiya (funksiya o'zini chaqiradi) | yo'q | build grafi aniq va chekli bo'lishi uchun |
class | yo'q (o'rniga struct) | sodda, yagona ma'lumot modeli |
import | yo'q (o'rniga load()) | bog'liqliklar aniq ko'rinishi uchun |
try / except / raise | yo'q (o'rniga fail()) | xatoni "yutib" yuborish mumkin emas |
| Global o'zgaruvchini o'zgartirish | yo'q — load'dan keyin muzlaydi | parallel ishlashda xavfsizlik |
float, set | yo'q | determinizm (float dumaloqlash muammolari) |
5 < 'foo' (tiplararo solishtirish) | xato | yashirin bug'larning oldini olish |
1 < x < 5 (zanjirli solishtirish) | yo'q | soddalik |
int cheksiz kattalikda | 32-bit (overflow → xato) | determinizm |
🧠 Esda tuting: Starlark Python emas, u Python'ga o'xshaydi. Siz Python bilgansiz deb o'ylab
whileyokiclassyozsangiz, Bazel darhol xato beradi. "Python kabi, lekin ataylab qisqartirilgan konfiguratsiya tili" — eng to'g'ri aqliy model shu.
Eng muhim xususiyat — immutability (o'zgarmaslik). rules/language hujjatidagi klassik misol: .bzl faylida e'lon qilingan global ro'yxat, fayl o'qib bo'linganidan keyin muzlaydi. Boshqa fayl uni load qilib, ichiga qiymat qo'sha olmaydi:
# foo.bzl
var = [] # ro'yxat e'lon qilamiz
def fct():
var.append(5) # ichiga qiymat qo'shamiz
fct() # funksiyani chaqiramiz -> var endi [5]
# bar.bzl
load(":foo.bzl", "var", "fct")
var.append(6) # XATO: foo.bzl tugagach, var muzlagan (frozen)
fct() # XATO: fct() muzlagan ro'yxatni o'zgartirmoqchi
Buning sababi: har bir .bzl va BUILD fayl o'z kontekstida ishlaydi va Bazel ularni parallel bajaradi. Agar bar.bzl foo.bzl'ning holatini o'zgartira olsa, natija qaysi fayl avval o'qilganiga bog'liq bo'lib qolardi — ya'ni nodeterministik.
2. BUILD fayl vs .bzl fayl — mehnat taqsimoti {#build-vs-bzl}
Bazel'da ikki xil Starlark fayli bor va ularning vazifasi aniq bo'lingan:
- **
BUILD(yokiBUILD.bazel) — deklarativ ro'yxat. Bu yerda siz faqat target e'lon qilasiz**:swift_library(...),ios_application(...). BUILD faylida funksiya yozib bo'lmaydi, top-levelfor/ifyozib bo'lmaydi,*args/**kwargsishlatib bo'lmaydi. - **
.bzl(Bazel extension)** — ta'riflar. Bu yerda konstantalar, funksiyalar, macro'lar va rule'lar yashaydi..bzlfaylidan tayyor narsalarniBUILD'gaload()orqali olib kirasiz.
Rasmiy hujjat buni shunday izohlaydi (concepts/build-files): "Kod va ma'lumotni toza ajratish uchun BUILD fayllarida funksiya e'loni, for va if operatorlari bo'lishi mumkin emas (lekin list comprehension va if ifodasi mumkin). Funksiyalarni .bzl fayllarida e'lon qilish kerak."
Keling, buni haqiqiy xato bilan ko'rsatamiz. Agar BUILD.bazel fayliga funksiya yozsak:
# scratch_def/BUILD.bazel
def helper():
return 1
bazel query //scratch_def:all
ERROR: .../scratch_def/BUILD.bazel:1:1: functions may not be defined in BUILD
files. You may move the function to a .bzl file and load it.
ERROR: package contains errors: scratch_def: functions may not be defined in
BUILD files. You may move the function to a .bzl file and load it.
Xato to'g'ridan-to'g'ri yechimni aytyapti: funksiyani .bzl fayliga ko'chir va load qil.
BUILD fayl deklarativ target ro'yxati, .bzl fayl esa funksiya, macro, rule va konstantalarni saqlaydi; load() ular orasidagi ko'prik
Yuqoridagi rasm ikki faylning rolini ko'rsatadi: chapda BUILD — faqat target e'loni, o'ngda .bzl — mantiq (konstanta, funksiya, macro, rule), va ular orasida bitta ko'prik: load().
🧠 Esda tuting: Oddiy qoida — **BUILD'da nima quriladi,
.bzl'da qanday quriladi.** BUILD'ni odam ham, vosita ham (masalan,bazel queryyoki IDE) oson o'qishi kerak; shuning uchun uni sodda va deklarativ saqlang.
load() sintaksisi (concepts/build-filesdan):
# bitta simvolni yuklash
load("//tools:defs.bzl", "ios_module")
# bir nechta simvol + taxallus (alias)
load(":my_rules.bzl", "some_rule", nice_alias = "some_other_rule")
Birinchi argument — .bzl faylining label'i (//tools:defs.bzl). Qolganlari — olib kiriladigan simvol nomlari (qo'shtirnoq ichida, o'zgaruvchi emas). load faqat faylning eng yuqorisida (top-level) turishi mumkin, funksiya ichida emas. Va muhim nuqta: .bzl faylida _ bilan boshlanadigan simvollar eksport qilinmaydi — ularni boshqa fayl load qila olmaydi. Shuning uchun macro/rule'ning ichki implementatsiya funksiyasi odatda _ bilan boshlanadi (masalan _ios_module_impl).
3. Starlark tili: tiplar, funksiya, comprehension, load() {#til}
Endi tilning o'zini bir "demo" bilan ko'ramiz. Quyidagi .bzl faylida Starlark'ning asosiy qurilmalarini jamladik: konstanta, funksiya, for-siz ishlaydigan list comprehension, dict comprehension, %-formatlash va if-ifoda:
# lang/demo.bzl
"""Starlark tilini ko'rsatuvchi kichik demo funksiyalar."""
# Global konstanta - odatda UPPERCASE bo'ladi
COMMON_COPTS = ["-warnings-as-errors", "-enable-actor-data-race-checks"]
def module_names(prefix, count):
"""prefix asosida modul nomlari ro'yxatini yasaydi (list comprehension)."""
return ["%s%d" % (prefix, i) for i in range(1, count + 1)]
def summarize(modules):
"""dict qaytaradi: har modulning nomi va uzunligi."""
return {name: len(name) for name in modules}
def describe():
mods = module_names("Feature", 3)
sizes = summarize(mods)
# print() - loading fazasida DEBUG log sifatida chiqadi
print("modullar =", mods)
print("uzunliklar =", sizes)
print("birinchi modul =", mods[0] if mods else "yo'q")
Endi uni BUILD faylidan chaqiramiz (e'lon qila olmaymiz, lekin chaqira olamiz):
# lang/BUILD.bazel
load(":demo.bzl", "describe")
# BUILD faylida funksiya E'LON qilib bo'lmaydi, lekin .bzl dan
# yuklangan funksiyani CHAQIRISH mumkin.
describe()
bazel query //lang:all — bu paketni yuklaydi, ya'ni loading fazasini ishga tushiradi va describe() chaqiriladi:
bazel query //lang:all
DEBUG: .../lang/demo.bzl:18:10: modullar = ["Feature1", "Feature2", "Feature3"]
DEBUG: .../lang/demo.bzl:19:10: uzunliklar = {"Feature1": 8, "Feature2": 8, "Feature3": 8}
DEBUG: .../lang/demo.bzl:20:10: birinchi modul = Feature1
INFO: Empty results
E'tibor bering: describe() bironta target yasamagani uchun query natijasi bo'sh (Empty results), lekin print() chiqishlari loading fazasida ko'rindi. Bu Starlark kodingiz qachon ishlashini aniq ko'rsatadi: hali hech narsa kompilyatsiya qilinmasdan, Bazel faqat fayllarni o'qiyotganda.
Keling, chiqishni tahlil qilamiz:
["%s%d" % (prefix, i) for i in range(1, count + 1)]— list comprehension.range(1, 4)→1, 2, 3, va har biriga"Feature%d"formatlanadi →["Feature1", "Feature2", "Feature3"]. Starlark top-level'daforoperatorini taqiqlaydi, lekin comprehension'ga ruxsat beradi.{name: len(name) for name in modules}— dict comprehension. Natija:{"Feature1": 8, ...}.mods[0] if mods else "yo'q"— if-ifoda (statement emas, ifoda). Starlark'daifstatement top-level'da yoki BUILD'da taqiqlangan, lekin bu ifoda shakli ruxsat etilgan."%s%d" % (prefix, i)— Python uslubidagi string formatlash. Starlark string'lari"qo'shtirnoq bilan yoziladi.
💡 Pro tip: Starlark string'lari iteratsiya qilinmaydi (
for c in "abc"xato beradi) va yashirin string birlashtirish yo'q —"a" "b"emas, aniq"a" + "b"yozasiz. Agar C-dan yoki Python'dan kelgan odatlaringiz bilan yozsangiz, bu ikkisi eng ko'p uchraydigan "nega ishlamayapti?" lahzalari.
Qo'llab-quvvatlanadigan asosiy tiplar (rules/languagedan): None, bool, int (32-bit), string, list, dict, tuple, function. float va set yo'q.
4. Macro nima va nega kerak {#macro-nima}
Tasavvur qiling, loyihangizda 30 ta modul bor va har biri bir xil naqshdan iborat: kutubxona, uning test kutubxonasi, va ios_unit_test. Har bir modul uchun BUILD faylida uchta blok yozasiz — hammasi bir xil, faqat nomlar farq qiladi. Keyin kompaniya qarori chiqadi: "barcha modullar -warnings-as-errors bilan qurilsin". Endi 30 ta faylni qo'lda o'zgartirasiz. Bu — klassik iOS jamoasidagi og'riq.
Macro — aynan shu takrorlanishni yo'qotadi. Rasmiy ta'rif (extending/macros hujjati): "Macro — BUILD faylidan chaqiriladigan va rule'larni yaratadigan funksiya. Macro'lar asosan mavjud rule'larni enkapsulyatsiya qilish va qayta ishlatish uchun." Macro loading fazasida chaqiriladi; chaqiruv tugagach, Bazel deyarli faqat yakuniy, aniq rule target'larni ko'radi — xuddi siz ularni qo'lda yozganday.
Macro ikki xil bo'ladi (extending/macros):
- Legacy macro — eski uslub. Oddiy Starlark funksiyasi; hech qanday maxsus qoidasi yo'q.
- Symbolic macro — Bazel 8'da kiritilgan yangi tizim. Tipli (typed) argumentlar, label/
selectavtomatik konvertatsiyasi, va ichki target'larni yashirish (granular visibility) imkoniyati beradi.
Rasmiy tavsiya aniq (extending/macros): "Iloji bo'lsa, kodning aniqligi uchun symbolic macro'dan foydalaning." Biz ikkalasini ham yozamiz — avval farqni his qilish uchun legacy'ni, keyin asosiy qurol sifatida symbolic'ni.
⚠️ Diqqat: Macro — bepul emas.
.bzlstyle guide ogohlantiradi (rules/bzl-style): macro ichidagi target'larbazel querynatijasida go'yoki o'z-o'zidan paydo bo'ladi (ularni BUILD faylida ko'rmaysiz), nosozlik chiqsa foydalanuvchi sizning macro implementatsiyangizni o'qishga majbur bo'ladi, va aspect'lar (IDE'lar ishlatadigan) macro'ni ko'rmaydi. Shuning uchun macro'ni faqat haqiqiy takror bo'lganda yozing — "ehtimol kerak bo'lar" uchun emas (YAGNI).
Buni yanada kuchliroq aytadigan nuqta rules/bzl-styleda bor: "BUILD fayllarida takrorlanishni kamaytirish uchun o'zgaruvchi yoki macro yaratmang... DRY printsipi bu yerda unchalik qo'llanmaydi." Ya'ni BUILD faylidagi bir oz takror — bu ko'pincha yaxshi, chunki har bir e'lon mustaqil o'qiladi. Macro'ni jiddiy, tuzilmaviy takror bo'lgandagina kiriting.
5. Legacy macro — eski uslub {#legacy-macro}
Legacy macro — shunchaki funksiya. Keling, bitta Swift modul + uning "support" kutubxonasini yasaydigan oddiy legacy macro yozamiz:
# tools/legacy.bzl
"""Eski uslubdagi (legacy) macro - taqqoslash uchun."""
load("@rules_swift//swift:swift_library.bzl", "swift_library")
# Legacy macro - oddiy funksiya. native. kerak emas, chunki swift_library load qilingan.
# Nom sxemasi majburiy emas: "CoreSupport" kabi nom ham ishlaydi.
def swift_module_legacy(name, srcs, deps = [], visibility = None):
swift_library(
name = name,
srcs = srcs,
module_name = name,
deps = deps,
visibility = visibility,
)
swift_library(
name = name + "Support", # symbolic macro bunga ruxsat bermasdi
srcs = srcs,
module_name = name + "Support",
deps = [":" + name],
visibility = visibility,
)
BUILD faylida ishlatamiz:
# legacy/BUILD.bazel
load("//tools:legacy.bzl", "swift_module_legacy")
swift_module_legacy(
name = "Greeter",
srcs = ["Sources/Greeter.swift"],
)
bazel query //legacy:all
//legacy:Greeter
//legacy:GreeterSupport
Ikkala target ham yaratildi va GreeterSupport — Greeter'dan keyin hech qanday ajratuvchisiz (_, -, . yo'q) — bemalol ishladi. Buni yodda tuting: 3-bo'limda ko'ramizki, symbolic macro bunday nomga ruxsat bermaydi.
Legacy macro'ning kuchli va zaif tomonlari:
- ➕ Juda moslashuvchan: istalgan Starlark mantiqi, erkin nom.
- ➖ "Action at a distance": macro ichki target'larini boshqa paketdan ham ko'rish va ularga bog'lanish mumkin — ichki detallar oqib ketadi.
- ➖ Argumentlar tipsiz: noto'g'ri qiymat bersangiz, xato chuqur ichkarida, tushunarsiz joyda chiqadi.
- ➖ Visibility tizimi uchun shaffof: legacy macro ichidagi target go'yoki to'g'ridan-to'g'ri
BUILDfaylida yozilganday ko'rinadi.
💡 Pro tip: Eski Bazel versiyalarini (7.x) qo'llab-quvvatlashingiz kerak bo'lsa, legacy macro yagona yo'l — symbolic macro faqat Bazel 8'dan boshlab bor. Aks holda — symbolic macro.
6. Symbolic macro va ios_module {#symbolic-macro}
Endi kursimizning asosiy quroli: symbolic macro. U macro() funksiyasi bilan e'lon qilinadi va ikkita majburiy parametr oladi (extending/macros): attrs (tipli argumentlar) va implementation (mantiq).
Biz iOS jamoalarining klassik naqshini quramiz — ios_module: bitta chaqiriqdan uchta narsani yasaydi: asosiy swift_library, test swift_library (testonly), va ios_unit_test. Bu aynan Bazel 13: "Vazifalar" ilovasida 4 ta modulni qurishda ishlatadigan macro'imiz bo'ladi.
# tools/defs.bzl
"""iOS modul uchun symbolic macro: swift_library + test lib + ios_unit_test."""
load("@rules_apple//apple:ios.bzl", "ios_unit_test")
load("@rules_swift//swift:swift_library.bzl", "swift_library")
_DEFAULT_MIN_OS = "17.0"
def _ios_module_impl(name, visibility, srcs, deps, test_srcs, test_deps, minimum_os_version):
if not srcs:
fail("ios_module(%s): 'srcs' bo'sh bo'lishi mumkin emas" % name)
# 1) Asosiy kutubxona - modulning o'zi
swift_library(
name = name,
srcs = srcs,
module_name = name,
deps = deps,
visibility = visibility,
)
# Test manbasi berilmasa, test target yaratmaymiz
if not test_srcs:
return
# 2) Test kutubxonasi (testonly).
# Symbolic macro ichidagi target nomi name'dan keyin '_' bilan boshlanishi SHART.
swift_library(
name = name + "_tests_lib",
testonly = True,
srcs = test_srcs,
deps = [":" + name] + test_deps,
)
# 3) ios_unit_test - testlarni simulyatorda ishga tushiradigan target
ios_unit_test(
name = name + "_tests",
minimum_os_version = minimum_os_version,
deps = [":" + name + "_tests_lib"],
)
ios_module = macro(
doc = "Bitta iOS modul: swift_library + test kutubxona + ios_unit_test.",
implementation = _ios_module_impl,
attrs = {
"srcs": attr.label_list(
allow_files = [".swift"],
mandatory = True,
configurable = False,
),
"deps": attr.label_list(),
"test_srcs": attr.label_list(
allow_files = [".swift"],
configurable = False,
),
"test_deps": attr.label_list(),
"minimum_os_version": attr.string(
default = _DEFAULT_MIN_OS,
configurable = False,
),
},
)
Diqqat qilinadigan joylar (hammasi extending/macrosga mos):
- **
implementationfunksiyasi har bir argument uchun alohida parametr oladi** (rule implementatsiyasidan farqli — unda bittactxbo'ladi). Va u **namehamdavisibility** parametrlarini olishi shart. - **
attrs** — tipli.attr.label_list,attr.stringva h.k.namevavisibilityavtomatik qo'shiladi, ularniattrs'ga yozmaysiz. - **
configurable = False** — bu atributgaselect()berib bo'lmaydi degani.srcs'niconfigurable=Falseqildik, chunkiglob()natijasini kutyapmiz,selectemas. - **
mandatory = True** —srcsberilmasa, Bazel darhol xato beradi. - Symbolic macro qiymat qaytara olmaydi, argumentlarini o'zgartira olmaydi,
glob()chaqira olmaydi. Shuning uchunglob()niBUILDfaylida chaqiramiz va natijani macro'ga uzatamiz.
BUILD faylida ishlatamiz:
# Feature/BUILD.bazel
load("//tools:defs.bzl", "ios_module")
# glob() BUILD faylida chaqiriladi - symbolic macro ichida emas.
ios_module(
name = "Feature",
srcs = glob(["Sources/**/*.swift"]),
test_srcs = glob(["Tests/**/*.swift"]),
visibility = ["//visibility:public"],
)
Haqiqiy xato: symbolic macro nom sxemasi
Birinchi urinishda biz test target'larini odatdagidek name + "Tests" → FeatureTests deb nomladik (ko'p iOS jamoalari shunday ataydi). Natija — bu real xato edi:
bazel query //Feature:all
Error: macro 'Feature' cannot declare submacro named 'FeatureTests'. Name must
be the same as the macro's name, or the macro's name followed by '_'
(recommended), '-', or '.', and a non-empty string.
Ikkita narsa ma'lum bo'ldi:
- Symbolic macro nom sxemasini majburlaydi (
extending/macros):my_macro(name = "foo")faqatfoo, yokifoo_...,foo-...,foo....bilan boshlanadigan target yarata oladi.FeatureTests—Feature'dan keyin darholTkeladi, ajratuvchi yo'q → taqiqlangan. To'g'ri nom:Feature_tests. - **
ios_unit_testning o'zi — symbolic macro!** Xato "submacro" deb aytdi; demak rules_apple 5.3.0'daios_unit_testichida yana macro'lar chaqiriladi (manba: rules_appleapple/ios.bzl,macro_factory.create_apple_test_macro). Shuning uchun nom sxemasi qatlamma-qatlam tekshiriladi.
Yechim — _ ajratuvchidan foydalanish (name + "_tests", name + "_tests_lib"). Biz yuqoridagi kodda shuni qildik.
🧠 Esda tuting: Symbolic macro ichida yaratgan har bir target nomi
namebilan boshlanib, keyin_/-/.ajratuvchisi kelishi shart. Bu — g'alati emas, balki foydali: Bazel qaysi macro qaysi target'ni yaratganini nomidan biladi va kelajakda "lazy evaluation"ni (faqat kerakli macro'ni ochish) shu bilan amalga oshiradi.
Symbolic macro visibility'ni qanday himoya qiladi
Symbolic macro'ning eng muhim ustunligi — ichki target'larni yashirish. Rasmiy qoida (extending/macros): macro ichida yaratilgan target, agar siz unga visibility bermasangiz, faqat macro'ning o'z paketiga ko'rinadi, macro chaqirilgan paketga emas.
Biz ios_module'da swift_libraryga visibility = visibility berdik (ya'ni chaqiruvchi so'ragan ko'rinish), lekin ios_unit_testga hech narsa bermadik. Natijada (keyingi bo'limdagi query --output=build chiqishida ko'rasiz) test target'ning visibility'si ["//tools:__pkg__"] bo'lib chiqadi — ya'ni macro e'lon qilingan //tools paketiga. Bu shuni anglatadi: boshqa modul tasodifan //Feature:Feature_testsga deps orqali bog'lana olmaydi. Ichki detal — ichki bo'lib qoladi.
🚀 Developer superpower: Legacy macro'da ichki target'lar butun workspace'ga ochiq edi ("action at a distance"). Symbolic macro bilan siz arxitekturani kod darajasida himoya qilasiz: modulning tashqi API'si (asosiy kutubxona) ko'rinadi, ichki test apparati esa yashirin. Hech kim sizning ichki target'ingizga bog'lanib, keyinroq refaktoringni buzib qo'ya olmaydi.
7. Macro'ni "ochib ko'rish": query --output=build {#macro-expansion}
Macro loading fazasida "ochiladi" (expand) — bitta chaqiruv bir nechta real target'ga aylanadi. Bu ochilishni o'z ko'zingiz bilan ko'rishning eng yaxshi yo'li — bazel query --output=build. Avval oddiy query bilan qaysi target'lar paydo bo'lganini sanaymiz:
bazel query //Feature:all
//Feature:Feature
//Feature:Feature_tests
//Feature:Feature_tests.__internal__.__test_bundle
//Feature:Feature_tests_lib
Bitta ios_module(name="Feature", ...) chaqiruvi to'rtta target yasadi: asosiy kutubxona (Feature), test target (Feature_tests), test bundle (ios_unit_test ichida yaratilgan __internal__ target), va test kutubxonasi (Feature_tests_lib).
Endi --output=build bilan ularning to'liq ko'rinishini olamiz (qisqartirilgan):
bazel query --output=build //Feature:all
# Feature/BUILD.bazel:4:11
swift_library(
name = "Feature",
visibility = ["//visibility:public"],
generator_name = "Feature",
generator_function = "ios_module",
generator_location = "Feature/BUILD.bazel:4:11",
deps = [],
module_name = "Feature",
srcs = ["//Feature:Sources/Task.swift"],
)
...
ios_unit_test(
name = "Feature_tests",
visibility = ["//tools:__pkg__"],
generator_name = "Feature",
generator_function = "ios_module",
runner = "@rules_apple//apple/testing/default_runner:ios_default_runner",
deps = ["//Feature:Feature_tests.__internal__.__test_bundle"],
minimum_os_version = "17.0",
)
...
swift_library(
name = "Feature_tests_lib",
generator_name = "Feature",
generator_function = "ios_module",
testonly = True,
deps = ["//Feature:Feature"],
module_name = "Feature_tests_lib",
...
)
Bu chiqishda uchta qimmatli narsa bor:
- **
generator_function = "ios_module"vagenerator_name = "Feature"** — har bir target qaysi macro tomonidan, qaysi chaqiruvdan yaratilganini aytadi. Nosozlik qidirganda bu oltin:bazel query --output=build 'attr(generator_function, ios_module, //...)'bilan macro yaratgan barcha target'larni topasiz. - **
Featurekutubxonasivisibility = ["//visibility:public"]** — bizvisibility = visibilityberganimiz uchun. Lekin **Feature_testsesavisibility = ["//tools:__pkg__"]** — biz unga hech narsa bermaganimiz uchun u macro paketiga (//tools) yashirildi. Bu symbolic macro visibility himoyasining amaldagi isboti. - **
runner = "@rules_apple//apple/testing/default_runner:ios_default_runner"** —ios_unit_testo'zi qo'shgan standart runner. rules_apple 5.x'da bu xctestrun runner (manba: rules_apple 5.0.0 release notes, "Switch default iOS test runner to xctestrun runner").
Endi macro haqiqatan ishlashiga ishonch hosil qilamiz — modulni quramiz va test'ni simulyatorda ishga tushiramiz:
bazel build //Feature:Feature
Target //Feature:Feature up-to-date:
bazel-bin/Feature/Feature.swiftmodule
bazel-bin/Feature/libFeature.a
INFO: Elapsed time: 3.956s
bazel test //Feature:Feature_tests --test_output=errors
//Feature:Feature_tests PASSED in 55.4s
Executed 1 out of 1 test: 1 test passes.
INFO: Elapsed time: 67.005s
Birinchi test ~55 soniya oldi, chunki Bazel simulyator yaratib, uni yuklashi kerak bo'ldi (M4 Pro, Xcode 27.1). Lekin hech narsa o'zgarmasa, ikkinchi marta:
bazel test //Feature:Feature_tests
//Feature:Feature_tests (cached) PASSED in 55.4s
INFO: Elapsed time: 0.212s
Executed 0 out of 1 test: 1 test passes.
**(cached) PASSED, 0.212 soniya, "Executed 0 out of 1 test".** Bazel test natijasini cache'dan oldi — hech narsa qayta ishlamadi. Test cache'i haqida to'liq Bazel 6: Testlarda gaplashamiz, lekin bu yerda muhimi: siz yozgan bitta macro → to'liq ishlaydigan, cache'lanadigan test infratuzilmasi.
ios_module(name=Feature) chaqiruvi loading fazasida to'rtta targetga ochiladi: Feature kutubxona, Feature_tests_lib test kutubxona, Feature_tests ios_unit_test va ichki test bundle
Rasm macro ochilishini ko'rsatadi: chapda BUILD faylidagi bitta ios_module(...) chaqiruvi, o'ngda loading fazasidan keyin paydo bo'lgan to'rtta real target va ular orasidagi deps bog'lanishlari.
8. select() — build vaqtidagi tanlov {#select}
Mana darsning eng amaliy qismi. Bir xil kodni turli muhitlar uchun quraylik: dev muhitida API http://localhost:8080, stagingda https://staging.api.vazifalar.uz, prodda https://api.vazifalar.uz bo'lsin. Xcode'da buni odatda .xcconfig fayllari yoki sxema (scheme) sozlamalari bilan qilasiz. Bazel'da — bitta select().
select() — Bazel'ning "configurable attribute" imkoniyati (manba: docs/configurable-attributes). U atribut qiymatini build vaqtidagi bayroqlarga qarab tanlaydi. Ichki mexanizm: select() — bu placeholder (o'rinbosar); analiz fazasida Bazel qaysi shart (config_setting) to'g'ri kelishini aniqlab, bitta shoxni tanlaydi.
Avval Swift tomonini yozamiz. API manzili kompilyatsiya vaqtidagi -D bayrog'iga qarab tanlanadi:
// cli/main.swift
// API manzili build vaqtida tanlanadi: -D STAGING / -D PROD / (hech narsa = dev)
#if PROD
let apiBaseURL = "https://api.vazifalar.uz"
let envName = "prod"
#elseif STAGING
let apiBaseURL = "https://staging.api.vazifalar.uz"
let envName = "staging"
#else
let apiBaseURL = "http://localhost:8080"
let envName = "dev"
#endif
print("env=\(envName) url=\(apiBaseURL)")
Endi BUILD faylida defines atributini select() bilan to'ldiramiz:
# cli/BUILD.bazel
load("@rules_swift//swift:swift_binary.bzl", "swift_binary")
swift_binary(
name = "envinfo",
srcs = ["main.swift"],
# select() build vaqtida api_env qiymatiga qarab bitta shoxni tanlaydi.
# Swift defines - bu shunchaki identifikator (qiymatsiz), #if uchun.
defines = select({
"//config:is_staging": ["STAGING"],
"//config:is_prod": ["PROD"],
"//conditions:default": ["DEV"],
}),
)
🧠 Esda tuting: Swift'da
defines— bu C'dagideknom=qiymatemas, balki shunchaki identifikator (STAGING). Har biri-Dbilan oldiga qo'shilib,swiftc'ga uzatiladi va#if STAGING'da ishlatiladi (manba: rules_swiftswift_librarydocs,definesatributi). Qiymatli konfiguratsiya (masalan URL'ning o'zini-Dorqali uzatish) Swift'da ishlamaydi — shuning uchun#ifbilan qiymatni kod ichida tanlaymiz.
//conditions:default — hech bir shart to'g'ri kelmaganda ishlaydigan standart shox (docs/configurable-attributes). Uni yozmasangiz va hech narsa mos kelmasa, build xato beradi (buni 10-bo'limda ko'ramiz).
Endi eng yoqimli qismi — uni ishga tushiramiz. Standart holatda (hech qanday bayroqsiz) //conditions:default ishlaydi:
bazel run //cli:envinfo
env=dev url=http://localhost:8080
:is_staging shartini yoqadigan bayroq bilan (buni 9-bo'limda sozlaymiz):
bazel run //cli:envinfo --config=staging
env=staging url=https://staging.api.vazifalar.uz
bazel run //cli:envinfo --config=prod
env=prod url=https://api.vazifalar.uz
**Bitta Swift fayl, bitta select() — uchta muhit.** Hech bir #if'ni qo'lda o'zgartirmadik, hech bir sxema sozlamasini bosmadik. Bayroqni o'zgartirdik — Bazel qolganini qildi.
select() qanday ishlaganini "ichidan" ko'rish
select() haqiqatan swiftc buyrug'ini o'zgartirganini isbotlaymiz. bazel aquery — har bir action'ning aniq buyruq qatorini ko'rsatadi. Standart (dev) holatda:
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' | grep -oE "\-D ?[A-Z]+"
-DDEBUG
-DDEV
Endi staging bayrog'i bilan:
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' --//config:api_env=staging | grep -oE "\-D ?[A-Z]+"
-DDEBUG
-DSTAGING
Mana mexanizm: standartda swiftc -DDEV oldi, stagingda -DSTAGING. select() defines atributini mos shoxga analiz fazasida hal qildi, rules_swift esa buni -D bayrog'i qilib swiftc'ga uzatdi. (-DDEBUG — bu rules_swift'ning fastbuild rejimida avtomatik qo'shadigan bayrog'i; -c opt bilan u yo'qoladi — buni 10-bo'limda ko'ramiz.)
⚠️ Diqqat:
select()rule atributlarida ishlaydi, macro ichida emas. Rasmiy hujjat (docs/configurable-attributes) buni alohida ta'kidlaydi: macro loading fazasida, ya'ni bayroqlar ma'lum bo'lishidan oldin ishlaydi — shuning uchun macroselect()ning qaysi shoxini tanlashini bila olmaydi. Macroselect()ni faqat "qora quti" sifatida rule'ga uzata oladi, lekin uning ichini ko'ra olmaydi. Agar macro ichidaselectqiymatini.upper()qilmoqchi bo'lsangiz,type 'select' has no method upper()xatosini olasiz.
select() uchta shoxdan bittasini config_setting shartiga qarab analiz fazasida tanlaydi; tanlangan qiymat swiftc buyrug'iga -D bayrog'i bo'lib kiradi va action key'ga ta'sir qiladi
Rasm select() oqimini ko'rsatadi: yuqorida uchta shox (dev/staging/prod), o'rtada --//config:api_env bayrog'i qaysi config_settingni yoqishi, pastda tanlangan qiymat swiftc buyrug'iga va undan action key'ga qanday o'tishi.
9. config_setting va o'z build setting'laringiz {#build-settings}
8-bo'limda //config:is_staging degan shartni ishlatdik, lekin uni hali ta'riflamadik. Endi shuni quramiz. Ikki qism kerak:
- Build setting —
api_envdegan loyihaga xos "sozlama". Udev/staging/prodqiymatlardan birini oladi. - **
config_setting** — "api_envstagingga teng bo'lsa, shu shart to'g'ri keladi" degan ko'prik.
--cpu yoki --compilation_mode kabi bayroqlar Bazel'ning ichiga o'rnatilgan (built-in). Lekin bizning api_env — loyihaga xos. Buni Bazel'ni o'zgartirmasdan, .bzl orqali e'lon qilamiz. Eng oson yo'l — bazel_skylib'ning tayyor string_flag rule'i (manba: extending/config → "Predefined settings"):
# config/BUILD.bazel
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# Loyihaga xos build setting: qaysi muhit uchun quramiz.
# flag = command line'dan o'zgartirsa bo'ladigan setting.
string_flag(
name = "api_env",
build_setting_default = "dev",
values = [
"dev",
"staging",
"prod",
],
visibility = ["//visibility:public"],
)
# Har bir muhit uchun bitta config_setting: api_env qiymatiga qarab mos keladi.
config_setting(
name = "is_staging",
flag_values = {":api_env": "staging"},
visibility = ["//visibility:public"],
)
config_setting(
name = "is_prod",
flag_values = {":api_env": "prod"},
visibility = ["//visibility:public"],
)
Bu yerda:
- **
string_flag** —@bazel_skylib//rules:common_settings.bzl'dan keladi.flag = Truema'nosini beradi (string_flagnomidagiflag), ya'ni uni command line'dan--//config:api_env=stagingdeb o'zgartirsa bo'ladi.values = [...]— ruxsat etilgan qiymatlar; boshqa qiymat bersangiz, Bazel xato beradi. - **
config_setting** —flag_valuesorqali bizning build setting'imizga ulanadi:is_stagingshartining ma'nosi "api_envaynanstaging" (manba:docs/configurable-attributes→ "Custom flags").
Bu uch qismning select() bilan birikishi: swift_binary'ning defines = select({"//config:is_staging": [...], ...}) ifodasi, foydalanuvchi --//config:api_env=staging berganda, is_staging to'g'ri kelib, STAGING shoxini tanlaydi.
Command line'ni qisqartirish: --config
--//config:api_env=staging — uzun. .bazelrc'da uni qisqa --config=staging ortiga yashiramiz (manba: run/bazelrc; .bazelrc bilan Bazel 2da tanishganmiz):
# .bazelrc
# Muhit config'lari: --config=staging / --config=prod
build:staging --//config:api_env=staging
build:prod --//config:api_env=prod
Shundan keyin bazel build //... --config=staging yozsangiz, Bazel avtomatik --//config:api_env=staging qo'shadi. Ikkala yo'l ham ishlaydi:
bazel run //cli:envinfo --//config:api_env=staging # to'g'ridan-to'g'ri
bazel run //cli:envinfo --config=staging # .bazelrc orqali qisqa
Real-world: rules_apple ham aynan shu naqshni ishlatadi
Bu "o'yinchoq" misol emas — aynan shu mexanizm rules_apple'ning o'zida ishlaydi. Oldingi darslarda simulyatorni .bazelrc'da shunday tanlaganmiz:
build --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17 Pro"
build --@rules_apple//apple/build_settings:ios_simulator_version="27.0"
ios_simulator_device va ios_simulator_version — bular aynan string_flag build setting'lar. rules_apple ularni o'zining apple/build_settings/BUILD faylida, xuddi biz yuqorida qilganimizdek, bazel_skylib'ning string_flag/bool_flag rule'lari bilan e'lon qiladi (manba: rules_apple apple/build_settings/build_settings.bzl va .../BUILD). Masalan ios_simulator_devicening standart qiymati "". Yana bir misol — apple_support 2.10.0 Xcode versiyasini select() orqali solishtiradigan mexanizm qo'shgan (xcode/xcode_version_at_least.bzl; manba: apple_support release notes, 2026). Ya'ni siz shu darsda o'rgangan string_flag + config_setting + select() — bu rasmiy iOS rule'lari ichida kundalik ishlatiladigan asbob.
📊 Real raqamlar: rules_apple 5.3.0'da simulyator tanlash tartibi (manba: refs skeptic-tekshiruvi, rules_apple manbasi): (1) test target atributlari
device_type/os_version→ (2)ios_simulator_device/ios_simulator_versionbuild setting'lar → (3) native Bazel bayroqlari--ios_simulator_device/--ios_simulator_version→ (4) hech biri yo'q bo'lsa, qattiq kodlangan "iPhone 15" (Xcode 27'da ishlamaydi). Shuning uchun biz build-setting shaklini ishlatamiz — u kelajakka chidamli.
💡 Pro tip:
bool_flagham bor (@bazel_skylib//rules:common_settings.bzl). Masalan--//config:enable_loggingkabi true/false sozlama. Command line'da maxsus sintaksis:--//config:enable_logging(yoqish) va--no//config:enable_logging(o'chirish) (manba:extending/config).
10. --define, -c opt, platforms — konfiguratsiya xaritasi {#other-config}
string_flag/config_settingdan tashqari yana bir nechta konfiguratsiya vositasi bor. Qisqa xarita:
**--define (eski usul).** --define api_env=staging ko'rinishidagi eski sintaksis. config_settingda values = {"define": "api_env=staging"} yoki define_values = {"api_env": "staging"} bilan tutiladi. Rasmiy hujjat aniq aytadi (docs/configurable-attributes): "--define faqat orqaga moslik uchun qo'llab-quvvatlanadi. Iloji bo'lsa, Starlark build setting'lardan foydalaning." Ya'ni yangi kodda string_flag yozing, --define emas.
**-c opt / -c dbg / -c fastbuild (compilation mode).** Bu — built-in bayroq. config_settingda values = {"compilation_mode": "opt"} bilan tutiladi. Buni amalda ko'ramiz: 8-bo'limda swiftc fastbuildda -DDEBUG olgandi. -c opt bilan quradigan bo'lsak:
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' | grep -o "\-DDEBUG"
# -> -DDEBUG (fastbuild)
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' -c opt | grep -o "\-DDEBUG"
# -> (hech narsa; -c opt da -DDEBUG yo'q)
-DDEBUG rejimga qarab paydo bo'lib yo'qoladi. Demak siz defines = select({"//conditions:default": [...]}) bilan, masalan, config_setting(values = {"compilation_mode": "dbg"}) orqali Debug/Release uchun turli qiymat bera olasiz. Rejimlar bilan iOS Debug/Release build'i Bazel 5da batafsil.
Platforms va constraints. Katta loyihalarda har bir bayroqni alohida berish o'rniga ularni to'plam (platform) qilib bog'laysiz. constraint_setting — "enum tipi", constraint_value — "enum qiymati", platform esa ularning to'plami (manba: docs/configurable-attributes → Platforms). select() constraint_valuelarni to'g'ridan-to'g'ri o'qiy oladi:
# misol: OS'ga qarab tanlash (bazel docs'dan moslashtirilgan)
swift_library(
name = "Net",
srcs = ["Net.swift"],
defines = select({
"@platforms//os:ios": ["PLATFORM_IOS"],
"@platforms//os:macos": ["PLATFORM_MAC"],
"//conditions:default": [],
}),
)
iOS kontekstida platforms odatda rules_apple ichida avtomatik sozlanadi (--ios_multi_cpus=sim_arm64 kabi bayroqlar orqali), shuning uchun kundalik ishda ko'pincha string_flag + config_setting yetarli. Platforms'ning to'liq mexanizmi Bazel 5 va Bazel 17da.
⚠️ Diqqat:
select()da to'g'ridan-to'g'ri platform'ni tekshirmang (":my_platform": [...]). Rasmiy hujjat buni taqiqlaydi, chunki semantikasi noaniq: platform ikkita constraint'ga ega bo'lsa-yu, aynan sizning platform'ingiz bo'lmasa, qaysi shox tanlanishi tushunarsiz. O'rnigaconfig_setting(constraint_values = [...])yozing — u "shu constraint'larga ega har qanday platform"ni anglatadi (manba:docs/configurable-attributes→ FAQ).
11. Konfiguratsiya action key'ga qanday ta'sir qiladi {#action-key}
select() nega to'g'ri ishlaydi — ya'ni staging build'i dev build'ining cache'ini "ifloslantirmaydi"? Javob — action keyda. Bu mavzuni Bazel 10: Cacheda to'liq ochamiz, lekin bu yerda ko'prik tashlaymiz, chunki konfiguratsiya aynan shu joyda ishlaydi.
Har bir action (masalan "main.swift'ni swiftc bilan kompilyatsiya qil") uchun Bazel bir action key hisoblaydi. Unga kiradigan narsalar (manba: remote/caching): to'liq buyruq qatori, muhit o'zgaruvchilari, barcha kirish fayllarining content-hash'i, va konfiguratsiya.
select() qaysi shoxni tanlasa, buyruq qatori o'zgaradi: devda -DDEV, stagingda -DSTAGING. Buyruq qatori o'zgargani uchun action key ham o'zgaradi. Natijada:
devbuild vastagingbuild — ikki xil action key → ikki xil cache yozuvi. Ular bir-birini buzmaydi.- Siz
devdanstagingga, keyin yanadevga qaytsangiz —devnatijasi hali ham cache'da turibdi, Bazel uni qayta hisoblamaydi.
Buni cquery bilan ko'rsa bo'ladi. query loading fazasida ishlab, select()ning qaysi shoxini tanlashni bilmaydi (barcha shoxlarni ko'rsatadi). cquery esa analiz fazasidan keyin ishlaydi — u konfiguratsiyani biladi:
bazel cquery //cli:envinfo
//cli:envinfo (71ac6bd)
Qavs ichidagi (71ac6bd) — bu konfiguratsiya hash'i. --config=staging bilan boshqa hash chiqadi, chunki konfiguratsiya boshqacha. Aynan shu hash action key'ga kiradi va select()ning cache'dagi ajralishini ta'minlaydi.
🧠 Esda tuting: "Build = e'lon qilingan kirishlarning sof funksiyasi" degan Bazel printsipini (Bazel 1dan) eslang. Konfiguratsiya — o'sha "kirish"ning bir qismi.
select()qiymatni o'zgartiradi → kirish o'zgaradi → action key o'zgaradi → to'g'ri, mustaqil cache. Shuning uchun Bazel'da "Debug build Release cache'ni buzib qo'ydi" degan muammo yo'q.
12. Starlark'ni debug qilish: print(), fail(), buildifier {#debugging}
Starlark kodingiz xato qilsa, uchta asosiy quroldan foydalanasiz.
print() — loading fazasidagi log
print() xabarni loading fazasida DEBUG log qatori sifatida chiqaradi (3-bo'limdagi demo'da ko'rdik). U faqat debug uchun. .bzl style guide ogohlantiradi (rules/bzl-style): "Ishlab chiqarish (production) kodida print() ishlatmang; u sizning .bzl faylingizning barcha foydalanuvchilarini spam qiladi." Agar macro'da vaqtincha debug kerak bo'lsa, uni if DEBUG: (standart False) ortiga yashiring va topshirishdan oldin olib tashlang.
fail() — qattiq xato
fail() build'ni darhol to'xtatadi va xabar chiqaradi. Bu Starlark'ning raise/exceptsiz yagona xato mexanizmi — xatoni "ushlab" davom etib bo'lmaydi (manba: extending/legacy-macros → Errors). Biz ios_moduleda fail()ni validatsiya uchun ishlatdik: srcs bo'sh bo'lsa, foydalanuvchiga tushunarli xabar beramiz.
Keling, uni ishga tushiramiz — srcs = [] bilan chaqiramiz:
# scratch_fail/BUILD.bazel
load("//tools:defs.bzl", "ios_module")
ios_module(
name = "Broken",
srcs = [],
)
bazel query //scratch_fail:all
ERROR: .../scratch_fail/BUILD.bazel:3:11, in <toplevel>
fail("ios_module(%s): 'srcs' bo'sh bo'lishi mumkin emas" % name)
Error in fail: ios_module(Broken): 'srcs' bo'sh bo'lishi mumkin emas
Xato aniq, loyihaga xos va foydalanuvchini to'g'ri joyga yo'naltiradi. Yaxshi fail() xabari — "nima noto'g'ri va qanday tuzatish kerak"ni aytadi.
select()ning "mos kelmadi" xatosi
select()da //conditions:default yozmasangiz va hech bir shart to'g'ri kelmasa, Bazel aniq xato beradi. Buni ko'rish uchun standart shoxsiz select() yozamiz (api_env standart qiymati dev, lekin faqat is_staging/is_prod shoxlari bor):
bazel build //scratch_nomatch:nomatch
ERROR: .../scratch_nomatch/BUILD.bazel:3:13: configurable attribute "defines"
in //scratch_nomatch:nomatch doesn't match this configuration. Would a default
condition help?
Conditions checked:
//config:is_staging
//config:is_prod
Bazel hatto yechimni ham taklif qilyapti ("Would a default condition help?") va qaysi shartlarni tekshirganini sanaydi. Agar qasddan faqat ma'lum muhitlarni qo'llab-quvvatlasangiz, bu xato foydali — kutilmagan sozlamadan himoya qiladi. Aks holda //conditions:default qo'shing.
buildifier — formatlash va lint
buildifier (brew install buildifier) — BUILD/.bzl fayllari uchun rasmiy formatter va linter (rules/bzl-styleda tavsiya etilgan). Avval formatlash. Mana tartibsiz fayl:
# messy_BUILD.bazel (formatlashdan oldin)
load("@rules_swift//swift:swift_library.bzl","swift_library")
swift_library(name="Core",srcs=["a.swift"],deps=[":Zebra",":Apple",":Mango"])
buildifier messy_BUILD.bazel
# formatlashdan keyin
load("@rules_swift//swift:swift_library.bzl", "swift_library")
swift_library(name = "Core", srcs = ["a.swift"], deps = [":Zebra", ":Apple", ":Mango"])
E'tibor bering: = atrofida va vergullardan keyin bo'sh joy qo'yildi, load'dan keyin bo'sh qator qo'shildi. buildifier load() ichidagi simvollarni va fayldagi load operatorlarini ham alifbo tartibida saralaydi:
# oldin
load("@rules_swift//swift:swift_library.bzl", "swift_library")
load("@bazel_skylib//rules:common_settings.bzl", "string_flag", "bool_flag")
buildifier loadsort.bzl
# keyin: load statement'lar va ularning ichidagi simvollar saralandi
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag")
load("@rules_swift//swift:swift_library.bzl", "swift_library")
Lint rejimi (--lint=warn) uslub ogohlantirishlarini beradi:
buildifier --lint=warn --mode=check messy.bzl
messy.bzl:1: module-docstring: The file has no module docstring.
A module docstring is a string literal (not a comment) which should be the
first statement of a file (it may follow comment lines).
💡 Pro tip:
buildifierni CI'dabuildifier --mode=check --lint=warnbilan ishlating — formatlanmagan yoki lint ogohlantirishi bor fayl PR'ni yiqitsin. Shunda BUILD fayllaringiz butun jamoada bir xil ko'rinishda qoladi. CI sozlamasi Bazel 11da.
Amaliy mashqlar {#mashqlar}
Mashqlarni shu darsdagi starlark_ws workspace'ida bajaring (cli, config, Feature, tools, lang paketlari bilan). Har biridan keyin natijani bazel run/bazel query/bazel build bilan tekshiring — kodni ko'z bilan tekshirish yetarli emas.
- **
bool_flagbilan logging.**config/BUILD.bazel'gabazel_skylib'danbool_flagyuklab,enable_loggingnomli setting qo'shing (standartFalse) va unga mosconfig_settingyozing.cli'ningdefinesiga ikkinchiselect()qo'shib,enable_loggingyoqilgandaLOGGINGdefine'i kiradigan qiling (ikkitaselect()ni+bilan qo'shish mumkin —docs/configurable-attributes→ "Combining selects").main.swift'da#if LOGGINGbilan qo'shimcha qator chiqaring. Tekshiring:bazel run //cli:envinfo --no//config:enable_loggingva--//config:enable_logging. - **
ios_module'ni mustahkamlang.** Macro'gafail()tekshiruvi qo'shing:minimum_os_version"15.0"dan past bo'lsa, tushunarli xato bersin. (Maslahat: string'ni solishtirishdan ehtiyot bo'ling — oddiy<string'lar uchun leksikografik;minimum_os_versionqiymatini ruxsat etilganlar ro'yxati bilan solishtiring yoki.split(".")qiling.) - Modul fabrikasi (comprehension). Yangi
.bzlfaylda legacy macro yozing:many_libs(prefix, count)—for i in range(count)orqalicounttaswift_libraryyaratsin (name = "%s%d" % (prefix, i)).BUILD'dan chaqiring vabazel query //pkg:allbilan hammasi yaratilganini ko'ring. Bu — comprehension/loop faqat.bzl(yoki legacy macro) ichida ishlashining amaliy isboti. - Macro yaratgan target'larni toping.
bazel query --output=build 'attr(generator_function, ios_module, //...)'ni ishga tushiring.ios_moduleyaratgan barcha target'lar ro'yxatini oling. Bu katta kod bazasida "bu target qayerdan keldi?" degan savolga javob beradigan asosiy texnika. - **⭐
selectni macro ichida "ushlashga" urining.** Bir legacy macro yozing, u parametr sifatida kelgandefinesqiymatini[d.upper() for d in defines]qilib o'zgartirmoqchi bo'lsin, vaBUILD'da ungaselect({...})bering. Build qiling vatype 'select' has no method ...xatosini o'qing. Keyin macro'niselectni shunchaki rule'ga uzatadigan qilib tuzating. Bu mashq "negaselect()macro'da ishlamaydi"ni bir umrga esda qoldiradi. - **⭐
cquerybilan konfiguratsiyani kuzating.**bazel cquery //cli:envinfovabazel cquery //cli:envinfo --config=stagingni solishtiring — qavs ichidagi konfiguratsiya hash'i o'zgarishini ko'ring. Keyinbazel config <hash>bilan o'sha konfiguratsiya ichidagi qiymatlarni oching (docs/configurable-attributes→ "Why doesn't my select() choose what I expect?").
O'zingizni tekshiring {#checklist}
- [ ] Starlark'ning Python'dan farq qiladigan kamida 5 ta joyini (
whileyo'q, rekursiya yo'q,classyo'q,importo'rnigaload(), muzlash) va nega shundayligini ayta olaman. - [ ] BUILD faylida nima qilib bo'lmasligini (funksiya e'loni, top-level
for/if) va buni qanday xato chiqishini bilaman. - [ ]
load()sintaksisini,_bilan boshlanadigan simvol eksport qilinmasligini tushunaman. - [ ] Legacy macro va symbolic macro farqini (nom sxemasi, tipli atributlar, visibility himoyasi) ayta olaman.
- [ ]
ios_modulekabi symbolic macro yoza olaman:implementation+attrs,name/visibilityparametrlari, nom sxemasi (name + "_..."). - [ ]
bazel query --output=buildbilan macro ochilishini vagenerator_functionni ko'ra olaman. - [ ]
select()+config_setting+string_flagbilan bitta kodni dev/staging/prod uchun quradigan qilib sozlay olaman. - [ ] Nega
select()macro'da emas, rule atributida ishlashini va u action key'ga qanday ta'sir qilishini tushunaman. - [ ]
print(),fail()vabuildifierbilan Starlark kodini debug/lint qila olaman.
Xulosa {#xulosa}
- Starlark — Python emas, balki ataylab qisqartirilgan konfiguratsiya tili.
while/rekursiya/class/importyo'q, qiymatlarload'dan keyin muzlaydi — hammasi determinizm, hermetiklik va parallel xavfsizlik uchun. - **BUILD'da nima quriladi (deklarativ target'lar),
.bzl'da qanday quriladi (funksiya, macro, rule, konstanta).** Ular orasidagi yagona ko'prik —load(). - Macro takrorlanishni yo'qotadi. Symbolic macro (Bazel 8+) tipli atributlar, nom sxemasi va visibility himoyasini beradi; legacy macro moslashuvchan, lekin "action at a distance"ga yo'l qo'yadi. Yangi kodda symbolic macro'ni tanlang.
- **Bitta
ios_modulechaqiruvi →swift_library+ test kutubxona +ios_unit_test.** Biz uni qurdik,query --output=buildbilan ochilishini ko'rdik va test'ni simulyatordaPASSEDqilib ishlatdik. - **
select()— build vaqtidagi tanlov.**string_flag+config_settingbilan bitta Swift CLI'ni dev/staging/prod uchun qurdik;aqueryswiftcga-DDEVyoki-DSTAGINGo'tganini ko'rsatdi. Aynan shu naqsh rules_apple va apple_support ichida ishlatiladi. - Konfiguratsiya action key'ning bir qismi — shuning uchun turli muhit build'lari bir-birining cache'ini buzmaydi (to'liq Bazel 10da).
- Debug uch quroli:
print()(faqat vaqtincha),fail()(tushunarli xato),buildifier(format + lint, CI'da majburiy). - Keyingi dars: Bazel 5: Birinchi iOS ilova — rules_apple, rules_swift, SwiftUI, Info.plist va simulator
Manbalar {#manbalar}
- Starlark Language — Bazel docs — tiplar, Python'dan farqlar, muzlash (immutability).
- BUILD files — Bazel docs — BUILD vs
.bzl,load(), hermetiklik. - Macros (symbolic) — Bazel docs —
macro(),attrs, nom sxemasi, visibility. - Legacy Macros — Bazel docs — eski usul,
native.,fail(), label hal qilish. - Creating a Symbolic Macro (tutorial) — Bazel docs —
implementation+inherit_attrsnaqshi. - Configurable Build Attributes (select) — Bazel docs —
select(),config_setting, default shart, nega macro'da ishlamaydi. - Configurations / build settings — Bazel docs —
string_flag,bool_flag,--flag_alias, label-typed setting'lar. - .bzl style guide — Bazel docs — macro konvensiyalari,
print()ogohlantirishi, DRY. - Sharing Variables — Bazel docs — BUILD'lar orasida konstanta ulashish.
- bazel-skylib common_settings.bzl —
string_flag/bool_flagmanbasi. - rules_swift — swift_library atributlari —
defines,copts,local_definessemantikasi. - rules_apple — apple/build_settings/build_settings.bzl —
ios_simulator_device/_versionreal build setting'lari. - Bazel 9.0 blogi — WORKSPACE olib tashlandi,
objc_library/cc_*endi@rules_cc'dan yuklanadi,--incompatible_autoload_externallybo'sh. - apple_support releases — 2.10.x, Xcode versiyasini
select()bilan solishtirish.