Azimjon Abdurasulov

13. Migration Case Study

🚀 Tuist — Mukammal Qo‘llanma & Amaliyot

🔄 Real Loyiha #5: Migration Case Study

"Eng yaxshi migration — bosqichma-bosqich, har bir qadamda tekshirib borilgan migration!"


Vaziyat

Sizda mavjud Xcode loyiha bor — "NotesApp". U 3 ta target'dan iborat:

NotesApp.xcodeproj
├── NotesApp (iOS App)
├── NotesKit (Framework — biznes logika)
└── NotesAppTests (Unit Tests)

Dependency'lar (SPM orqali):
├── Realm (local database)
└── SnapKit (Auto Layout)

Muammolar:

  • .xcodeproj da doimiy merge conflict'lar
  • Build settings tartibsiz
  • Yangi dasturchi 1 soat kutadi build uchun

Maqsad: Bu loyihani to'liq Tuist'ga ko'chirish.


1-Qadam: Loyiha Tahlili

Mavjud tuzilma

NotesApp/
├── NotesApp.xcodeproj          ← Bu yo'qoladi!
├── NotesApp/
│   ├── AppDelegate.swift
│   ├── SceneDelegate.swift
│   ├── Views/
│   │   ├── NoteListView.swift
│   │   └── NoteDetailView.swift
│   ├── Info.plist
│   └── Assets.xcassets
├── NotesKit/
│   ├── Models/
│   │   └── Note.swift
│   ├── Services/
│   │   └── NoteService.swift
│   └── Info.plist
└── NotesAppTests/
    └── NoteServiceTests.swift

Migration tartibi (tuist migration list-targets)

tuist migration list-targets -p NotesApp.xcodeproj
Target          Dependencies
NotesKit        0         ← Birinchi shu!
NotesApp        1
NotesAppTests   2         ← Oxirgi shu!

2-Qadam: Tuist Scaffold Yaratish

cd NotesApp/

# Tuist fayllar yaratish
touch Tuist.swift
mkdir -p Tuist

Tuist.swift

import ProjectDescription

let tuist = Tuist()

Project.swift (bo'sh scaffold)

import ProjectDescription

let project = Project(
    name: "NotesApp-Tuist",  // ⚠️ -Tuist suffix
    targets: [
        // Target'lar keyinroq qo'shiladi
    ]
)

Tuist/Package.swift

// swift-tools-version: 5.9
import PackageDescription

#if TUIST
import ProjectDescription

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

let package = Package(
    name: "NotesApp",
    dependencies: [
        // Hozircha bo'sh — keyinroq qo'shamiz
    ]
)

3-Qadam: Build Settings → .xcconfig

# Papka yaratish
mkdir -p xcconfigs

# Project-level settings
tuist migration settings-to-xcconfig \
    -p NotesApp.xcodeproj \
    -x xcconfigs/Project.xcconfig

# Target-level settings
tuist migration settings-to-xcconfig \
    -p NotesApp.xcodeproj \
    -t NotesKit \
    -x xcconfigs/NotesKit.xcconfig

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

tuist migration settings-to-xcconfig \
    -p NotesApp.xcodeproj \
    -t NotesAppTests \
    -x xcconfigs/NotesAppTests.xcconfig

Tekshirish:

tuist migration check-empty-settings -p NotesApp.xcodeproj
# ✅ No build settings found — barcha settings .xcconfig ga ko'chdi

**xcconfigs/NotesApp.xcconfig misol:**

// Auto-generated
PRODUCT_BUNDLE_IDENTIFIER = com.example.NotesApp
INFOPLIST_FILE = NotesApp/Info.plist
SWIFT_VERSION = 5.9
IPHONEOS_DEPLOYMENT_TARGET = 16.0
CODE_SIGN_STYLE = Automatic
DEVELOPMENT_TEAM = ABCDE12345
TARGETED_DEVICE_FAMILY = 1,2

4-Qadam: SPM Dependencies Ko'chirish

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

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

#if TUIST
import ProjectDescription

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

let package = Package(
    name: "NotesApp",
    dependencies: [
        .package(url: "https://github.com/realm/realm-swift", from: "10.45.0"),
        .package(url: "https://github.com/SnapKit/SnapKit", from: "5.7.0"),
    ]
)
# Dependency'larni yuklash
tuist install

5-Qadam: NotesKit Target Migrate Qilish (Birinchi!)

Project.swift yangilash

import ProjectDescription

let project = Project(
    name: "NotesApp-Tuist",
    settings: .settings(configurations: [
        .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"),
        .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"),
    ]),
    targets: [
        // ✅ NotesKit — birinchi migrate qilingan target
        .target(
            name: "NotesKit",
            destinations: .iOS,
            product: .framework,
            bundleId: "com.example.NotesKit",
            sources: ["NotesKit/**/*.swift"],  // Mavjud papka strukturasi
            dependencies: [
                .external(name: "RealmSwift"),
            ],
            settings: .settings(configurations: [
                .debug(name: "Debug", xcconfig: "./xcconfigs/NotesKit.xcconfig"),
                .release(name: "Release", xcconfig: "./xcconfigs/NotesKit.xcconfig"),
            ])
        ),
    ]
)

Tekshirish

tuist generate --no-open
xcodebuild build \
    -workspace NotesApp-Tuist.xcworkspace \
    -scheme NotesKit \
    -destination "platform=iOS Simulator,name=iPhone 16"
✅ BUILD SUCCEEDED — NotesKit migrate bo'ldi!

6-Qadam: NotesApp Target Migrate Qilish

Project.swift ga qo'shish

targets: [
    // NotesKit (avvalgi qadam)
    .target(
        name: "NotesKit",
        // ... (avvalgi kod)
    ),
    
    // ✅ NotesApp — ikkinchi migrate
    .target(
        name: "NotesApp",
        destinations: .iOS,
        product: .app,
        bundleId: "com.example.NotesApp",
        infoPlist: .file(path: "NotesApp/Info.plist"),  // Mavjud Info.plist
        sources: ["NotesApp/**/*.swift"],
        resources: [
            "NotesApp/Assets.xcassets",
            "NotesApp/LaunchScreen.storyboard",
        ],
        dependencies: [
            .target(name: "NotesKit"),
            .external(name: "SnapKit"),
        ],
        settings: .settings(configurations: [
            .debug(name: "Debug", xcconfig: "./xcconfigs/NotesApp.xcconfig"),
            .release(name: "Release", xcconfig: "./xcconfigs/NotesApp.xcconfig"),
        ])
    ),
]

Tekshirish

tuist generate --no-open
xcodebuild build \
    -workspace NotesApp-Tuist.xcworkspace \
    -scheme NotesApp \
    -destination "platform=iOS Simulator,name=iPhone 16"

7-Qadam: NotesAppTests Migrate Qilish

// Project.swift targets ga qo'shing:
.target(
    name: "NotesAppTests",
    destinations: .iOS,
    product: .unitTests,
    bundleId: "com.example.NotesAppTests",
    sources: ["NotesAppTests/**/*.swift"],
    dependencies: [
        .target(name: "NotesKit"),
        .target(name: "NotesApp"),
    ],
    settings: .settings(configurations: [
        .debug(name: "Debug", xcconfig: "./xcconfigs/NotesAppTests.xcconfig"),
        .release(name: "Release", xcconfig: "./xcconfigs/NotesAppTests.xcconfig"),
    ])
),

Test ishga tushirish

tuist test
Test Suite 'All tests' started
Test Suite 'NoteServiceTests' started
  testCreateNote ✅ (0.012s)
  testDeleteNote ✅ (0.008s)
  testFetchNotes ✅ (0.015s)
Test Suite 'NoteServiceTests' passed (3/3)
✅ ALL TESTS PASSED

8-Qadam: Eski .xcodeproj O'chirish

# Project nomidan -Tuist suffixni olib tashlash
# Project.swift: name: "NotesApp" (NotesApp-Tuist emas)

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

# .gitignore yangilash
cat >> .gitignore << 'EOF'
# Tuist generated
*.xcodeproj
*.xcworkspace
Derived/
Tuist/.build
EOF

# Qayta generate
tuist generate

9-Qadam: App Store Archive Tekshirish

# Archive
tuist generate --no-open

xcodebuild archive \
    -workspace NotesApp.xcworkspace \
    -scheme NotesApp \
    -configuration Release \
    -destination "generic/platform=iOS" \
    -archivePath ./build/NotesApp.xcarchive

# ✅ ARCHIVE SUCCEEDED

Yakuniy Project.swift

import ProjectDescription

let project = Project(
    name: "NotesApp",
    settings: .settings(configurations: [
        .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"),
        .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"),
    ]),
    targets: [
        .target(
            name: "NotesKit",
            destinations: .iOS,
            product: .framework,
            bundleId: "com.example.NotesKit",
            sources: ["NotesKit/**/*.swift"],
            dependencies: [
                .external(name: "RealmSwift"),
            ],
            settings: .settings(configurations: [
                .debug(name: "Debug", xcconfig: "./xcconfigs/NotesKit.xcconfig"),
                .release(name: "Release", xcconfig: "./xcconfigs/NotesKit.xcconfig"),
            ])
        ),
        .target(
            name: "NotesApp",
            destinations: .iOS,
            product: .app,
            bundleId: "com.example.NotesApp",
            infoPlist: .file(path: "NotesApp/Info.plist"),
            sources: ["NotesApp/**/*.swift"],
            resources: [
                "NotesApp/Assets.xcassets",
            ],
            dependencies: [
                .target(name: "NotesKit"),
                .external(name: "SnapKit"),
            ],
            settings: .settings(configurations: [
                .debug(name: "Debug", xcconfig: "./xcconfigs/NotesApp.xcconfig"),
                .release(name: "Release", xcconfig: "./xcconfigs/NotesApp.xcconfig"),
            ])
        ),
        .target(
            name: "NotesAppTests",
            destinations: .iOS,
            product: .unitTests,
            bundleId: "com.example.NotesAppTests",
            sources: ["NotesAppTests/**/*.swift"],
            dependencies: [
                .target(name: "NotesKit"),
                .target(name: "NotesApp"),
            ]
        ),
    ]
)

Migration Checklist ✅

[x] Tuist.swift yaratildi
[x] Project.swift scaffold yaratildi
[x] Tuist/Package.swift yaratildi
[x] Project build settings → xcconfigs/Project.xcconfig
[x] NotesKit build settings → xcconfigs/NotesKit.xcconfig
[x] NotesApp build settings → xcconfigs/NotesApp.xcconfig
[x] SPM dependencies → Tuist/Package.swift
[x] tuist install — dependencies yuklandi
[x] NotesKit target → Project.swift ✅ Build OK
[x] NotesApp target → Project.swift ✅ Build OK
[x] NotesAppTests target → Project.swift ✅ Tests OK
[x] Eski .xcodeproj o'chirildi
[x] .gitignore yangilandi
[x] Archive tekshirildi ✅

Nimani o'rgandik?

QadamTushuntirish
tuist migration settings-to-xcconfigBuild settings → .xcconfig
tuist migration list-targetsMigration tartibi
tuist migration check-empty-settingsSettings ko'chganini tekshirish
Bosqichma-bosqichTarget by target migrate
TekshirishHar qadamda build va test

💡 Keyingi darslik: Best Practices va Expert Tips — Ekspert darajasidagi maslahatlar.

"Migration — investitsiya. Bugun sarflagan vaqtingiz, ertaga o'n baravar qaytadi!" 💰

Markdown manba

iOS Lessons