Flutter CI/CD 全自動化詳細解析:一個產品 App 的環境管理


嗨大家好,我是 yu,幾個月前我寫下了 iOS CI/CD 的自動化詳細解析(iOS App 環境管理 : 靈活運用 Xcode Scheme、GitLab 和 Fastlane 設置不同的開發環境)。在那篇文章中,我們模擬了各種實戰中會遇到的環境狀況,以及如何針對這些狀況分配自動化工具,完成我們期望而且環境乾淨的技巧。時至今日,Flutter 開始大放異彩,是時候來把這套核心邏輯放置到 Flutter 上了!

首先,這套腳本與理論已經在我進行的專案中經過無數次的驗證,而且前置作業與腳本較為複雜。不過沒有關係,我已經為大家準備好起始的開源模板,這套模板甚至能用在各個 flutter 起始專案上哦!

在我們開始之前,請先確認一下環境中已經裝有以下需要的指令集與開發軟體!環境需求列表:

  • npx or yarn
  • Xcode 12
  • VS Code 或習慣使用的編輯器
  • Android SDK
  • Flutter SDK
  • Cocoapods
  • Fastlane
  • Gitlab CI Runner (機器上需要可以運行 iOS 以及 Flutter SDK,並且需要帶有 flutter 與 ios 標籤,如下圖所示)
gitlab-ci-runner

啟動專案

好的!環境設置好之後,就先讓我們啟用開始模板吧!

npx @klearthinkk/create-ktnest-temp

yarn create @klearthinkk/ktnest-temp

執行完成後,讓我們選擇 flutter-gitlab

flutter-gitlab

然後輸入專案名稱:

project-name-flutter

執行並且等待完成:

run-flutter

讓我們打開專案觀察一下,可以發現指令產生的模板已經有許多預設的檔案設置:

flutter-overview

其中 fastlane 是已經開發完成的腳本,包含測試、部署功能;main_prod.dart 代表正式環境的入口;main_staging.dart 代表測試環境的入口;main.dart 代表開發環境入口;而 .gitlab-ci.yml 代表 Gitlab CI 的設定檔。

Flutter Flavor 設定

Flutter Flavor 可以幫助我們建構不同環境的 App,詳細資料可以參考這份文件。好的!讓我們先來設置 Xcode 的 Flavor!

首先,直接用快捷方式打開 Flutter Xcode 專案:

flutter-flavor

新增兩個 Scheme StagingProd 代表測試環境與正式環境:

flutter-new-scheme
flutter-staging-scheme
flutter-product-scheme

接著,讓我們點擊 PROJECT 設置 Configurations

flutter-runner-configurations
add-configurations
all-configurations

這邊我們多設置了四種:

  • Debug-Staging
  • Debug-Prod
  • Release-Staging
  • Release-Prod

分別代表 Flutter 模式 + Flavor。Release-Prod 就代表使用 Release 模式,並且使用 Flavor Prod

設置完成後,讓我們再次回到 Flutter 專案中,並且使用指令 cd ios && fastlane test 來驗證我們的測試結果。

test-flutter-project

如果看到這個畫面就代表設置成功!好的,讓我們換到 Android 平台!

flutter-fastlane-summary

首先,使用 Android Studio 打開 Flutter 專案中的 Android 專案。

flutter-android

並且切換到 app/build.gradle 加入以下程式碼:

flavorDimensions "flavor-type"

    productFlavors {
        Staging {
            dimension "flavor-type"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            resValue "string", "app_name", "內部測試版"
        }
        Prod {
            dimension "flavor-type"
            resValue "string", "app_name", "正式版"
        }
    }

iOS 相同,我們建立兩種 Flavor StagingProd

staging-prod-flavor

設置完成之後,同樣地我們回到 Flutter 專案中,並且輸入指令 cd.. && cd android && fastlane test

android-test

成功的話一樣會看到這個畫面!

android-fastlane-summary

試驗

當我們做好這些設定的時候,就可以啟動 App 測試一下了!讓我們在介面上新增幾個設定檔案。

main.dart
staging.dart
prod.dart

將入口跟 Flavor 設置一下,並且修改一下程式碼:

然後看看成果!

我們完成這些環境配置了!

部署設置

部署設置我們一樣為大家做好腳本了,但是有些前置動作需要大家先行完成。

Android

Android 推上 Google Play 時,需要設置一些 API 權限。拿到權限後到 android/fastlane/Appfile 設置:

json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("") # e.g. com.krausefx.app
for_platform :android do
    for_lane :beta do
      package_name ""
    end
  end

json_key_file 代表 Google Play API Services 的授權,通常是 json 檔案。讓我們在 package_name 輸入於商店設定好的名稱,並在下面 beta 模式時添加 .staging,讓 fastlane 運行時自動判斷與替換。

先在 app/build.gradle 加入以下設定。

signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }

同時,在 app 目錄下把要簽名的 key store 放入資料夾中。在 Android 目錄底下新增檔案 key.properties,這是關於 android key store 的密碼,內容如下:

storePassword={{store 密碼}}
keyPassword={{store key 密碼}}
keyAlias={{keyAlias}}
storeFile={{store 檔名}}

記得要移除 {{}}。

接下來,使用已經寫好的 fastlane betabuildfastlane beta,就可以將 APK or AAB 上傳部署至 Google Play Console

iOS

iOS 部分比較單純,先設置好自動簽名跟 App Connentidentifier,然後到 ios/fastlane/Appfile 填入相關資訊。

app_identifier("") # The bundle identifier of your app
apple_id("") # Your Apple email address

itc_team_id("") # App Store Connect Team ID
team_id("") # Developer Portal Team ID

for_platform :ios do
    for_lane :beta do
      app_identifier "{{your prod app_identifier}}-staging"
    end
    for_lane :betagitlab do
      app_identifier "{{your prod app_identifier}}"
    end
  end
# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile

接下來,到 ios/fastlane/Fastlane 設置好 App 密碼
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = ""

如果不了解如何產生 App 專用密碼,可以參考這篇文件

然後,同樣使用 fastlane betabuildfastlane beta,就可以將 iOS App 部署到商店中!

Fastlane 腳本分析

首先,先讓我們分析下 android/fastlane/Fastfile 的腳本設置。腳本主要以 beta 部署為主,其他腳本其實都是一些參數的微小變化。

desc "Submit a new build to apk"
    lane :beta do
        # Return the number of commits in current git branch
        build_number = number_of_commits(all: true)
        ci_pipeline_id = ENV['CI_PIPELINE_ID']
        if !ci_pipeline_id.nil? && !ci_pipeline_id.empty?
          build_number = ci_pipeline_id
        end
        Dir.chdir "../.." do
          sh("flutter", "packages", "get")
          sh("flutter", "clean")
          sh("flutter", "build", "appbundle", "-t", "lib/main_staging.dart", "--flavor=Staging", "--build-number=#{build_number}")
        end
      supply(track: "internal", aab:"../build/app/outputs/bundle/StagingRelease/app-Staging-release.aab", rollout: "1.0",
        skip_upload_images: true,
        skip_upload_screenshots: true,
        skip_upload_metadata: true,)
  end

build_number = number_of_commits(all: true) 這邊,適用於取得 commit 次數來作為版本建置號。當在 CI 環境中,這個數字會取代成 CI 工作的代號。

Dir.chdir "../.." do
          sh("flutter", "packages", "get")
          sh("flutter", "clean")
          sh("flutter", "build", "appbundle", "-t", "lib/main_staging.dart", "--flavor=Staging", "--build-number=#{build_number}")
        end

這邊是建置 flutter 的核心,因為我們執行的目錄是在 ios or android 之下,想要建置 flutter 就需要回到上層目錄。

接下來,執行建置指令 flutter build appbundle -t lib/main_staging.dart --flavor=Staging --build-number=#{build_number}

如此一來,就一口氣把入口、flavorbuild-number 都做了指定。

supply(track: "internal", aab:"../build/app/outputs/bundle/StagingRelease/app-Staging-release.aab", rollout: "1.0",
        skip_upload_images: true,
        skip_upload_screenshots: true,
        skip_upload_metadata: true,)

最後,就是把 aab 檔案上傳到 Google Play 的內部測試版中。

接下來,讓我們看看 ios/fastlane/Fastfilebeta

desc "Push a new beta build to TestFlight"
  lane :beta do
    # Return the number of commits in current git branch
    build_number = number_of_commits(all: true)
    copy_config_file("Staging")
    Dir.chdir "../.." do
      sh("flutter", "packages", "get")
      sh("flutter", "clean")
      sh("flutter", "build", "ios", "-t", "lib/main_staging.dart", "--flavor=Staging", "--no-codesign", "--build-number=#{ENV['CI_PIPELINE_ID']}")
    end
    sigh(force: true)
    build_app(workspace: "Runner.xcworkspace", scheme: "Staging", configuration: "Release-Staging")
    # upload_to_testflight
    pilot(skip_waiting_for_build_processing: true)
    # post_slack_message("Staging version #{ENV['CI_PIPELINE_ID']}")
  end

建置的邏輯跟方式都跟 android 差不多,build_app 時選擇正確的 schemeconfiguration 檔案。

總結

自動化的邏輯與方式的核心觀念,不會因為專案或語言的不同而改變,我們都是需要「想辦法」,把環境分開,然後透過工具來自動化。如此一來,就可以大大減少工程師開發時的心智負擔,甚至還發展出快速指令建置專案模板的方式,讓開發工程師可以專注在設計 App 與介面,而不需要花心力思考如何讓 App 能夠與大眾見面。有了這些基礎設施底層,絕對可以讓大家事半功倍。

DevOps 或 CI/CD 自動化的領域需要創意與思考,才能用最精簡的方式,組合出最符合需求的應用場景。工具沒有分好與壞,只是哪一項工具比較適合當下的應用場景。希望今天的分享能夠給大家一些啟發。如果你有任何問題,或者有其他關於自動化、DevOps、微服務、容器化、或者其他軟體工程的問題,都可以聯絡我,很高興為您解答!讓我們下次見。

如果你需要一個範例對照,可以查看這個專案


自認為終身學習者,對多領域都有濃厚興趣,喜歡探討各種事物。目前專職軟體開發,系統架構設計,企業解決方案。最喜歡 iOS with Swift。

blog comments powered by Disqus
Shares
Share This