← Yazılara dön

iOS için CI/CD: Pratik Bir Rehber

iOS için CI/CD: Fastlane, GitHub Actions ve Apple servisleriyle yayıma giden tüm hattın gerçekçi bir kurulumu.


1. Giriş: iOS’ta CI/CD Neden Zordur?

Sürekli entegrasyon ve sürekli teslim (CI/CD), her commit’in otomatik olarak derlenmesini, test edilmesini ve dağıtılmasını sağlar. Web veya backend projelerinde bu görece kolaydır. iOS’ta ise üç özel zorluk vardır: kod imzalama (code signing) Apple’ın sertifika ve profil sistemine bağlıdır, derleme yalnızca macOS makinelerinde yapılabilir ve derleme süreleri uzundur. Bu rehber, bu zorlukları pratik biçimde aşan bir pipeline kurmayı anlatır.

2. Pipeline’ın Aşamaları

Tipik bir iOS pipeline’ı şu sırayı izler:

  1. Checkout: Kodu çek ve bağımlılıkları çöz.
  2. Lint: Statik analiz çalıştır (SwiftLint).
  3. Test: Birim ve UI testlerini çalıştır.
  4. Build: Uygulamayı imzalayıp arşivle (.ipa).
  5. Deploy: TestFlight veya App Store Connect’e yükle.

Her aşama başarısız olursa pipeline durur. Bu sayede bozuk kod hiçbir zaman dağıtım aşamasına ulaşmaz.

3. Derleme Otomasyonu: Fastlane

Ham xcodebuild komutları güçlüdür ama uzun ve okunması zordur. Fastlane bu komutları okunabilir “lane”lere sarar. Aşağıda bir Fastfile örneği vardır:

# fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Testleri çalıştır"
  lane :test do
    run_tests(
      scheme: "MyApp",
      devices: ["iPhone 15"],
      clean: true
    )
  end

  desc "TestFlight'a beta yükle"
  lane :beta do
    setup_ci
    match(type: "appstore", readonly: true)
    build_app(
      scheme: "MyApp",
      export_method: "app-store"
    )
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end
end

Karşılığında ham xcodebuild ile aynı işi yapmak şöyle görünürdü:

xcodebuild test \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  -resultBundlePath ./TestResults \
  CODE_SIGNING_ALLOWED=NO

4. Kod İmzalama: En Kritik Adım

CI’da en çok sorun çıkaran konu imzalamadır. Sertifikaları her geliştiricinin makinesinde elle yönetmek sürdürülemez. Fastlane’in match aracı, sertifika ve profilleri şifreli bir git deposunda saklar ve CI dahil her makineye aynısını dağıtır. Tek bir doğruluk kaynağı oluşturur.

# İlk kurulum (bir kez çalıştırılır)
fastlane match init
fastlane match appstore

# CI üzerinde (readonly modda)
fastlane match appstore --readonly

CI ortamında geçici bir anahtarlık (keychain) oluşturmak için setup_ci çağrılır. Bu, sertifikaların geçici ve izole bir alana yüklenmesini sağlar:

lane :beta do
  setup_ci   # geçici keychain oluşturur
  match(type: "appstore", readonly: true)
  # ...
end

5. GitHub Actions ile Tam Bir İş Akışı

Aşağıdaki workflow, bir PR açıldığında testleri, main dalına push yapıldığında ise TestFlight dağıtımını tetikler:

# .github/workflows/ios.yml
name: iOS CI

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - name: Bağımlılıkları önbelleğe al
        uses: actions/cache@v4
        with:
          path: |
            ~/Library/Caches/org.swift.swiftpm
            .build
          key: spm-${{ hashFiles('**/Package.resolved') }}

      - name: SwiftLint çalıştır
        run: swiftlint --strict

      - name: Testleri çalıştır
        run: fastlane test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - name: TestFlight'a yükle
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.ASC_API_KEY }}
        run: fastlane beta

deploy işi yalnızca test işi başarılı olduğunda ve dal main ise çalışır. Bu sıralama bozuk kodun yayınlanmasını imkansız kılar.

6. App Store Connect API ile Kimlik Doğrulama

Apple ID parolası ve iki faktörlü doğrulama CI için uygun değildir. Bunun yerine App Store Connect API anahtarı (ES256 JWT tabanlı) kullanılır. Bu anahtar bir kez oluşturulur ve CI’a güvenli bir secret olarak verilir:

# Fastfile içinde API anahtarını kullanma
lane :beta do
  api_key = app_store_connect_api_key(
    key_id: ENV["ASC_KEY_ID"],
    issuer_id: ENV["ASC_ISSUER_ID"],
    key_content: ENV["ASC_KEY_CONTENT"],   # .p8 içeriği
    is_key_content_base64: true
  )
  build_app(scheme: "MyApp")
  upload_to_testflight(api_key: api_key)
end

Bu yöntem, parola gerektirmediği için tamamen otomatik ve güvenlidir.

7. Sırların (Secrets) Yönetimi

CI’da hiçbir sertifika, parola veya anahtar düz metin olarak depoda bulunmamalıdır. Tüm hassas değerler CI sağlayıcısının secret deposunda tutulur ve çalışma anında ortam değişkeni olarak enjekte edilir. .p8 gibi dosyalar base64 ile kodlanıp tek bir secret olarak saklanabilir:

# Yerel makinede .p8'i base64'e çevir, çıktıyı secret olarak ekle
base64 -i AuthKey_ABC123.p8 | pbcopy

Pipeline içinde tekrar dosyaya çözmek gerekirse:

echo "$ASC_KEY_CONTENT" | base64 --decode > AuthKey.p8

Sırları log’a yazdırmaktan kaçının; çoğu CI sağlayıcısı secret değerlerini otomatik maskeler ama yine de dikkatli olmak gerekir.

8. Derleme Süresini Kısaltmak

iOS derlemeleri yavaştır ve CI dakikası maliyetlidir. Süreyi kısaltan başlıca teknikler:

  • Bağımlılık önbelleği: Swift Package Manager bağımlılıklarını önbelleğe alın (yukarıdaki workflow’da gösterildi).
  • Türev veri (derived data) önbelleği: Mümkünse derleme çıktılarını önbelleğe alın.
  • Test bölme: UI testlerini ayrı bir işe ayırıp birim testlerinden bağımsız paralel çalıştırın.
  • Sadece gerekeni çalıştır: PR’larda tüm cihaz matrisini değil, tek bir referans simülatörü hedefleyin.
# Birim ve UI testlerini paralel bölmeye örnek
strategy:
  matrix:
    test-plan: [UnitTests, UITests]
steps:
  - run: fastlane test plan:${{ matrix.test-plan }}

9. Sürümleme ve Otomatik Yapı Numarası

Her TestFlight yüklemesi benzersiz bir yapı numarası (build number) gerektirir. Bunu elle yönetmek hataya açıktır. CI üzerinde otomatikleştirin:

lane :beta do
  latest = latest_testflight_build_number(version: get_version_number)
  increment_build_number(build_number: latest + 1)
  build_app(scheme: "MyApp")
  upload_to_testflight
end

Bu, App Store Connect’teki son yapı numarasını okur ve bir fazlasını atar, böylece çakışma asla yaşanmaz.

10. Sonuç

iOS’ta sağlam bir CI/CD pipeline’ı kurmak, üç temel zorluğu çözmekten geçer: imzalamayı match ile merkezileştirmek, kimlik doğrulamayı App Store Connect API anahtarıyla otomatikleştirmek ve derleme süresini önbellekleme ile yönetmek. Aşamaları lint, test, build ve deploy olarak sıralayıp her birini bir öncekinin başarısına bağladığınızda, bozuk kodun kullanıcıya ulaşması imkansız hale gelir. İyi kurulmuş bir pipeline, sürüm çıkarmayı stresli bir ritüelden tek bir merge işlemine indirger.




← Yazılara dön