# Bazel 11: CI/CD — GitHub Actions, remote cache, faqat o'zgarganini build/test qilish va release

> **Holat:** 2026-yil oktabr. Bu darsda Bazel'ning jamoaviy va CI qudratini to'liq ishga tushiramiz: GitHub Actions (macos-15 Apple Silicon runner), remote cache bilan bog'lash, Bazel query orqali **selective testing** (faqat PR'dagi o'zgarishga taalluqli testlarni topish va yugurtirish), test hisobotlari, sharding hamda App Store / TestFlight uchun release pipeline qurish. Kod misollari Bazel 9.3.0 (Bazelisk 1.29.0), rules_apple 5.3.0, rules_swift 4.2.1, Xcode 27.1 va GitHub Actions runner'larida tekshirilgan.

> **Bu darsdan keyin siz:** har bir Pull Request'da butun loyihani 30 daqiqa kutmasdan, 1–2 daqiqada faqat o'zgargan modullarni test qiladigan, CI'dan developerlar uchun build cache isitadigan (warm qiladigan) va release'ni avtomatlashtiradigan to'liq CI/CD pipeline yarata olasiz.

- Mobil CI nega qimmat va sekin ekanini hamda Bazel uni qanday qilib 80–90% ga arzonlashtirishini raqamlar bilan ko'rasiz.
- GitHub Actions'da macOS runner'larni (`macos-14`, `macos-15` M1/M2/M3 arxitektura) to'g'ri sozlashni va Bazelisk keshini boshqarishni o'rganasiz.
- CI va lokal dasturchilar o'rtasida Remote Cache rolini xavfsiz taqsimlaysiz: CI yozadi (`upload=true`), dasturchilar faqat o'qiydi.
- **Selective testing** mo''jizasini o'z qo'lingiz bilan qurasiz: `git diff` + `bazel query 'tests(rdeps(...))'` yordamida faqat o'zgarish tekkan testlarni yugurtirasiz.
- Flaky testlarni `--flaky_test_attempts` va test sharding (`--test_sharding_strategy`, `shard_count`) bilan nazorat qilasiz.
- TestFlight va App Store uchun `.ipa` tayyorlash, provisioning profile va imzolash bosqichini CI'da xavfsiz o'rnatasiz.

## Mundarija

1. [Mobil CI og'rig'i: 30 daqiqa va qimmat runner'lar](#mobil-ci-ogriq)
2. [GitHub Actions runner arxitekturasi: macos-14 va macos-15](#github-runners)
3. [Bazelisk va toolchain'ni CI'da keshlashtirish](#bazelisk-ci)
4. [CI'da Remote Cache xavfsizligi: Read vs Write](#remote-cache-ci)
5. [Selective Testing: faqat o'zgargan kodni test qilish](#selective-testing)
6. [To'liq GitHub Actions PR workflow namunasi](#pr-workflow)
7. [Test natijalari, xatolar va JUnit XML hisobotlari](#test-hisobotlari)
8. [Flaky testlar va Sharding bilan kurashish](#flaky-sharding)
9. [Release Workflow: TestFlight va App Store .ipa](#release-workflow)
10. [Real kompaniyalar tajribasi va narx tejash](#real-tajriba)
11. [Amaliy mashqlar](#mashqlar)
12. [O'zingizni tekshiring](#checklist)
13. [Xulosa](#xulosa)
14. [Manbalar](#manbalar)

---

## 1. Mobil CI og'rig'i: 30 daqiqa va qimmat runner'lar {#mobil-ci-ogriq}

iOS dasturchilarining kundalik ishida CI (Continuous Integration) ko'pincha eng og'riqli nuqtadir. Backend olamida Docker konteynerlari 30 soniyada ko'tarilib, Linux'da bepul yoki arzon runner'larda soniyalar ichida testlar o'tadi. Mobil olamda esa manzara butunlay boshqacha:

1. **macOS runner narxi:** GitHub Actions'da standart Linux runner daqiqasi bepul (yoki $0.008) bo'lsa, macOS runner'lar $0.08 dan $0.16 gacha — ya'ni **10 barobar qimmatroq**.
2. **"Clean build" azobi:** Xcode loyihalarida kesh buzilmasligi uchun jamoalar CI'da har safar `xcodebuild clean` yoki toza muhit ochishadi. Katta loyihada bu 20–40 daqiqa vaqt oladi.
3. **Navbatda turish:** Jamoada 15 dasturchi bo'lsa va har bir PR 30 daqiqa yursa, macOS runner'lar band bo'lib qoladi, PR merge qilish uchun soatlab navbat kutiladi.

![iOS CI/CD Pipeline: Bazel + GitHub Actions oqimi va xarajatlar taqqoslanishi](/lessons/images/bazel/bazel_11_ci_pipeline.svg)

10-darsda ko'rganimizdek, Bazel uchun build — bu sof matematik funksiya. Agar siz faqat `Core/Book.swift` faylida bitta qatorni o'zgartirgan bo'lsangiz, nega CI butun ilovani, UI kutubxonalarni va aloqasi bo'lmagan 200 ta testni boshidan kompilyatsiya qilishi kerak?

Bazel CI'da ikkita asosiy kuch beradi:
- **Remote Cache hit:** O'zgarmagan har bir action (Swift compile, asset compilation, link) tarmoq keshidan bir necha millisoniyada tortib olinadi.
- **Selective Testing (rdeps):** Graf tahlili orqali faqat o'sha o'zgargan faylga bevosita yoki bilvosita bog'liq testlar ishga tushiriladi. Qolganlari hatto boshlanmaydi ham!

---

## 2. GitHub Actions runner arxitekturasi: macos-14 va macos-15 {#github-runners}

2026-yil holatiga ko'ra, GitHub Actions'da Intel (x86_64) macOS runner'lar butunlay eskirgan va olib tashlangan. Bugungi standart:

| Runner nomi | CPU Arxitekturasi | Apple Chip | RAM | Xcode Standart |
|:---|:---|:---|:---|:---|
| `macos-14` | arm64 (Apple Silicon) | M1 / M2 vCPU | 14 GB | Xcode 15 / 16 |
| `macos-15` | arm64 (Apple Silicon) | M3 / M4 vCPU | 14–28 GB | Xcode 26 / 27 |

> **⚠️ Muhim qoida:** Lokal dasturchi mashinasi (Apple Silicon arm64) va CI runner arxitekturasi bir xil bo'lishi shart (`darwin_arm64`). Aks holda 10-darsda ko'rganimizdek, platform farqi tufayli action key mos kelmaydi va remote cache miss beradi!

Runner'da Xcode versiyasini to'g'ri tanlash uchun `develop` bosqichida `xcode-select` ishlatiladi:

```yaml
- name: Select Xcode Version
  run: |
    sudo xcode-select -s /Applications/Xcode_27.1.app/Contents/Developer
    xcodebuild -version
```

---

## 3. Bazelisk va toolchain'ni CI'da keshlashtirish {#bazelisk-ci}

Bazelisk — rasmiy Bazel launcher. U loyihadagi `.bazelversion` fayliga qarab kerakli Bazel binarini avtomatik yuklab oladi. Har bir CI ishga tushganda Bazel'ni internetdan qayta tortib o'tirmaslik uchun GitHub Actions cache action'dan foydalanamiz:

```yaml
- name: Cache Bazelisk and tools
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/bazelisk
      ~/.cache/bazel
    key: ${{ runner.os }}-bazelisk-${{ hashFiles('.bazelversion') }}
    restore-keys: |
      ${{ runner.os }}-bazelisk-
```

Bazelisk'ning o'zi GitHub Actions runner'larida oldindan o'rnatilgan bo'ladi. Agar o'rnatilmagan bo'lsa:

```bash
brew install bazelisk
```

---

## 4. CI'da Remote Cache xavfsizligi: Read vs Write {#remote-cache-ci}

Eng xavfli xatolardan biri — har qanday PR yoki xohlagan shoxchaning natijasini asosiy Remote Cache'ga yozishga ruxsat berish (cache poisoning). Noto'g'ri yozilgan binar butun jamoaning lokal build'larini buzishi mumkin.

Shuning uchun qat'iy qoida joriy etiladi:

1. **Pull Request (PR) oqimida:** Faqat o'qish (Read-only).
   `--remote_upload_local_results=false`
2. **Main / Master (Merged) oqimida:** O'qish va yozish (Read & Write).
   `--remote_upload_local_results=true`

Buni `.bazelrc` faylida config profillari orqali hal qilamiz:

```ini
# .bazelrc

# Umumiy remote cache sozlamalari
build:remote_shared --remote_cache=grpcs://cache.buildbuddy.io
build:remote_shared --remote_download_outputs=toplevel

# CI faqat o'qish uchun (PR'lar)
build:ci_pr --config=remote_shared
build:ci_pr --remote_upload_local_results=false

# CI asosiy shoxcha uchun (keshni isitish)
build:ci_main --config=remote_shared
build:ci_main --remote_upload_local_results=true
build:ci_main --remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}
```

GitHub Actions ichida API kalitni faqat `main` shoxchasida yoki ruxsat berilgan maxfiy parametr (Secret) orqali uzatamiz:

```yaml
- name: Bazel Test on PR
  run: |
    bazel test --config=ci_pr //...
  env:
    BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_READ_ONLY_KEY }}
```

---

## 5. Selective Testing: faqat o'zgargan kodni test qilish {#selective-testing}

Monolit yoki ko'p modulli loyihalarda har bir PR'da `//...` (butun repo) ni test qilish katta resurs talab qiladi. Hatto remote cache bo'lsa ham, test runner simulyatorni ochishi va xotirada tekshirishi kerak.

Bazel'ning eng katta ustunligi — **dependency graph**ni mukammal bilishi. `bazel query` orqali "Qaysi testlar aynan manashu o'zgargan fayllarga bog'liq?" degan savolga 0.3 soniyada javob olamiz!

![Selective Testing: rdeps orqali o'zgargan moduldan yuqoriga qarab ta'sirlangan testlarni filtrlash](/lessons/images/bazel/bazel_11_selective_testing.svg)

### Qanday ishlaydi?

1. Git orqali `origin/main` bilan solishtirib o'zgargan fayllarni olamiz:
   ```bash
   CHANGED_FILES=$(git diff --name-only origin/main...HEAD)
   ```
2. O'zgargan fayllarni Bazel manba target'lariga o'giramiz:
   ```bash
   # Masalan: Core/Book.swift -> //Core:Core
   ```
3. `rdeps(//..., target)` — berilgan target'dan **tepaga** qarab barcha teskari bog'liqliklarni topadi.
4. `tests(...)` — topilgan natijalar ichidan faqat `*_test` qoidalarini ajratib oladi.

Bir qatorlik sehrli bash skripti (`scripts/ci_affected_tests.sh`):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Asosiy shoxchadan farqni topamiz
CHANGED_FILES=$(git diff --name-only origin/main...HEAD)

if [ -z "$CHANGED_FILES" ]; then
  echo "Hech qanday fayl o'zgarmagan. Test o'tkazib yuboriladi."
  exit 0
fi

echo "O'zgargan fayllar:"
echo "$CHANGED_FILES"

# Fayllarni bazel query formatiga yig'amiz
TARGET_QUERY=""
for f in $CHANGED_FILES; do
  # Faqat loyiha fayllari (BUILD, Swift, h, json)
  if [[ "$f" =~ \.(swift|m|h|BUILD|bazel|json|xcassets)$ ]]; then
    TARGET_QUERY="$TARGET_QUERY $f"
  fi
done

if [ -z "$TARGET_QUERY" ]; then
  echo "Build target'lariga daxli yo'q fayllar o'zgargan. Test talab etilmaydi."
  exit 0
fi

# Ta'sirlangan testlarni qidirish
echo "Ta'sirlangan testlarni aniqlash..."
AFFECTED_TESTS=$(bazel query --keep_going \
  "tests(rdeps(//..., set($TARGET_QUERY)))" 2>/dev/null || true)

if [ -z "$AFFECTED_TESTS" ]; then
  echo "O'zgarishlarga bog'liq hech qanday test topilmadi."
else
  echo "Topilgan ta'sirlangan testlar:"
  echo "$AFFECTED_TESTS"
  # Faqat shu testlarni yugurtiramiz!
  bazel test --config=ci_pr $AFFECTED_TESTS
fi
```

Bu usul bilan 200 ta testdan iborat loyihada bitta modul o'zgarsa, atigi 3 ta test yuguradi. Qolgan 197 ta test soniyalarni va simulyator resursini tejaydi!

---

## 6. To'liq GitHub Actions PR workflow namunasi {#pr-workflow}

Keling, haqiqiy ishlab chiqarish darajasidagi `.github/workflows/ios_pr.yml` faylini tuzamiz:

```yaml
name: iOS CI - Pull Request

on:
  pull_request:
    branches: [ main, develop ]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  validate-and-test:
    runs-on: macos-15
    timeout-minutes: 25

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # git diff to'liq ishlashi uchun zarur

      - name: Select Xcode 27.1
        run: |
          sudo xcode-select -s /Applications/Xcode_27.1.app/Contents/Developer
          xcodebuild -version

      - name: Cache Bazel Tools
        uses: actions/cache@v4
        with:
          path: ~/.cache/bazelisk
          key: ${{ runner.os }}-bazelisk-${{ hashFiles('.bazelversion') }}

      - name: Run Buildifier (Linter & Format Check)
        run: |
          # BUILD fayllar to'g'ri formatlanganini tekshiramiz
          bazel run @buildifier_prebuilt//:buildifier -- -mode=check -r .

      - name: Selective Test (Faqat ta'sirlanganlar)
        env:
          BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_READ_ONLY_KEY }}
        run: |
          chmod +x scripts/ci_affected_tests.sh
          ./scripts/ci_affected_tests.sh

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: bazel-testlogs/**/test.xml
```

---

## 7. Test natijalari, xatolar va JUnit XML hisobotlari {#test-hisobotlari}

Bazel testlarni hermetik sandbox'da yugurtiradi. Test bajarilganda har bir test target uchun `bazel-testlogs/` papkasida avtomatik ravishda standart **JUnit XML** (`test.xml`) va to'liq log fayl (`test.log`) hosil bo'ladi.

Muhim flaglar:

```bash
# Faqat xato bergan testlarning konsol chiqishini ko'rsatish
bazel test --test_output=errors //...

# Har bir testning to'liq logini konsolga chiqarish (debug payti)
bazel test --test_output=all //Core:CoreTests

# Test muvaffaqiyatsiz bo'lsa darhol to'xtatmasdan, qolganlarini ham tekshirish
bazel test --keep_going //...
```

GitHub Actions'da test natijalarini to'g'ridan-to'g'ri PR interfeysida ko'rsatish uchun mashhur `dorny/test-reporter` action'idan foydalanish mumkin:

```yaml
- name: Publish Test Report
  uses: dorny/test-reporter@v1
  if: always()
  with:
    name: Bazel XCTest Results
    path: 'bazel-testlogs/**/test.xml'
    reporter: java-junit
```

---

## 8. Flaky testlar va Sharding bilan kurashish {#flaky-sharding}

Mobil testlarda tarmoq kutishi, animatsiyalar yoki simulyator kechikishi tufayli ba'zan bir xil test bir marta o'tib, bir marta yiqiladi. Bunday testlar **Flaky test** deb ataladi.

### 1. Avtomatik qayta urinish (`--flaky_test_attempts`)

Bazel'da flaky testlar tufayli butun CI pipeline qizilga aylanmasligi uchun o'rnatilgan qayta urinish mexanizmi bor:

```bash
# Agar test yiqilsa, uni yana 2 marta qayta yugurtirib ko'radi
bazel test --flaky_test_attempts=3 //...
```

Agar test 2-urinishda o'tsa, Bazel uni `FLAKY` statusi bilan belgilaydi (build yiqilmaydi, lekin hisobotda ogohlantirish beradi).

### 2. Test Sharding (Katta testlarni bo'lib yuborish)

Agar bitta test target ichida 150 ta UI test bo'lsa va u 15 daqiqa vaqt olsa, uni target darajasida bir nechta parallel bo'laklarga bo'lish mumkin:

```python
# CoreTests/BUILD.bazel
ios_unit_test(
    name = "CoreLargeTests",
    srcs = glob(["*.swift"]),
    shard_count = 4, # 4 ta alohida parallel jarayonga bo'linadi!
    deps = ["//Core:Core"],
)
```

Bazel testlarni avtomatik tarzda 4 ta runner o'rtasida teng taqsimlaydi va umumiy test vaqti 4 barobarga qisqaradi!

---

## 9. Release Workflow: TestFlight va App Store .ipa {#release-workflow}

CI nafaqat test qiladi, balki asosiy shoxchaga merge bo'lganda (`main`) avtomatik ravishda imzolangan `.ipa` yig'ishi va TestFlight'ga jo'natishi kerak.

9-darsda aytganimizdek, Bazel'da `.ipa` yig'ish Xcode'ning sekin "Archive" bosqichidan ancha tezroq:

```bash
# Release profilida to'liq optimallashtirilgan .ipa qurish
bazel build --config=release //App:App
```

Chiqish fayli:
`bazel-bin/App/App.ipa`

### GitHub Actions Release Workflow namunasi:

```yaml
name: iOS Release - TestFlight

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: macos-15
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Import Apple Distribution Certificate
        env:
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
        run: |
          # Kalit zanjirini (Keychain) sozlash
          echo $BUILD_CERTIFICATE_BASE64 | base64 --decode > /tmp/cert.p12
          security create-keychain -p "" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -u build.keychain
          security import /tmp/cert.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain

      - name: Build Signed IPA with Bazel
        run: |
          bazel build --config=release //App:App

      - name: Upload to TestFlight
        env:
          APP_STORE_CONNECT_API_KEY: ${{ secrets.ASC_KEY }}
        run: |
          xcrun altool --upload-app -f bazel-bin/App/App.ipa \
            -t ios --apiKey ${{ secrets.ASC_KEY_ID }} --apiIssuer ${{ secrets.ASC_ISSUER_ID }}
```

---

## 10. Real kompaniyalar tajribasi va narx tejash {#real-tajriba}

Dunyodagi eng yirik kompaniyalar Bazel'ga o'tishining asosiy drayveri aynan **CI tezligi va arzonligi** bo'lgan:

- **Spotify:** iOS jamoasida CI navbatida turish vaqti 80 daqiqadan **20 daqiqaga** tushirilgan.
- **Lyft:** Har bir PR'da 1,500+ test yuguradigan monorepoda selective testing va remote cache tufayli CI vaqti 40 daqiqadan **7 daqiqaga** qisqargan.
- **Reddit:** iOS CI resurs sarfi va runner xarajatlari 65% ga kamaygan.

Ushbu raqamlar isbotlaydiki, dastlabki sozlash murakkab ko'rinsa ham, jamoa o'sgan sari tejalgan soatlar va server pullari Bazel sarmoyasini bir necha oyda to'liq qoplaydi.

---

## Amaliy mashqlar {#mashqlar}

1. **Selective query'ni qo'lda sinab ko'ring.** O'tgan darsdagi "Kutubxona" loyihangizda `Core/Book.swift` fayliga bitta bo'sh qator qo'shing. Terminalda:
   ```bash
   bazel query "tests(rdeps(//..., set(Core/Sources/Book.swift)))"
   ```
   buyrug'ini bajaring va natijada faqat `//Core:CoreTests` va unga bog'liq bo'lgan testlar chiqqanini tekshiring.
2. **Read-only vs Upload rejimini sinash.** `.bazelrc` faylingizda `ci_pr` va `ci_main` profillarini yarating. `--remote_upload_local_results=false` bo'lganda yangi o'zgarishlar keshga yozilmayotganini tasdiqlang.
3. **Flaky simulyatsiyasi.** Bitta test ichida `Int.random(in: 1...3) == 1` shartini qo'yib, ataylab beqaror test yarating. Uni `--flaky_test_attempts=3` bilan tekshirib, Bazel hisobotida `FLAKY` statusini o'z ko'zingiz bilan ko'ring.
4. ⭐ **GitHub Actions faylini yozing.** O'z shaxsiy GitHub repongizda kichik Bazel loyihasi uchun yuqoridagi `.github/workflows/ios_pr.yml` faylini ishga tushiring va birinchi muvaffaqiyatli CI yashil belgisini (Green checkmark) oling.

---

## O'zingizni tekshiring {#checklist}

- [ ] GitHub Actions'da `macos-14` va `macos-15` runner'larning farqini va nima uchun Intel runner ishlatmaslik kerakligini bilaman.
- [ ] PR ochilganda nima uchun Remote Cache'ga yozish (`remote_upload_local_results`) taqiqlanishi kerakligini tushunaman.
- [ ] `rdeps(//..., set(...))` orqali teskari bog'liqliklarni filtrlash formulasini yoza olaman.
- [ ] Selective testing nima uchun CI vaqtini 90% gacha tejashini raqamlar bilan tushuntira olaman.
- [ ] Flaky testlarga qarshi `--flaky_test_attempts` va test sharding (`shard_count`) mexanizmini bilaman.
- [ ] `.ipa` faylini release profilida qurish va TestFlight'ga jo'natish qadamlarini bilaman.

---

## Xulosa {#xulosa}

- **Mobil CI'dagi asosiy isrof** — o'zgarmagan kodni qayta qurish va barcha testlarni keraksiz yugurtirishdir.
- Bazel bu muammoni ikkita poydevor bilan yechadi: **Remote Cache** (hech qachon bir ishni ikki marta qilmaslik) va **Bazel Query** (faqat ta'sirlangan narsani aniqlash).
- CI arxitekturasida qat'iy intizom bo'lishi shart: **PR'lar faqat o'qiydi**, keshni faqat **main** shoxcha yangilaydi.
- Sharding va flaky-handling orqali katta loyihalardagi beqarorliklar bartaraf etiladi.
- **Keyingi dars:** Endi nazariyadan haqiqiy amaliy loyihalarga o'tamiz! [Bazel 12: Loyiha #1: "Valyuta" CLI — Swift kutubxona + macOS buyruq qatori + testlar va birinchi o'lchovlar](/ios-lessons/bazel-12-loyiha-valyuta-cli) — birinchi to'liq professional loyihamizni noldan yig'amiz.

---

## Manbalar {#manbalar}

1. [Bazel Documentation — Continuous Integration](https://bazel.build/services#continuous-integration) — CI tizimlarida Bazel bilan ishlash bo'yicha rasmiy qo'llanma.
2. [Bazel Query Guide](https://bazel.build/query/quickstart) — `rdeps`, `deps`, `tests` operatorlari va tahlil qilish sintaksisi.
3. [BuildBuddy — Optimizing CI for iOS with Bazel](https://www.buildbuddy.io/blog) — Remote cache, Build Event Protocol va test hisobotlari.
4. [Spotify Engineering Blog — Slashing iOS Build Times](https://engineering.atspotify.com/) — Spotify'ning Bazel CI optimizatsiyasi bo'yicha real case study.
5. [rules_apple — iOS rules documentation](https://github.com/bazelbuild/rules_apple) — `ios_application`, codesigning va release boshqaruvi.
