Azimjon Abdurasulov

05. Migration — Xcode → Tuist

🚀 Tuist — Mukammal Qo‘llanma & Amaliyot

🔄 Migration — Xcode Loyihasidan Tuistga O'tish

"Eng yaxshi vaqt daraxtni 20 yil oldin ekish edi. Ikkinchi eng yaxshi vaqt — hozir. Loyihangizni Tuist'ga ko'chiring!"


Nima uchun Migration kerak?

Sizda allaqachon ishlayotgan Xcode loyiha bor. Nega uni Tuist'ga o'tkazish kerak?

Muammo (hozirgi)Yechim (Tuist bilan)
.xcodeproj da merge conflict'lar.xcodeproj Git'da bo'lmaydi
Build settings tartibsiz.xcconfig fayllarda toza saqlash
Dependency'lar qo'lda boshqariladiTuist avtomatik graph validation
Build time uzoqBinary caching bilan tezlashtirish
Yangi dasturchi tushunmaydiProject.swift o'zi dokumentatsiya

Migration Strategiyasi

Migration bir kunda qilinmaydi. Bosqichma-bosqich, har bir qadamda tekshirib borish kerak.

📋 Migration Rejasi:
┌─────────────────────────────────────┐
│ 1. Scaffold yaratish                │
│ 2. CI da parallel build qo'shish   │
│ 3. Build settings → .xcconfig      │
│ 4. Package dependencies ko'chirish  │
│ 5. Target migration tartibini      │
│    aniqlash                         │
│ 6. Target'larni birma-bir migrate  │
│    qilish                           │
│ 7. Eski .xcodeproj ni o'chirish    │
│ 8. App Store Archive tekshirish    │
└─────────────────────────────────────┘

1-Qadam: Scaffold Yaratish

Avval Tuist uchun kerakli fayllarni yarating:

# Loyiha papkasiga kiring
cd /path/to/MyApp

Tuist.swift yaratish

// Tuist.swift
import ProjectDescription

let tuist = Tuist()

Project.swift yaratish

// Project.swift
import ProjectDescription

let project = Project(
    name: "MyApp-Tuist",  // ⚠️ -Tuist suffix — eski project bilan conflict bo'lmasligi uchun
    targets: [
        // Target'lar keyinroq qo'shiladi
    ]
)

**Nima uchun -Tuist suffix?** Chunki ikkita .xcodeproj bir xil nomda bo'lsa Xcode xato beradi. To'liq migrate qilganingizdan keyin suffix'ni olib tashlaymiz.

Package.swift yaratish

mkdir -p Tuist
// Tuist/Package.swift
// swift-tools-version: 5.9
import PackageDescription

#if TUIST
import ProjectDescription

let packageSettings = PackageSettings(
    productTypes: [:]
)
#endif

let package = Package(
    name: "MyApp",
    dependencies: [
        // Dependency'lar keyinroq qo'shiladi
    ]
)

2-Qadam: CI da Parallel Build

Migration paytida ikkala loyiha (eski Xcode va yangi Tuist) parallel ishlashi kerak. CI pipeline'ga qo'shing:

# CI script — Tuist loyihani ham build qilish
tuist install
tuist generate --no-open
xcodebuild build \
    -workspace MyApp-Tuist.xcworkspace \
    -scheme MyApp-Tuist \
    -destination "platform=iOS Simulator,name=iPhone 16"

Bu har bir PR da Tuist loyiha ham build bo'lishini ta'minlaydi — xato bo'lsa darhol ko'rasiz.


3-Qadam: Build Settings → .xcconfig

Xcode project'dagi build settings'ni .xcconfig fayllariga ko'chiring. Bu migration'ning eng muhim qadami.

Nima uchun?

Build settings .xcodeproj ichida yashiringan. .xcconfig ga ko'chirsangiz:

  • Kod sifatida boshqarish mumkin
  • Git diff ko'rish oson
  • Tuist manifest'da ulash oson

Tuist migration tool

# Papka yaratish
mkdir -p xcconfigs

# Project-level settings ni ko'chirish
tuist migration settings-to-xcconfig \
    -p MyApp.xcodeproj \
    -x xcconfigs/MyApp-Project.xcconfig

**Natija — xcconfigs/MyApp-Project.xcconfig:**

// Auto-generated by tuist migration
SWIFT_VERSION = 5.9
IPHONEOS_DEPLOYMENT_TARGET = 16.0
CODE_SIGN_STYLE = Automatic
DEVELOPMENT_TEAM = ABCDE12345
// ... boshqa settings

Project.swift ga ulash

let project = Project(
    name: "MyApp-Tuist",
    settings: .settings(configurations: [
        .debug(
            name: "Debug",
            xcconfig: "./xcconfigs/MyApp-Project.xcconfig"
        ),
        .release(
            name: "Release",
            xcconfig: "./xcconfigs/MyApp-Project.xcconfig"
        ),
    ]),
    targets: [...]
)

Settings bo'shligini tekshirish

# Eski project'da settings qolmaganini tekshirish
tuist migration check-empty-settings -p MyApp.xcodeproj

Agar settings qolgan bo'lsa — ularni ham .xcconfig ga ko'chiring.


4-Qadam: Package Dependencies Ko'chirish

Xcode project'dagi SPM dependency'larni Tuist/Package.swift ga ko'chiring.

Xcode da dependency'larni ko'rish

Xcode → Project Navigator → Project → Package Dependencies

Package.swift ga yozish

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

#if TUIST
import ProjectDescription

let packageSettings = PackageSettings(
    productTypes: [
        "Alamofire": .framework,
    ]
)
#endif

let package = Package(
    name: "MyApp",
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire", from: "5.9.0"),
        .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")),
        .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.0.0"),
    ]
)
tuist install

5-Qadam: Migration Tartibini Aniqlash

tuist migration list-targets -p MyApp.xcodeproj

Natija (misol):

Target              Dependencies
CoreData            0
Networking          1
Analytics           1
HomeFeature         3
ProfileFeature      2
MyApp               5
MyAppTests          6

Qoida: Eng kam dependency bo'lgan target'dan boshlang (yuqoridan pastga). Chunki ular boshqa target'larga bog'liq emas — mustaqil migrate qilish oson.

Migration tartibi:

1. CoreData        (0 dependency)
2. Networking      (1 dependency)
3. Analytics       (1 dependency)
4. ProfileFeature  (2 dependencies)
5. HomeFeature     (3 dependencies)
6. MyApp           (5 dependencies)
7. MyAppTests      (6 dependencies)

6-Qadam: Target'larni Birma-bir Migrate Qilish

Har bir target uchun quyidagi qadamlarni takrorlang:

6.1 Target Build Settings → .xcconfig

tuist migration settings-to-xcconfig \
    -p MyApp.xcodeproj \
    -t CoreData \
    -x xcconfigs/CoreData.xcconfig

6.2 Target'ni Project.swift ga qo'shish

// Project.swift
let project = Project(
    name: "MyApp-Tuist",
    settings: .settings(configurations: [
        .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"),
        .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"),
    ]),
    targets: [
        // ✅ Birinchi migrate qilingan target
        .target(
            name: "CoreData",
            destinations: .iOS,
            product: .framework,
            bundleId: "com.example.CoreData",
            sources: ["Sources/CoreData/**"],
            dependencies: [],
            settings: .settings(configurations: [
                .debug(name: "Debug", xcconfig: "./xcconfigs/CoreData.xcconfig"),
                .release(name: "Release", xcconfig: "./xcconfigs/CoreData.xcconfig"),
            ])
        ),

        // ✅ Ikkinchi target (CoreData ga bog'liq)
        .target(
            name: "Networking",
            destinations: .iOS,
            product: .framework,
            bundleId: "com.example.Networking",
            sources: ["Sources/Networking/**"],
            dependencies: [
                .target(name: "CoreData"),
                .external(name: "Alamofire"),
            ],
            settings: .settings(configurations: [
                .debug(name: "Debug", xcconfig: "./xcconfigs/Networking.xcconfig"),
                .release(name: "Release", xcconfig: "./xcconfigs/Networking.xcconfig"),
            ])
        ),

        // ... qolgan target'lar
    ]
)

6.3 Tekshirish

# Generate
tuist generate --no-open

# Build
xcodebuild build \
    -workspace MyApp-Tuist.xcworkspace \
    -scheme CoreData \
    -destination "platform=iOS Simulator,name=iPhone 16"

# Test (agar test target bor bo'lsa)
tuist test CoreDataTests

6.4 Takrorlash

Har bir target uchun 6.1–6.3 qadamlarni takrorlang. Har bir target uchun alohida Pull Request yarating — code review osonroq bo'ladi.


7-Qadam: Eski .xcodeproj ni O'chirish

Barcha target'lar migrate bo'lganidan keyin:

# 1. Project nomidagi -Tuist suffixni olib tashlash
# Project.swift da: name: "MyApp" (MyApp-Tuist emas)

# 2. Eski fayllarni o'chirish
rm -rf MyApp.xcodeproj
rm -rf MyApp.xcworkspace

# 3. Qayta generate
tuist generate

# 4. .gitignore yangilash
echo "*.xcodeproj" >> .gitignore
echo "*.xcworkspace" >> .gitignore
echo "Derived/" >> .gitignore

8-Qadam: App Store Archive va Publish

Migration'dan keyin eng muhim savol: App Store'ga qanday chiqaraman?

8.1 Tuist bilan Archive Qilish

# 1. Avval generate qiling
tuist install
tuist generate --no-open

# 2. xcodebuild archive
xcodebuild archive \
    -workspace MyApp.xcworkspace \
    -scheme MyApp \
    -configuration Release \
    -destination "generic/platform=iOS" \
    -archivePath ./build/MyApp.xcarchive \
    CODE_SIGN_IDENTITY="Apple Distribution" \
    DEVELOPMENT_TEAM="ABCDE12345"

8.2 Export IPA

# ExportOptions.plist yaratish
cat > ExportOptions.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>ABCDE12345</string>
    <key>uploadBitcode</key>
    <false/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>
EOF

# IPA export qilish
xcodebuild -exportArchive \
    -archivePath ./build/MyApp.xcarchive \
    -exportPath ./build/export \
    -exportOptionsPlist ExportOptions.plist

8.3 App Store Connect ga Upload

# xcrun altool bilan upload (eski usul)
xcrun altool --upload-app \
    -f ./build/export/MyApp.ipa \
    -t ios \
    -u "your@email.com" \
    -p "app-specific-password"

# Yoki yangi usul — Apple Transporter
xcrun notarytool submit ./build/export/MyApp.ipa \
    --apple-id "your@email.com" \
    --team-id "ABCDE12345" \
    --password "app-specific-password"

8.4 Xcode UI orqali Archive (oddiy usul)

Agar terminal bilan qilishni xohlamasangiz:

  1. tuist generate — Xcode ochiladi
  2. Xcode'da: Product → Archive
  3. Organizer ochiladi → Distribute App
  4. App Store Connect tanlang → Upload

Muhim: Tuist bilan generate qilingan loyiha oddiy Xcode loyiha bilan bir xil. Archive va publish jarayoni hech qanday farq qilmaydi!

8.5 Signing (Code Imzolash) Sozlash

Project.swift da signing sozlashni unutmang:

.target(
    name: "MyApp",
    destinations: .iOS,
    product: .app,
    bundleId: "com.example.MyApp",
    // ...
    settings: .settings(
        base: [
            "CODE_SIGN_STYLE": "Automatic",
            "DEVELOPMENT_TEAM": "ABCDE12345",
            "CODE_SIGN_IDENTITY": "Apple Development",
            // Release uchun:
            // "CODE_SIGN_IDENTITY": "Apple Distribution",
        ]
    )
)

Migration Checklist ✅

[ ] Tuist.swift yaratildi
[ ] Project.swift scaffold yaratildi
[ ] Tuist/Package.swift yaratildi
[ ] CI da parallel build qo'shildi
[ ] Project build settings → .xcconfig
[ ] Package dependencies → Tuist/Package.swift
[ ] tuist migration list-targets bilan tartib aniqlandi
[ ] Har bir target migrate qilindi:
    [ ] Build settings → .xcconfig
    [ ] Target → Project.swift
    [ ] Build tekshirildi
    [ ] Test'lar o'tdi
[ ] Eski .xcodeproj o'chirildi
[ ] .gitignore yangilandi
[ ] App Store Archive ishlaydi
[ ] CI/CD pipeline yangilandi

Troubleshooting

"Compilation errors due to missing files"

❌ Error: MyFile.swift: No such file or directory

Sabab: sources glob pattern noto'g'ri. Yechim: Fayl yo'llarini tekshiring:

sources: ["Sources/MyModule/**"]  // ← papka nomini tekshiring

"Build settings don't match"

Sabab: .xcconfig da settings yetishmayapti. Yechim: Eski Xcode project build settings bilan .xcconfig ni solishtiring.

"Framework search paths"

settings: .settings(
    base: [
        "FRAMEWORK_SEARCH_PATHS": "$(inherited) $(SRCROOT)/Frameworks",
    ]
)

Xulosa

QadamBuyruq
Settings ko'chirishtuist migration settings-to-xcconfig
Target tartibini ko'rishtuist migration list-targets
Settings tekshirishtuist migration check-empty-settings
Generatetuist generate
Archivexcodebuild archive
Testtuist test

💡 Keyingi darslik: TMA — Modular Architecture — Loyihani professional tarzda modullarga bo'lish.

"Migration — bu investitsiya. Bugun bir kun sarflaysiz, ertaga har kuni soatlab vaqt tejaysiz!" ⏰

Markdown manba

iOS Lessons