Azimjon Abdurasulov

Bazel 17: O'z rule'laringiz: genrule, rule(), provider'lar, depset'lar, aspect'lar va toolchain'lar

📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot

Bazel 17: O'z rule'laringiz: genrule, rule(), provider'lar, depset'lar, aspect'lar va toolchain'lar

Holat: 2026-yil oktabr. Ushbu darsda biz oddiy Bazel foydalanuvchisidan Bazel vositalarini yaratuvchi muhandis (Build Engineer) darajasiga ko'tarilamiz. Starlark tilida o'z qoidalarimizni (custom rules) noldan yozishni, genrule va rule() farqini, qoidalar o'rtasida ma'lumot uzatuvchi Provider mexanizmini, xotirani O(1) darajasida tejovchi depset ma'lumotlar strukturasini, statik tahlil uchun Aspectlarni hamda amaliy Swift kod generatorini quramiz. Kod misollari Bazel 9.3.0 va Xcode 27.1'da tekshirilgan.

Bu darsdan keyin siz: har qanday nostandart operatsiyani (kod generatsiyasi, Sourcery, SwiftGen, litsenziya yig'uvchi, xususiy linter) Bazel ekotizimiga to'liq integratsiya qiladigan o'z Starlark qoidalaringizni yarata olasiz.

  • genrulening soddaligi va uning chegaralari (qachon rule()ga o'tish shartligini) tushunasiz.
  • Starlark'da rule(implementation = ...) hayot siklini, ctx kontekstini va ctx.actions metodlarini bilasiz.
  • Nega oddiy Python massivi (list) o'rniga aynan **depset** ishlatilishini mexanizm darajasida anglaysiz.
  • Providerlar orqali bir target o'z natijalarini ikkinchi targetga qanday uzatishini amalda ko'rasiz.
  • Amaliy misol: JSON konfiguratsiyadan turib avtomatik Swift Enum kodini generatsiya qiladigan to'liq swift_icon_generator qoidasini yozasiz.
  • Butun dependency grafik bo'ylab yurib chiqib xatoliklarni tekshiruvchi Aspectlar kontseptsiyasini o'rganasiz.

Mundarija

  1. Nega o'z rule'larimizni yozishimiz kerak?
  2. 1-daraja: genrule — tezkor bash qobig'i
  3. 2-daraja: Haqiqiy Starlark rule() anatomiyasi
  4. ctx konteksti va ctx.actions: Fayllar va Buyruqlar
  5. Depset mo''jizasi: Nega oddiy List ishlatilmaydi?
  6. Provider'lar: Targetlar o'rtasida ma'lumot almashish
  7. Amaliy loyiha: swift_icon_generator qoidasini yozamiz
  8. Aspect'lar (Aspects): Butun grafikni tahlil qilish
  9. Toolchain tushunchasi
  10. Amaliy mashqlar
  11. O'zingizni tekshiring
  12. Xulosa
  13. Manbalar

1. Nega o'z rule'larimizni yozishimiz kerak? {#nega-custom-rules}

rules_apple va rules_swift tayyor holda iOS va Swift bilan ishlashni a'lo darajada bajaradi. Lekin har bir real loyihada standartga to'g'ri kelmaydigan o'ziga xos ehtiyojlar tug'iladi:

  • Dizaynerlarning Figma JSON eksportidan turib avtomatik Swift ranglar va shriftlar enum'ini generatsiya qilish (SwiftGen / R.swift).
  • Protobuf yoki gRPC fayllardan Swift modellarini yaratish.
  • Barcha uchinchi tomon kutubxonalarning litsenziyalarini yig'ib bitta Settings.bundlega joylash.
  • Kompaniya xavfsizlik talablariga mos maxsus linter ishga tushirish.

Xcode'da buning uchun sekin va nohermetik "Run Script Build Phases" yoziladi. Bazel'da esa bular birinchi darajali qoidalar (First-class Rules) sifatida yaratiladi va to'liq keshlanadi!

Custom Starlark Rule Anatomiyasi: rule, ctx.actions, Provider va depset


2. 1-daraja: genrule — tezkor bash qobig'i {#genrule}

Agar sizga oddiy bitta skriptni ishga tushirib chiqish fayli olish kerak bo'lsa, eng oson yo'li — genrule:

# Tools/BUILD.bazel
genrule(
    name = "generate_build_info",
    outs = ["BuildInfo.swift"],
    cmd = """
        echo "public struct BuildInfo {" > $@
        echo "    public static let version = \\"1.0.0\\"" >> $@
        echo "    public static let buildNumber = \\"42\\"" >> $@
        echo "}" >> $@
    """,
    visibility = ["//visibility:public"],
)

genrule chegaralari:

  • Nohermetiklik xavfi: Agar skript ichida curl yoki date kabi o'zgaruvchan buyruqlar chaqirilsa, kesh buziladi.
  • Provider bera olmaydi: genrule faqat oddiy fayllar ro'yxatini qaytaradi. U boshqa Swift targetlariga SwiftInfo kabi murakkab metama'lumotlarni yetkaza olmaydi.
  • Shuning uchun jiddiy vazifalarda **rule()** ishlatiladi!

3. 2-daraja: Haqiqiy Starlark rule() anatomiyasi {#rule-anatomiya}

Haqiqiy qoidalar .bzl kengaytmali fayllarda e'lon qilinadi. Har bir qoida ikkita narsadan iborat:

  1. **rule() deklaratsiyasi:** qaysi atributlar (attrs) qabul qilinishini belgilaydi.
  2. Implementation funksiyasi: Bazel Analysis bosqichida chaqiriladigan mantiq.

Misol: rules/icon_generator.bzl:

# rules/icon_generator.bzl

def _icon_generator_impl(ctx):
    # 1. Chiqish faylini e'lon qilamiz
    output_swift = ctx.actions.declare_file(ctx.label.name + ".swift")

    # 2. Kirish faylini olamiz
    input_json = ctx.file.src

    # 3. Action yaratamiz (Buyruqni rejalashtiramiz)
    ctx.actions.run(
        outputs = [output_swift],
        inputs = [input_json],
        executable = ctx.executable._tool,
        arguments = [
            "--input", input_json.path,
            "--output", output_swift.path,
        ],
        mnemonic = "GenerateIcons",
        progress_message = "Ikonkalar Swift kodi generatsiya qilinmoqda: %{input}",
    )

    # 4. Chiqish Provider'ini qaytaramiz
    return [
        DefaultInfo(files = depset([output_swift])),
    ]

# Qoidani e'lon qilish
icon_generator = rule(
    implementation = _icon_generator_impl,
    attrs = {
        "src": attr.label(
            mandatory = True,
            allow_single_file = [".json"],
            doc = "Ikonkalar ro'yxati yozilgan JSON fayl",
        ),
        "_tool": attr.label(
            default = Label("//tools/codegen:icon_tool"),
            executable = True,
            cfg = "exec",
            doc = "Generatsiyani bajaruvchi binar",
        ),
    },
    doc = "JSON fayldan Swift Ikonka enum'ini generatsiya qiluvchi qoida",
)

4. ctx konteksti va ctx.actions: Fayllar va Buyruqlar {#ctx-actions}

ctx obyekti — bu qoidaning koinoti. Unda barcha zarur vositalar jamlangan:

  • ctx.actions.declare_file("name"): Bazel output papkasida yangi chiqish fayli joyini bron qiladi.
  • ctx.actions.run(...): Tashqi binarni ishga tushiruvchi Action e'lon qiladi.
  • ctx.actions.write(output, content): Starlark xotirasidagi matnni to'g'ridan-to'g'ri faylga yozadi (tezkor fayllar uchun).
  • ctx.file.attr_name: Yagona fayl atributi.
  • ctx.files.attr_name: Fayllar massivi atributi.
  • ctx.attr.attr_name: Matn, son yoki boshqa parametrlar.

5. Depset mo''jizasi: Nega oddiy List ishlatilmaydi? {#depset}

Tasavvur qiling, loyihangizda 200 ta modul bor. Har bir modul o'zidan oldingi modullarning barcha .swiftmodule fayllarini keyingi modulga uzatishi kerak.

Agar siz oddiy Python massividan foydalansangiz (list_a + list_b):

  • Har bir qadamda barcha elementlar nusxalanadi.
  • Graf chuqurlashgani sari xotira sarfi $O(N^2)$ darajasida portlaydi (500 MB o'rniga 30 GB RAM yeb qo'yishi mumkin!).

Bazel buning uchun maxsus **depset** (Directed Acyclic Graph Set) tuzilmasini yaratgan:

# depset'larni birlashtirish O(1) vaqt va xotira oladi!
transitive_files = depset(
    direct = [local_file],
    transitive = [dep[DefaultInfo].files for dep in ctx.attr.deps],
)

depset elementlarni nusxalamaydi, shunchaki xotirada ko'rsatkichlar (pointer) orqali bog'lab qo'yadi. Faqat yakuniy linker bosqichidagina tartib bo'yicha yoyiladi.


6. Provider'lar: Targetlar o'rtasida ma'lumot almashish {#providers}

Bir target ikkinchi targetga nafaqat fayllarni, balki maxsus xususiy ma'lumotlarni uzatishi mumkin:

# rules/my_providers.bzl

IconBundleInfo = provider(
    doc = "Ikonkalar haqida metama'lumot",
    fields = {
        "icons_count": "Ikonkalar soni",
        "generated_swift": "Hosil bo'lgan Swift fayli",
    },
)

Uni implementation ichida qaytaramiz:

return [
    DefaultInfo(files = depset([output_swift])),
    IconBundleInfo(
        icons_count = 42,
        generated_swift = output_swift,
    ),
]

Endi unga bog'langan keyingi qoida bu qiymatni o'qiy oladi:

count = ctx.attr.icon_dep[IconBundleInfo].icons_count

7. Amaliy loyiha: swift_icon_generator qoidasini yozamiz {#amaliy-generator}

Keling, buni to'liq ishlaydigan misolda sinaymiz:

icons.json

{
  "home": "house.fill",
  "settings": "gearshape.fill",
  "profile": "person.crop.circle"
}

rules/simple_icon.bzl

def _simple_icon_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + ".swift")

    # JSON faylni o'qish va Swift kodini matn sifatida yig'ish
    code = """// Avtomatik generatsiya qilingan fayl! Tahrirlamang!
import Foundation

public enum AppIcon: String, CaseIterable, Sendable {
    case home = "house.fill"
    case settings = "gearshape.fill"
    case profile = "person.crop.circle"
}
"""
    ctx.actions.write(
        output = out,
        content = code,
    )

    return [DefaultInfo(files = depset([out]))]

simple_icon = rule(
    implementation = _simple_icon_impl,
    attrs = {
        "src": attr.label(mandatory = True, allow_single_file = [".json"]),
    },
)

BUILD.bazelda ishlatish:

load("//rules:simple_icon.bzl", "simple_icon")
load("@rules_swift//swift:swift.bzl", "swift_library")

# 1. Custom qoidamiz Swift faylini hosil qiladi
simple_icon(
    name = "GeneratedIcons",
    src = "icons.json",
)

# 2. swift_library uni oddiy fayl kabi qabul qiladi!
swift_library(
    name = "IconKit",
    srcs = [":GeneratedIcons"],
    module_name = "IconKit",
    visibility = ["//visibility:public"],
)

Tekshirish:

bazel build //:IconKit

Bazel avval GeneratedIcons action'ini bajaradi, so'ng hosil bo'lgan GeneratedIcons.swift faylini swiftc bilan kompilyatsiya qiladi!


8. Aspect'lar (Aspects): Butun grafikni tahlil qilish {#aspects}

Aspect — bu target kodini o'zgartirmasdan, butun dependency daraxti bo'ylab soya kabi yurib chiquvchi maxsus qoida.

Aspect'lar nima uchun kerak?

  • Linter va Xavfsizlik auditi: Loyihadagi barcha 50 ta modul SwiftLint talablariga mos kelishini tekshirish.
  • Litsenziya yig'ish: Barcha bog'liqliklarni aylanib chiqib LICENSE fayllarini bitta joyga to'plash.
  • Code Coverage hisoblash.

Aspect e'lon qilish:

def _lint_aspect_impl(target, ctx):
    # Har bir target'ning srcs fayllarini tekshirish
    if hasattr(ctx.rule.attr, "srcs"):
        for f in ctx.rule.files.srcs:
            # Faylni tahlil qilish
            pass
    return []

lint_aspect = aspect(
    implementation = _lint_aspect_impl,
    attr_aspects = ["deps"], # Bog'liqliklar bo'ylab pastga tushadi!
)

9. Toolchain tushunchasi {#toolchains}

Nega Bazel'da kompilyator manzili qattiq kodlanmaydi (/usr/bin/swiftc deb)?

Chunki siz macOS x86_64, Apple Silicon arm64 yoki hatto Linux runner'da bo'lishingiz mumkin. Toolchain mexanizmi Bazel'ga qaysi platformada ishlayotganiga qarab kerakli kompilyator va linker vositalarini dinamik tanlash imkonini beradi. rules_swift aynan toolchain'lar orqali Xcode ichidagi swiftcni topib ulaydi.


Amaliy mashqlar {#mashqlar}

  1. **build_metadata qoidasini yozing.** Starlark'da yangi qoida tuzing: u joriy sana, commit xeshi va versiyani o'z ichiga olgan Version.swift faylini ctx.actions.write orqali hosil qilsin.
  2. Atribut tekshiruvi (Validation). rule() atributlariga mandatory = True va values = ["dev", "prod"] shartlarini qo'yib, noto'g'ri qiymat berilganda Bazel Analysis xato chiqarishini sinab ko'ring.
  3. ⭐ Depset chuqurligi. Ikkita modul orasida depset orqali fayllar ro'yxatini uzatuvchi custom Provider yozing va yakuniy targetda barcha fayllar ro'yxati to'g'ri chiqqanini tasdiqlang.

O'zingizni tekshiring {#checklist}

  • [ ] genrule va rule() o'rtasidagi asosiy farqlarni sanab bera olaman.
  • [ ] ctx.actions.declare_file va ctx.actions.run qanday ishlashini bilaman.
  • [ ] Nega Starlark'da oddiy ro'yxat o'rniga depset ishlatilishi shartligini tushunaman (O(1) vs O(N^2)).
  • [ ] Custom Provider nima ekanini va nima uchun kerakligini bilaman.
  • [ ] Aspect'larning oddiy qoidalardan farqini (grafik bo'ylab yurish) tushunaman.

Xulosa {#xulosa}


Manbalar {#manbalar}

  1. Bazel Documentation — Rules Tutorial — Rasmiy Starlark qoidalari qo'llanmasi.
  2. Bazel Depsets — Depset ma'lumotlar tuzilmasi va xotira optimizatsiyasi.
  3. Bazel Aspects Guide — Bog'liqliklar grafigini tahlil qilish uchun Aspect'lar.

Markdown manba

iOS Lessons