Azimjon Abdurasulov

02. Manifest Fayllar

🚀 Tuist — Mukammal Qo‘llanma & Amaliyot

Tuist 02: Manifest fayllar — Project.swift, Workspace.swift, Tuist.swift va Package.swift (2026 API)

Holat: 2026-yil oktabr. Bu dars Tuist loyihasini belgilaydigan to'rtta manifest faylni — Project.swift, Target, Workspace.swift, Tuist.swift va Tuist/Package.swift — 2026-yilgi (Tuist 4.211.1) API bo'yicha noldan ekspert darajasigacha o'rgatadi. Hamma kod misollari Tuist 4.211.1 va Xcode 27.1 bilan Apple M4 Pro'da (12 core, 24 GB) tekshirilgan.

Bu darsdan keyin siz: bo'sh papkadan boshlab, bir nechta modul, ikkita configuration, xcconfig, custom scheme va resource'lari bo'lgan loyihani faqat Swift kodi bilan ta'riflab, uni generate qilib, build va test qila olasiz.

  • Project.swift ichidagi har bir maydonni (name, options, settings, targets, schemes, ...) tushunasiz va nima uchun kerakligini bilasiz.
  • Target ning 26 ta parametri, ayniqsa destinations vs deploymentTargets, product turlari va buildableFolders vs sources/resources farqini amalda ishlatasiz.
  • Tuist.swift ning 2026-yilgi yangi Tuist(project: .tuist(...)) shaklini yozasiz va eskirgan (deprecated) API'larni taniysiz.
  • Workspace.swift bilan ko'p loyihali workspace quryapsiz, Tuist/Package.swift bilan tashqi paketlarni ulash sintaksisini bilasiz.
  • tuist edit, tuist dump va manifest caching qanday ishlashini, implicit dependency'ni tuist inspect dependencies bilan qanday ushlashni ko'rasiz.

Mundarija

  1. Manifest nima va nega Swift tilida?
  2. ProjectDescription va to'rtta manifest fayl
  3. Project.swift — loyihaning yuragi
  4. Target anatomiyasi — bitta build mahsuloti
  5. buildableFolders vs sources/resources globs (2026 yangiligi)
  6. Settings, configuration va xcconfig
  7. Scheme'lar — avtomatik va custom
  8. Tuist.swift — 2026 global konfiguratsiya
  9. Workspace.swift — ko'p loyihali workspace
  10. Tuist/Package.swift va PackageSettings
  11. tuist edit, tuist dump va manifest caching
  12. To'liq misol: noldan generate → build → test
  13. Amaliy mashqlar
  14. O'zingizni tekshiring
  15. Xulosa
  16. Manbalar

1. Manifest nima va nega Swift tilida? {#nega-swift}

Xcode loyihasini oddiy usulda yaratganingizda, hamma narsa MyApp.xcodeproj ichidagi project.pbxproj faylga yoziladi. Bu fayl — mashina uchun yozilgan, OpenStep plist formatidagi, yuzlab UUID'lardan iborat matn. Uni odam qo'li bilan tahrirlab bo'lmaydi, va eng yomoni: ikki dasturchi bir vaqtda target yoki fayl qo'shsa, Git'da deyarli har safar merge conflict chiqadi.

Tuist bu muammoni boshqa tomondan hal qiladi: siz .pbxproj ni umuman tahrirlamaysiz. Uning o'rniga loyihani Swift kodida ta'riflaysiz, Tuist esa .xcodeproj ni har safar yangidan generate qiladi. Loyihani ta'riflaydigan bu Swift fayllar manifest fayllar deb ataladi.

Bu bo'yicha Tuist hujjati aniq aytadi: Swift tanlanishiga Swift Package Manager (Package.swift) ilhom bergan. Swift ishlatilgani uchun kompilyator manifest to'g'riligini tekshiradi, fayllar orasida kodni qayta ishlatish mumkin, Xcode esa syntax highlighting, autocomplete va validatsiya beradi.

🧠 Esda tuting: Manifest — bu Xcode loyihangizning manbasi (source of truth). .xcodeproj esa — kompilyatsiya mahsuloti kabi: generate qilinadigan, Git'ga qo'shilmaydigan natija. Shuning uchun .xcodeproj / .xcworkspace odatda .gitignore ga tushadi.

Manifestlar Swift bo'lgani uchun kompilyatsiya qilinadi. Shuning uchun Tuist ularni birinchi marta yurgizganda sal sekinroq, keyingi safar esa tezroq ishlaydi — kompilyatsiya natijasini u cache'laydi. Bu haqda 11-bo'limda aniq raqamlar bilan ko'ramiz.

🚀 Motivatsiya: .pbxproj merge conflict'lari jamoangizning eng behuda vaqt yo'qotishlaridan biri. Manifestga o'tganingizda bu conflict'lar deyarli nolga tushadi, chunki har kim o'zgartiradigan narsa — o'qiladigan, diff bo'ladigan, review qilinadigan oddiy Swift kodi. Asana muhandislik jamoasi (asana.com, 2023-yil blog) o'z iOS kod bazasini Tuist'ga ko'chirganda, ~1,400 qatorlik bitta manifest yozib, keyin kodni modullarga ajratgan — natijada feature'ni o'zgartirib qayta build qilish vaqti ularning o'lchoviga ko'ra 45 soniyadan 15 soniyaga tushgan.


2. ProjectDescription va to'rtta manifest fayl {#tort-manifest}

Har bir manifest fayl eng yuqorida import ProjectDescription bilan boshlanadi. ProjectDescription — bu Tuist bilan birga keladigan Swift framework; Project, Target, Workspace, Tuist, Settings, Scheme kabi hamma turlar shu yerda. Ya'ni manifest yozganingizda siz aslida shu framework'ning API'sidan foydalanasiz.

2026-yilda bir Tuist loyihasida siz asosan to'rtta manifest fayl bilan ishlaysiz:

FaylVazifasiMajburiymi?
Tuist.swiftLoyiha root'ini belgilaydi + global konfiguratsiya (server, cache, generation options)Tavsiya etiladi (root'ni aniqlaydi)
Project.swiftBitta Xcode project'ni ta'riflaydi: target'lar, settings, scheme'larHa (loyihada kamida bitta)
Workspace.swiftBir nechta project'ni bitta workspace'ga yig'adiYo'q (faqat ko'p loyihada)
Tuist/Package.swiftTashqi SPM paketlarini (dependency) e'lon qiladiYo'q (faqat tashqi paket bo'lsa)

Tuist manifest xaritasi: qaysi fayl nimani ta'riflaydi va qayerda joylashadi

Diagramma qaysi fayl nimaga javobgarligini ko'rsatadi: Tuist.swift repozitoriy ildizida turadi va "bu yer loyiha boshi" deb belgilaydi; har bir Project.swift o'z papkasida bitta .xcodeproj hosil qiladi; Workspace.swift project'larni bitta .xcworkspace'ga bog'laydi; Tuist/Package.swift esa tashqi paketlarni olib keladi. Tuist bularni o'qib, generate vaqtida tayyor Xcode fayllarini yozadi.

🧠 Esda tuting: import ProjectDescription — manifestning birinchi qatori. Agar uni unutib qo'ysangiz, tuist generate "Cannot find type 'Project' in scope" degan kompilyatsiya xatosini beradi, chunki manifest oddiy Swift fayl sifatida kompilyatsiya qilinadi.

Keling, tartib bilan — avval bitta project'dan (Project.swift), keyin target, settings, scheme, so'ng global konfiguratsiya (Tuist.swift), workspace va paketlar bilan tanishamiz. Dars oxirida (12-bo'lim) bularning hammasini bitta ishlaydigan loyihada birlashtiramiz.

3. Project.swift — loyihaning yuragi {#project-swift}

Project.swift bitta Xcode project'ni ta'riflaydi. U qaysi papkada tursa, .xcodeproj o'sha papkada, name dagi nom bilan hosil bo'ladi.

Eng muhim qoida Tuist hujjatida katta harf bilan yozilgan: **manifest ildizida faqat bitta let project = Project(...) o'zgaruvchisi bo'lishi kerak.** Kodni qayta ishlatmoqchi bo'lsangiz — Swift funksiyalariga chiqaring, lekin ildizda boshqa top-level let/var qoldirmang.

Project ning to'liq konstruktori (ProjectDescription manbasidagi Project.swift dan, Tuist 4.211.1):

// ProjectDescription — Project init (2026)
public init(
    name: String,
    organizationName: String? = nil,
    classPrefix: String? = nil,
    options: Options = .options(),
    packages: [Package.Dependency] = [],
    settings: Settings? = nil,
    targets: [Target] = [],
    schemes: [Scheme] = [],
    fileHeaderTemplate: FileHeaderTemplate? = nil,
    additionalFiles: [FileElement] = [],
    resourceSynthesizers: [ResourceSynthesizer] = .default
)

Har bir maydon nimaga kerak:

MaydonVazifasi
nameProject nomi va .xcodeproj fayl nomi (Kutubxona → Kutubxona.xcodeproj).
organizationNameYangi fayl yaratilganda Xcode header comment'idagi tashkilot nomi.
classPrefixXcode yangi class yaratganda qo'yadigan prefiks (masalan KX).
optionsProject darajasidagi sozlamalar (Project.Options) — avtomatik scheme generatsiyasi, hududlar, bundle accessor va h.k.
packagesProject'ga ulanadigan Swift paketlari (SPM-mos Package.Dependency modeli).
settingsProject darajasidagi build settings va configuration'lar.
targetsTarget'lar ro'yxati — loyihaning asosiy qismi.
schemesCustom scheme'lar (bermasangiz, Tuist avtomatik yaratadi).
fileHeaderTemplateXcode fayl shablonlari uchun maxsus header.
additionalFilesBuild qilinmaydigan, lekin Xcode'da ko'rinadigan fayllar (masalan, Dangerfile.swift, hujjatlar).
resourceSynthesizersResource'lar uchun type-safe accessor'lar generatsiyasi (keyingi darslarda).

⚠️ Diqqat: organizationName, classPrefix, settings — hammasi optional (nil yoki default). Eng minimal valid project atigi name va targets bilan ishlaydi. Qolgan maydonlarni kerak bo'lgandagina qo'shing (YAGNI) — manifestni behuda shishirmang.

Project.Options ham bir qancha foydali sozlamani ochadi (manbadan, ProjectOptions.swift):

// ProjectDescription — Project.Options.options(...)
options: .options(
    automaticSchemesOptions: .enabled(),   // avtomatik scheme'lar qanday guruhlanadi
    defaultKnownRegions: ["en", "uz"],     // localization hududlari
    developmentRegion: "uz",               // asosiy til
    disableBundleAccessors: false,
    disableSynthesizedResourceAccessors: false,
    xcodeProjectName: nil                  // .xcodeproj uchun boshqa nom
)

Masalan, automaticSchemesOptions: .disabled qilsangiz, Tuist avtomatik scheme yaratmaydi — hamma scheme'ni o'zingiz schemes: da yozasiz (buni 7-bo'limda ko'ramiz).


4. Target anatomiyasi — bitta build mahsuloti {#target-anatomiyasi}

Target — loyihaning build qilinadigan bitta bo'lagi: bitta .app, bitta .framework, bitta test bundle. Tuist'da target .target(...) factory funksiyasi bilan yaratiladi. 2026-yilda bu factory 26 ta parametrga ega (manbadagi Target.swift). Hammasini yodlash shart emas — ko'pini default qiymatda qoldirasiz. Eng muhim boshlang'ich parametrlar tartibi:

// ProjectDescription — Target.target(...) (muhim parametrlar)
public static func target(
    name: String,
    destinations: Destinations,
    product: Product,
    productName: String? = nil,
    bundleId: String,
    deploymentTargets: DeploymentTargets? = nil,
    infoPlist: InfoPlist? = .default,
    sources: SourceFilesList? = nil,
    resources: ResourceFileElements? = nil,
    buildableFolders: [BuildableFolder] = [],
    copyFiles: [CopyFilesAction]? = nil,
    headers: Headers? = nil,
    entitlements: Entitlements? = nil,
    scripts: [TargetScript] = [],
    additionalHashingInputs: [HashingInput] = [],
    dependencies: [TargetDependency] = [],
    settings: Settings? = nil,
    // ... coreDataModels, environmentVariables, launchArguments,
    //     additionalFiles, buildRules, mergedBinaryType, mergeable,
    //     onDemandResourcesTags, metadata
) -> Target

Target anatomiyasi: identifikatsiya, platforma, mahsulot, fayllar, bog'liqliklar va settings guruhlari

Diagramma target maydonlarini olti guruhga ajratadi: kim ekanligi (name, bundleId), qayerda ishlashi (destinations, deploymentTargets), nima ishlab chiqarishi (product), nimadan qurilishi (sources/resources/buildableFolders), nimaga tayanishi (dependencies) va qanday sozlanishi (settings, infoPlist, entitlements, scripts).

4.1 destinations vs deploymentTargets — ikki xil narsa

Bu ikkisi boshlang'ichlar uchun eng ko'p chalkashlik beradigan juftlik:

  • **destinations — target qaysi qurilma oilalarida** ishlashi (.iPhone, .iPad, .mac, .appleVision...). Bu Set<Destination>.
  • **deploymentTargets — har bir platformaning minimal OS versiyasi** (iOS 17.0, macOS 14.0...).

destinations: .iOS — bu aslida bitta qulay to'plam. Manbadagi Destination.swift ga ko'ra:

// ProjectDescription — Destinations.iOS
public static let iOS: Destinations = [.iPhone, .iPad, .macWithiPadDesign]

Buni men tuist dump chiqishida ham ko'rdim — .iOS aynan shu uchtaga yoyilgan:

"destinations": [ "iPhone", "iPad", "macWithiPadDesign" ]

Ya'ni destinations: .iOS yozsangiz, ilovangiz iPhone, iPad va "Mac (iPad dizayni bilan, ya'ni Apple Silicon Mac'da iPad ilovasi sifatida)" ni qo'llab-quvvatlaydi. Faqat iPhone kerak bo'lsa: destinations: [.iPhone].

4.2 product — nima ishlab chiqariladi

Product enum'i (manbadagi Product.swift, raw qiymatlari bilan) — eng ko'p ishlatiladiganlari:

QiymatTushuntirish
.appiOS/macOS ilova — foydalanuvchi o'rnatadigan asosiy mahsulot.
.frameworkDynamic framework — run vaqtida bog'lanadi.
.staticFrameworkStatic framework — compile vaqtida ilovaga qo'shiladi (modul uchun ko'p ishlatiladi).
.staticLibrary / .dynamicLibraryStatic / dynamic kutubxona.
.unitTestsUnit test bundle.
.uiTestsUI test bundle.
.bundleResource bundle (iOS'da hozircha resource bundle'lar uchun).
.appExtensionApp extension (Widget, Share, Notification...).
.appClipApp Clip (iOS).
.macroSwift Macro target.

💡 Pro tip: Modullar uchun default tanlov — .staticFramework. Bu link vaqtini kamaytiradi va dynamic framework'ning run vaqtidagi yuklanish xarajatini yo'q qiladi. Dynamic (.framework) ni faqat kerak bo'lgandagina — masalan, bir framework'ni bir nechta jarayon (app + extension) baham ko'rganda tanlang.

4.3 bundleId va infoPlist

bundleId — majburiy (Target.swift da default yo'q). Teskari-domen uslubida yozing: uz.azimjondev.kutubxona.

infoPlist ning to'rt varianti bor (manbadagi InfoPlist.swift):

// 1) Default — Tuist Info.plist ni to'liq o'zi generate qiladi
infoPlist: .default

// 2) Default ustiga qo'shimcha kalitlar
infoPlist: .extendingDefault(with: [
    "UILaunchScreen": [:],
    "CFBundleDisplayName": "Kutubxona",
])

// 3) Mavjud fayl
infoPlist: .file(path: "Support/Info.plist")

// 4) xcconfig o'zgaruvchisi (har configuration uchun boshqa plist)
infoPlist: .variable("$(INFO_PLIST_FILE)")

Default .default — bu aslida .extendingDefault(with: [:]). Ko'p holatlarda .extendingDefault(with: [...]) eng qulay: Tuist majburiy kalitlarni o'zi qo'yadi, siz faqat o'zingiznikini qo'shasiz.

4.4 dependencies — target nimaga tayanadi

Bog'liqliklar TargetDependency ro'yxati. Eng ko'p ishlatiladiganlari:

dependencies: [
    .target(name: "BooksKit"),                              // shu project ichidagi target
    .project(target: "Katalog", path: "../Features/Katalog"), // boshqa project'dagi target
    .external(name: "Alamofire"),                           // Tuist/Package.swift dagi tashqi paket
    .sdk(name: "StoreKit", type: .framework),               // Apple SDK framework
]

Bu darsda biz .target va .project ni ishlatamiz; tashqi paketlar (.external) va ularning turlari — keyingi (T04) darsning mavzusi: 04. Dependency'lar.

🧠 Esda tuting: tuist dump chiqishida bog'liqlik aniq ko'rinadi — men yuqoridagi loyihada BooksApp ni tuist dump qilganimda, uning bog'liqligi shunday chiqdi:

text "dependencies": [ { "target": { "name": "BooksKit", "status": "required" } } ]


5. buildableFolders vs sources/resources globs (2026 yangiligi) {#buildable-folders}

Target'ning kodi va resource'larini Tuist'ga ikki xil usulda ko'rsatish mumkin. Bu — 2026-yilda bilib qo'yish kerak bo'lgan eng muhim farqlardan biri.

5.1 Eski (lekin hali ishlaydigan) usul — globs

An'anaviy usul sources va resources ga glob pattern berish:

.target(
    name: "BooksApp",
    // ...
    sources: ["BooksApp/Sources/**"],
    resources: ["BooksApp/Resources/**"]
)

** — rekursiv (barcha ichki papkalar). Ba'zi fayllarni chiqarib tashlash ham mumkin (manbadagi SourceFilesList.swift):

sources: [
    .glob("BooksApp/Sources/**", excluding: "BooksApp/Sources/**/*Mock.swift"),
]

Bu usulning kamchiligi: har safar yangi .swift fayl qo'shganingizda yoki o'chirganingizda, glob natijasi o'zgaradi — demak .xcodeproj o'zgaradi, demak tuist generate ni qayta yurgizish kerak. Katta jamoada bu tez-tez takrorlanadi.

5.2 Yangi usul — buildableFolders (Xcode 16 synchronized groups)

Xcode 16 "synchronized root group" (sinxronlanadigan papka) tushunchasini kiritdi: Xcode papkaning ichini o'zi kuzatadi, fayllar ro'yxatini .pbxproj da saqlamaydi. Bu aynan .pbxproj merge conflict'larini kamaytirish uchun qilingan.

Tuist buni buildableFolders orqali qo'llab-quvvatlaydi (Tuist changelog, 2025-08-11, #7984). Manbadagi BuildableFolder.swift izohi buni shunday ta'riflaydi: "Xcode 16 da branch'lar orasida fayl referenslari tez-tez o'zgarishidan kelib chiqadigan Git conflict'larini kamaytirish uchun kiritilgan."

.target(
    name: "BooksApp",
    // ...
    buildableFolders: [
        "BooksApp/Sources",
        "BooksApp/Resources",
    ]
)

Bu yerda siz papkani ko'rsatasiz, glob emas. Papka ichidagi hamma fayl avtomatik target'ga kiradi. Natijada: yangi fayl qo'shsangiz, tuist generate ni qayta yurgizish shart emas — Xcode papkani o'zi o'qiydi.

🚀 Motivatsiya: buildableFolders bilan "fayl qo'shdim → yana generate qilishim kerakmi?" degan savol yo'qoladi. Ayniqsa AI agent, CLI yoki CI yangi fayl generatsiya qilganda, hech narsa qayta generate qilinmasligi juda qulay. Shuning uchun 2026-yilda yangi modullar uchun buildableFolders tavsiya etiladi.

tuist dump natijasi buildableFolders ni qanday ko'rishini tasdiqlaydi (men o'z loyihamda tekshirdim):

"buildableFolders": [
  { "path": { "pathString": "BooksApp/Sources", "type": "relativeToManifest" },
    "optional": false, "exceptions": { "exceptions": [] } },
  { "path": { "pathString": "BooksApp/Resources", "type": "relativeToManifest" },
    "optional": false, "exceptions": { "exceptions": [] } }
]

5.3 Istisnolar (exceptions)

Agar papkaning aksariyatini olib, ba'zi fayllarni chiqarmoqchi yoki ularga maxsus compiler flag bermoqchi bo'lsangiz, .folder(_:exceptions:) dan foydalaning. Tuist'ning rasmiy fixture'laridagi real misol:

buildableFolders: [
    .folder("App/Sources", exceptions: .exceptions([
        .exception(
            excluded: ["App/Sources/Excluded.swift"],
            compilerFlags: ["App/Sources/WithCompilerFlags.swift": "-print-stats"]
        ),
    ])),
]

⚠️ Diqqat: buildableFolders — bu Xcode 16+ imkoniyati; Xcode 15 va undan eski versiyalar uni qo'llab-quvvatlamaydi. Agar jamoangiz hali eski Xcode'da bo'lsa, sources/resources globs'da qoling. Xcode 27.1 da buildableFolders men tekshirgan loyihada muammosiz ishladi (generate → build → test — hammasi yashil).

5.4 Qaysi birini tanlash?

UsulAfzalligiKamchiligi
buildableFoldersFayl qo'shganda qayta generate shart emas; kamroq .pbxproj conflictXcode 16+ talab qiladi; per-fayl nazorat exceptions orqali
sources/resources globsHar qanday Xcode versiyasida ishlaydi; glob bilan nozik nazoratFayl qo'shilsa/o'chsa qayta generate kerak

6. Settings, configuration va xcconfig {#settings}

Build settings — bu SWIFT_VERSION, IPHONEOS_DEPLOYMENT_TARGET, SWIFT_OPTIMIZATION_LEVEL kabi Xcode sozlamalari. Tuist'da ular Settings turi orqali beriladi (manbadagi Settings.swift).

Eng oddiy shakl — base (hamma configuration'ga umumiy) + Debug/Release:

settings: .settings(
    base: [
        "SWIFT_VERSION": "6.0",
        "IPHONEOS_DEPLOYMENT_TARGET": "17.0",
    ],
    debug: ["SWIFT_OPTIMIZATION_LEVEL": "-Onone"],
    release: ["SWIFT_OPTIMIZATION_LEVEL": "-O"]
)

Lekin production loyihalarda build settings'ni Swift ichida emas, **.xcconfig fayllarda** saqlash tavsiya etiladi. Shunda sozlamalar matn fayl bo'lib, diff/review qulay bo'ladi va Swift manifestni shishirmaydi. Buning uchun custom configuration'lar bilan .settings(base:configurations:) shaklini ishlating:

settings: .settings(
    base: [
        "SWIFT_VERSION": "6.0",
        "IPHONEOS_DEPLOYMENT_TARGET": "17.0",
    ],
    configurations: [
        .debug(name: "Debug", xcconfig: "Config/Debug.xcconfig"),
        .release(name: "Release", xcconfig: "Config/Release.xcconfig"),
    ],
    defaultConfiguration: "Debug"
)

.debug(name:settings:xcconfig:) va .release(name:settings:xcconfig:) — ikkalasi ham name, ixtiyoriy settings (dictionary) va xcconfig (fayl yo'li) oladi. defaultConfiguration — command-line'dan build qilganda qaysi configuration ishlatilishini belgilaydi (bermasangiz, alifbo bo'yicha birinchi debug configuration tanlanadi).

Mana men tekshirgan Config/Debug.xcconfig:

// Config/Debug.xcconfig
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DEBUG
SWIFT_OPTIMIZATION_LEVEL = -Onone
ENABLE_TESTABILITY = YES

tuist dump chiqishi xcconfig aynan configuration'ga bog'langanini tasdiqladi:

"configurations": [
  { "name": { "rawValue": "Debug" }, "variant": "debug",
    "xcconfig": { "pathString": "Config/Debug.xcconfig", "type": "relativeToManifest" } },
  { "name": { "rawValue": "Release" }, "variant": "release",
    "xcconfig": { "pathString": "Config/Release.xcconfig", "type": "relativeToManifest" } }
]

6.1 Project-level vs target-level settings

Settings'ni ham project darajasida (hamma target'ga), ham bitta target ichida berish mumkin. Target-level settings project-level'ni ustidan yozadi (override):

.target(
    name: "BooksApp",
    // ...
    settings: .settings(base: [
        "DEVELOPMENT_TEAM": "ABCDE12345",
    ])
)

6.2 DefaultSettings — Tuist qancha tayyor sozlama qo'shadi

.settings(...) da defaultSettings: parametri Tuist target'ga qancha tayyor sozlama qo'shishini boshqaradi (manbadagi Settings.swift):

  • .recommended (default) — Tuist tavsiya qiladigan to'liq to'plam, jumladan ogohlantirish flag'lari.
  • .essential — faqat minimal kerakli sozlamalar (PRODUCT_NAME, TARGETED_DEVICE_FAMILY va h.k.).
  • .none — Tuist hech qanday build setting qo'shmaydi (hammasini o'zingiz berasiz).
settings: .settings(
    base: ["SWIFT_VERSION": "6.0"],
    defaultSettings: .essential   // Tuist kamroq aralashsin
)

💡 Pro tip: Ba'zi sozlamani Tuist'ning tavsiya qiladigan qiymatidan tozalamoqchi bo'lsangiz, hammasini .none qilib tashlamang — .recommended(excluding: ["CLANG_ENABLE_OBJC_WEAK"]) ko'rinishida faqat kerakli kalitni chiqarib tashlang.


7. Scheme'lar — avtomatik va custom {#schemes}

Scheme — "qanday build / test / run / profile / archive qilish kerak"ni belgilaydigan konfiguratsiya. Tuist'ning eng qulay xususiyatlaridan biri: siz scheme yozmasangiz ham, u har bir target uchun avtomatik scheme yaratadi.

Men yuqoridagi Kutubxona loyihasini generate qilib, xcodebuild -list bilan scheme'larni ko'rganimda, natija shunday bo'ldi:

Information about workspace "Kutubxona":
    Schemes:
        BooksApp
        BooksKit
        Generate Project
        Kutubxona
        Kutubxona-Workspace

Bu ro'yxatni o'qib chiqaylik:

  • BooksApp, BooksKit — har bir non-test target uchun avtomatik scheme.
  • Kutubxona — men qo'lda yozgan custom scheme (pastda).
  • Kutubxona-Workspace — butun workspace'ni qamrab oluvchi scheme (barcha target'lar birga).
  • Generate Project — tuist generate ni qayta yurgizadigan maxsus scheme. U default bo'yicha qo'shiladi (buni Tuist.swift dagi includeGenerateScheme boshqaradi, 8-bo'limga qarang).

🧠 Esda tuting: Test target alohida scheme bo'lmaydi — u o'zi tegishli target scheme'iga biriktiriladi. Men BooksKitTests ni yaratdim, lekin ro'yxatda BooksKitTests degan scheme yo'q: uning testlari BooksKit (va men yozgan Kutubxona) scheme'ining test action'ida ishlaydi. Shu sabab tuist test BooksKitTests xato beradi — scheme nomini (BooksKit yoki Kutubxona) berish kerak.

Custom scheme yozish

Avtomatik scheme kamlik qilsa (masalan, test action'ni aniq belgilamoqchi bo'lsangiz yoki maxsus argument qo'shmoqchi bo'lsangiz), schemes: da o'zingiz yozasiz. Scheme.scheme(...) ning signaturasi (manbadagi Scheme.swift):

// ProjectDescription — Scheme.scheme(...)
.scheme(
    name: "Kutubxona",
    shared: true,                                      // repozitoriyga commit qilinadimi
    hidden: false,                                     // Xcode dropdown'da ko'rinadimi
    buildAction: .buildAction(targets: ["BooksApp"]),
    testAction: .targets(["BooksKitTests"]),
    runAction: .runAction(configuration: .debug)
)
  • buildAction — qaysi target'lar build qilinadi (.buildAction(targets:)). findImplicitDependencies: false berib, Xcode'ning implicit dependency izlashini o'chirib qo'yish ham mumkin.
  • testAction — qaysi testlar ishlaydi (.targets([...])) yoki test plan'lar (.testPlans([...])).
  • runAction — qaysi configuration bilan ishga tushiriladi.
  • archiveAction, profileAction, analyzeAction — kerak bo'lsa qo'shiladi.

💡 Pro tip: shared: true — scheme .xcodeproj'ning xcshareddata papkasiga tushadi, ya'ni xcodebuild uni command-line'dan ko'radi. CI'da ishlatiladigan har qanday scheme shared: true bo'lishi shart, aks holda CI "scheme topilmadi" deydi.

⚠️ Diqqat (API o'zgarishi): runAction da ilgari executable: "AppName" (string → TargetReference) ishlatilardi. 4.211.1 da bu shakl deprecated; yangi shakl executable: .executable("AppName") yoki umuman bermaslik (.runAction(configuration: .debug)). Oddiy, bitta app target'li scheme'da executable'ni tushirib qoldirsangiz, Tuist uni o'zi aniqlaydi.


8. Tuist.swift — 2026 global konfiguratsiya {#tuist-swift}

Tuist.swift ikki vazifani bajaradi: (1) repozitoriyda loyiha ildizini belgilaydi (Tuist yuqoriga qarab shu faylni qidiradi), (2) global konfiguratsiyani saqlaydi. U eski Tuist 3 dagi Config.swift ning o'rnini bosgan — Config.swift endi yo'q.

8.1 Minimal Tuist.swift (2026 shakli)

// Tuist.swift
import ProjectDescription

let tuist = Tuist(project: .tuist())

Diqqat qiling: Tuist(project: .tuist()). Bu — 2026-yilgi to'g'ri shakl. Men buni Tuist'ning rasmiy generated_app_with_buildable_folders fixture'ida ham, o'z loyihamda ham aynan shunday ko'rdim.

⚠️ Diqqat (eng muhim 2026 o'zgarishi): Internetdagi ko'p eski qo'llanma Tuist(compatibleXcodeVersions:..., generationOptions: ...) ko'rinishini ko'rsatadi. Manba kodida (Tuist.swift) bu eski konstruktor endi @available(*, deprecated) bilan belgilangan va izohda: "Use the new .init(project: .tuist(...)) that nests the property-related attributes into project." Ya'ni compatibleXcodeVersions, plugins, generationOptions, installOptions endi .tuist(...) ning ichiga ko'chgan.

8.2 Kengaytirilgan Tuist.swift

Konfiguratsiya qo'shmoqchi bo'lsangiz, hammasi .tuist(...) ichiga tushadi (manbadagi TuistProject.swift):

// Tuist.swift
import ProjectDescription

let tuist = Tuist(
    project: .tuist(
        compatibleXcodeVersions: "27.1",          // faqat shu Xcode versiyasi
        generationOptions: .options(
            defaultSwiftVersion: "6.0",           // SWIFT_VERSION bermagan target'lar uchun
            onOutdatedDependencies: .warn,        // Package.resolved eskirsa nima qilish
            projectFormat: .pbxproj               // yoki .xcproj (Xcode 27.2 JSON5 formati)
        )
    )
)

compatibleXcodeVersions enum'i (manbadagi CompatibleXcodeVersions.swift): .all (default), .exact("27.1"), .upToNextMajor("27.0"), .upToNextMinor("27.1"). String literal ("27.1") avtomatik .exact ga aylanadi.

GenerationOptions 2026-yilda ancha boyigan (manbadagi ConfigGenerationOptions.swift). Eng foydali maydonlari:

MaydonVazifasi
defaultSwiftVersionSWIFT_VERSION bermagan lokal target'larga qo'yiladigan Swift tili rejimi (default "5").
onOutdatedDependenciesPackage.resolved o'zgargan bo'lsa: .warn (default), .install (avtomatik tuist install), .fail.
projectFormat.pbxproj (default) yoki .xcproj — Xcode 27.2 ning eksperimental JSON5 project formati.
includeGenerateScheme"Generate Project" scheme'ini qo'shish (default true — shuning uchun yuqorida ko'rgan edik).
enableCachingXcode compilation cache sozlamalarini qo'shish.
warningsAsErrorsGeneration ogohlantirishlarini xatoga aylantirish.

8.3 enforceExplicitDependencies — endi deprecated

Eski qo'llanmalarda generationOptions: .options(enforceExplicitDependencies: true) degan sozlama bor edi — u implicit dependency'larni build vaqtida taqiqlardi. 4.211.1 da bu deprecated. Manba kodidagi izoh aniq aytadi:

enforceExplicitDependencies is deprecated.
Use 'tuist inspect dependencies --only implicit' instead.

Ya'ni endi implicit dependency'ni build vaqtida bloklash o'rniga, uni alohida buyruq bilan tekshirish tavsiya etiladi. Bu buyruqni amalda 11-bo'limda ishlatamiz va u qanday xato berishini ko'ramiz.

8.4 fullHandle — server bilan bog'lanish

Agar Tuist serverining cache/insights imkoniyatlaridan foydalanmoqchi bo'lsangiz, loyiha "handle"ini shu yerga yozasiz:

let tuist = Tuist(
    fullHandle: "mening-tashkilotim/kutubxona",
    project: .tuist()
)

⚠️ Diqqat: fullHandle faqat Tuist serveriga (tuist.dev yoki self-hosted) ulanganda kerak va hisob/obuna talab qiladi. Lokal generate, build, test, hatto binary cache uchun u shart emas — biz bu darsda hech qanday hisobsiz ishladik. Server imkoniyatlari T07 va T15 darslarida.


9. Workspace.swift — ko'p loyihali workspace {#workspace-swift}

Default holatda tuist generate bitta .xcworkspace yaratadi: u generate qilinayotgan project'ni va uning bog'liqliklari project'larini o'z ichiga oladi. Ya'ni oddiy holatda Workspace.swift shart emas.

Workspace.swift faqat o'zingiz workspace tarkibini boshqarmoqchi bo'lsangiz kerak bo'ladi — masalan, bir-biriga bog'liq bo'lmagan bir nechta project'ni bitta oynada ko'rmoqchisiz yoki workspace'ga hujjat fayllarini qo'shmoqchisiz.

Workspace ning konstruktori (manbadagi Workspace.swift):

// ProjectDescription — Workspace init
public init(
    name: String,
    projects: [Path],
    schemes: [Scheme] = [],
    fileHeaderTemplate: FileHeaderTemplate? = nil,
    additionalFiles: [FileElement] = [],
    generationOptions: GenerationOptions = .options()
)

Men ikki loyihali workspace tuzib, uni tekshirdim. Struktura:

MultiLoyiha/
├── Tuist.swift
├── Workspace.swift
├── Tuist/
│   └── Package.swift
├── Apps/
│   └── Kutubxona/
│       ├── Project.swift        (.app, Katalog'ga bog'liq)
│       └── Sources/App.swift
└── Features/
    └── Katalog/
        ├── Project.swift        (.framework)
        └── Sources/Katalog.swift

Workspace.swift:

// Workspace.swift
import ProjectDescription

let workspace = Workspace(
    name: "MultiLoyiha",
    projects: [
        "Apps/Kutubxona",
        "Features/Katalog",
    ]
)

App project'ning Katalog'ga bog'lanishi — .project(target:path:) orqali:

// Apps/Kutubxona/Project.swift (qisqartirilgan)
.target(
    name: "Kutubxona",
    destinations: .iOS,
    product: .app,
    bundleId: "uz.azimjondev.kutubxona",
    deploymentTargets: .iOS("17.0"),
    infoPlist: .extendingDefault(with: ["UILaunchScreen": [:]]),
    buildableFolders: ["Sources"],
    dependencies: [
        .project(target: "Katalog", path: "../../Features/Katalog"),
    ]
)

tuist generate --no-open natijasi (real chiqish):

Generating workspace MultiLoyiha.xcworkspace
Generating project Katalog
Generating project Kutubxona
Total time taken: 0.782s
✔ Success
  Project generated.

Ikkala project ham generate bo'ldi va xcodebuild -list workspace scheme'larini ko'rsatdi: Katalog, Kutubxona, MultiLoyiha-Workspace, Generate Project.

🧠 Esda tuting: Workspace.swift da faqat asosiy project'lar yo'lini berasiz — ularning bog'liqliklari (dependency graph) avtomatik qo'shiladi. Men Workspace.swift da faqat ikkita yo'l berdim, lekin Katalog Kutubxona'ning bog'liqligi bo'lgani uchun u ham to'g'ri ulandi. Build system bog'liqliklarni to'g'ri hal qilishi uchun bu kerak.

Mono-project yoki multi-project?

Ko'p so'raladigan savol: bitta katta Project.swift yaxshiroqmi yoki har modul uchun alohida project? Tuist hujjati buni ochiq qoldiradi, lekin o'z sababini aytadi:

🏢 Real-world: Tuist jamoasi o'z loyihasida mono-project (bitta Project.swift) ishlatadi, chunki: (1) cold generate tezroq — kamroq manifest fayl kompilyatsiya qilinadi; (2) kodni qayta ishlatish uchun .xcodeproj o'rniga ProjectDescriptionHelpers dan foydalaniladi. .xcodeproj Git'ga tushmagani uchun, "mono-project Git conflict beradi" degan klassik sabab Tuist'da yo'qoladi — demak tanlov sizniki (manba: Tuist "Manifests" hujjati). Multi-project'ni ko'proq domenlarni ajratish uchun, Xcode'ning tavsiya qilgan strukturasiga yaqinroq bo'lish uchun tanlashadi.

Modullarni alohida project qilishning chuqur arxitekturasi (TMA, µFeature) — T06 darsida: 06. Modular arxitektura.


10. Tuist/Package.swift va PackageSettings {#package-swift}

Tashqi SPM paketlari (Alamofire, Kingfisher...) Tuist'da Project.swift da emas, alohida **Tuist/Package.swift** faylida e'lon qilinadi. Bu — Tuist 4 ning modeli (eski Tuist 3 dagi Tuist/Dependencies.swift va tuist fetch endi yo'q).

Fayl oddiy SPM Package.swift ga o'xshaydi, lekin #if TUIST bloki bilan Tuist-ga xos sozlamalarni qo'shadi (manbadagi PackageSettings.swift):

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

#if TUIST
    import ProjectDescription

    let packageSettings = PackageSettings(
        productTypes: [
            "Alamofire": .framework,   // default .staticFramework, buni .framework qildik
        ],
        baseProductType: .staticFramework
    )
#endif

let package = Package(
    name: "Kutubxona",
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"),
    ]
)

Bu yerda nima bo'layapti:

  • let package = Package(...) — oddiy SPM e'loni: qaysi paketlar, qaysi versiya.
  • #if TUIST ... #endif — faqat Tuist manifestni o'qiyotganda kompilyatsiya bo'ladi; oddiy swift build buni ko'rmaydi.
  • PackageSettings — paketlar qanday product turiga aylanishini boshqaradi. **Paketlar default bo'yicha .staticFramework** ga aylanadi (baseProductType), lekin productTypes orqali ayrimini .framework qilish mumkin.

Paketni ulagandan keyin tuist install ni yurgizasiz — u paketlarni yechib, Tuist/.build papkasiga yuklaydi.

⚠️ Diqqat: Tashqi paketlar Tuist/.build (SwiftPM'ning build papkasi) ichiga yechiladi — **Tuist/Dependencies emas** (bu Tuist 3 ning eski yo'li edi). Tuist/.build ni .gitignore ga qo'shing. Target'da paketdan foydalanish esa .external(name: "Alamofire") orqali bo'ladi.

🧠 Esda tuting: Bu darsda biz tashqi paketlarni chuqur ishlatmaymiz — bizning asosiy misolimiz (Kutubxona) hech qanday tashqi paketga tayanmaydi, shuning uchun uning Tuist/Package.swift dagi dependencies ro'yxati bo'sh. Paketlar, XCFramework, binary cache va .external ning hamma turlari — keyingi dars (T04) mavzusi: 04. Dependency'lar.


11. tuist edit, tuist dump va manifest caching {#edit-dump-cache}

Manifestlarni yozish, ko'rish va ularning ichki mexanizmini tushunishda uchta narsa yordam beradi: tuist edit, tuist dump va manifest caching.

Generate oqimi: manifest Swift fayllari → ProjectDescription kompilyatsiyasi (cache) → JSON model → .xcodeproj/.xcworkspace

Diagramma generate mexanizmini ko'rsatadi: Tuist avval manifest .swift fayllarini ProjectDescription bilan kompilyatsiya qiladi (natija cache'lanadi), keyin ularni ishga tushirib JSON model chiqaradi (tuist dump shuni ko'rsatadi), oxirida shu modeldan .xcodeproj/.xcworkspace ni generate qiladi.

11.1 tuist edit — autocomplete bilan tahrirlash

Manifestni istalgan matn muharririda yozsa bo'ladi, lekin Tuist maxsus workflow beradi:

tuist edit

Bu buyruq nima qiladi (rasmiy "Editing" hujjatidan): global papkada vaqtinchalik Xcode project yaratadi va uni Xcode'da ochadi. Ichida Manifests degan papka bo'ladi — uni build qilib, hamma manifestingiz to'g'riligini tekshirishingiz mumkin. Xcode'da bo'lgani uchun autocomplete, syntax highlighting va error checking ishlaydi.

tuist edit --help ko'rsatgan asosiy flaglar (men o'zim yurgizdim):

USAGE: tuist edit [--path <path>] [--permanent] [--only-current-directory]

  -P, --permanent/--no-permanent   Loyihani joriy papkada yaratadi va jarayonni
                                    bloklamaydi (default: --no-permanent)
  -o, --only-current-directory      Faqat joriy papkadagi manifestni qo'shadi

Default tuist edit jarayonni bloklab turadi (Xcode yopilguncha terminal band bo'ladi). Shuning uchun amaliy workflow: bitta terminal oynasida tuist edit ni ushlab turasiz (tahrirlash uchun), boshqasida tuist generate ni yurgizasiz.

🧠 Esda tuting: Tahrirlashni generate qilingan .xcodeproj dan qilib bo'lmaydi — bu ataylab shunday qilingan. Shunday qilib generate qilingan project Tuist'ga bog'lanib qolmaydi, kelajakda Tuist'dan voz kechmoqchi bo'lsangiz oson chiqib ketasiz. tuist edit manifestlarni **/{Manifest}.swift glob'i bilan topadi (loyiha ildizidan, ya'ni Tuist.swift turgan joydan). Shuning uchun ildizda to'g'ri Tuist.swift bo'lishi shart.

11.2 tuist dump — manifest nimaga aylanganini ko'rish

tuist dump manifestingizni Tuist ichki modeliga aylantirib, JSON ko'rinishida chiqaradi. Bu — manifestingiz haqiqatan nima bo'lishini ko'rishning eng aniq usuli (debug uchun oltin):

tuist dump project       # Project.swift ni JSON qilib chiqaradi
tuist dump workspace     # Workspace.swift ni
tuist dump config        # Tuist.swift ni

Men Kutubxona loyihamda tuist dump project yurgizganimda, 510 qatorlik JSON chiqdi. Shu yerda, masalan, destinations: .iOS ning aslida ["iPhone", "iPad", "macWithiPadDesign"] ekanini, xcconfig'larning configuration'ga bog'langanini va bog'liqliklarni aniq ko'rdim (yuqoridagi bo'limlarda shu chiqishlardan iqtibos keltirdim).

💡 Pro tip: Biror sozlama ishlamayotgandek tuyulsa, taxmin qilish o'rniga tuist dump qiling. Masalan, "destinations to'g'ri yoyilganmi?", "xcconfig bog'landimi?" degan savolga JSON darhol javob beradi.

11.3 Manifest caching — nega birinchi generate sekinroq?

Manifestlar Swift bo'lgani uchun kompilyatsiya qilinadi. Tuist bu kompilyatsiya natijasini cache'laydi (~/.cache/tuist/Manifests). Shuning uchun birinchi generate sekinroq, keyingilari tezroq.

Men buni Apple M4 Pro (12 core, 24 GB), Tuist 4.211.1, Xcode 27.1 da o'lchadim:

HolatVaqtIzoh
Birinchi generate (manifest cache bo'sh)1.408 sTuist "It might take a while if the cache is empty" deb ogohlantirdi
Takroriy generate (manifest cache issiq)~0.6 s (0.56–0.64 s)manifest qayta kompilyatsiya qilinmaydi
Manifestni o'zgartirgandan keyin generate~0.78 shash o'zgardi → faqat o'zgargan manifest qayta kompilyatsiya qilinadi

Ya'ni bu kichik loyihada manifest kompilyatsiyasi ~0.8 soniya tejamkorlik beradi. Yuzlab manifest fayli bo'lgan katta loyihada bu farq ancha kattaroq — shuning uchun katta loyihalarda CI'da manifest cache'ni saqlab qolish muhim.

📊 Real raqamlar: Yuqoridagi uch o'lchov — mening shu sessiyamdagi real tuist generate --no-open yugurishlarim (M4 Pro, Tuist 4.211.1, Xcode 27.1). "Issiq" o'lchov uchun generate'ni uch marta yurgizib, birinchisini (build-slot kutishi aralashgan) tashlab, qolgan ikkitasining medianini oldim.

11.4 Implicit dependency'ni ushlash (enforceExplicitDependencies o'rniga)

8-bo'limda aytdik: enforceExplicitDependencies deprecated, o'rniga tuist inspect dependencies --only implicit. Keling, buni amalda ko'raylik.

Toza loyihada buyruq hech narsa topmaydi:

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

Endi men ataylab muammo yaratdim: BooksKit ichida yangi Analytics framework'ni import qildim, lekin uni BooksKit ning dependencies ro'yxatiga qo'shmadim. tuist generate baribir muvaffaqiyatli o'tdi (generate kodni kompilyatsiya qilmaydi), lekin inspect buyrug'i xatoni ushladi:

✖ Error
  The following implicit dependencies were found:
   - BooksKit implicitly depends on: Analytics

Buyruq nol bo'lmagan exit kod bilan chiqadi — demak uni CI'ga qo'ysangiz, implicit dependency kirib qolsa, pipeline qizil bo'ladi. Yechim oddiy: BooksKit ning dependencies iga .target(name: "Analytics") ni qo'shish.

💡 Pro tip: Nega implicit dependency yomon? Xcode ba'zan target import qilgan, lekin e'lon qilmagan framework'ni ham topib qo'yadi (chunki hamma product bitta papkada bo'ladi). Bu "ishlab turgandek" ko'rinadi, lekin keyinroq incremental build'ni ishonchsiz qiladi va preview/autocomplete'ni buzadi. Rasmiy hujjat buyruqni CI'ning har bir push'ida yurgizishni tavsiya qiladi. Ogohlantirish: Tuist statik tahlilga tayanadi, shuning uchun compiler direktivasi (#if) ichidagi shartli import'larni ushlamasligi mumkin.


12. To'liq misol: noldan generate → build → test {#toliq-misol}

Endi hamma narsani bitta ishlaydigan loyihada birlashtiramiz. Loyiha — Kutubxona (kitoblar ro'yxati): ikkita modul (BooksApp ilova + BooksKit static framework), ikkita configuration, xcconfig fayllari, resource'lar, custom scheme va Swift Testing testlari. Men bu loyihani boshidan oxirigacha o'zim yurgizdim.

12.1 Papka tuzilmasi

Kutubxona/
├── Tuist.swift
├── Project.swift
├── Config/
│   ├── Debug.xcconfig
│   └── Release.xcconfig
├── Tuist/
│   └── Package.swift
├── BooksApp/
│   ├── Sources/
│   │   ├── BooksApp.swift
│   │   └── ContentView.swift
│   └── Resources/
│       ├── books.json
│       └── Assets.xcassets/
│           ├── AppIcon.appiconset/
│           ├── AccentColor.colorset/
│           └── Contents.json
└── BooksKit/
    ├── Sources/
    │   └── Book.swift
    └── Tests/
        └── BookTests.swift

12.2 Uch manifest

// Tuist.swift
import ProjectDescription

let tuist = Tuist(project: .tuist())
// Project.swift
import ProjectDescription

let project = Project(
    name: "Kutubxona",
    organizationName: "uz.azimjondev",
    options: .options(
        defaultKnownRegions: ["en", "uz"],
        developmentRegion: "uz"
    ),
    settings: .settings(
        base: [
            "SWIFT_VERSION": "6.0",
            "IPHONEOS_DEPLOYMENT_TARGET": "17.0",
        ],
        configurations: [
            .debug(name: "Debug", xcconfig: "Config/Debug.xcconfig"),
            .release(name: "Release", xcconfig: "Config/Release.xcconfig"),
        ],
        defaultConfiguration: "Debug"
    ),
    targets: [
        .target(
            name: "BooksApp",
            destinations: .iOS,
            product: .app,
            bundleId: "uz.azimjondev.kutubxona",
            deploymentTargets: .iOS("17.0"),
            infoPlist: .extendingDefault(with: [
                "UILaunchScreen": [:],
                "CFBundleDisplayName": "Kutubxona",
            ]),
            buildableFolders: [
                "BooksApp/Sources",
                "BooksApp/Resources",
            ],
            dependencies: [
                .target(name: "BooksKit"),
            ]
        ),
        .target(
            name: "BooksKit",
            destinations: .iOS,
            product: .staticFramework,
            bundleId: "uz.azimjondev.kutubxona.kit",
            deploymentTargets: .iOS("17.0"),
            buildableFolders: [
                "BooksKit/Sources",
            ]
        ),
        .target(
            name: "BooksKitTests",
            destinations: .iOS,
            product: .unitTests,
            bundleId: "uz.azimjondev.kutubxona.kit.tests",
            deploymentTargets: .iOS("17.0"),
            buildableFolders: [
                "BooksKit/Tests",
            ],
            dependencies: [
                .target(name: "BooksKit"),
            ]
        ),
    ],
    schemes: [
        .scheme(
            name: "Kutubxona",
            shared: true,
            buildAction: .buildAction(targets: ["BooksApp"]),
            testAction: .targets(["BooksKitTests"]),
            runAction: .runAction(configuration: .debug)
        ),
    ]
)
// Tuist/Package.swift
// swift-tools-version: 6.0
import PackageDescription

#if TUIST
    import ProjectDescription

    let packageSettings = PackageSettings(
        productTypes: [:],
        baseProductType: .staticFramework
    )
#endif

let package = Package(
    name: "Kutubxona",
    dependencies: []
)

12.3 Kod va resource

// BooksKit/Sources/Book.swift
import Foundation

public struct Book: Identifiable, Equatable, Sendable {
    public let id: UUID
    public let title: String
    public let author: String

    public init(id: UUID = UUID(), title: String, author: String) {
        self.id = id
        self.title = title
        self.author = author
    }
}

public enum Library {
    public static let sampleBooks: [Book] = [
        Book(title: "O'tkan kunlar", author: "Abdulla Qodiriy"),
        Book(title: "Sariq devni minib", author: "Xudoyberdi To'xtaboyev"),
        Book(title: "Mehrobdan chayon", author: "Abdulla Qodiriy"),
    ]
}
// BooksApp/Sources/BooksApp.swift
import BooksKit
import SwiftUI

@main
struct BooksApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
// BooksApp/Sources/ContentView.swift
import BooksKit
import SwiftUI

struct ContentView: View {
    private let books = Library.sampleBooks

    var body: some View {
        NavigationStack {
            List(books) { book in
                VStack(alignment: .leading, spacing: 4) {
                    Text(book.title).font(.headline)
                    Text(book.author)
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
            }
            .navigationTitle("Kutubxona")
        }
    }
}
// BooksKit/Tests/BookTests.swift — Swift Testing
import Testing
@testable import BooksKit

struct BookTests {
    @Test func sampleLibraryHasThreeBooks() {
        #expect(Library.sampleBooks.count == 3)
    }

    @Test func bookStoresTitleAndAuthor() {
        let book = Book(title: "Shum bola", author: "G'afur G'ulom")
        #expect(book.title == "Shum bola")
        #expect(book.author == "G'afur G'ulom")
    }
}

12.4 Generate → build → test

# 1. Generate
tuist generate --no-open
Loading and constructing the graph
It might take a while if the cache is empty
Generating workspace Kutubxona.xcworkspace
Generating project Kutubxona
Total time taken: 1.408s
✔ Success
  Project generated.
# 2. Build (ishlab chiqarilgan workspace'ni xcodebuild bilan)
xcodebuild -workspace Kutubxona.xcworkspace -scheme BooksApp \
  -destination 'generic/platform=iOS Simulator' build
** BUILD SUCCEEDED **
# 3. Test (tuist test — custom "Kutubxona" scheme'ini)
tuist test Kutubxona --device "iPhone 17" --os 27.0
Suite BookTests started
    ✔ sampleLibraryHasThreeBooks() (0.001 seconds)
    ✔ bookStoresTitleAndAuthor() (0.001 seconds)
Suite BookTests passed after 0.001 seconds
Test Succeeded
✔ Success
  The project tests ran successfully

🚀 Motivatsiya: E'tibor bering — biz bitta ham .xcodeproj faylga qo'l tegizmadik, bitta ham UUID yozmadik. Butun loyiha — o'qiladigan, review bo'ladigan Swift kodi. Yangi dasturchi Project.swift ni ochib, 30 soniyada loyiha tuzilmasini tushunadi. Mana shu — manifest-asosidagi loyihaning "superpower"i.

12.5 Men duch kelgan real xato: asset catalog

Birinchi urinishimda build muvaffaqiyatsiz bo'ldi. Men Assets.xcassets ni atigi bo'sh Contents.json bilan yaratgan edim. Xato:

error: None of the input catalogs contained a matching stickers icon set,
app icon set, or icon stack named "AppIcon".

Sabab: Tuist default build setting'da --app-icon AppIcon --accent-color AccentColor ni uzatadi, lekin mening asset catalog'imda AppIcon to'plami yo'q edi. Yechim (men qo'llagan): catalog ichiga to'g'ri strukturalangan AppIcon.appiconset/Contents.json va AccentColor.colorset/Contents.json qo'shdim:

// BooksApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
{
  "images": [{ "idiom": "universal", "platform": "ios", "size": "1024x1024" }],
  "info": { "author": "xcode", "version": 1 }
}

Shundan keyin build SUCCEEDED bo'ldi. Saboq: iOS .app target'ida asset catalog bo'lsa, u ichida AppIcon to'plami bo'lishi kerak — aks holda actool (asset compiler) build'ni to'xtatadi. Tuist bilan tuist init yangi loyihada buni avtomatik to'g'ri qo'yadi; biz esa manifestni qo'lda yozganimiz uchun o'zimiz qo'shishimiz kerak bo'ldi.


Amaliy mashqlar {#mashqlar}

  1. Minimal loyiha. Bo'sh papkada faqat Tuist.swift (let tuist = Tuist(project: .tuist())) va bitta target'li Project.swift yozing. tuist generate --no-open qiling. .xcworkspace ochilganini tekshiring.
  2. globs → buildableFolders. sources: ["App/Sources/**"] bilan loyiha yozing, generate qiling. Keyin uni buildableFolders: ["App/Sources"] ga o'zgartiring va qayta generate qiling. tuist dump project chiqishida farqni ko'ring ("sources" vs "buildableFolders").
  3. Ikki configuration + xcconfig. Loyihaga Debug va Release configuration'lar qo'shing, har biriga .xcconfig fayl bog'lang. tuist dump project da "xcconfig" yo'llari to'g'ri ko'rinayotganini tasdiqlang.
  4. Custom scheme. schemes: ga o'z scheme'ingizni yozing (build + test action bilan), shared: true qiling. xcodebuild -workspace <nom>.xcworkspace -list bilan scheme ro'yxatida paydo bo'lganini tekshiring.
  5. Implicit dependency'ni ushlash ⭐. Ikki modulli loyiha yozing; bir modulda ikkinchisini import qiling, lekin dependencies ga qo'shmang. tuist inspect dependencies --only implicit yurgizing — xato chiqishini ko'ring. Keyin bog'liqlikni qo'shib, buyruq toza o'tganini tasdiqlang.
  6. Workspace ⭐. Ikkita project'ni (App va Feature) bitta Workspace.swift ga yig'ing; App'ni Feature'ga .project(target:path:) bilan bog'lang. Generate qilib, workspace'da ikkala project ham ko'ringanini tekshiring.

O'zingizni tekshiring {#checklist}

  • [ ] import ProjectDescription har bir manifestning birinchi qatori ekanini va nega kerakligini tushunaman.
  • [ ] Project.swift ildizida faqat bitta let project = Project(...) bo'lishi kerakligini bilaman.
  • [ ] destinations (qurilma oilalari) va deploymentTargets (minimal OS versiyasi) farqini ayta olaman.
  • [ ] buildableFolders va sources/resources globs orasidagi farqni va qachon qaysi birini tanlashni bilaman.
  • [ ] .settings(base:configurations:) bilan xcconfig'li ikki configuration yoza olaman.
  • [ ] Avtomatik scheme'lar qanday hosil bo'lishini va test target o'z scheme'iga biriktirilishini bilaman.
  • [ ] Tuist.swift ning 2026 shakli Tuist(project: .tuist(...)) ekanini va eski init deprecated ekanini bilaman.
  • [ ] enforceExplicitDependencies o'rniga tuist inspect dependencies --only implicit ishlatilishini bilaman.
  • [ ] Tashqi paketlar Tuist/Package.swift da e'lon qilinib, Tuist/.build ga yechilishini bilaman.
  • [ ] tuist edit, tuist dump va manifest caching nima qilishini tushunaman.

Xulosa {#xulosa}

  • Tuist loyihasi Swift manifestlar bilan ta'riflanadi; .xcodeproj esa generate qilinadigan natija — Git'ga tushmaydi, merge conflict bermaydi.
  • To'rtta manifest: Tuist.swift (root + global config), Project.swift (bitta project), Workspace.swift (ko'p project), Tuist/Package.swift (tashqi paketlar). Hammasi import ProjectDescription bilan boshlanadi.
  • Target — bitta build mahsuloti; eng muhim parametrlar destinations, deploymentTargets, product, bundleId, buildableFolders/sources, dependencies.
  • 2026 yangiligi: **buildableFolders** (Xcode 16 synchronized groups) fayl qo'shilganda qayta generate ehtiyojini yo'qotadi.
  • **Tuist.swift ning to'g'ri 2026 shakli** Tuist(project: .tuist(...)); eski Tuist(generationOptions:...) va enforceExplicitDependencies — deprecated. Implicit dependency endi tuist inspect dependencies --only implicit bilan ushlanadi.
  • tuist dump manifestingiz nimaga aylanganini aniq ko'rsatadi; manifest caching birinchi generate'ni sekinroq, keyingilarini ~0.6 s qiladi (M4 Pro'da o'lchangan).
  • Hamma narsa hisobsiz, lokalda ishlaydi — biz to'liq loyihani generate → build → test qildik, bironta ham Tuist hisobi talab qilinmadi.
  • Keyingi dars: 03. Papka tuzilmasi va CLI — har bir buyruq 2026 holatida, real natijalar bilan

Manbalar {#manbalar}

  1. Tuist — Manifests (Project.swift, Workspace.swift, Tuist.swift)
  2. [Tuist — Editing (tuist edit)](https://tuist.dev/en/docs/guides/features/projects/editing)
  3. [Tuist — Dependencies (Tuist/Package.swift, PackageSettings)](https://tuist.dev/en/docs/guides/features/projects/dependencies)
  4. Tuist — Code sharing (ProjectDescriptionHelpers)
  5. [Tuist — Inspect implicit imports (tuist inspect dependencies)](https://tuist.dev/en/docs/guides/features/projects/inspect/implicit-dependencies)
  6. Tuist changelog — Buildable folders (2025-08-11, #7984)
  7. ProjectDescription manbasi — Target.swift (manifest API)
  8. Tuist — v3 dan v4 ga migratsiya (Config.swift → Tuist.swift, tuist fetch → install)
  9. ProjectDescription reference (Project, Settings, Scheme, BuildableFolder...)
  10. Asana Engineering — Scaling a mature iOS codebase with Tuist

Markdown manba

iOS Lessons