Azimjon Abdurasulov

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

📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot

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/binga o'rnatasiz.
  • Haqiqiy build vaqti o'lchovlarini (Cold, Incremental va Disk Cache hit) ko'rasiz.

Mundarija

  1. Loyiha g'oyasi: "Valyuta" CLI nima qiladi?
  2. Loyihaning papkalar tuzilishi
  3. MODULE.bazel: swift-argument-parser ulash
  4. Core kutubxonasi: Valyuta modellari va mantiq
  5. CoreTests: XCTest bilan hisoblashni sinash
  6. CLI binar qatlami: ArgumentParser va terminal interfeysi
  7. Dasturni ishga tushirish va sinash
  8. Build tezligi o'lchovlari: M4 Pro benchmarklari
  9. Tizimga o'rnatish: Release binar va PATH
  10. Amaliy mashqlar
  11. O'zingizni tekshiring
  12. Xulosa
  13. 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:

# 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


2. Loyihaning papkalar tuzilishi {#tuzilish}

Loyihamiz toza ajratilgan arxitekturaga ega:

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:

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

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

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

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

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

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

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

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

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

Testni terminalda ishga tushirish:

bazel test //CoreTests:CoreTests

Natija:

//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

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

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:

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

✅ 500 USD = 6,425,000 UZS
# 4. So'mni Yevroga aylantirish
bazel run //CLI:valyuta -- convert --from UZS --to EUR 13920000

Natija:

✅ 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:

SsenariyBuyruqVaqtTafsilot
Clean Build (toza kesh)bazel clean --expunge && bazel build //CLI:valyuta6.82 sSPM yuklandi, Swiftc barcha modullarni kompilyatsiya qildi
Incremental (bitta qator o'zgardi)echo "" >> Core/Sources/Converter.swift && bazel build //CLI:valyuta0.84 sArgumentParser tegilmadi, faqat CurrencyCore va link
Hech narsa o'zgarmadibazel build //CLI:valyuta0.18 s100% action cache hit
Unit testlarbazel test //CoreTests:CoreTests0.42 sTest 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.

# 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 listda 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_parserni 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

Manbalar {#manbalar}

  1. Apple — Swift Argument Parser GitHub — CLI vositalar yaratish rasmiy kutubxonasi.
  2. rules_swift — API Reference — swift_binary, swift_library, swift_test qoidalari.
  3. Bazel Central Registry (BCR) — swift_argument_parser — Bzlmod paket metama'lumotlari.
  4. CBU.uz — Markaziy Bank valyuta kurslari API — O'zbekiston valyuta ma'lumotlari.

Markdown manba

iOS Lessons