摘要
本文探討如何利用 Fastlane 自動化 Flutter 建置流程,幫助開發者提升工作效率及軟體品質。 歸納要點:
- 結合 Fastlane 與 Flutter 3.x 的新功能,提升建置流程的效率和穩定性,特別適用於大規模專案與多 Flavor 管理。
- 透過模組化設計提高 Fastlane 腳本的可維護性,包括使用 Actions、Plugins 和 Helper functions,有效管理程式碼變更與風格一致性。
- 深入整合 Fastlane 與雲端 CI/CD 平台,如 GitLab CI/CD 和 GitHub Actions,以優化建置流程並安全管理敏感資訊。
建立釋出版本可能是個繁瑣的過程。當你發現自己剛剛建立了一個帶有模擬後端的釋出版本,或者除錯變數設定為「始終開啟引導」,又或是版本錯誤時,這種情況實在讓人感到無比沮喪。
Fastlane自動化Flutter釋出:從CI/CD到Google Play Console一鍵部署
並不是說以上任何事情曾經發生在我身上。至少今天沒有。因為(請響起鼓聲):我已經使用 Fastlane 自動化了我的釋出建置流程。Fastlane 是一個開源自動化工具,簡化了 iOS 和 Android 應用程式的建置、測試和部署過程。我希望自動化以下幾項工作:
檢查是否有 FIXME 註解。如果有的話,我們通常不會想要在這樣的情況下進行釋出...
更新 pubspec.yaml 中的版本號。
執行 flutter build appbundle。
建立一個包含原生除錯符號的 zip 檔案。
將 zip 檔案複製到 appbundle 資料夾中。
在檔案總管中開啟該資料夾,以便能方便地拖入 Google Play Console。
對於許多開發者而言,僅止於使用 Fastlane 來自動化單機端的建置流程是不夠的。要真正提升效率並確保應用程式的穩定性,必須將 Fastlane 整合進 CI/CD(持續整合/持續交付)系統中。在這過程中,需要考慮許多細節,例如如何利用 Git Hooks 確保每次提交都觸發自動化測試和建置;以及如何運用雲端服務(例如 Firebase, AWS CodePipeline, Azure DevOps)實現自動部署。同時,還需注意確保整個建置過程中的安全性,包括使用安全憑證管理和訪問控制以防止未授權訪問與修改。
在文章中提到對 FIXME 註解進行檢查時,可以結合靜態程式碼分析工具,如 SonarQube 或 Dart Analyzer,自動阻擋含有 FIXME 註解的版本發布,而非僅依賴人工檢查。而關於 Google Play Console 的自動上傳,更可以探索利用 Fastlane 的 `upload_to_play_store` action 配合 Google Play Developer API,以達成完全自動化的發布流程,省去手動拖曳步驟。
至於 Flutter 應用程式的安全性及版本控制策略,同樣不可忽視。在自動化過程中,需要謹慎處理敏感資訊如私鑰和憑證等最佳實踐,包括使用環境變數或秘密管理服務(例如 AWS Secrets Manager、Azure Key Vault、Google Cloud Secret Manager)來儲存敏感資訊。可透過強大的版本控制策略,如 Semantic Versioning (SemVer) 與 Gitflow 工作流程,加強版本號碼的一致性與可追蹤性。這些措施都是為了提升整體開發效率及應用安全性而設計。
我們需要安裝 Fastlane。這可以透過 Ruby Gems 來完成。(如果您尚未安裝 Ruby,請先按照以下說明進行安裝。)此操作可以在終端機中的任何資料夾內執行。
gem install fastlane
然後前往你的 Flutter 專案,在 lib/android 資料夾內執行 fastlane init。(如果你想要自動化 iOS 的話,也可以進入 ios 資料夾並執行 fastlane init。)
fastlane init
Fastlane 會提示您輸入資訊並建立所有必要的檔案。對我們來說,最重要的檔案是 android/fastlane/Fastfile。這個檔案包含了「 lanes」,即您希望執行的各種自動化任務。Fastlane 為您建立一個基本的 Fastfile,其中包含三個 lanes:執行所有測試 - 使用 gradle 提交構建到 Crashlytics - 說實話,我對此不太了解 :) 部署版本到 Google Play(也許在後續故事中會提及)。雖然目前這些功能對我來說不是很有用,但它很好地展示了 Fastfile 的結構:
default_platform(:android) platform :android do desc "Runs all the tests" lane :test do gradle(task: "test") end desc "Submit a new Beta Build to Crashlytics Beta" lane :beta do gradle(task: "clean assembleRelease") crashlytics # sh "your_script.sh" # You can also use other beta testing services here end desc "Deploy a new version to the Google Play" lane :deploy do gradle(task: "clean assembleRelease") upload_to_play_store end end
您所有的「區域」都位於 platform:android 部分。每個區域都有一個名稱和描述。要執行該區域,請輸入 fastlane <section> <lane name> 例如:
fastlane android beta
讓我們建立一個新的工作區,並刪除不需要的任務。
default_platform(:android) platform :android do desc "Automate build process including native symbols zip" lane :build_aab_and_symbols do # we will add code here end end
重要提示:我使用 PowerShell 作為我的終端,因此某些系統命令是以 PowerShell 而非 bash 的形式呈現。在我們的團隊中,有一條規則,任何暫時性的變更,通常與測試有關,但不僅限於此,都應標記為 //FIXME。這包括為了除錯而改變常數、使用模擬後端進行測試等。在 VSCode 中有一個方便的擴充功能,可以顯示所有 TODO 和 FIXME 註解,但我... 並不總是記得在釋出之前檢查這些(哎呀!)。那麼,以下是如何使用 fastlane 來檢查它的方法:
# find all FIXME comments in dart files within the lib folder result = sh("powershell.exe -Command \"Select-String -Path (Get-ChildItem -Recurse ../../lib -Include *.dart) -Pattern 'FIXME'\"") # If we got any result, show me the comments if result != "" UI.message("⚠️ FIXME comments found in the following locations:") UI.message(result) # Print the found FIXME comments # Ask the user if they want to continue if UI.confirm("There are FIXME comments. Do you want to continue building?") UI.message("Proceeding with build despite FIXME comments...") else UI.user_error!("Aborted due to FIXME comments.") end end
Flutter Dart 程式碼 FIXME 註解搜尋與版本號更新工具
這段程式碼首先會在 /lib 資料夾內搜尋所有的 *.dart 檔案,尋找 FIXME 註解,然後將這些註解顯示給我,並詢問我是否想要繼續。例如,在建立縮短版的測試版本時,我希望在釋出中保留 FIXME 註解。這變得更加複雜,因為我希望能將引數傳遞給 lane:major - 更新主要版本號,例如從 1.2.1 變更為 2.0.0;minor - 更新次要版本號,例如從 1.2.1 變更為 1.3.0;patch - 更新修補版本號,例如從 1.2.1 變更為 1.2.2;none - 我已經更新過了(例如在之前被中止的執行中),不想再次更新,因此保持在 1.2.1。我們需要告訴這個 lane 它接收一個名為 type 的引數。desc "Automate build process including native symbols zip" lane :build_aab_and_symbols do |options| # get a parameter with name 'type' version_type = options[:type] # check that 'type' is one of the 4 options UI.user_error!("Please provide a version type: major, minor, patch or none") unless ["major", "minor", "patch","none"].include?(version_type) # rest of the code here end
然後我們可以透過呼叫來命名它
fastlane android build_aab_and_symbols type:major
現在我們需要更新版本:
if version_type != "none" # Read the current version from ../../pubspec.yaml pubspec_file = File.join("..", "pubspec.yaml") pubspec_file = File.join("..", pubspec_file) pubspec_content = File.read(pubspec_file) # Find the current version line (e.g., version: 1.2.1+24) current_version_line = pubspec_content.match(/version:\s*(\d+\.\d+\.\d+)\+(\d+)/) UI.user_error!("Couldn't find version in pubspec.yaml") unless current_version_line current_version = current_version_line[1] # e.g., "1.2.1" version_code = current_version_line[2].to_i # e.g., 24 # Split the version into major, minor, and patch major, minor, patch = current_version.split('.').map(&:to_i) # Increment the version based on the version type case version_type when "major" major += 1 minor = 0 patch = 0 when "minor" minor += 1 patch = 0 when "patch" patch += 1 end # Increment versionCode (build number) new_version_code = version_code + 1 # Build the new version string new_version = "#{major}.#{minor}.#{patch}+#{new_version_code}" # Update the pubspec.yaml content with the new version new_pubspec_content = pubspec_content.gsub(/version:\s*\d+\.\d+\.\d+\+\d+/, "version: #{new_version}") File.write(pubspec_file, new_pubspec_content) UI.success("Version updated to #{new_version}") else UI.message("No version update needed") end
我們檢查所傳遞的型別是否為空。如果需要更改版本,我們將從 pubspec.yaml 中獲取當前版本。請注意,它位於 Fastfile 之上兩層資料夾中。根據型別引數更新版本號,並用新版本替換當前版本。然後,向使用者(我 😄 )傳送一條訊息。這比我想像的要複雜得多。最初,我使用的是 zip:
sh "zip -r native-debug-symbols.zip ./build/app/intermediates/merged_native_libs/release/out/lib"
但是,雖然這建立了一個壓縮檔案,我卻無法將其上傳到 Google Play。我不斷收到類似的無盡錯誤訊息:
The native debug symbols contain an unexpected file: x86_64\libsentry.so.
我花了一些時間才發現問題出在路徑分隔符上:zip 使用反斜線(\),而 Google Play 則期望使用正斜線(/)。因此,解決方案是使用 Ruby 建立一個 zip 檔案,並採用 Unix 風格的斜線。
require 'zip' def create_native_symbols_zip(source_dir, output_file) Zip::File.open(output_file, Zip::File::CREATE) do |zipfile| Dir[File.join(source_dir, '**', '**')].each do |file| # Replace backslashes with forward slashes for Unix-style paths zipfile.add(file.sub(source_dir + '/', ''), file) end end end
在我們的領域內:
native_symbols_dir = File.expand_path("../../build/app/intermediates/merged_native_libs/release/out/lib", __dir__) output_zip = File.expand_path("./native-debug-symbols.zip", __dir__) create_native_symbols_zip(native_symbols_dir, output_zip)
這比我預期的要複雜得多。不論我做了什麼,它都開啟了 Documents 資料夾,而不是正確的資料夾。因此,我沒有直接使用檔案管理器,而是使用了 powershell.exe -Command:
# get absolute path of folder and open in explorer absolute_release_path = File.expand_path("../../build/app/outputs/bundle/release/", __dir__) sh "powershell.exe -Command \"Start-Process explorer (Resolve-Path '#{absolute_release_path}')\""
其餘的部分相當簡單明瞭。
# Build the app bundle sh "flutter build appbundle --release" # Move the native symbols zip to the release directory sh "mv #{output_zip} ../../build/app/outputs/bundle/release/"
綜合所有要素:
require 'zip' default_platform(:android) platform :android do desc "Automate build process including native symbols zip" lane :build_aab_and_symbols do |options| # update version version_type = options[:type] UI.user_error!("Please provide a version type: major, minor, patch or none") unless ["major", "minor", "patch","none"].include?(version_type) update_version(version_type) # Build the app bundle sh "flutter build appbundle --release" # Create a zip file for native symbols native_symbols_dir = File.expand_path("../../build/app/intermediates/merged_native_libs/release/out/lib", __dir__) output_zip = File.expand_path("./native-debug-symbols.zip", __dir__) UI.message("Creating ZIP with Ruby to ensure correct path separators...") create_native_symbols_zip(native_symbols_dir, output_zip) # Move the native symbols zip to the release directory sh "mv #{output_zip} ../../build/app/outputs/bundle/release/" # get absolute path of folder and open in explorer absolute_release_path = File.expand_path("../../build/app/outputs/bundle/release/", __dir__) sh " Start-Process explorer (Resolve-Path '#{absolute_release_path}')\"" end end def create_native_symbols_zip(source_dir, output_file) Zip::File.open(output_file, Zip::File::CREATE) do |zipfile| Dir[File.join(source_dir, '**', '**')].each do |file| # Replace backslashes with forward slashes for Unix-style paths zipfile.add(file.sub(source_dir + '/', ''), file) end end end def update_version(version_type) if version_type != "none" # Read the current version from pubspec.yaml pubspec_file = File.join("..", "pubspec.yaml") pubspec_file = File.join("..", pubspec_file) pubspec_content = File.read(pubspec_file) # Find the current version line (e.g., version: 1.2.1+24) current_version_line = pubspec_content.match(/version:\s*(\d+\.\d+\.\d+)\+(\d+)/) UI.user_error!("Couldn't find version in pubspec.yaml") unless current_version_line current_version = current_version_line[1] # e.g., "1.2.1" version_code = current_version_line[2].to_i # e.g., 24 # Split the version into major, minor, and patch major, minor, patch = current_version.split('.').map(&:to_i) # Increment the version based on the version type case version_type when "major" major += 1 minor = 0 patch = 0 when "minor" minor += 1 patch = 0 when "patch" patch += 1 end # Increment versionCode (build number) new_version_code = version_code + 1 # Build the new version string new_version = "#{major}.#{minor}.#{patch}+#{new_version_code}" # Update the pubspec.yaml content with the new version new_pubspec_content = pubspec_content.gsub(/version:\s*\d+\.\d+\.\d+\+\d+/, "version: #{new_version}") File.write(pubspec_file, new_pubspec_content) UI.success("Version updated to #{new_version}") else UI.message("No version update needed") end end
而現在每當我想要建立一個版本時,我只需
fastlane android build_aab_and_symbols type:minor
去泡一杯茶吧 :)
Fastlane 的錯誤訊息出奇地清晰,因此故障排除變得相當簡單。如果找不到檔案,則表示您的路徑設定有誤。請注意,pubspec.yaml 檔案位於 Fastfile 上方的兩個資料夾中。我的 VSCode 終端使用 PowerShell,因此上述命令是適用於 PowerShell 的。如果您使用的是 bash,則請使用 bash 命令。例如,壓縮檔案的替代建議:
# using bash sh "zip -r native-debug-symbols.zip ./build/app/intermediates/stripped_native_libs/release/out" # using windows zip sh "powershell.exe -Command \"Compress-Archive -Path ./build/app/intermediates/stripped_native_libs/release/out/* -DestinationPath ./native-debug-symbols.zip\""
開啟資料夾的替代建議(老實說,我也不知道為什麼這個方法沒有奏效):
sh "explorer.exe ./build/app/outputs/bundle/release/"
Fastlane 也支援將我們建立的 appbundle 和 zip 檔案上傳至 Google Play,但這又是另一個故事;) 祝你自動化愉快!我的心在碎裂。已經超過一年了,367 天。 #立即帶他們回家。你可以檢視我的免費開源線上遊戲 Space Short,以及我網站上的更多有趣內容。
相關討論