# 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 `.bzl` fayllar, 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 `.bzl` fayllarini 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 chaqiriqdan `swift_library` + test kutubxona + `ios_unit_test` yasaydigan **symbolic macro** yozasiz.
- `select()` + `config_setting` + `string_flag` bilan `API_BASE_URL`'ni build vaqtida tanlaysiz (dev → staging → prod), bironta `#if`'ni qo'lda o'zgartirmasdan.
- `print()`, `fail()` va `buildifier` bilan Starlark kodini debug va lint qilasiz.

> **🚀 Motivatsiya:** Xcode loyihasida "Debug" va "Release" uchun ikki xil `API_BASE_URL` kerak bo'lsa, odatda `#if DEBUG` yoki `.xcconfig` fayllari bilan ovora bo'lasiz — va qaysi target qaysi qiymatni olishini `project.pbxproj` ichida qidirasiz. Bazel'da bu bitta `select()` — va biz uni shu darsda haqiqiy CLI'da **ishga tushirib**, `dev → staging → prod` o'tishini o'z ko'zimiz bilan ko'ramiz.

---

## Mundarija

1. [Starlark nima va nega Python emas](#starlark-nima)
2. [BUILD fayl vs `.bzl` fayl — mehnat taqsimoti](#build-vs-bzl)
3. [Starlark tili: tiplar, funksiya, comprehension, `load()`](#til)
4. [Macro nima va nega kerak](#macro-nima)
5. [Legacy macro — eski uslub](#legacy-macro)
6. [Symbolic macro va `ios_module`](#symbolic-macro)
7. [Macro'ni "ochib ko'rish": `query --output=build`](#macro-expansion)
8. [`select()` — build vaqtidagi tanlov](#select)
9. [`config_setting` va o'z build setting'laringiz](#build-settings)
10. [`--define`, `-c opt`, platforms — konfiguratsiya xaritasi](#other-config)
11. [Konfiguratsiya action key'ga qanday ta'sir qiladi](#action-key)
12. [Starlark'ni debug qilish: `print()`, `fail()`, `buildifier`](#debugging)
13. [Amaliy mashqlar](#mashqlar)
14. [O'zingizni tekshiring](#checklist)
15. [Xulosa](#xulosa)
16. [Manbalar](#manbalar)

---

## 1. Starlark nima va nega Python emas {#starlark-nima}

Oldingi darsda ([Bazel 3: Asosiy tushunchalar](/ios-lessons/bazel-03-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:

1. **Deterministik (aniq natija).** Bir xil kirish → har doim bir xil chiqish. Agar `BUILD` fayli tasodifiy son, hozirgi vaqt yoki diskdagi faylga qarab har safar boshqacha target yasasa, cache ishonchsiz bo'lib qoladi.
2. **Hermetik (yopiq).** `BUILD`/`.bzl` kodi 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).
3. **Parallel va xavfsiz.** Bazel minglab `.bzl` va `BUILD` fayllarini 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 `while` yoki `class` yozsangiz, 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:

```starlark
# foo.bzl
var = []            # ro'yxat e'lon qilamiz

def fct():
    var.append(5)   # ichiga qiymat qo'shamiz

fct()               # funksiyani chaqiramiz -> var endi [5]
```

```starlark
# 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 konteksti**da 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` (yoki `BUILD.bazel`)** — _deklarativ ro'yxat_. Bu yerda siz faqat **target e'lon qilasiz**: `swift_library(...)`, `ios_application(...)`. BUILD faylida funksiya yozib bo'lmaydi, top-level `for`/`if` yozib bo'lmaydi, `*args`/`**kwargs` ishlatib bo'lmaydi.
- **`.bzl` (Bazel extension)** — _ta'riflar_. Bu yerda konstantalar, funksiyalar, macro'lar va rule'lar yashaydi. `.bzl` faylidan tayyor narsalarni `BUILD`'ga `load()` 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:

```starlark
# scratch_def/BUILD.bazel
def helper():
    return 1
```

```bash
bazel query //scratch_def:all
```

```text
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](/lessons/images/bazel/bazel_04_build_vs_bzl.svg)

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 query` yoki IDE) oson o'qishi kerak; shuning uchun uni sodda va deklarativ saqlang.

`load()` sintaksisi (`concepts/build-files`dan):

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

```starlark
# 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):

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

```bash
bazel query //lang:all
```

```text
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 fazasi**da 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'da `for` operatorini 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'da `if` statement 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/language`dan): `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/`select` avtomatik 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. `.bzl` style guide ogohlantiradi (`rules/bzl-style`): macro ichidagi target'lar `bazel query` natijasida 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-style`da 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:

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

```starlark
# legacy/BUILD.bazel
load("//tools:legacy.bzl", "swift_module_legacy")

swift_module_legacy(
    name = "Greeter",
    srcs = ["Sources/Greeter.swift"],
)
```

```bash
bazel query //legacy:all
```

```text
//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 `BUILD` faylida 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" ilovasi](/ios-lessons/bazel-13-loyiha-vazifalar)da 4 ta modulni qurishda ishlatadigan macro'imiz bo'ladi.

```starlark
# 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/macros`ga mos):

- **`implementation` funksiyasi har bir argument uchun alohida parametr oladi** (rule implementatsiyasidan farqli — unda bitta `ctx` bo'ladi). Va u **`name` hamda `visibility`** parametrlarini olishi _shart_.
- **`attrs`** — tipli. `attr.label_list`, `attr.string` va h.k. `name` va `visibility` avtomatik qo'shiladi, ularni `attrs`'ga yozmaysiz.
- **`configurable = False`** — bu atributga `select()` berib bo'lmaydi degani. `srcs`'ni `configurable=False` qildik, chunki `glob()` natijasini kutyapmiz, `select` emas.
- **`mandatory = True`** — `srcs` berilmasa, Bazel darhol xato beradi.
- Symbolic macro **qiymat qaytara olmaydi**, argumentlarini o'zgartira olmaydi, `glob()` chaqira olmaydi. Shuning uchun `glob()` ni `BUILD` faylida chaqiramiz va natijani macro'ga uzatamiz.

`BUILD` faylida ishlatamiz:

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

```bash
bazel query //Feature:all
```

```text
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:

1. **Symbolic macro nom sxemasini majburlaydi** (`extending/macros`): `my_macro(name = "foo")` faqat `foo`, yoki `foo_...`, `foo-...`, `foo....` bilan boshlanadigan target yarata oladi. `FeatureTests` — `Feature`'dan keyin darhol `T` keladi, ajratuvchi yo'q → taqiqlangan. To'g'ri nom: `Feature_tests`.
2. **`ios_unit_test`ning o'zi — symbolic macro!** Xato "submacro" deb aytdi; demak rules_apple 5.3.0'da `ios_unit_test` ichida yana macro'lar chaqiriladi (manba: rules_apple `apple/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 `name` bilan 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_library`ga `visibility = visibility` berdik (ya'ni chaqiruvchi so'ragan ko'rinish), lekin `ios_unit_test`ga **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_tests`ga `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:

```bash
bazel query //Feature:all
```

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

```bash
bazel query --output=build //Feature:all
```

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

1. **`generator_function = "ios_module"` va `generator_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.
2. **`Feature` kutubxonasi `visibility = ["//visibility:public"]`** — biz `visibility = visibility` berganimiz uchun. Lekin **`Feature_tests` esa `visibility = ["//tools:__pkg__"]`** — biz unga hech narsa bermaganimiz uchun u macro paketiga (`//tools`) yashirildi. Bu symbolic macro visibility himoyasining amaldagi isboti.
3. **`runner = "@rules_apple//apple/testing/default_runner:ios_default_runner"`** — `ios_unit_test` o'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:

```bash
bazel build //Feature:Feature
```

```text
Target //Feature:Feature up-to-date:
  bazel-bin/Feature/Feature.swiftmodule
  bazel-bin/Feature/libFeature.a
INFO: Elapsed time: 3.956s
```

```bash
bazel test //Feature:Feature_tests --test_output=errors
```

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

```bash
bazel test //Feature:Feature_tests
```

```text
//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: Testlar](/ios-lessons/bazel-06-testlar)da 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](/lessons/images/bazel/bazel_04_macro_expansion.svg)

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`, `staging`da `https://staging.api.vazifalar.uz`, `prod`da `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:

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

```starlark
# 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'dagidek `nom=qiymat` emas, balki shunchaki **identifikator** (`STAGING`). Har biri `-D` bilan oldiga qo'shilib, `swiftc`'ga uzatiladi va `#if STAGING`'da ishlatiladi (manba: rules_swift `swift_library` docs, `defines` atributi). Qiymatli konfiguratsiya (masalan URL'ning o'zini `-D` orqali uzatish) Swift'da ishlamaydi — shuning uchun `#if` bilan 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:

```bash
bazel run //cli:envinfo
```

```text
env=dev url=http://localhost:8080
```

`:is_staging` shartini yoqadigan bayroq bilan (buni 9-bo'limda sozlaymiz):

```bash
bazel run //cli:envinfo --config=staging
```

```text
env=staging url=https://staging.api.vazifalar.uz
```

```bash
bazel run //cli:envinfo --config=prod
```

```text
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:

```bash
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' | grep -oE "\-D ?[A-Z]+"
```

```text
-DDEBUG
-DDEV
```

Endi `staging` bayrog'i bilan:

```bash
bazel aquery 'mnemonic("SwiftCompile", //cli:envinfo)' --//config:api_env=staging | grep -oE "\-D ?[A-Z]+"
```

```text
-DDEBUG
-DSTAGING
```

Mana mexanizm: standartda `swiftc` `-DDEV` oldi, `staging`da `-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 macro `select()`ning qaysi shoxini tanlashini bila olmaydi. Macro `select()`ni faqat "qora quti" sifatida rule'ga uzata oladi, lekin uning ichini ko'ra olmaydi. Agar macro ichida `select` qiymatini `.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](/lessons/images/bazel/bazel_04_select_flow.svg)

Rasm `select()` oqimini ko'rsatadi: yuqorida uchta shox (dev/staging/prod), o'rtada `--//config:api_env` bayrog'i qaysi `config_setting`ni 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:

1. **Build setting** — `api_env` degan loyihaga xos "sozlama". U `dev`/`staging`/`prod` qiymatlardan birini oladi.
2. **`config_setting`** — "`api_env` `staging`ga 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"):

```starlark
# 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 = True` ma'nosini beradi (`string_flag` nomidagi `flag`), ya'ni uni command line'dan `--//config:api_env=staging` deb o'zgartirsa bo'ladi. `values = [...]` — ruxsat etilgan qiymatlar; boshqa qiymat bersangiz, Bazel xato beradi.
- **`config_setting`** — `flag_values` orqali bizning build setting'imizga ulanadi: `is_staging` shartining ma'nosi "`api_env` aynan `staging`" (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 2](/ios-lessons/bazel-02-ornatish)da tanishganmiz):

```text
# .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:

```bash
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:

```text
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_device`ning 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_version` build 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_flag` ham bor (`@bazel_skylib//rules:common_settings.bzl`). Masalan `--//config:enable_logging` kabi 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_setting`dan tashqari yana bir nechta konfiguratsiya vositasi bor. Qisqa xarita:

**`--define` (eski usul).** `--define api_env=staging` ko'rinishidagi eski sintaksis. `config_setting`da `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_setting`da `values = {"compilation_mode": "opt"}` bilan tutiladi. Buni amalda ko'ramiz: 8-bo'limda `swiftc` `fastbuild`da `-DDEBUG` olgandi. `-c opt` bilan quradigan bo'lsak:

```bash
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 5](/ios-lessons/bazel-05-ios-ilova)da 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_value`larni to'g'ridan-to'g'ri o'qiy oladi:

```starlark
# 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](/ios-lessons/bazel-05-ios-ilova) va [Bazel 17](/ios-lessons/bazel-17-custom-rules)da.

> **⚠️ 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'rniga `config_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 key**da. Bu mavzuni [Bazel 10: Cache](/ios-lessons/bazel-10-cache)da 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: `dev`da `-DDEV`, `staging`da `-DSTAGING`. Buyruq qatori o'zgargani uchun action key ham o'zgaradi. Natijada:

- `dev` build va `staging` build — **ikki xil action key** → ikki xil cache yozuvi. Ular bir-birini buzmaydi.
- Siz `dev`dan `staging`ga, keyin yana `dev`ga qaytsangiz — `dev` natijasi 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:

```bash
bazel cquery //cli:envinfo
```

```text
//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 1](/ios-lessons/bazel-01-kirish)dan) 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`/`except`siz yagona xato mexanizmi — xatoni "ushlab" davom etib bo'lmaydi (manba: `extending/legacy-macros` → Errors). Biz `ios_module`da `fail()`ni validatsiya uchun ishlatdik: `srcs` bo'sh bo'lsa, foydalanuvchiga tushunarli xabar beramiz.

Keling, uni ishga tushiramiz — `srcs = []` bilan chaqiramiz:

```starlark
# scratch_fail/BUILD.bazel
load("//tools:defs.bzl", "ios_module")

ios_module(
    name = "Broken",
    srcs = [],
)
```

```bash
bazel query //scratch_fail:all
```

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

```bash
bazel build //scratch_nomatch:nomatch
```

```text
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-style`da tavsiya etilgan). Avval formatlash. Mana tartibsiz fayl:

```starlark
# 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"])
```

```bash
buildifier messy_BUILD.bazel
```

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

```starlark
# oldin
load("@rules_swift//swift:swift_library.bzl", "swift_library")
load("@bazel_skylib//rules:common_settings.bzl", "string_flag", "bool_flag")
```

```bash
buildifier loadsort.bzl
```

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

```bash
buildifier --lint=warn --mode=check messy.bzl
```

```text
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:** `buildifier`ni CI'da `buildifier --mode=check --lint=warn` bilan ishlating — formatlanmagan yoki lint ogohlantirishi bor fayl PR'ni yiqitsin. Shunda BUILD fayllaringiz butun jamoada bir xil ko'rinishda qoladi. CI sozlamasi [Bazel 11](/ios-lessons/bazel-11-ci-cd)da.

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

1. **`bool_flag` bilan logging.** `config/BUILD.bazel`'ga `bazel_skylib`'dan `bool_flag` yuklab, `enable_logging` nomli setting qo'shing (standart `False`) va unga mos `config_setting` yozing. `cli`'ning `defines`iga ikkinchi `select()` qo'shib, `enable_logging` yoqilganda `LOGGING` define'i kiradigan qiling (ikkita `select()`ni `+` bilan qo'shish mumkin — `docs/configurable-attributes` → "Combining selects"). `main.swift`'da `#if LOGGING` bilan qo'shimcha qator chiqaring. Tekshiring: `bazel run //cli:envinfo --no//config:enable_logging` va `--//config:enable_logging`.

2. **`ios_module`'ni mustahkamlang.** Macro'ga `fail()` 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_version` qiymatini ruxsat etilganlar ro'yxati bilan solishtiring yoki `.split(".")` qiling.)

3. **Modul fabrikasi (comprehension).** Yangi `.bzl` faylda legacy macro yozing: `many_libs(prefix, count)` — `for i in range(count)` orqali `count` ta `swift_library` yaratsin (`name = "%s%d" % (prefix, i)`). `BUILD`'dan chaqiring va `bazel query //pkg:all` bilan hammasi yaratilganini ko'ring. Bu — comprehension/loop faqat `.bzl` (yoki legacy macro) ichida ishlashining amaliy isboti.

4. **Macro yaratgan target'larni toping.** `bazel query --output=build 'attr(generator_function, ios_module, //...)'` ni ishga tushiring. `ios_module` yaratgan barcha target'lar ro'yxatini oling. Bu katta kod bazasida "bu target qayerdan keldi?" degan savolga javob beradigan asosiy texnika.

5. **⭐ `select`ni macro ichida "ushlashga" urining.** Bir legacy macro yozing, u parametr sifatida kelgan `defines` qiymatini `[d.upper() for d in defines]` qilib o'zgartirmoqchi bo'lsin, va `BUILD`'da unga `select({...})` bering. Build qiling va `type 'select' has no method ...` xatosini o'qing. Keyin macro'ni `select`ni shunchaki rule'ga uzatadigan qilib tuzating. Bu mashq "nega `select()` macro'da ishlamaydi"ni bir umrga esda qoldiradi.

6. **⭐ `cquery` bilan konfiguratsiyani kuzating.** `bazel cquery //cli:envinfo` va `bazel cquery //cli:envinfo --config=staging` ni solishtiring — qavs ichidagi konfiguratsiya hash'i o'zgarishini ko'ring. Keyin `bazel 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 (`while` yo'q, rekursiya yo'q, `class` yo'q, `import` o'rniga `load()`, 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_module` kabi symbolic macro yoza olaman: `implementation` + `attrs`, `name`/`visibility` parametrlari, nom sxemasi (`name + "_..."`).
- [ ] `bazel query --output=build` bilan macro ochilishini va `generator_function`ni ko'ra olaman.
- [ ] `select()` + `config_setting` + `string_flag` bilan 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()` va `buildifier` bilan Starlark kodini debug/lint qila olaman.

## Xulosa {#xulosa}

- **Starlark — Python emas, balki ataylab qisqartirilgan konfiguratsiya tili.** `while`/rekursiya/`class`/`import` yo'q, qiymatlar `load`'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_module` chaqiruvi → `swift_library` + test kutubxona + `ios_unit_test`.** Biz uni qurdik, `query --output=build` bilan ochilishini ko'rdik va test'ni simulyatorda `PASSED` qilib ishlatdik.
- **`select()` — build vaqtidagi tanlov.** `string_flag` + `config_setting` bilan bitta Swift CLI'ni dev/staging/prod uchun qurdik; `aquery` `swiftc`ga `-DDEV` yoki `-DSTAGING` o'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 10](/ios-lessons/bazel-10-cache)da).
- **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](/ios-lessons/bazel-05-ios-ilova)

## Manbalar {#manbalar}

1. [Starlark Language — Bazel docs](https://bazel.build/rules/language) — tiplar, Python'dan farqlar, muzlash (immutability).
2. [BUILD files — Bazel docs](https://bazel.build/concepts/build-files) — BUILD vs `.bzl`, `load()`, hermetiklik.
3. [Macros (symbolic) — Bazel docs](https://bazel.build/extending/macros) — `macro()`, `attrs`, nom sxemasi, visibility.
4. [Legacy Macros — Bazel docs](https://bazel.build/extending/legacy-macros) — eski usul, `native.`, `fail()`, label hal qilish.
5. [Creating a Symbolic Macro (tutorial) — Bazel docs](https://bazel.build/rules/macro-tutorial) — `implementation` + `inherit_attrs` naqshi.
6. [Configurable Build Attributes (select) — Bazel docs](https://bazel.build/docs/configurable-attributes) — `select()`, `config_setting`, default shart, nega macro'da ishlamaydi.
7. [Configurations / build settings — Bazel docs](https://bazel.build/extending/config) — `string_flag`, `bool_flag`, `--flag_alias`, label-typed setting'lar.
8. [.bzl style guide — Bazel docs](https://bazel.build/rules/bzl-style) — macro konvensiyalari, `print()` ogohlantirishi, DRY.
9. [Sharing Variables — Bazel docs](https://bazel.build/build/share-variables) — BUILD'lar orasida konstanta ulashish.
10. [bazel-skylib common_settings.bzl](https://github.com/bazelbuild/bazel-skylib/blob/main/rules/common_settings.bzl) — `string_flag`/`bool_flag` manbasi.
11. [rules_swift — swift_library atributlari](https://github.com/bazelbuild/rules_swift/blob/main/doc/rules.md) — `defines`, `copts`, `local_defines` semantikasi.
12. [rules_apple — apple/build_settings/build_settings.bzl](https://github.com/bazelbuild/rules_apple/blob/main/apple/build_settings/build_settings.bzl) — `ios_simulator_device`/`_version` real build setting'lari.
13. [Bazel 9.0 blogi](https://blog.bazel.build/2026/01/20/bazel-9.html) — WORKSPACE olib tashlandi, `objc_library`/`cc_*` endi `@rules_cc`'dan yuklanadi, `--incompatible_autoload_externally` bo'sh.
14. [apple_support releases](https://github.com/bazelbuild/apple_support/releases) — 2.10.x, Xcode versiyasini `select()` bilan solishtirish.
