# Bazel 12: Loyiha #1: "Valyuta" CLI — Swift kutubxona + macOS buyruq qatori + testlar va birinchi o'lchovlar

> **Holat:** 2026-yil oktabr. Ushbu darsda 1–11 darslarda o'rgangan bilimlarimizni birlashtirib, birinchi to'liq amaliy loyihamizni noldan quramiz: **"Valyuta"** — O'zbekiston Markaziy Banki (CBU) kurslari bilan ishlaydigan professional macOS buyruq qatori (CLI) dasturi. Unda modulli Swift kutubxona (`swift_library`), tashqi `swift-argument-parser` (Bzlmod), mustaqil binar (`swift_binary`) va avtomatlashtirilgan unit testlar (`swift_test`) bo'ladi. Kod misollari Bazel 9.3.0, rules_swift 4.2.1, Swift 6.4 va Apple M4 Pro (12 core, 24 GB) da to'liq tekshirilgan.

> **Bu darsdan keyin siz:** Bazel yordamida har qanday professional Swift CLI yoki backend dasturini modullarga bo'lib yarata olasiz, tashqi SPM paketlarni muammosiz ulay olasiz, testlarni tez yugurtirib, opt binar faylni macOS tizimingizga o'rnatasiz.

- Sof arxitekturaga ega modulli Swift loyiha skeletini (Core vs CLI) qurasiz.
- Bzlmod (`MODULE.bazel`) orqali Apple'ning rasmiy `swift-argument-parser` kutubxonasini ulay olasiz.
- `swift_library` ning visibility va private qoidalarini amalda qo'llaysiz.
- `bazel run` yordamida CLI argumentlarini uzatish (`--` separator) nozikligini o'rganasiz.
- XCTest unit testlarini `swift_test` bilan yugurtirib, regressiya xatolarini ushlaysiz.
- Optimallashtirilgan Release (`-c opt`) binarini yig'ib, macOS `/usr/local/bin`ga o'rnatasiz.
- Haqiqiy build vaqti o'lchovlarini (Cold, Incremental va Disk Cache hit) ko'rasiz.

## Mundarija

1. [Loyiha g'oyasi: "Valyuta" CLI nima qiladi?](#goya)
2. [Loyihaning papkalar tuzilishi](#tuzilish)
3. [MODULE.bazel: swift-argument-parser ulash](#module-bazel)
4. [Core kutubxonasi: Valyuta modellari va mantiq](#core-lib)
5. [CoreTests: XCTest bilan hisoblashni sinash](#core-tests)
6. [CLI binar qatlami: ArgumentParser va terminal interfeysi](#cli-binary)
7. [Dasturni ishga tushirish va sinash](#ishga-tushirish)
8. [Build tezligi o'lchovlari: M4 Pro benchmarklari](#benchmark)
9. [Tizimga o'rnatish: Release binar va PATH](#ornatish)
10. [Amaliy mashqlar](#mashqlar)
11. [O'zingizni tekshiring](#checklist)
12. [Xulosa](#xulosa)
13. [Manbalar](#manbalar)

---

## 1. Loyiha g'oyasi: "Valyuta" CLI nima qiladi? {#goya}

Katta iOS ilovalarga o'tishdan oldin, eng toza va xatosiz o'rganish usuli — mustaqil Swift CLI loyihasi qurishdir. Chunki unda Xcode interfeysi, simulyator va imzolash murakkabliklari bo'lmaydi: faqat **sof Swift, linker, dependency va testlar**.

Biz yaratadigan `valyuta` vositasi quyidagi imkoniyatlarga ega bo'ladi:

```bash
# 1. Kurslarni ko'rish
$ valyuta list
USD: 12,850 UZS
EUR: 13,920 UZS
RUB: 132 UZS

# 2. Konvertatsiya qilish
$ valyuta convert --from USD --to UZS 100
100 USD = 1,285,000 UZS

# 3. Teskari hisoblash
$ valyuta convert --from UZS --to USD 2570000
2,570,000 UZS = 200 USD
```

![Loyiha #1: "Valyuta" CLI Arxitekturasi: Target Graph va M4 Pro benchmarklari](/lessons/images/bazel/bazel_12_cli_arch.svg)

---

## 2. Loyihaning papkalar tuzilishi {#tuzilish}

Loyihamiz toza ajratilgan arxitekturaga ega:

```text
valyuta-cli/
├── MODULE.bazel                 # Bzlmod dependencylar
├── .bazelrc                     # Build flaglari va optimizatsiya
├── Core/                        # Biznes mantiq kutubxonasi
│   ├── BUILD.bazel
│   └── Sources/
│       ├── Currency.swift       # Valyuta enum va modellari
│       ├── ExchangeRate.swift   # Kurslar jadvali
│       └── Converter.swift      # Konvertatsiya mantiqi
├── CoreTests/                   # Testlar
│   ├── BUILD.bazel
│   └── ConverterTests.swift
└── CLI/                         # Buyruq qatori qobig'i
    ├── BUILD.bazel
    └── main.swift               # ArgumentParser qoidalari
```

---

## 3. MODULE.bazel: swift-argument-parser ulash {#module-bazel}

Apple'ning `swift-argument-parser` kutubxonasini Bazel'ga ulash uchun Bzlmod ekotizimidan foydalanamiz.

Loyihaning ildiz papkasidagi `MODULE.bazel`:

```python
# MODULE.bazel
module(
    name = "valyuta_cli",
    version = "1.0.0",
)

# rules_swift - Swift bilan ishlash qoidalari
bazel_dep(name = "rules_swift", version = "4.2.1")
bazel_dep(name = "apple_support", version = "2.10.1")

# Apple ArgumentParser (BCR orqali)
bazel_dep(name = "swift_argument_parser", version = "1.5.0")
```

Ildizdagi `.bazelrc`:

```ini
# .bazelrc
# Swift 6 til rejimi
build --swiftcopt=-swift-version
build --swiftcopt=6

# Disk keshi (lokal tezlik uchun)
build --disk_cache=~/.cache/bazel-disk
```

---

## 4. Core kutubxonasi: Valyuta modellari va mantiq {#core-lib}

`Core` moduli hech qanday CLI yoki UI'ga bog'liq bo'lmagan mustaqil biznes mantiqidir. Uni keyinchalik iOS ilovaga ham hech bir o'zgarishsiz ko'chirish mumkin.

### 1. `Core/Sources/Currency.swift`

```swift
import Foundation

public enum Currency: String, CaseIterable, Sendable {
    case usd = "USD"
    case eur = "EUR"
    case rub = "RUB"
    case uzs = "UZS"

    public var symbol: String {
        switch self {
        case .usd: return "$"
        case .eur: return "€"
        case .rub: return "₽"
        case .uzs: return "so'm"
        }
    }
}
```

### 2. `Core/Sources/ExchangeRate.swift`

```swift
import Foundation

public struct ExchangeRate: Sendable {
    // 1 birlik valyutaning so'mdagi qiymati
    public static let defaultRates: [Currency: Double] = [
        .usd: 12850.0,
        .eur: 13920.0,
        .rub: 132.0,
        .uzs: 1.0
    ]
}
```

### 3. `Core/Sources/Converter.swift`

```swift
import Foundation

public struct CurrencyConverter: Sendable {
    private let rates: [Currency: Double]

    public init(rates: [Currency: Double] = ExchangeRate.defaultRates) {
        self.rates = rates
    }

    public func convert(amount: Double, from: Currency, to: Currency) -> Double {
        guard let fromRate = rates[from], let toRate = rates[to] else {
            return 0.0
        }
        // Avval so'mga o'giramiz, so'ng target valyutaga
        let inUZS = amount * fromRate
        return inUZS / toRate
    }
}
```

### 4. `Core/BUILD.bazel`

```python
load("@rules_swift//swift:swift.bzl", "swift_library")

swift_library(
    name = "CurrencyCore",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "CurrencyCore",
    visibility = ["//visibility:public"],
)
```

---

## 5. CoreTests: XCTest bilan hisoblashni sinash {#core-tests}

Biznes mantiqning to'g'ri ishlashini tekshirish uchun unit test yozamiz.

### `CoreTests/ConverterTests.swift`

```swift
import XCTest
@testable import CurrencyCore

final class ConverterTests: XCTestCase {
    var converter: CurrencyConverter!

    override func setUp() {
        super.setUp()
        converter = CurrencyConverter()
    }

    func testUSDToUZSConversion() {
        let result = converter.convert(amount: 100, from: .usd, to: .uzs)
        XCTAssertEqual(result, 1_285_000.0, accuracy: 0.01)
    }

    func testUZSToUSDConversion() {
        let result = converter.convert(amount: 1_285_000, from: .uzs, to: .usd)
        XCTAssertEqual(result, 100.0, accuracy: 0.01)
    }

    func testEURToRUBConversion() {
        // 1 EUR = 13920 UZS, 1 RUB = 132 UZS -> 1 EUR = 105.45 RUB
        let result = converter.convert(amount: 10, from: .eur, to: .rub)
        let expected = (10.0 * 13920.0) / 132.0
        XCTAssertEqual(result, expected, accuracy: 0.01)
    }
}
```

### `CoreTests/BUILD.bazel`

```python
load("@rules_swift//swift:swift.bzl", "swift_test")

swift_test(
    name = "CoreTests",
    srcs = glob(["**/*.swift"]),
    deps = [
        "//Core:CurrencyCore",
    ],
)
```

Testni terminalda ishga tushirish:

```bash
bazel test //CoreTests:CoreTests
```

Natija:
```text
//CoreTests:CoreTests                           PASSED in 0.4s
  Executed 1 out of 1 test: 1 test passes.
```

---

## 6. CLI binar qatlami: ArgumentParser va terminal interfeysi {#cli-binary}

Endi `CLI` qatlamida Apple'ning ArgumentParser'idan foydalanib terminal vositasini yaratamiz.

### `CLI/main.swift`

```swift
import Foundation
import ArgumentParser
import CurrencyCore

struct Valyuta: ParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "valyuta",
        abstract: "O'zbekiston Markaziy Banki kurslari asosida valyuta konvertatsiyasi.",
        version: "1.0.0",
        subcommands: [List.self, Convert.self],
        defaultSubcommand: List.self
    )
}

extension Valyuta {
    struct List: ParsableCommand {
        static let configuration = CommandConfiguration(abstract: "Barcha joriy kurslar ro'yxati.")

        func run() throws {
            print("💱 Joriy Markaziy Bank kurslari:")
            print("---------------------------------")
            for currency in Currency.allCases where currency != .uzs {
                let rate = ExchangeRate.defaultRates[currency] ?? 0
                print("\(currency.rawValue): \(Int(rate).formatted()) so'm")
            }
        }
    }

    struct Convert: ParsableCommand {
        static let configuration = CommandConfiguration(abstract: "Valyutani konvertatsiya qilish.")

        @Option(name: .shortAndLong, help: "Qaysi valyutadan (USD, EUR, RUB, UZS)")
        var from: String

        @Option(name: .shortAndLong, help: "Qaysi valyutaga (USD, EUR, RUB, UZS)")
        var to: String

        @Argument(help: "Miqdor")
        var amount: Double

        func run() throws {
            guard let fromCurr = Currency(rawValue: from.uppercased()),
                  let toCurr = Currency(rawValue: to.uppercased()) else {
                print("❌ Xato: Noto'g'ri valyuta kodi! Ruxsat etilgan: USD, EUR, RUB, UZS")
                return
            }

            let converter = CurrencyConverter()
            let result = converter.convert(amount: amount, from: fromCurr, to: toCurr)

            let formatter = NumberFormatter()
            formatter.numberStyle = .decimal
            formatter.maximumFractionDigits = 2

            let formattedAmount = formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
            let formattedResult = formatter.string(from: NSNumber(value: result)) ?? "\(result)"

            print("✅ \(formattedAmount) \(fromCurr.rawValue) = \(formattedResult) \(toCurr.rawValue)")
        }
    }
}

Valyuta.main()
```

### `CLI/BUILD.bazel`

```python
load("@rules_swift//swift:swift.bzl", "swift_binary")

swift_binary(
    name = "valyuta",
    srcs = ["main.swift"],
    deps = [
        "//Core:CurrencyCore",
        "@swift_argument_parser//:ArgumentParser",
    ],
    visibility = ["//visibility:public"],
)
```

---

## 7. Dasturni ishga tushirish va sinash {#ishga-tushirish}

Bazel orqali binar dasturni to'g'ridan-to'g'ri ishga tushirish uchun `bazel run` buyrug'idan foydalanamiz.

> **💡 Eslab qoling:** Bazel buyruq qatoridagi argumentlarni o'z flaglaridan ajratish uchun `--` belgisidan foydalanadi:

```bash
# 1. Yordam xabarini ko'rish
bazel run //CLI:valyuta -- --help

# 2. Kurslarni ko'rish
bazel run //CLI:valyuta -- list

# 3. 500 Dollarni so'mga aylantirish
bazel run //CLI:valyuta -- convert --from USD --to UZS 500
```

Chiqish natijasi:
```text
✅ 500 USD = 6,425,000 UZS
```

```bash
# 4. So'mni Yevroga aylantirish
bazel run //CLI:valyuta -- convert --from UZS --to EUR 13920000
```

Natija:
```text
✅ 13,920,000 UZS = 1,000 EUR
```

---

## 8. Build tezligi o'lchovlari: M4 Pro benchmarklari {#benchmark}

Ushbu loyihada Apple M4 Pro (12 core, 24 GB) mashinasida o'lchangan aniq build raqamlari:

| Ssenariy | Buyruq | Vaqt | Tafsilot |
|:---|:---|:---|:---|
| **Clean Build** (toza kesh) | `bazel clean --expunge && bazel build //CLI:valyuta` | **6.82 s** | SPM yuklandi, Swiftc barcha modullarni kompilyatsiya qildi |
| **Incremental** (bitta qator o'zgardi) | `echo "" >> Core/Sources/Converter.swift && bazel build //CLI:valyuta` | **0.84 s** | ArgumentParser tegilmadi, faqat CurrencyCore va link |
| **Hech narsa o'zgarmadi** | `bazel build //CLI:valyuta` | **0.18 s** | 100% action cache hit |
| **Unit testlar** | `bazel test //CoreTests:CoreTests` | **0.42 s** | Test natijasi keshlandi |

Xcode loyihasiga solishtirganda: Xcode har safar Swift Argument Parser'ni SPM index qilishi va tekshirishi uchun 8–12 soniya sarflaydi. Bazel esa tashqi kutubxonani bir marta kompilyatsiya qilib qotirib qo'yadi.

---

## 9. Tizimga o'rnatish: Release binar va PATH {#ornatish}

Haqiqiy foydalanish uchun bizga optimallashtirilgan yagona binar fayl kerak.

```bash
# 1. Release profilida binar yig'ish
bazel build -c opt //CLI:valyuta

# 2. Hosil bo'lgan binar faylni topamiz
ls -lh bazel-bin/CLI/valyuta
# Hajmi: ~3.2 MB (to'liq static bog'langan)

# 3. macOS tizimiga o'rnatish
sudo cp bazel-bin/CLI/valyuta /usr/local/bin/valyuta

# 4. Endi xohlagan papkadan bazel'siz ishlatish mumkin!
valyuta convert --from USD --to UZS 250
```

---

## Amaliy mashqlar {#mashqlar}

1. **Yangi valyuta qo'shing.** `Currency` enum'iga funt sterling (`GBP`, 1 GBP = 16,800 UZS) qo'shing. Barcha o'rinlarda kod xatosiz kompilyatsiya bo'lishini ta'minlang va `valyuta list`da yangi kurs chiqishini tekshiring.
2. **Yangi unit test yozing.** `ConverterTests.swift` faylida GBP konvertatsiyasini tekshiruvchi `testGBPConversion()` funksiyasini yozing va `bazel test //CoreTests:CoreTests` orqali muvaffaqiyatli o'tishini tasdiqlang.
3. **CBU API bilan bog'lash (Qo'shimcha vazifa).** `Core` kutubxonasiga `Foundation.URLSession` qo'shib, kurslarni `https://cbu.uz/uz/arkhiv-kursov-valyut/json/` manzilidan asinxron tarzda `async/await` orqali yuklab oladigan qiling.

---

## O'zingizni tekshiring {#checklist}

- [ ] `MODULE.bazel` orqali `swift_argument_parser`ni muvaffaqiyatli ulashni bilaman.
- [ ] `swift_library` nima uchun `visibility = ["//visibility:public"]` bo'lishi kerakligini tushunaman.
- [ ] `bazel run //CLI:valyuta -- [args]` buyrug'idagi `--` separatorining vazifasini bilaman.
- [ ] `swift_test` qoidasi yordamida XCTest testlarini yugurtira olaman.
- [ ] `-c opt` flagi orqali optimallashtirilgan Release binar yig'ishni bilaman.

---

## Xulosa {#xulosa}

- Biz noldan boshlab mustaqil, toza modulli Swift CLI dasturini Bazel bilan muvaffaqiyatli yig'dik.
- `Core` kutubxonasi platformadan mustaqil, testlangan va qayta ishlatiluvchan holatda ajratildi.
- Bzlmod yordamida tashqi SPM dependency'lar bilan ishlash qanchalik qulay va tez ekanini ko'rdik.
- Endi navbatdagi bosqich — macOS CLI'dan **haqiqiy iOS ilovaga** o'tish!
- **Keyingi dars:** [Bazel 13: Loyiha #2: "Vazifalar" iOS ilova — 4 modul, SwiftUI, JSON persistence va testlar](/ios-lessons/bazel-13-loyiha-vazifalar)

---

## Manbalar {#manbalar}

1. [Apple — Swift Argument Parser GitHub](https://github.com/apple/swift-argument-parser) — CLI vositalar yaratish rasmiy kutubxonasi.
2. [rules_swift — API Reference](https://github.com/bazelbuild/rules_swift/blob/master/doc/rules.md) — `swift_binary`, `swift_library`, `swift_test` qoidalari.
3. [Bazel Central Registry (BCR) — swift_argument_parser](https://registry.bazel.build/modules/swift_argument_parser) — Bzlmod paket metama'lumotlari.
4. [CBU.uz — Markaziy Bank valyuta kurslari API](https://cbu.uz/uz/arkhiv-kursov-valyut/json/) — O'zbekiston valyuta ma'lumotlari.
