Flutter CI/CD: Otomatik Build, Test ve Release Süreci
# Flutter CI/CD: Otomatik Build, Test ve Release Pipeline
Bir Flutter uygulamasını elle yayınlamak yavaş, hataya açık ve stresli bir süreçtir. İyi tasarlanmış bir CI/CD pipeline, release sürecindeki insan kaynaklı darboğazları ortadan kaldırır ve build'leri takip etmek yerine kod yazmaya odaklanmanızı sağlar. Bu yazıda, linting aşamasından mağaza dağıtımına kadar üretimde kullandığım pipeline'ın tamamını gerçek yapılandırma örnekleriyle anlatıyorum.
Flutter İçin CI/CD Neden Önemli
Flutter tek kod tabanından birden fazla platformu hedefler. Bu da her release'in potansiyel olarak bir Android APK/AAB, bir iOS IPA, bir web build ve masaüstü artifact'leri içerdiği anlamına gelir. Bunu elle yapmak sadece sıkıcı değil, güvenilirlik açısından da risk taşır. CI/CD pipeline size şunları kazandırır:
Temel Pipeline Aşamaları
Sağlam bir Flutter CI/CD pipeline dört aşamadan oluşur:
Her aşama bir kapı görevi görür. Herhangi bir adım başarısız olursa pipeline durur ve ekip anında bilgilendirilir.
GitHub Actions Workflow
GitHub Actions, Flutter CI/CD için en çok tercih ettiğim araç. Analiz, test ve her iki platform için build aşamalarını kapsayan, üretime hazır bir workflow:
name: Flutter CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
FLUTTER_VERSION: "3.24.0"
JAVA_VERSION: "17"
jobs:
analyze-and-test:
name: Analiz & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Bağımlılıkları yükle
run: flutter pub get
- name: Format kontrolü
run: dart format --set-exit-if-changed .
- name: Analiz çalıştır
run: flutter analyze --fatal-infos
- name: Testleri coverage ile çalıştır
run: flutter test --coverage
- name: Coverage yükle
uses: codecov/codecov-action@v4
with:
file: coverage/lcov.info
build-android:
name: Android Build
needs: analyze-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Keystore decode
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore
- name: key.properties oluştur
env:
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
run: |
cat > android/key.properties <<EOF
storePassword=$STORE_PASSWORD
keyPassword=$KEY_PASSWORD
keyAlias=$KEY_ALIAS
storeFile=release.keystore
EOF
- name: App Bundle oluştur
run: flutter build appbundle --release
- name: Artifact yükle
uses: actions/upload-artifact@v4
with:
name: android-release
path: build/app/outputs/bundle/release/*.aab
build-ios:
name: iOS Build
needs: analyze-and-test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: CocoaPods yükle
run: cd ios && pod install
- name: İmzalama sertifikasını içe aktar
env:
P12_BASE64: ${{ secrets.IOS_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
run: |
echo "$P12_BASE64" | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
security list-keychains -d user -s build.keychain
- name: Provisioning profile yükle
env:
PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: |
echo "$PROFILE_BASE64" | base64 --decode > profile.mobileprovision
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
- name: IPA oluştur
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- name: Artifact yükle
uses: actions/upload-artifact@v4
with:
name: ios-release
path: build/ios/ipa/*.ipaKendi release pipeline'larımda Flutter sürümünü her zaman sabit tutarım. "Latest" sürüme güvenmek, pazartesi sabahı sürpriz hatalarla karşılaşmanın en kısa yoludur.
Codemagic Yapılandırması
Codemagic, Flutter'a özel olarak geliştirilmiş bir CI/CD servisidir. Kod imzalama ve mağaza dağıtımını minimum yapılandırmayla gerçekleştirir. İşte bir `codemagic.yaml` yapılandırması:
workflows:
production-release:
name: Production Release
max_build_duration: 60
instance_type: mac_mini_m2
environment:
flutter: 3.24.0
xcode: latest
groups:
- google_play_credentials
- app_store_credentials
- code_signing_ios
vars:
APP_ID: com.example.myflutterapp
triggering:
events:
- tag
tag_patterns:
- pattern: "v*"
include: true
scripts:
- name: Bağımlılıkları yükle
script: flutter pub get
- name: Analiz çalıştır
script: flutter analyze --fatal-infos
- name: Testleri çalıştır
script: flutter test
- name: iOS kod imzalama ayarla
script: |
keychain initialize
app-store-connect fetch-signing-files "$APP_ID" \
--type IOS_APP_STORE \
--create
keychain add-certificates
- name: iOS build
script: |
flutter build ipa --release \
--build-number=$PROJECT_BUILD_NUMBER \
--export-options-plist=/Users/builder/export_options.plist
- name: Android build
script: |
flutter build appbundle --release \
--build-number=$PROJECT_BUILD_NUMBER
artifacts:
- build/ios/ipa/*.ipa
- build/app/outputs/bundle/release/*.aab
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internalTag tabanlı tetikleme yaklaşımını şiddetle tavsiye ediyorum. `v1.2.0` gibi bir tag push'lamak, release sürecinizdeki tek manuel adım olmalıdır.
Fastlane Entegrasyonu
Fastlane, CI/CD'nin "son kilometre" kısmını üstlenir — kod imzalama, ekran görüntüleri, metadata ve mağaza yüklemeleri. Build için GitHub Actions veya Codemagic kullansanız bile, mağaza gönderimlerini yönetmek için Fastlane vazgeçilmezdir.
Android Fastfile
default_platform(:android)
platform :android do
desc "Google Play dahili kanalına dağıt"
lane :internal do
upload_to_play_store(
track: "internal",
aab: "../build/app/outputs/bundle/release/app-release.aab",
json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"],
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
desc "Dahili kanaldan üretime yükselt"
lane :promote_to_production do
upload_to_play_store(
track: "internal",
track_promote_to: "production",
json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"],
skip_upload_changelogs: false
)
end
endiOS Fastfile
default_platform(:ios)
platform :ios do
desc "TestFlight'a yeni sürüm gönder"
lane :beta do
setup_ci if ENV["CI"]
match(
type: "appstore",
app_identifier: "com.example.myflutterapp",
readonly: is_ci
)
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store"
)
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
desc "App Store'a gönder"
lane :release do
deliver(
submit_for_review: true,
automatic_release: false,
force: true,
skip_metadata: false,
skip_screenshots: true
)
end
endKendi release pipeline'larımda iOS kod imzalama için her zaman `match` kullanırım; çünkü sertifikaları özel bir Git deposunda tutarak tüm ekibin tutarlı bir şekilde erişmesini sağlar.
iOS ve Android İçin Kod İmzalama
Kod imzalama, CI/CD kurulumlarının en çok sorun çıkardığı noktadır. Baştan doğru yaparsanız haftalarca sürecek hata ayıklama süreçlerinden kurtulursunuz.
iOS Kod İmzalama
İki temel yaklaşım vardır:
Manuel yaklaşım: `.p12` sertifikanızı ve provisioning profile'ınızı dışa aktarın, base64 ile kodlayın, CI secret'ları olarak saklayın ve build sırasında decode edin. Yukarıdaki GitHub Actions workflow'u bu yöntemi göstermektedir.
Fastlane Match: Match, sertifikalarınızı ve profile'larınızı şifrelenmiş bir Git deposunda saklar. Her ekip üyesi ve CI runner aynı kaynaktan çeker. "Benim makinemde çalışıyor" tarzı imzalama sorunları ortadan kalkar.
# Match'i ilk kez başlat
fastlane match init
# Yeni sertifika oluştur
fastlane match appstore
fastlane match developmentDikkat edilmesi gerekenler:
Android Kod İmzalama
Android imzalama daha basittir ama yine de dikkat gerektirir:
"code-comment">// android/app/build.gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}Kendi release pipeline'larımda Android keystore'unun yedeğini her zaman ayrı ve şifrelenmiş bir konumda saklarım. Keystore kaybı kalıcıdır — Google bunu kurtarmanıza izin vermez.
Release Stratejisi
Kod yayınlamak işin yalnızca yarısıdır. Kontrol, görünürlük ve geri alma planı sunan bir stratejiye ihtiyacınız var.
Semantik Sürümleme
Semantik sürümlemeyi (`MAJOR.MINOR.PATCH`) benimseyin ve iş akışınızın bir parçası yapın:
Flutter'da sürüm bilgisi `pubspec.yaml` içinde yer alır:
version: 2.4.1+42
# ^ semver ^ build numarası`+` işaretinden sonraki build numarası her mağaza gönderiminde artmalıdır. CI'da bunu pipeline çalışma numarasını kullanarak otomatikleştiriyorum:
flutter build appbundle --build-number=$GITHUB_RUN_NUMBEROtomatik Changelog Oluşturma
Changelog'ları commit mesajlarınızdan otomatik oluşturun. Bu, commit kurallarında disiplin gerektirir:
# conventional-changelog kurulumu
npm install -g conventional-changelog-cli
# CHANGELOG.md oluştur
conventional-changelog -p angular -i CHANGELOG.md -sEkibiniz conventional commit formatını (`feat:`, `fix:`, `chore:`) kullanıyorsa bu süreç tamamen otomatik hale gelir.
Beta Dağıtımı
Asla doğrudan production'a yayınlamayın. Kademeli bir dağıtım stratejisi izleyin:
iOS için TestFlight beta kanalınız olarak hizmet verir. Android için Google Play'in dahili ve kapalı test kanallarını kullanın. Firebase App Distribution, mağaza inceleme gecikmesi olmadan daha hızlı geri bildirim döngüleri istediğinizde her iki platform için de mükemmel bir alternatiftir.
Geri Alma Planı
Her release'in bir kaçış yolu olmalıdır. Pratikte bu şu anlama gelir:
Sık Yapılan CI/CD Hataları
Bu hataları defalarca gördüm — hem kendi projelerimde hem de çalıştığım ekiplerde.
1. Bağımlılıkları Cache'lememek
Flutter; Dart paketlerini, Gradle bağımlılıklarını, CocoaPods'u ve bazen Flutter SDK'nın kendisini bile indirir. Cache olmadan her build 5-15 dakikayı indirmelere harcar.
# GitHub Actions cache örneği
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.0"
cache: true # Flutter SDK'yı cache'ler
- uses: actions/cache@v4
with:
path: |
~/.pub-cache
~/.gradle/caches
ios/Pods
key: ${{ runner.os }}-deps-${{ hashFiles('**/pubspec.lock', '**/Podfile.lock') }}2. iOS Build'lerini Linux Runner'da Çalıştırmak
iOS build'leri macOS gerektirir. Bu kulağa bariz gelebilir ama `flutter build ipa` komutunu Ubuntu runner'da çalıştırıp sessizce başarısız olan pipeline'lar gördüm. iOS job'ları için her zaman `runs-on: macos-latest` kullanın.
3. Secret'ları Depoya Gömmek
Keystore'lar, API anahtarları, servis hesabı JSON dosyaları — bunların hiçbiri Git deponuzda olmamalıdır, `.gitignore` ile hariç tutulan bir dizinde bile olsa. CI sağlayıcınızın şifrelenmiş secret özelliğini veya HashiCorp Vault gibi özel bir secret yöneticisini kullanın.
4. Pipeline'ı "Hızlandırmak" İçin Testleri Atlamak
Testleri "çok uzun sürüyor" diye devre dışı bırakmak CI'ın amacını yok eder. Testleriniz yavaşsa onları optimize edin — mock kullanın, testleri paralel çalıştırın, entegrasyon testlerini ayrı bir aşamaya taşıyın. Atlamayın.
5. Build Numarası Stratejisi Olmaması
Build numaranız tutarlı bir şekilde artmıyorsa mağaza yüklemeleri reddedilir. Build numarasını artması garanti olan bir değerden türetin: CI çalışma numarası, zaman damgası veya sürüm kontrolündeki bir sayaç.
6. Kararsız Testleri Görmezden Gelmek
Görmezden gelinen kararsız bir test, hiç test olmamasından daha kötüdür. Ekibi CI hatalarını önemsememeye alıştırır. Kararsız testleri hemen düzeltin veya silin.
7. Build ve Deploy Aşamalarını Ayırmamak
Build ve deploy ayrı pipeline aşamaları olmalıdır. Deploy adımınız başarısız olursa aynı artifact'i yeniden build etmeden deploy edebilmelisiniz. Build adımında artifact'inizi yükleyin, deploy adımında indirin.
Her Şeyi Bir Araya Getirmek
Olgun bir Flutter CI/CD pipeline şöyle görünür:
Tek manuel adımlar PR'ı merge etmek, tag'i oluşturmak ve promotion'ı onaylamaktır. Geri kalan her şey otomatiktir.
Sonuç
CI/CD, Flutter projeleri için "olsa iyi olur" değil, olmazsa olmazdır. Pipeline yapılandırmasına yapılan ön yatırım ilk birkaç release'te kendini amorti eder. Daha hızlı yayınlarsınız, daha az hata yaparsınız ve her release'in aynı titiz, otomatik süreçten geçtiğini bilerek daha rahat uyursunuz.
İsterseniz Flutter projenize ve ekip iş akışınıza özel bir CI/CD pipeline tasarlayabilirim.
İlgili Makaleler
Flutter Performans Optimizasyonu: Kapsamlı Rehber
Flutter uygulamanızın performansını sistematik olarak artırın. Rebuild optimizasyonu, bellek yönetimi, lazy loading ve profiling tekniklerini öğrenin.
Flutter Testing Rehberi: Unit, Widget ve Integration Test
Flutter projelerinde test stratejisi kurun. Unit, widget ve integration test katmanlarını doğru sorumluluklarla yapılandırın.
Flutter Firebase Entegrasyonu: Auth, Firestore ve Push Bildirim
Flutter projelerinde Firebase kurulumunu, kimlik doğrulama akışlarını ve üretim odaklı güvenlik ayarlarını öğrenin.
Flutter Projeniz mi Var?
iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.
İletişime Geç