Azimjon Abdurasulov

04. Dependencies Boshqarish

🚀 Tuist — Mukammal Qo‘llanma & Amaliyot

Tuist 04: Dependency'lar — target'dan Registry'gacha

Holat: 2026-yil oktabr. Bu dars Tuist loyihasida dependency'larning hamma turini — ichki target, boshqa project, binary framework/XCFramework, system SDK, Swift Package va Swift macro — qanday e'lon qilish, ularni tuist install bilan yuklash, tuist graph bilan ko'rish va tuist inspect dependencies bilan nazorat qilishni o'rgatadi. Kod misollari Tuist 4.211.1, Xcode 27.1 (Swift 6.4) va Apple M4 Pro (12 core, 24 GB) da tekshirilgan.

Bu darsdan keyin siz: loyihangizning butun dependency grafigini aniq, statik va tekshiriladigan qilib boshqara olasiz — qaysi modul qaysisiga bog'langanini bir qarashda bilasiz va tasodifiy ("implicit") bog'lanishlarni CI'da avtomatik tuta olasiz.

  • TargetDependencyning barcha 10 holatini (.target, .project, .framework, .library, .xcframework, .sdk, .xctest, .external, .package, .macro) qachon ishlatishni bilasiz.
  • Tashqi Swift Package'larni ikki xil yo'l bilan (Xcode native va Tuist XcodeProj-based) ulashni va orasidagi farqni tushunasiz.
  • tuist install paketlarni aynan qayerga yuklashini (va rasmiy hujjat bu yerda eskirganini) o'z ko'zingiz bilan ko'rasiz.
  • static va dynamic linking farqini, "static side effect" ogohlantirishini va uni qachon jiddiy qabul qilishni bilasiz.
  • tuist inspect dependencies bilan implicit import'ni atayin yaratib, uni real xato bilan tutasiz.
  • Swift Package Registry resolve'ni nega tezlashtirishini va u qanday hisob talab qilishini bilasiz.

Mundarija

  1. Nega dependency grafigi muhim — Tuist nimani boshqacha qiladi
  2. [Local dependency'lar — .target va .project](#local)
  3. Binary va system dependency'lar — framework, library, XCFramework, SDK
  4. [Tashqi paketlar — Swift Package'lar va tuist install](#external)
  5. [PackageSettings — paketlarni sozlash](#package-settings)
  6. [static yoki dynamic — va "static side effect"](#static-dynamic)
  7. Swift macro'lar — tashqi va o'zingniki
  8. [tuist graph — grafikni ko'rish](#graph)
  9. [tuist inspect dependencies — implicit import'ni tutish](#inspect)
  10. Swift Package Registry — resolve'ni tezlashtirish
  11. [Troubleshooting — -ObjC, Firebase, TCA, CocoaPods](#troubleshooting)
  12. Amaliy mashqlar
  13. O'zingizni tekshiring
  14. Xulosa
  15. Manbalar

1. Nega dependency grafigi muhim — Tuist nimani boshqacha qiladi {#nega-graf}

Loyiha o'sgani sari uni bir nechta target'ga bo'lamiz: kodni qayta ishlatish, chegaralarni belgilash va build vaqtini qisqartirish uchun. Ko'p target degani — ular orasida dependency (bog'lanish) e'lon qilish degani. Shu bog'lanishlar yig'indisi dependency graph deyiladi, va u loyihangizning tashqi paketlarni ham o'z ichiga oladi.

Muammo shundaki, Xcode va XcodeProj grafikni qo'lda saqlashni og'ir va xatoga moyil qiladi. Tuist hujjatlari aynan shu uch og'riqni sanaydi (dependencies.md):

  • Xcode build system barcha target mahsulotlarini bitta DerivedData papkasiga chiqaradi. Shuning uchun bir target o'ziga ruxsat berilmagan modulni ham import qila oladi — mahalliy mashinada build o'tadi, lekin CI'dagi toza build'da yoki boshqa configuration'da kutilmaganda sinadi.
  • Dinamik target'ning transitive (bilvosita) bog'lanishlari LD_RUNPATH_SEARCH_PATHS ro'yxatidagi papkalardan biriga nusxalanishi kerak, aks holda ular runtime'da topilmaydi. Grafik kichik bo'lsa buni esda tutish oson, kattalashganda esa muammoga aylanadi.
  • Target statik XCFramework'ni link qilsa, Xcode uchun paketni qayta ishlab, kerakli binary'ni chiqaruvchi qo'shimcha build phase kerak — u avtomatik qo'shilmaydi va uni esdan chiqarish oson.

Tuist'ning falsafasi bitta jumlada: dependency graph aniq (explicit) va statik bo'lishi kerak, chunki faqat shundagina u tekshirilishi va optimallashtirilishi mumkin. Siz "nima nimaga bog'liq"ni tasvirlaysiz, qolganini (RUNPATH, build phase'lar, embed qilish) Tuist generatsiya paytida o'zi hal qiladi.

🧠 Esda tuting: Tuist graph'ni generatsiya paytida validatsiya qiladi — cycle (aylanma bog'lanish) bo'lsa yoki bog'lanish noto'g'ri bo'lsa, tuist generate to'xtaydi. Bu butun jamoaga grafikni buzmasdan o'zgartirish imkonini beradi.

🚀 Motivatsiya: Toss jamoasi (~1,000 modul) binary cache'dan foyda ko'rishdan oldin grafikni "cacheable" qilishga vaqt sarfladi: ishlatilmagan import'larni avtomatik olib tashladi, uchinchi tomon paketlarni alohida repozitoriyga ko'chirdi va har bir PR'da cacheable-modul ulushi tushib ketsa ogohlantiruvchi tekshiruv qo'shdi. Tuist'ning maslahati: implicit import'larni statik tahlil bilan tuting, chunki explicit modules va CAS faqat implicit bog'lanishlar yo'q bo'lgandagina ishlaydi (manba: Toss case study, 2026-10; Tuist "Scaling" blogi, 2025-09-22). Bu darsning 9-bo'limida siz aynan shu tekshiruvni ishga tushirasiz.

Grafikdagi bog'lanishlarning hammasi Targetning dependencies: parametrida e'lon qilinadi. Keling, uning har bir turini ko'rib chiqaylik.

Tuist TargetDependency turlarining xaritasi: local (target, project), binary (framework, library, xcframework), system (sdk, xctest) va external (external, package, macro) guruhlarga bo'lingan

Yuqoridagi xarita TargetDependencyning barcha holatlarini to'rt guruhga ajratadi: loyiha ichidagi local bog'lanishlar, oldindan kompilyatsiya qilingan binary'lar, Apple'ning system framework'lari va tashqi external paketlar. Keyingi bo'limlarda har birini real kod bilan ko'ramiz.


2. Local dependency'lar — .target va .project {#local}

Local dependency — loyihangiz ichidagi boshqa target yoki boshqa project'dagi target'ga bog'lanish. Bu eng ko'p ishlatiladigan tur: modulyar loyihaning asosi.

2.1 .target(name:) — bir project ichida

Eng oddiy holat: bitta Project.swift ichidagi ikki target. Quyida biz haqiqatan build qilgan loyiha — valyuta formatlovchi Core moduli va uni ishlatuvchi ilova:

// Project.swift
import ProjectDescription

let project = Project(
    name: "DepsDemo",
    targets: [
        // Core — hech kimga bog'liq emas, sof Swift modul
        .target(
            name: "Core",
            destinations: .iOS,
            product: .staticFramework,
            bundleId: "uz.azimjondev.depsdemo.core",
            deploymentTargets: .iOS("17.0"),
            sources: ["Core/Sources/**"]
        ),
        // RatesUI — Core ga bog'liq
        .target(
            name: "RatesUI",
            destinations: .iOS,
            product: .framework,
            bundleId: "uz.azimjondev.depsdemo.ratesui",
            deploymentTargets: .iOS("17.0"),
            sources: ["RatesUI/Sources/**"],
            dependencies: [
                .target(name: "Core")   // ← local dependency
            ]
        ),
    ]
)

Core — sof Swift modul, hech kimga bog'liq emas. Uning ichida oddiy public API:

// Core/Sources/MoneyFormatter.swift
import Foundation

public enum MoneyFormatter {
    public static func format(_ amount: Decimal, code: String) -> String {
        let f = NumberFormatter()
        f.numberStyle = .decimal
        f.maximumFractionDigits = 2
        let n = f.string(from: amount as NSDecimalNumber) ?? "0"
        return "\(n) \(code)"
    }
}

RatesUI endi o'z kodida import Core qila oladi va Corening public API'sidan foydalanadi:

// RatesUI/Sources/RatesView.swift
import UIKit
import Core

public final class RatesView: UIView {
    private let label = UILabel()
    public override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(label)
        label.text = MoneyFormatter.format(1250, code: "UZS")  // Core'dan
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
}

💡 Pro tip: .targetning ikkita shakli bor. .target(name: "Core") — nom bilan. Agar target obyektini o'zgaruvchiga olgan bo'lsangiz, .target(_ target:) sintaktik shakli ham bor: u ichki tomondan .target(name: target.name)ga aylanadi (manba: TargetDependency.swift). Nom bilan yozish ko'pincha soddaroq.

2.2 Transitive (bilvosita) bog'lanish

Agar App → RatesUI → Core bo'lsa, App faqat RatesUIga bog'lansa yetarli. Core transitive (bilvosita) tarzda keladi — uni link qilish, embed qilish va RUNPATH'ni to'g'rilashni Tuist o'zi bajaradi.

⚠️ Diqqat: Transitive kelishi "import qila olaman" degani emas. App o'z kodida import Core qilmoqchi bo'lsa, Coreni Appning dependencies:iga ham aniq qo'shishi kerak. Aks holda bu "implicit dependency" bo'ladi — build tasodifan o'tishi mumkin, lekin 9-bo'limdagi tuist inspect dependencies buni xato deb topadi. Shuning uchun bizning App target'imiz Coreni ham, RatesUIni ham to'g'ridan-to'g'ri e'lon qiladi.

2.3 .project(target:path:) — boshqa project'dagi target

Katta loyihalar bir nechta Project.swiftga bo'linadi (buni 06. Modular arxitektura darsida chuqur ko'ramiz). Boshqa papkadagi project'ning target'iga shunday bog'lanasiz:

// Signature (manba: TargetDependency.swift):
// case project(target: String, path: Path, status: LinkingStatus = .required, condition: PlatformCondition? = nil)

dependencies: [
    .project(
        target: "HomeFeature",
        path: "../Features/Home"   // Project.swift joylashgan papkaga nisbiy yo'l
    )
]

path — bog'langan Project.swift turgan papka yo'li (faylning emas). Bu bog'lanish ishlashi uchun ikkala project ham bitta Workspace da bo'lishi yoki ildiz Project.swiftdan topilishi kerak.

2.4 status — majburiy, optional yoki link qilinmaydigan

.target, .project, .framework, .xcframework va .sdk holatlari status parametrini oladi. U LinkingStatus enum'i (manba: TargetDependency.swift):

public enum LinkingStatus: String, Codable, Hashable, Sendable {
    case required   // doim link qilinadi (default)
    case optional   // weak link — symbol bo'lmasa crash emas, nil
    case none       // link qilinmaydi, faqat build order uchun
}

.optional — framework runtime'da bo'lmasligi mumkin bo'lgan holatlar uchun (masalan, yangi OS API'si eski qurilmada yo'q). U -weak_framework bilan link qiladi.

2.5 condition — platforma bo'yicha shartli bog'lanish

Har bir dependency turi condition parametrini oladi. Default'da target qo'llab-quvvatlaydigan hamma platformaga link qilinadi. Faqat ma'lum platformada kerak bo'lsa:

dependencies: [
    .target(name: "iOSOnlyModule", condition: .when([.ios])),
    .sdk(name: "WatchKit", type: .framework, condition: .when([.watchos])),
]

Bu multi-platform target'larda (bitta target iOS + watchOS + macOS) juda muhim — keraksiz framework'ni noto'g'ri platformaga link qilmaslik uchun.


3. Binary va system dependency'lar — framework, library, XCFramework, SDK {#binary}

Hamma dependency ham source kod bo'lavermaydi. Ko'pincha sizga oldindan kompilyatsiya qilingan binary (vendor bergan SDK) yoki Apple'ning system framework'i kerak bo'ladi. TargetDependency buning uchun to'rt holatni beradi.

3.1 .framework(path:) — tayyor .framework

// case framework(path: Path, status: LinkingStatus = .required, condition: PlatformCondition? = nil)
dependencies: [
    .framework(path: "Frameworks/MySDK.framework")
]

Bitta platforma uchun kompilyatsiya qilingan dinamik yoki statik framework. status: .optional bilan weak link qilsa bo'ladi.

3.2 .library(path:publicHeaders:swiftModuleMap:) — tayyor kutubxona

C/Objective-C statik kutubxonasi (.a) uchun. Unga header'lar yo'li va (ixtiyoriy) Swift module map kerak:

// case library(path: Path, publicHeaders: Path, swiftModuleMap: Path?, condition: PlatformCondition? = nil)
dependencies: [
    .library(
        path: "Libraries/libparser.a",
        publicHeaders: "Libraries/include",
        swiftModuleMap: "Libraries/module.modulemap"
    )
]

3.3 .xcframework(path:) — ko'p platformali binary

.xcframework — bitta paketda bir nechta platforma/arxitektura slice'ini saqlaydigan Apple formati (iOS qurilma + iOS simulator + macOS...). Zamonaviy vendor SDK'lar shu formatda tarqatiladi:

// case xcframework(path:, expectedSignature:, status:, condition:)
dependencies: [
    .xcframework(path: "Frameworks/Analytics.xcframework")
]

Tuist statik XCFramework uchun kerak bo'lgan maxsus build phase'ni avtomatik qo'shadi — bu Xcode'da qo'lda qilishda eng ko'p esdan chiqadigan narsa.

**Imzo tekshiruvi (expectedSignature).** XCFramework imzolangan bo'lsa, Tuist uning haqiqiyligini tekshirishi mumkin. XCFrameworkSignature enum'i uch holat beradi (manba: TargetDependency.swift):

dependencies: [
    // Apple sertifikati bilan imzolangan:
    .xcframework(
        path: "Frameworks/Vendor.xcframework",
        expectedSignature: .signedWithAppleCertificate(
            teamIdentifier: "ABCDE12345",
            teamName: "Vendor Inc."
        )
    ),
]

Yana ikki holat: .unsigned (imzolanmagan) va .selfSigned(fingerprint:) (o'z-o'zini imzolagan). Agar hisoblangan imzo kutilganidan farq qilsa, Tuist generatsiyani to'xtatadi — bu supply-chain hujumiga qarshi himoya.

3.4 .sdk(name:type:status:) — Apple system framework

Apple'ning system framework va kutubxonalari uchun: AVFoundation, StoreKit, libz va h.k. SDKType enum'i uch tur beradi: .framework, .library, .swiftLibrary.

Bizning tekshirilgan loyihamizda App target'iga AVFoundationni shunday qo'shdik:

dependencies: [
    .target(name: "Core"),
    .target(name: "RatesUI"),
    .sdk(name: "AVFoundation", type: .framework),   // ← system framework
]

.sdkning ikki shakli bor (manba: TargetDependency.swift):

// To'liq: status bilan
.sdk(name: "c++", type: .library, status: .required)
// Qisqa: status default .required bo'ladi
.sdk(name: "AVFoundation", type: .framework)

🧠 Esda tuting: UIKit, SwiftUI, Foundation kabi eng asosiy framework'lar avtomatik keladi — ularni .sdk bilan qo'shish shart emas. .sdk faqat qo'shimcha system framework'lar (AVFoundation, StoreKit, CoreBluetooth...) uchun kerak.

3.5 .xctest — test framework'i

Test target'larida XCTest'ni aniq qo'shish uchun (odatda product: .unitTests buni o'zi beradi, lekin ba'zi hollarda kerak):

dependencies: [
    .xctest
]

📊 Real raqamlar: Yuqoridagi DepsDemo loyihasi — Core (staticFramework) + RatesUI (framework, SnapKit'ga bog'liq) + App (app, AVFoundation'ga bog'liq) — Apple M4 Pro'da, Xcode 27.1, Tuist 4.211.1 bilan xcodebuild ... build orqali ** BUILD SUCCEEDED ** natijasini berdi. Build log'da Core, RatesUI, App va tashqi SnapKit target'lari (arm64 + x86_64) kompilyatsiya qilinib, DepsDemo.app va RatesUI.framework chiqdi. Ya'ni yuqoridagi barcha dependency turlari (local .target, external, .sdk) bir loyihada birga ishlaydi.


4. Tashqi paketlar — Swift Package'lar va tuist install {#external}

Endi eng ko'p ishlatiladigan tashqi dependency turiga — Swift Package'larga o'tamiz. Tuist buni ikki xil mexanizm bilan ulaydi, va orasidagi farqni tushunish katta loyihada juda muhim.

4.1 Ikki integratsiya: Xcode native vs Tuist XcodeProj-based

Xcode native integratsiyaTuist XcodeProj-based integratsiya
Qanday e'lon qilinadiProject(packages:) + .package(product:type:)Tuist/Package.swift + .external(name:)
Paket qanday ulanadiXcode'ning yopiq SPM mexanizmiHar bir paket XcodeProj target'iga aylanadi (source'dan)
Binary cache bilan❌ ishlamaydi✅ ishlaydi
Selective testing bilan❌✅
Yangi SPM feature'lariDarhol (Apple qiladi)Ba'zan kechikadi (Tuist mapping kerak)
Kim saqlaydiApple (yopiq manba)Tuist (ochiq manba, hamjamiyat hissa qo'shadi)

Tuist hujjati buni to'g'ridan-to'g'ri aytadi: Xcode native integratsiya eng qulay, lekin o'rta va katta loyihalar uchun kerakli moslik va nazoratdan mahrum. XcodeProj-based integratsiya paketni XcodeProj target'lariga aylantirgani uchun uni cache va selective testing bilan birga ishlatish mumkin bo'ladi.

Shuning uchun **tavsiya etiladigan yo'l — .external(name:)** (XcodeProj-based). Keyingi bo'limlarda shuni ishlatamiz; Xcode native yo'lni esa macro va build-tool plugin'lar uchun saqlab qo'yamiz (7-bo'lim).

4.2 Tuist/Package.swift — tashqi paketlar interfeysi

Tashqi paketlar Tuist/Package.swift faylida e'lon qilinadi (ildizda ham bo'lishi mumkin). Diqqat qiling: bu fayl faqat dependency ro'yxati — unda hech qanday targets yoki products yozilmaydi.

Quyida biz haqiqatan ishlatgan fayl — SnapKit paketini qo'shadi:

// Tuist/Package.swift
// swift-tools-version: 6.0
import PackageDescription

#if TUIST
import ProjectDescription

let packageSettings = PackageSettings(
    productTypes: [
        "SnapKit": .staticFramework,   // default ham .staticFramework
    ]
)
#endif

let package = Package(
    name: "DepsDemo",
    dependencies: [
        .package(url: "https://github.com/SnapKit/SnapKit", from: "5.7.1"),
    ]
)

🧠 Esda tuting: #if TUIST bloki — Tuist uchun maxsus. Oddiy swift build bu blokni ko'rmaydi (chunki TUIST compilation flag'i faqat Tuist manifestni kompilyatsiya qilganda yoqiladi). Shu blok ichida PackageSettings orqali har bir paketning product type'ini va boshqa sozlamalarni belgilaysiz. Default'da bu blok kerak emas — faqat sozlash kerak bo'lganda yozasiz.

4.3 tuist install — va paketlar aynan qayerga tushadi

Paketlarni yuklash alohida buyruq — xuddi CocoaPods'dagi pod install kabi. Bu sizga paketlar qachon resolve/update bo'lishini nazorat qilish imkonini beradi:

tuist install

Bizning mashinamizda real natija:

Resolving and fetching plugins.
Resolving and fetching dependencies.
Fetching https://github.com/SnapKit/SnapKit
[1/4295] Fetching snapkit
Fetched https://github.com/SnapKit/SnapKit (2.04s)
Computing version for https://github.com/SnapKit/SnapKit
Computed https://github.com/SnapKit/SnapKit at 5.7.1 (2.77s)
Creating working copy for https://github.com/SnapKit/SnapKit
Working copy of https://github.com/SnapKit/SnapKit resolved at 5.7.1
✔ Success
  Plugins resolved and fetched successfully.

⚠️ Diqqat — rasmiy hujjat bu yerda eskirgan: Tuist'ning rasmiy hujjati hali ham "paketlar Tuist/Dependencies papkasiga tushadi" deydi. Bu Tuist 3 davridan qolgan eski ma'lumot. Biz 4.211.1 da o'z ko'zimiz bilan tekshirdik — paketlar aslida SwiftPM'ning .build papkasiga, ya'ni **Tuist/.build** ga tushadi:

$ ls -a Tuist/
.build/              ← bu yerga tushadi (checkouts, repositories, tuist-derived)
Package.resolved     ← lock fayl shu yerda yaratiladi
Package.swift

$ find Tuist/.build -maxdepth 2 -iname "*snapkit*"
Tuist/.build/checkouts/SnapKit
Tuist/.build/repositories/SnapKit-a5691e5b

Ya'ni Tuist/Dependencies papkasi umuman yaratilmaydi. Bu Tuist CLI manbasida ham ko'rinadi: Constants.SwiftPackageManager.packageBuildDirectoryName = ".build". Shuning uchun .gitignore ga **Tuist/.build** ni qo'shing (eski misollardagi Tuist/Dependencies emas). Tuist/Package.resolved ni esa commit qiling — u butun jamoa uchun bir xil versiyalarni qotiradi.

# .gitignore
Tuist/.build/

4.4 .external(name:) — paketni target'ga ulash

Endi istalgan target'dan paketga TargetDependency.external bilan murojaat qilasiz. Nom — paketning product nomi (odatda kutubxona nomi):

// Project.swift (RatesUI target'i)
.target(
    name: "RatesUI",
    destinations: .iOS,
    product: .framework,
    bundleId: "uz.azimjondev.depsdemo.ratesui",
    deploymentTargets: .iOS("17.0"),
    sources: ["RatesUI/Sources/**"],
    dependencies: [
        .target(name: "Core"),
        .external(name: "SnapKit"),   // ← tashqi paket
    ]
)

Kodda endi oddiy import SnapKit ishlaydi. tuist generate dan keyin Tuist tashqi paketni alohida Xcode project sifatida yaratadi va uni source'dan kompilyatsiya qiladi. Bizning generate natijamizda bu aniq ko'rindi:

Generating workspace DepsDemo.xcworkspace
Generating project DepsDemo
Generating project SnapKit          ← SnapKit alohida project bo'ldi
Total time taken: 10.472s

Build paytida esa SnapKit haqiqatan source'dan kompilyatsiya qilindi (Tuist/.build/checkouts/SnapKit/Sources/...). Aynan shuning uchun XcodeProj-based integratsiya binary cache bilan ishlay oladi — paket "qora quti" emas, oddiy target.

💡 Pro tip: Tashqi Swift Package project'lari uchun Tuist scheme yaratmaydi (scheme ro'yxati toza qolishi uchun). Agar sizga paketning o'z scheme'i kerak bo'lsa, uni Xcode UI orqali qo'lda yaratasiz.

4.5 Ketma-ketlik: install → generate → build

Amaliy ish oqimi doim shu tartibda:

tuist install     # Tuist/Package.swift ni o'qib, paketlarni Tuist/.build ga yuklaydi
tuist generate    # .xcworkspace + .xcodeproj yaratadi (tashqi paketlar alohida project)
# keyin build:
xcodebuild -workspace DepsDemo.xcworkspace -scheme DepsDemo \
  -destination 'generic/platform=iOS Simulator' build

Yangi paket qo'shsangiz yoki versiyani o'zgartirsangiz — tuist install ni qayta chaqirasiz. Faqat kod o'zgarsa — install kerak emas, to'g'ridan-to'g'ri build qilaverasiz.

SPM paketni Tuist XcodeProj-based integratsiya bilan ulash oqimi: Tuist/Package.swift dan tuist install orqali Tuist/.build ga, keyin .external(name:) orqali alohida XcodeProj target'iga; yonida Xcode native yo'l

Diagrammada chapdagi yo'l — tavsiya etilgan XcodeProj-based integratsiya (tuist install → Tuist/.build → .external), o'ngdagi — Xcode native yo'l (Project(packages:) → .package(product:)). Faqat chapdagi yo'l cache va selective testing bilan ishlaydi.


5. PackageSettings — paketlarni sozlash {#package-settings}

PackageSettings — Tuist/Package.swiftning #if TUIST blokida yoziladigan va tashqi paketlar XcodeProj target'lariga qanday aylanishini boshqaradigan tuzilma. Uning to'liq imzosi (manba: PackageSettings.swift, Tuist 4.211.1):

public init(
    productTypes: [String: Product] = [:],
    baseProductType: Product = .staticFramework,
    productDestinations: [String: Destinations] = [:],
    baseSettings: Settings = .settings(),
    expectedSignatures: [String: XCFrameworkSignature] = [:],
    targetSettings: [String: Settings] = [:],
    projectOptions: [String: Project.Options] = [:],
    includeLocalPackageTestTargets: Bool = false
)

Eng ko'p ishlatiladigan uchtasi:

**productTypes** — ma'lum paketning product type'ini o'zgartiradi. Default — .staticFramework (buni baseProductType boshqaradi). Masalan, Alamofire'ni dinamik qilish:

let packageSettings = PackageSettings(
    productTypes: [
        "Alamofire": .framework,       // dinamik framework
        "SnapKit": .staticFramework,   // statik (default)
    ]
)

**baseSettings** — paketlardan yaratilgan barcha target'larga qo'llanadigan build settings. Loyihangizda standart Debug/Release dan boshqa configuration bo'lsa, buni shart sozlaysiz — aks holda tashqi paketlar sizning configuration'laringizni bilmaydi va noto'g'ri build bo'ladi (manba: Tuist docs, issue #8345):

let packageSettings = PackageSettings(
    productTypes: [:],
    baseSettings: .settings(configurations: [
        .debug(name: "Base"),
        .release(name: "Production"),
    ])
)

**targetSettings** — alohida paket target'iga qo'shimcha build settings. Ko'pincha -ObjC yoki module-alias uchun ishlatiladi (11-bo'limda ko'ramiz).

**expectedSignatures** — SPM binary target'lari (masalan paket ichidagi .binaryTarget) uchun kutilgan imzolar. 3.3-bo'limdagi XCFrameworkSignature enum'i bilan ishlaydi:

let packageSettings = PackageSettings(
    expectedSignatures: [
        "SelfSignedXCFramework": .selfSigned(
            fingerprint: "EF61C3C0339FC84805357AFEC2E0BB0E6A0D5EE64165B333F934BF9E282785BC"
        ),
    ]
)

💡 Pro tip: includeLocalPackageTestTargets (default false) — local path paketlarining test target'larini generatsiyaga kiritish-kiritmaslikni boshqaradi. Remote paketlarning testlari hech qachon kiritilmaydi. Katta loyihada buni false qoldirish generatsiyani yengillashtiradi.


6. static yoki dynamic — va "static side effect" {#static-dynamic}

Framework va kutubxonalar statik yoki dinamik link qilinishi mumkin. Tuist hujjati buni to'g'ridan-to'g'ri aytadi: bu tanlov ilova hajmi va ishga tushish (launch) vaqtiga sezilarli ta'sir qiladi, lekin ko'pincha o'ylamasdan tanlanadi.

Umumiy qoida (manba: Tuist docs — "Static or dynamic"):

  • Release build'da iloji boricha ko'proq narsa statik link qilinsin → tez launch.
  • Debug build'da iloji boricha ko'proq narsa dinamik link qilinsin → tez incremental build (iteratsiya).
Static (.staticFramework/.staticLibrary)Dynamic (.framework)
App launchTezroq (bitta binary, dyld kam ish qiladi)Sekinroq (har bir framework alohida yuklanadi)
Incremental buildSekinroq (ko'proq re-link)Tezroq
Resurslar❌ Statik kutubxona resurs saqlay olmaydi✅ Saqlaydi
Tuist default✅ (.staticFramework)

🏢 Real-world: Monzo jamoasi (Tuist case study, 2026-05-26 — ~50 dan ~200 modulgacha, 45+ iOS muhandis, 1.9M qator Swift, 20k unit + 8k snapshot test) aynan shu qoidani ishlatadi: testlar dinamik framework bilan quriladi (tez iteratsiya), release esa statik bilan (tez launch). Ularning module cache hit rate'i ~80% atrofida; cache hech qachon production build uchun ishlatilmaydi. Manba: tuist/tuist repozitoriyidagi Monzo case study.

"Static side effect" — biz uchragan real ogohlantirish

Statik va dinamik link'ni grafikda almashtirish arzimas ish emas: o'zgarish butun grafikga kaskad ta'sir qiladi. Eng ko'p uchraydigan muammo — bitta statik target bir nechta joydan link qilinishi. Bu kodni ikki nusxalaydi (binary kattalashadi) yoki eng yomon holatda runtime crash'ga olib keladi.

Biz buni atayin yaratdik: Core ni .staticFramework qilib, uni ham Appdan, ham RatesUIdan link qildik. tuist generate darhol ogohlantirdi (real natija):

! Warning
  The following items may need attention:
   ▸ Target 'Core' has been linked from target 'DepsDemo' and target 'RatesUI',
     it is a static product so may introduce unwanted side effects.

Nega? RatesUI (dinamik framework) Core (statik) ni o'z ichiga oladi. App ham Coreni to'g'ridan-to'g'ri link qiladi. Natijada Corening kodi ikki joyda — RatesUI.framework ichida va App binary'sida. Bu "duplicate symbols" yoki holat (static state) ikki nusxada bo'lishiga olib kelishi mumkin.

Yechim: yo Coreni .framework (dinamik) qiling (shunda bitta nusxa bo'ladi), yo App Coreni to'g'ridan-to'g'ri link qilmay, faqat RatesUI orqali transitive olsin. Qaysi biri to'g'riligini grafik va launch-vaqt talablari hal qiladi.

🧠 Esda tuting: Tuist "qulaylik uchun implicit sozlash" qilmaydi (bu uning "cost of convenience" falsafasi). Ya'ni link type'ni va ba'zan kerak bo'ladigan qo'shimcha flag'larni (masalan -ObjC) siz to'g'ri qo'yasiz. Buning evaziga grafik aniq va bashorat qilinadigan bo'ladi.

Linking type'ni environment orqali boshqarish

Tuist statik↔dinamik murakkabligini "siqadi" va bundle accessor'larni har ikki holat uchun bir xil qilib synthesize qiladi. Shuning uchun linking type'ni chaqiruv vaqtida environment orqali uzatish mumkin (manba: Tuist docs):

// Manifest ichida: ENV orqali kelgan qiymatga qarab product type tanlash
func productType() -> Product {
    if case let .string(linking) = Environment.linking {
        return linking == "static" ? .staticFramework : .framework
    }
    return .framework
}

Keyin TUIST_LINKING=static tuist generate kabi chaqirasiz. Bu bitta manifestdan ham debug (dinamik), ham release (statik) grafigini olish imkonini beradi.


7. Swift macro'lar — tashqi va o'zingniki {#macros}

Swift macro — kompilyator plagini: Swift kompilyatori compile vaqtida yuklab ishga tushiradigan executable. Tuist macro'larni ikki stsenariyda qo'llab-quvvatlaydi (manba: Tuist docs — "Swift Macros"), va qaysi birida ekaningizni aniq bilish muhim.

7.1 Tashqi paketdagi macro'ni ishlatish

Ko'pincha siz boshqa birovning macro'sini ishlatasiz (masalan @Reducer — The Composable Architecture'dan, yoki @Builder — StructBuilder'dan). Ikkala integratsiya ham buni qo'llaydi:

XcodeProj-based (tavsiya etilgan): paketni Tuist/Package.swiftda e'lon qilasiz va .external(name:) bilan bog'lanasiz — Tuist macro product'ini native target'ga xaritalaydi. Bu Tuist'ning rasmiy fixture'ida aynan shunday (manba: generated_framework_with_native_swift_macro):

// Project.swift
.target(
    name: "Framework",
    destinations: [.iPhone, .mac],
    product: .staticLibrary,
    bundleId: "dev.tuist.FrameworkWithSwiftMacro",
    sources: ["Sources/**/*"],
    dependencies: [
        .external(name: "ComposableArchitecture"),
        .external(name: "CasePaths"),
        .external(name: "StructBuilder"),   // macro shu paket ichida
    ]
)

Xcode native: Project(packages:) + .package(product:type: .macro). PackageTypening .macro holati aynan shu uchun (manba: TargetDependency.swift):

let project = Project(
    name: "FrameworkWithSwiftMacro",
    packages: [
        .remote(url: "https://github.com/alschmut/StructBuilderMacro", requirement: .exact("0.2.0")),
    ],
    targets: [
        .target(
            name: "Framework",
            destinations: .iOS,
            product: .framework,
            bundleId: "dev.tuist.FrameworkWithSwiftMacro",
            sources: ["Sources/**/*"],
            dependencies: [
                .package(product: "StructBuilder", type: .macro),
            ]
        ),
    ]
)

🧠 Esda tuting: Build-tool plugin'lardan (.plugin) farqli o'laroq, macro'lar faqat Xcode native yo'l bilan cheklanmagan — ularni XcodeProj-based integratsiya bilan ham ishlatsa bo'ladi. Build-tool plugin'lar esa faqat Xcode native yo'l bilan ishlaydi (masalan SwiftLint'ni build phase'da ishga tushirish).

7.2 O'z macro'ngizni yaratish

Tuist'ning .macro degan first-class product type'i bor (manba: Product.swiftda case macro). Xcode o'zi "Swift Macro target" turini bermaydi — Tuist buni command-line tool target yaratib, uni macro ishlatuvchi target'larga dependency qilib ulab, Swift kompilyator sozlamalariga -load-plugin-executable flag'ini inject qilib amalga oshiradi. Bularning hammasini Tuist o'zi qiladi.

Macro odatda ikki targetga bo'linadi:

  1. Plugin target (product: .macro) — macro'ni amalga oshiradigan executable. U build mashinasida ishlagani uchun faqat macOS (host-only).
  2. Library target — macro'ning public interfeysini (@attached/@freestanding e'lonlarini) beradi va plugin'ga .macro(name:) bilan bog'lanadi.

Macro implementatsiyasi swift-syntaxga bog'liq — uni Tuist/Package.swiftda oddiy tashqi dependency sifatida e'lon qilasiz:

// Tuist/Package.swift
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "PackageName",
    dependencies: [
        .package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.0"),
    ]
)

Keyin target'larni to'g'ridan-to'g'ri Project.swiftda e'lon qilasiz (manba: Tuist docs — "Defining your own macro"):

// Project.swift
let project = Project(
    name: "MyLibrary",
    targets: [
        // Macro'ni amalga oshiruvchi plugin — macOS-only, compile vaqtida ishlaydi
        .target(
            name: "MyMacrosImplementation",
            destinations: [.mac],
            product: .macro,
            bundleId: "dev.tuist.MyMacrosImplementation",
            sources: ["Sources/MyMacrosImplementation/**"],
            dependencies: [
                .external(name: "SwiftSyntaxMacros"),
                .external(name: "SwiftCompilerPlugin"),
            ]
        ),
        // Macro e'lonlarini beruvchi library — plugin'ga .macro(name:) bilan bog'lanadi
        .target(
            name: "MyLibrary",
            destinations: .iOS,
            product: .framework,
            bundleId: "dev.tuist.MyLibrary",
            sources: ["Sources/MyLibrary/**"],
            dependencies: [
                .macro(name: "MyMacrosImplementation"),
            ]
        ),
    ]
)

MyLibraryni link qilgan har qanday target endi macro'ni ishlatadi — Tuist plugin'ni propagatsiya qiladi, kompilyator uni yuklaydi.

⚠️ Diqqat: Biz bu macro misolini tuist generate gacha olib bormadik, chunki swift-syntax 600.x ni noldan kompilyatsiya qilish og'ir (bir necha daqiqa) va bu mashinada u cache'da yo'q edi. Yuqoridagi manifest naqshlari Tuist'ning rasmiy hujjati, API manbasi (Product.swift, TargetDependency.swift) va ishlaydigan fixture'lari (generated_framework_with_macros_and_tests) bilan tasdiqlangan. Fixture'da library plugin'ga .target(name:) bilan ham bog'lanadi; hujjat esa maxsus .macro(name:) holatini tavsiya qiladi — ikkalasi ham ishlaydi, .macro(name:) aniqroq.


8. tuist graph — grafikni ko'rish {#graph}

tuist graph loyihaning dependency grafigini tasvir yoki matn ko'rinishida chiqaradi. Katta loyihada bu "qaysi modul qaysisiga bog'liq?" degan savolga bir qarashda javob beradi.

Default'da u PNG yaratib ochadi. Skript yoki CI uchun matn (dot/json) formatini --no-open bilan birga ishlatgan ma'qul. Flaglar (manba: tuist graph --help, 4.211.1):

tuist graph --format dot --no-open -o graphout        # dot matni graphout/graph.dot ga
tuist graph --format json --no-open -o graphout       # JSON
tuist graph -t --skip-external-dependencies ...        # test target'lar va tashqi paketlarsiz
tuist graph App                                        # faqat App va unga bog'liqlar

⚠️ Diqqat: -o — chiqish papkasi, fayl nomi emas. Biz boshida -o graph.dot deb fayl nomi berganimizda real xato oldik: ✖ Error — Parent directory not found: .../graph.dot. To'g'risi — mavjud papkani berish: -o graphout (ichida graph.dot yaratiladi).

Bizning DepsDemo loyihamizdan olingan real dot natija (faqat ichki target'lar):

digraph {
  Core [fillcolor=coral1 shape=cylinder style=filled]
  DepsDemo [fillcolor=deepskyblue penwidth="1.5" shape=box3d style=filled]
  RatesUI [fillcolor=darkgoldenrod1 shape=cylinder style=filled]
  DepsDemo -> RatesUI
  DepsDemo -> Core
  RatesUI -> Core
}

Tashqi paketlar va system framework'lar bilan to'liq grafik (--no-skip-external-dependencies):

digraph {
  AVFoundation [fillcolor=violet shape=rectangle style=filled]
  Core [fillcolor=coral1 shape=cylinder style=filled]
  DepsDemo [fillcolor=deepskyblue shape=box3d style=filled]
  RatesUI [fillcolor=darkgoldenrod1 shape=cylinder style=filled]
  SnapKit [fillcolor=coral1 shape=cylinder style=filled]
  DepsDemo -> RatesUI
  DepsDemo -> AVFoundation
  DepsDemo -> Core
  RatesUI -> Core
  RatesUI -> SnapKit
}

E'tibor bering: DepsDemo -> Core va RatesUI -> Core — ya'ni grafik 6-bo'limdagi "static side effect"ni ham ko'rsatib turibdi (Core ikki joydan kelmoqda). Grafik — nafaqat chiroyli rasm, balki muammoni ko'rsatuvchi vosita.

💡 Pro tip: Macro grafikini ko'rayotganda --skip-macro-support-targets (-m) SwiftSyntax/SwiftCompilerPlugin kabi yordamchi target'larni yashiradi va asosiy grafikni toza ko'rsatadi. --format svg ni esa to'g'ridan-to'g'ri hujjatlarga qo'ysa bo'ladi.


9. tuist inspect dependencies — implicit import'ni tutish {#inspect}

Bu — Tuist'ning eng muhim "nazorat" buyrug'i va 1-bo'limdagi "aniq grafik" falsafasining amaliy quroli.

Muammo: Xcode build system dependency'larni implicit (yashirin) aniqlashga ruxsat beradi. Har qanday dasturchi kodga import SomeFramework yozsa, implicit bog'lanish paydo bo'ladi — garchi u Project.swiftda e'lon qilinmagan bo'lsa ham. Kichik loyihada bu muammo emas, lekin grafik o'sgani sari ishonchsiz incremental build'lar, buzilgan preview'lar va kod-to'ldirish (completion) muammolariga aylanadi. Eng yomoni — binary cache yoqilganda: Tuist statik tahlil bilan topa olmagan bog'lanish "missing symbol" xatosiga olib keladi.

Yechim: tuist inspect dependencies kodni statik tahlil qilib, e'lon qilinmagan (implicit) va keraksiz (redundant) import'larni topadi. Topsa — noldan farqli exit code bilan to'xtaydi, demak CI'da ushlab qolasiz.

tuist inspect dependencies                   # ikkalasini ham: implicit + redundant
tuist inspect dependencies --only implicit   # faqat implicit
tuist inspect dependencies --only redundant  # faqat redundant (e'lon qilingan, lekin import qilinmagan)

Real demo: implicit import'ni atayin yaratamiz

Bizning RatesUI target'i kodda import Core qiladi. Endi Project.swiftdan uning .target(name: "Core") bog'lanishini atayin olib tashlaymiz (kodga tegmasdan):

.target(
    name: "RatesUI",
    // ...
    dependencies: [
        // .target(name: "Core")  ← ATAYLAB o'chirdik, lekin kodda `import Core` bor
        .external(name: "SnapKit"),
    ]
),

tuist generate o'tadi (generatsiya import'larni tekshirmaydi — faqat cycle'ni). Lekin tekshiruvni ishga tushirsak, real natija:

$ tuist inspect dependencies --only implicit
Loading and constructing the graph
✖ Error
  The following implicit dependencies were found:
   - RatesUI implicitly depends on: Core

$ echo $?
1

Exit code 1 — aynan shuning uchun buni CI'ga qo'yish mumkin. Tuzatsak (bog'lanishni qaytarsak) — toza o'tadi:

$ tuist inspect dependencies --only implicit
We did not find any dependency issues in your project (checked: implicit).
$ echo $?
0

Implicit import tekshiruvi mexanizmi: kodda import Core bor, lekin grafikda RatesUI dan Core ga qirra yo'q; tuist inspect dependencies statik tahlil qiladi va exit code 1 bilan to'xtaydi

Diagramma mexanizmni ko'rsatadi: chapda kodda import Core bor, lekin grafikda RatesUI → Core qirrasi yo'q (implicit). O'ngda tuist inspect dependencies statik tahlildan so'ng exit code 1 qaytaradi — CI shu yerda to'xtaydi.

💡 Pro tip: 2025-12-13 dan beri tuist inspect dependencies ikkala tekshiruvni (implicit + redundant) birga ishga tushiradi va grafikni bir marta yuklagani uchun ~2 barobar tezroq. Eski tuist inspect implicit-imports buyrug'i hali ishlaydi, lekin deprecated — biz uni ishga tushirganimizda aniq ogohlantirdi: The 'tuist inspect implicit-imports' command is deprecated. Use 'tuist inspect dependencies --only implicit' instead. Shuningdek u tuist inspect --help ro'yxatida ko'rinmaydi.

⚠️ Diqqat: Tuist statik tahlilga tayangani uchun hamma holatni tuta olmaydi. Masalan, compiler directive (#if ...) ichidagi shartli import'larni tushunmaydi. Shuning uchun bu — ishonchli, lekin 100% emas, ikkinchi himoya qatlami.

🚀 Motivatsiya: Toss aynan shu tekshiruvni PR pipeline'iga qo'ygan: ularning ~1,000 modulli loyihasida cacheable-modul ulushi tushib ketsa (ya'ni implicit bog'lanishlar ko'paysa), PR tekshiruvi ogohlantiradi. Bu binary cache'ni ishchi holatda saqlaydi — chunki explicit modules va CAS faqat implicit bog'lanishlar bo'lmaganda ishlaydi (manba: Toss case study, 2026-10).


10. Swift Package Registry — resolve'ni tezlashtirish {#registry}

Dependency'lar soni ortgani sari ularni resolve qilish vaqti ham ortadi. SwiftPM markazlashmagan (decentralized): u har bir paketni chuqur klonlash (deep clone) orqali resolve qiladi — bu sekin va ko'p xotira oladi. Tuist buni Swift Package Registry implementatsiyasi bilan hal qiladi: butun repozitoriyni emas, faqat kerakli commit'larni yuklaysiz. Registry'dagi paketlar Swift Package Index asosida — agar paket o'sha yerda bo'lsa, Tuist Registry'da ham bor (manba: Tuist docs — "Swift Package Registry").

Ikki xil o'rnatish bor:

1) Generatsiya paytida avtomatik (tavsiya etilgan): Tuist.swiftda yoqasiz:

// Tuist.swift
import ProjectDescription

let tuist = Tuist(
    project: .tuist(
        generationOptions: .options(
            registryEnabled: true   // (manba: ConfigGenerationOptions.swift)
        )
    )
)

Shunda tuist generate registry konfiguratsiya faylini avtomatik yaratadi — alohida tuist registry setup kerak emas.

2) Qo'lda:

tuist registry setup    # registry konfiguratsiya faylini yaratadi (commit qiling)

Registry identifikatorlari

URL o'rniga registry {organization}.{repository} ko'rinishidagi identifikatordan foydalanadi. Masalan:

  • https://github.com/pointfreeco/swift-composable-architecture → pointfreeco.swift-composable-architecture
  • https://github.com/groue/GRDB.swift → groue.GRDB_swift (nuqta bittadan ko'p bo'lmasligi kerak, shuning uchun . → _)

Autentifikatsiya va limitlar

Autentifikatsiya ixtiyoriy:

  • Hisobsiz: IP manzil uchun daqiqasiga 1,000 so'rov limiti.
  • Hisob bilan (tuist registry login): daqiqasiga 20,000 so'rov. Buning uchun Tuist hisobi va project kerak.

⚠️ Diqqat: Registry — Tuist serveriga tayanadigan xizmat. Asosiy foyda (tez resolve) hisobsiz ham ishlaydi, lekin yuqori limit va jamoaviy ishlatish uchun Tuist hisobi kerak. Biz bu bo'limni mashinada ishga tushirmadik (hisob/tarmoq xizmati talab qiladi) — ma'lumotlar Tuist rasmiy hujjati va ConfigGenerationOptions.swift manbasiga asoslangan.

📊 Real raqamlar (vendor): SwiftPM resolve "daqiqalardan soniyalarga" tushadi — bu Tuist'ning o'z da'vosi (registry sahifasi meta-tavsifi). Neytral, uchinchi tomon o'lchovi yo'q, shuning uchun uni vendor raqami sifatida qabul qiling.


11. Troubleshooting — -ObjC, Firebase, TCA, CocoaPods {#troubleshooting}

Tashqi paketlar bilan eng ko'p uchraydigan real muammolar va ularning yechimi (hammasi Tuist'ning o'z hujjatidan — "Troubleshooting").

11.1 Objective-C paketlar va -ObjC

Tuist implicit linker config qo'ymaydi. Objective-C paketlari runtime crash'dan qochish uchun -ObjC flag'ini talab qilishi mumkin (Apple Technical Q&A QA1490). SwiftPM -ObjCni .unsafeFlag deb hisoblagani uchun ko'p paket uni o'zi qo'sha olmaydi. Yechim — iste'mol qiluvchi target'da OTHER_LDFLAGSni qo'yish:

.target(
    // ...
    settings: .settings(
        base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"]
    )
)

11.2 Firebase va Google kutubxonalari

Google'ning open-source kutubxonalari ko'pincha nostandart arxitektura ishlatadi. Ikki tipik muammo:

  1. **-ObjC kerak** — ko'p Google kutubxonasi Objective-C'da (yuqoridagi 11.1).
  2. **FBLPromises crash** — ba'zi Google kutubxonalari FBLPromisesga bog'liq. Quyidagidek crash ko'rsangiz:
NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]:
unrecognized selector sent to instance 0x600000cb2640.

yechim — FBLPromisesni dinamik framework qilish:

let packageSettings = PackageSettings(
    productTypes: [
        "FBLPromises": .framework,
    ]
)

🧠 Esda tuting: FBLPromisesni .framework qilish — ma'lum bir crashning yechimi, har doim shart emas. Avval oddiy holda sinab ko'ring, crash bo'lsa shu sozlamani qo'shing.

11.3 The Composable Architecture (TCA) va previews

TCA statik link qilinganda (Tuist default) app va test target'lari odatda ishlaydi, lekin SwiftUI preview'lar buziladi. Yechim — hamma narsani dinamik link qilish. Tuist hujjati buning uchun to'liq productTypes ro'yxatini beradi (CasePaths, ComposableArchitecture, Dependencies, Perception, Sharing...) va Sharing paketi uchun module-alias kerakligini ko'rsatadi:

let packageSettings = PackageSettings(
    productTypes: [
        "ComposableArchitecture": .framework,
        "CasePaths": .framework,
        "Dependencies": .framework,
        // ... (hujjatdagi to'liq ro'yxat)
        "Sharing": .framework,
    ],
    targetSettings: [
        "Sharing": .settings(base: [
            "PRODUCT_NAME": "SwiftSharing",
            "OTHER_SWIFT_FLAGS": ["-module-alias", "Sharing=SwiftSharing"],
        ]),
    ]
)

⚠️ Diqqat: Bunda kodda import Sharing emas, import SwiftSharing yozasiz. Dinamik link doim tavsiya etilmaydi — bu yerda soddalik uchun hammasi dinamik qilingan.

11.4 Statik symbol'lar .swiftmodule orqali "sizib chiqishi"

Dinamik framework statik modulga import StaticModule orqali bog'lansa, symbol'lar dinamik framework'ning .swiftmoduleiga kirib, build'ni buzishi mumkin. Swift 6'da yechim — internal import:

internal import StaticModule

Eski Swift versiyalarida @_implementationOnly import StaticModule ishlatiladi.

11.5 CocoaPods va Carthage

  • Carthage: carthage update → .framework/.xcframework chiqaradi → .framework/.xcframework dependency turi bilan ulaysiz. Build/test'dan oldin carthage update chaqirilgan bo'lishi kerak.
  • CocoaPods: tuist generate → keyin pod install. Lekin CocoaPods dependency'lari binary cache va selective testing bilan mos emas (fingerprint logikasi Pod'larni hisobga olmaydi), shuningdek generatsiyadan keyin darhol xcodebuild ishlatuvchi build/test oqimlari bilan ham mos emas.

🏢 Real-world: Asana jamoasi (~10 iOS muhandis, ~300k qator Swift, 2023-yil blog) Bazel o'rniga Tuist'ni tanladi — sabab: Bazel "juda og'ir", "yangi mental model talab qiladi" va "breakpoint muammolari" bor edi. µFeature'larga bo'lgandan keyin o'zgartir-qayta-qur sikli mediana 45 soniyadan ~15 soniyaga, Inbox modulidagi bir qatorlik o'zgarish esa 30–50 soniyadan ~4 soniyaga tushdi (Asana'ning o'z log'lari). Narx: git checkout 5–15 s sekinlashdi, ~3 oy migratsiya, minglab symbol skript bilan public qilindi. Manba: asana.com inside-asana blogi.


Amaliy mashqlar {#mashqlar}

Hammasini vaqtinchalik papkada bajaring (repozitoriyga tegmang). Har bir mashqdan keyin tuist graph bilan natijani ko'ring.

  1. Ikki modulli loyiha. Core (.staticFramework) va Feature (.framework) target'li Project.swift yozing. Feature Corega .target(name:) bilan bog'lansin va kodda MoneyFormatterga o'xshash public funksiyani ishlatsin. tuist generate && tuist graph --format dot --no-open -o out bilan Feature -> Core qirrasini tasdiqlang.
  2. Tashqi paket qo'shish. Tuist/Package.swiftga bitta kichik paket qo'shing (masalan SnapKit). tuist install ni ishga tushiring va ls -a Tuist/ bilan paket **Tuist/.build**ga tushganini (Tuist/Dependencies emas) tasdiqlang. Keyin uni .external(name:) bilan Featurega ulab, import qilib, xcodebuild ... build bilan BUILD SUCCEEDED oling. .gitignorega Tuist/.build/ qo'shing.
  3. ⭐ Implicit import'ni tuting. Feature kodida import Core bo'la turib, Project.swiftdan .target(name: "Core")ni olib tashlang. tuist generate o'tadi. Keyin tuist inspect dependencies --only implicit ishga tushiring va echo $? bilan exit code 1 ekanini ko'ring. Bog'lanishni qaytaring — endi exit code 0 va "no dependency issues" bo'lsin.
  4. ⭐ "Static side effect"ni yuzaga keltiring va tuzating. Coreni .staticFramework qoldirib, uni ikki target'dan (masalan App va Feature) link qiling. tuist generate ogohlantirishini o'qing. So'ng ikki yechimdan birini qo'llang: Coreni .framework qiling yoki App dan to'g'ridan-to'g'ri bog'lanishni olib tashlang. Ogohlantirish yo'qolganini tasdiqlang.
  5. **⭐ status va condition.** Bitta .sdk dependency'ni .optional qiling (.sdk(name:type:status: .optional)) va bitta .targetni condition: .when([.ios]) bilan shartli qiling. tuist generate o'tishini va grafik to'g'ri ekanini tekshiring.
  6. ⭐⭐ O'z macro'ngizning skeleti. 7.2-bo'limdagi naqsh asosida MyMacrosImplementation (product: .macro, .mac) va MyLibrary (.macro(name:) bilan bog'langan) target'larini yozing. Tuist/Package.swiftga swift-syntax qo'shing. (Diqqat: tuist install swift-syntax'ni yuklab, kompilyatsiya qiladi — bu bir necha daqiqa olishi mumkin.)

O'zingizni tekshiring {#checklist}

  • [ ] .target, .project, .external va .sdk orasidagi farqni va har biri qachon kerakligini aytib bera olaman.
  • [ ] Xcode native va Tuist XcodeProj-based integratsiya farqini bilaman va nega .external cache bilan ishlashini tushunaman.
  • [ ] tuist install paketlarni **Tuist/.build**ga yuklashini bilaman va uni .gitignore qilaman, Package.resolvedni esa commit qilaman.
  • [ ] .staticFramework va .framework farqini, va "static side effect" ogohlantirishi nimani anglatishini bilaman.
  • [ ] Implicit import nima ekanini va uni tuist inspect dependencies --only implicit qanday tutishini (exit code 1) bilaman.
  • [ ] tuist graph bilan dot/json chiqara olaman va -o papka ekanini eslayman.
  • [ ] Macro'ni tashqaridan ishlatish va o'zim yaratishni farqlay olaman (.external / .package(.macro) vs .macro product + .macro(name:)).
  • [ ] -ObjC, Firebase/FBLPromises, TCA/previews muammolarining yechimini bilaman.

Xulosa {#xulosa}

  • Dependency graph aniq (explicit) va statik bo'lishi kerak — faqat shundagina tekshiriladi va optimallashadi. Tuist buni generatsiya paytida validatsiya qiladi.
  • TargetDependencyning 10 holati bor: local (.target, .project), binary (.framework, .library, .xcframework), system (.sdk, .xctest), tashqi (.external, .package, .macro). Har biri status va condition oladi.
  • Tashqi Swift Package'lar uchun tavsiya — XcodeProj-based integratsiya (Tuist/Package.swift + .external(name:)): u binary cache va selective testing bilan ishlaydi.
  • tuist install paketlarni **Tuist/.build**ga yuklaydi (rasmiy hujjatdagi Tuist/Dependencies eskirgan — biz tekshirdik). Package.resolved — commit qilinadi, .build — gitignore.
  • .staticFramework default; statik target'ni bir nechta joydan link qilish "static side effect" ogohlantirishini beradi — buni jiddiy qabul qiling.
  • tuist inspect dependencies --only implicit — grafik tozaligini CI'da nazorat qiluvchi asosiy quroldir (implicit topilsa exit code 1). Toss buni ~1,000 modulda cache'ni ishchi saqlash uchun ishlatadi.
  • Swift Package Registry resolve'ni tezlashtiradi (Swift Package Index asosida); yuqori limit uchun Tuist hisobi kerak.
  • Keyingi dars: 05. Migratsiya — Xcode (va XcodeGen/SPM/Bazel) loyihasidan Tuist'ga bosqichma-bosqich o'tish

Manbalar {#manbalar}

  1. Tuist Docs — Dependencies — local/external dependency turlari, PackageSettings, static/dynamic, troubleshooting (FBLPromises, TCA, -ObjC).
  2. Tuist Docs — Implicit imports (inspect dependencies) — implicit import nazorati va CI tavsiyasi.
  3. Tuist changelog — inspect dependencies (2025-12-13) — birlashtirilgan implicit+redundant tekshiruv.
  4. Tuist Docs — Swift Package Registry — registry identifikatorlari, limitlar, registryEnabled.
  5. Tuist Docs — v3 → v4 migration — fetch→install, Dependencies.swift→Package.swift o'zgarishlari.
  6. Monzo case study (Tuist, 2026-05-26) — testlar dinamik, release statik; ~200 modul, ~80% cache hit.
  7. Toss case study (Tuist, 2026-10) va Tuist "Scaling" blogi (2025-09-22) — grafikni cacheable qilish, implicit import nazorati.
  8. Asana — Scaling a mature iOS codebase with Tuist — Bazel o'rniga Tuist tanlovi, real build raqamlari.
  9. [Apple Technical Q&A QA1490 — -ObjC/-force_load](https://developer.apple.com/library/archive/qa/qa1490/_index.html) — Objective-C kategoriyalari uchun linker flag.
  10. The Composable Architecture — static linking muhokamasi — -ObjC va previews muammosi.
  11. swift-syntax — macro implementatsiyasi bog'liqligi.
  12. Swift Package Index — Tuist Registry paketlari manbasi.
  13. SnapKit — darsdagi tekshirilgan tashqi paket (5.7.1).

Markdown manba

iOS Lessons