Flutter 構建自動化探索與實踐

前言

在 2022 年年底,我從 0 到 1 主導負責公司的 “商品開發” 移動端項目,並在 2023 年中旬完成了該項目的第一期的需求開發。期間從 Flutter 基礎學起,並在較短時間內完成相關業務功能開發,同時搭建起該項目的整個自動化構建流程,支持一鍵分發 ipa 與 apk,在這個過程中,我學到了 Flutter 的相關知識,同時也瞭解了整個應用程序的分發流程。在此,感謝在這條路上幫助我的各位網友以及同事,讓我在極短時間內完成整個項目的第一期開發。在這篇文章中,你將瞭解如下內容:

Flutter 開發環境搭建

公司下發的電腦是 MacBook Pro,因此,關於 Flutter 的開發環境搭建是基於 macOS 操作系統進行搭建的,可參考我過往寫過的這篇文章:《Flutter 完整開發環境搭建》。

關於 windows 操作系統如何搭建 Flutter 開發環境,大家可參考技術胖過往發佈的視頻:《Flutter 開發環境搭建 windows 版》。我記得我第一次接觸 Flutter 時,就是從技術胖的博客網站的這篇文章看起的,相較於 Flutter 官網介紹的安裝方式,技術胖所講解的對於當時的我來說通俗易懂。

如果,你想了解接下來如何分發 Ipa 文件,我建議還是用 mac 最爲合適。我記得在大學期間做老師的一個校企項目中瞭解到了一個比較流行的跨端框架,是 DCloud 團隊推出的 MUI,那會用 Hbuilder 完成了 apk 和 ipa 文件的上傳,但是還是會繞不過藉助 mac 申請相關證書,才能在 Hbuilder 上去上傳我們的移動端應用程序。當然,也有第三方的工具支持,或者黑蘋果,但是我不是很建議,主要還是出於安全以及異常問題的處理考慮。

Fastlane 安裝 & 編寫 Fastlane 腳本

Fastlane 是一個用於 iOS 和 Android 應用程序開發的工具集,旨在簡化開發流程中的一些重複性任務。它提供了一組命令行工具,幫助開發者自動化構建、測試、部署和發佈移動應用。Fastlane 的功能包括自動化代碼簽名、生成和管理應用截圖、自動化發佈到 App Store 和 Google Play 等。它可以與持續集成 / 持續交付(CI/CD)系統集成,以進一步提高開發團隊的效率和工作流程。我在項目中,主要是用該工具完成了 IOS 的自動化構建,同時將其分發至 App Store 或蒲公英。

安裝

安裝前先檢查是否安裝了 Ruby 和 Xcode 命令行工具(在參考鏈接中給出了相關安裝教程):

# 1. 查看是否安裝了 Ruby
ruby -v
# 2. 檢查是否安裝了 Xcode 命令行工具
xcode-select -p
# bundler
bundler --version

# fastlane
sudo gem install fastlane
# xcode
xcode-select --install
# bundler
sudo gem install bundler

這裏我的安裝方式是通過 Ruby + Bundler 的方式來安裝。Fastlane 是通過 Ruby 編寫的,故 Fastlane 對 Ruby 版本是有一定要求的,Fastlane 支持的 Ruby 版本是 2.5+。

如果,發現當前系統版本低於 2.5,建議對 Ruby 進行升級,相關升級文檔已經貼在參考鏈接欄目中。

Bundler

Bundler 是一個用於管理 Ruby 程序的依賴關係的工具。當你開發一個 Ruby 項目時,通常會依賴於其他的 Ruby gem 包,而 Bundler 能夠幫助你確保項目依賴的 gem 包能夠正確地被安裝和加載。它會根據你的項目中的 Gemfile 文件來安裝和管理 gem 包。Gemfile 是一個指定了項目所需 gem 包及其版本的清單文件。

在 ios 根目錄下新建 Gemfile 文件,並輸入以下內容:

source "https://rubygems.org"
gem "fastlane"

這樣明確定義依賴和版本,能加快 fastlane 的執行速度,當然,在執行 fastlane init 後也會自動生成。

Fastlane 配置

1. 初始化 fastlane 配置文件

# 進入項目的 ios 文件夾
cd ios
# 初始化 fastlane 配置,執行後會自動生成 fastlane 文件夾
fastlane init

執行 init 指令後,我們就能看到有幾個重要的文件,分別是:Appfile、Fastfile、Pluginfile,從文件名上看,我們不免就能大致明白各個文件的作用是什麼。

VjSYSz

2. Appfile 文件配置

# ios 的 Bundle id
app_identifier "******"
# apple developer 賬號
apple_id "******"

3. Fastfile 文件配置

因爲我們需要將 Ipa 分發到 App Store 和蒲公英上,因此會需要一系列的 key:

瞭解了當中的 key 之後,我們就可以編寫出以下代碼:

# fastlane 版本
fastlane_version = "2.212.2"
default_platform(:ios)
# 執行超時時間
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "30"
# 行失敗後重試的次數
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "20"
platform :ios do
  desc "構建 IPA 生產腳本"
  lane :ProductRelease do
    time = Time.new.strftime("%Y%m%d")
    version = get_version_number
    ipaName = "ProductApp_Release_#{version}_#{time}.ipa"
    gym(
      clean: true,
      output_directory: "./../build",
      output_name: "#{ipaName}",
      scheme: "ProductRelease",
      configuration: "Release",
      # include_bitcode:true,
      # include_symbols:true,
      export_method: "app-store",
      export_xcargs: "-allowProvisioningUpdates"
    )
    notification(
      app_icon: "./fastlane/successful.png",
      title: "ProductApp Build Success",
      subtitle: "打包成功,已導出安裝包",
      message: "準備發佈中……"
    )
    # 配置上傳到App Store connect的api_key
    api_key = app_store_connect_api_key(
      key_id: "*********",
      issuer_id: "***************",
      key_filepath: "./../buildConfig/ios/AuthKey_******.p8",
      duration: 1200,
      in_house: false
    )
    # 上傳到testflight
    upload_to_testflight(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
      ipa: "./../build/#{ipaName}",
      skip_submission: true
    )
    notification(
      app_icon: "icon.png",
      title: "LoanManager",
      subtitle: "IPA上傳成功",
      message: "自動打包完成!"
    )
  end
  desc "構建 IPA AdHoc 腳本"
  lane :ProductAdhoc do
    time = Time.new.strftime("%Y%m%d")
    version = get_version_number
    ipaName = "ProductApp_Adhoc_#{version}_#{time}.ipa"
    gym(
      scheme: "ProductAdhoc",
      configuration: "AdHoc",
      output_directory: "./../build",
      output_name: ipaName,
      export_method: "ad-hoc",
      export_options: {
        uploadBitcode: false,
        compileBitcode: false,
        provisioningProfiles: {
          "********""product-Ad-hoc" # key 是你的 App bundlerId
        }
      }
    )
    notification(
      app_icon: "./fastlane/successful.png",
      title: "ProductApp Build Success",
      subtitle: "打包成功,已導出安裝包",
      message: "準備發佈中……"
    )
    pgyer(
      api_key: "***************",
      ipa: "./../build/#{ipaName}",
      update_description: "ProductApp for test befoe upload app to testflight",
      install_type: "2",
      password: "*********"
    )
    notification(
      app_icon: "icon.png",
      title: "LoanManager",
      subtitle: "IPA上傳至蒲公英成功",
      message: "自動打包完成!"
    )
  end
  desc "僅上傳 ipa 文件至 AppStore"
  lane :UploadIpaToAppStore do
    notification(
      app_icon: "./fastlane/successful.png",
      title: "ProductApp Build Success",
      subtitle: "打包成功,已導出安裝包",
      message: "準備發佈中……"
    )
    # 配置上傳到App Store connect的api_key
    api_key = app_store_connect_api_key(
      key_id: "*********",
      issuer_id: "***************"# AppConnect id
      key_filepath: "./../buildConfig/ios/AuthKey_******.p8",
      duration: 1200,
      in_house: false
    )
    # 上傳到testflight
    upload_to_testflight(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
      ipa: "./../build/production/ipa/productApp.ipa",
      skip_submission: true
    )
    notification(
      app_icon: "icon.png",
      title: "LoanManager",
      subtitle: "IPA上傳成功",
      message: "自動打包完成!"
    )
  end
  desc "僅上傳 ipa 文件至蒲公英"
  lane :UploadIpaToPgyer do
    notification(
      app_icon: "./fastlane/successful.png",
      title: "ProductApp Build Success",
      subtitle: "打包成功,已導出安裝包",
      message: "準備發佈中……"
    )
    pgyer(
      api_key: "***************"# 蒲公英的 key
      ipa: "./../build/test/ipa/productApp.ipa",
      update_description: "ProductApp for test befoe upload app to testflight",
      install_type: "2",
      password: "*********" # 在蒲公英上配置的密碼
    )
    notification(
      app_icon: "icon.png",
      title: "LoanManager",
      subtitle: "IPA上傳至蒲公英成功",
      message: "自動打包完成!"
    )
  end
end

我們執行 Fastlane 任務時,需要通過 Ruby 來編寫相關配置,在 Fastlane 中,有 lane 的概念,lane 的作用是定義一系列的任務(tasks),用於執行特定的操作序列,例如構建應用程序、打包應用程序、上傳到測試環境或發佈到應用商店等。每個 lane 通常代表了一個特定的工作流程或者部署流程。通過定義不同的 lane,你可以在 fastlane 中輕鬆地管理和執行各種自動化任務,從而簡化了應用程序的構建和部署流程,提高了開發效率。

我們在構建 Ipa 應用程序時,通常需要這樣的文件:

Ad-hoc 分發是指將應用程序安裝包(IPA 文件)通過特定的方式分發給受限制的用戶羣體,例如應用的測試人員或者內部員工。這種方式與通過應用商店進行正式發佈不同,AdHoc 分發不會將應用程序提交到 App Store 審覈流程中,而是直接將應用程序安裝包分發給指定的用戶。

Debug 配置用於開發過程中的調試目的。它通常包含了一些用於幫助開發者調試和排查問題的設置,比如啓用調試符號、關閉優化等,除此之外,在 Debug 配置中,通常會允許開發者使用 Xcode 的調試工具,如斷點調試、日誌輸出等,以便更輕鬆地發現和修復代碼中的 bug。

Release 配置用於應用程序發佈階段。它通常包含了一些用於優化應用程序性能和減小應用程序體積的設置,以及一些用於保護應用程序安全的設置。在 Release 配置中,通常會關閉調試符號和調試工具的輸出,以減小應用程序的體積,並啓用一些代碼優化策略,以提高應用程序的運行性能。

除了以上 Profile 之外,還需要 .p12、.p8、.cer 等文件,其中,.p12 需要根據多環境要求申請不同的類型的 .p12 文件。

編寫構建執行腳本

我們先看看手動執行構建,即在本地通過終端輸入相關指令去構建應用程序。

1. 構建 APK

# 第一步
flutter pub get
# 第二步
flutter build apk --release --dart-define=ENVIROMENT=production

2. 構建 IPA

# 第一步
flutter pub get
# 第二步
cd /ios && arch -x86_64 pod install
# 第三步
cd ..
# 第四步
flutter build ios --release --dart-define=ENVIROMENT=production
# 第五步
xcodebuild -workspace $PWD/ios/Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive

在這個指令中,$PWD 是一個環境變量,代表當前工作目錄的路徑。$PWD 會被替換成執行該指令時所處的當前工作目錄的路徑。在這個命令中,$PWD 被用來指定工作空間和輸出歸檔文件的路徑。

至此,我們就知道如果在終端上構建應用程序了。接下來,我們需要編寫 shell 腳本,然後通過 Jenkins 去調用 shell 腳本實現我們自動化構建的目的。下面是我編寫的 shell 腳本指令,僅供參考:

#!/bin/bash
# buildType -構建類型,值爲 test 和 production
# isUpload -是否上傳,值爲 1 表示上傳,0 表示不上傳
# appType -APP的類型,值爲 ipa、apk 和 all
for arg in "$@"
do
  case "$arg" in
    --buildType=*) buildType="${arg#*=}" ;;
    --appType=*) appType="${arg#*=}" ;;
    --isUpload=*) isUpload="${arg#*=}" ;;
  esac
done
# 常量
readonly REMOVE_BUILD_FOLDER="刪除build文件夾"
readonly PULL_NEW_CODE="從gitlab上拉取最新代碼"
readonly FLUTTER_INSTALL="安裝flutter最新依賴"
readonly BUILD_APK="構建APK"
readonly POD_INSTALL="安裝IOS最新依賴"
readonly BUILD_RUNNER_APP="構建RunnerApp"
readonly BUILD_RUNNER_XCARCHIVE="構建RunnerXcarchive"
readonly BUILD_IPA="構建IPA"
readonly UPLOAD_APP_TO_APP_STORE="上傳APP到AppStore"
readonly UPLOAD_APP_TO_PGY="上傳APP到蒲公英"
CODER=0
# 狀態
function getExecuteStatus() {
  case "$1" in
    "$REMOVE_BUILD_FOLDER")
      CODER=10
      ;;
    "$PULL_NEW_CODE")
      CODER=20
      ;;
    "$FLUTTER_INSTALL")
      CODER=30
      ;;
    "$BUILD_APK")
      CODER=40
      ;;
    "$POD_INSTALL")
      CODER=50
      ;;
    "$BUILD_RUNNER_APP")
      CODER=60
      ;;
    "$BUILD_RUNNER_XCARCHIVE")
      CODER=70
      ;;
    "$BUILD_IPA")
      CODER=80
      ;;
    "$UPLOAD_APP_TO_APP_STORE")
      CODER=90
      ;;
    "$UPLOAD_APP_TO_PGY")
      CODER=100
      ;;
    *)
      CODER=0
      ;;
  esac
}
# 異常捕捉
# $1 -腳本執行後的狀態
# $2 -狀態類型
function throwExecuteStatus() {
  getExecuteStatus $2
  tip=$2
  if [ $1 -ne 0 ]; then
    echo "ERROR: ${tip}失敗"
    exit $CODER
  else
    echo "🎉 💯 SUCCESS: ${tip}成功"
  fi
}
# 包裹函數,統一輸出
function wrapperFunction() {
  printf "\n\n"
  local function_desc=$1
  local function_name=$2
  echo "============= $function_desc Start ============="
  printf "\n"
  $function_name
  printf "\n"
  echo "============= $function_desc End ============="
}
# 如果只執行 sh build.sh 腳本,沒有輸入任何參數的話,則需要讓用戶輸入指定的一些參數
function judgeBuildTypeIsNull() {
  if [ -z "$buildType" ]; then
    read -p "構建生產包,取值爲:production(生產) 或 test(測試):" buildType
  fi
  if [ -z "$appType" ]; then
    read -p "請選擇構建類型,取值爲 apk(Android)、ipa(IOS)、all(apk 和 ipa):" appType
  fi
  if [ -z "$isUpload" ]; then
    read -p "是否將打包生成的應用程序上傳,取值爲:1(上傳) 或 0(不上傳):" isUpload
  fi
}
# 打印輸出的參數類型
function printParams() {
  # 環境類型
  if [ "$buildType" = "production" ]; then
    targetUploadPlatform="App Store"
    echo "環境類型:生產"
  else
    targetUploadPlatform="蒲公英"
    echo "環境類型:測試"
  fi
  # 產物類型
  if [ "$appType" = "all" ]; then
    targetBuildAppType="ipa、apk"
    echo "構建產物:Ipa、Apk"
  elif [ "$appType" = "apk" ]; then
    targetBuildAppType='apk'
    echo "構建產物:Apk"
  else
    targetBuildAppType='ipa'
    echo "構建產物:Ipa"
  fi
  # 是否上傳 App Store 或 蒲公英
  if [ "$isUpload" = "1" ]; then
    echo "是否上傳:是,構建成的 ${targetBuildAppType} 將會上傳至 ${targetUploadPlatform}"
  else
    echo "是否上傳:否"
  fi
}
# 刪除 build 文件夾
function removeBuildFolder() {
  if [ -d "build" ]; then
    rm -r build
    throwExecuteStatus $? $REMOVE_BUILD_FOLDER
  fi
}
# 拉取代碼
function getLatestCode() {
  git pull
  throwExecuteStatus $? $PULL_NEW_CODE
}
# 安裝 Flutter 依賴
function installFlutterRely() {
  flutter pub get
  throwExecuteStatus $? $FLUTTER_INSTALL
}
# 安裝 IOS 依賴
function installIosRely() {
  cd ios
  pod install
  throwExecuteStatus $? $POD_INSTALL
  cd ..
}
# 上傳 IPA 到 App Store
function uploadIpaToAppStore() {
  cd ios/fastlane
  fastlane UploadIpaToAppStore
  echo "IPA 文件已成功上傳至 App Store,請及時前往 https://appstoreconnect.apple.com/apps/***/testflight/ios 進行確認"
  throwExecuteStatus $? $UPLOAD_APP_TO_APP_STORE
}
# 上傳 IPA 到蒲公英
function uploadIpaToPgyer() {
  cd ios/fastlane
  fastlane UploadIpaToPgyer
  echo "IPA 文件已成功上傳至蒲公英,請及時前往 https://www.pgyer.com/manager/version/index/*** 進行確認"
  echo "輸入 123456 提取文件"
  throwExecuteStatus $? $UPLOAD_APP_TO_PGY
}
# TODO 上傳 APK
function uploadApk() {
  echo "TODO upload Apk"
}
# 構建可以上傳到 App Store 的 IPA 文件並自動上傳
function buildIpaUploadAppStore() {
  # 構建 Runner.app 文件
  flutter build ios --release --dart-define=ENVIROMENT=production
  throwExecuteStatus $? $BUILD_RUNNER_APP
  # 生成 Runner.xcarchive 文件
  xcodebuild -workspace $PWD/ios/Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive
  throwExecuteStatus $? $BUILD_RUNNER_XCARCHIVE
  # 生成 ipa 文件
  xcodebuild -exportArchive -exportOptionsPlist $PWD/buildConfig/ios/ExportOptionsProduction.plist -archivePath $PWD/build/Runner.xcarchive -exportPath $PWD/build/production/ipa -allowProvisioningUpdates
  throwExecuteStatus $? $BUILD_IPA
  if [ "$isUpload" = "1" ]; then
    wrapperFunction "上傳 Ipa 文件到 AppStore" uploadIpaToAppStore
  fi
}
# 構建可以上傳到蒲公英的 IPA 文件並自動上傳
function buildIpaUploadPgyer() {
  # 構建 Runner.app 文件
  flutter build ios --release --dart-define=ENVIROMENT=test
  throwExecuteStatus $? $BUILD_RUNNER_APP
  # 生成 Runner.xcarchive 文件
  xcodebuild -workspace $PWD/ios/Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath $PWD/build/Runner.xcarchive
  throwExecuteStatus $? $BUILD_RUNNER_XCARCHIVE
  # 生成 ipa 文件
  xcodebuild -exportArchive -exportOptionsPlist $PWD/buildConfig/ios/ExportOptionsAdhoc.plist -archivePath $PWD/build/Runner.xcarchive -exportPath $PWD/build/test/ipa -allowProvisioningUpdates
  throwExecuteStatus $? $BUILD_IPA
  if [ "$isUpload" = "1" ]; then
    wrapperFunction "上傳 Ipa 文件到蒲公英" uploadIpaToPgyer
  fi
}
# 構建 IOS 應用程序
function buildIosApp() {
  wrapperFunction "安裝 IOS 依賴" installIosRely
  echo "當前路徑:$PWD"
  if [ "$buildType" = "production" ]; then
    wrapperFunction "構建 IPA 文件" buildIpaUploadAppStore
  else
    wrapperFunction "構建 IPA 文件" buildIpaUploadPgyer
  fi
}
# 構建 APK 應用程序
function buildApkApp() {
  if [ "$buildType" = "production" ]; then
    output=$(flutter build apk --release --dart-define=ENVIROMENT=production | grep Built)
  else
    output=$(flutter build apk --release --dart-define=ENVIROMENT=test | grep Built)
  fi
  apkPath=$(echo "$output" | sed -n 's/.*Built \(.*\.apk\) .*/\1/p')
  echo "APK存儲路徑:$apkPath"
  throwExecuteStatus $? $BUILD_APK
}
# 開始構建
function startBuildTask() {
  # 檢查參數是否配置了比較的參數
  judgeBuildTypeIsNull
  wrapperFunction "構建參數" printParams
  # 刪除 build 文件夾
  wrapperFunction "刪除 build 文件夾" removeBuildFolder
  # 拉取代碼
  # TODO 這個只有在上Jenkins調試前用於拉取最新代碼,在 Jenkins 中可以通過 SCM 來執行
  # wrapperFunction "拉取代碼" getLatestCode
  wait
  # 安裝依賴
  wrapperFunction "安裝 Flutter 依賴" installFlutterRely
  wait
  # 構建
  if [ "$appType" = "all" ]; then
    wrapperFunction "構建 APK" buildApkApp
    wait
    buildIosApp
  elif [ "$appType" = "apk" ]; then
    wrapperFunction "構建 APK" buildApkApp
  else
    buildIosApp
  fi
  wait
  echo "build.sh腳本執行成功!"
}
startBuildTask

執行成果

Jenkins 安裝 & 編寫 Jenkins 腳本

爲了協同操作,我們通常會將代碼上傳到 Gitlab,然後通過一系列的 hooks 來觸發我們的 Jenkins 任務,在這個章節中,我將基於 macOS 操作系統搭建整個 Jenkins 環境,並通過編寫 Jenkinsfile 文件,來執行我們的構建任務。

爲什麼選擇 macOS 來搭建 Jenkins 呢?主要是因爲經過我調研,目前暫無相關雲上產品支持構建 IPA 文件,雖然有第三方的,但是在我看來都不太安全或者需要額外的收費,故我的方式是:用一臺 mac 專門來構建,同時支持協同操作。下面,就詳細介紹一下怎麼開始與如何編寫 Jenkinsfile 文件。

在開始之前,我先簡單介紹一些基礎概念:

Groovy 腳本:是一種基於 Java 虛擬機(JVM)的動態編程語言,它被廣泛用於編寫腳本、自動化任務和擴展應用程序的能力。

Pipeline 腳本:使用 Groovy 代碼來執行各種任務和操作。

Jenkinsfile:用於定義 Pipeline 的文件,它採用了一種特定的語法格式。Jenkinsfile 通常與代碼存儲在同一個代碼庫中,並與版本控制系統一起管理。

安裝 Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 如果上述這個指令更新緩慢的話,可用下面這個指令
/usr/bin/ruby -e "$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install)"

該腳本用了中科大鏡像加速訪問,僅修改倉庫地址部分,不會產生安全隱患。

Jenkins

# 安裝
brew install jenkins-lts
# 啓動
brew services start jenkins-lts
# 重啓
brew services restart jenkins-lts
# 停止
brew services stop jenkins-lts
# 查看管理員密碼,登錄時用到
sudo cat /Users/Shared/Jenkins/Home/secrets/initialAdminPassword

訪問:http://locathost:8080

至此,我們就完成了 Jenkins 的安裝,接下來就創建 Jenkins 工程

1. 創建流水線構建任務

2. 常規配置

解讀:

3. 構建觸發器

解讀

4. 流水線

解讀:特別是當你希望將 Pipeline 腳本與源代碼存儲在一起,並利用 SCM 的版本控制和協作功能來管理和更新 Pipeline 腳本時。這使得 Pipeline 腳本的維護和版本控制更加方便,並與應用程序代碼的版本管理保持一致

5. 全局配置

解讀

解讀:這裏只配置環境變量,因爲項目在構建中需要用到服務器當前的環境變量,需要用服務器上安裝的 flutter 和 CocoaPods 來安裝相關依賴和構建。"PATH+EXTRA" 是指在現有的 PATH 環境變量值的基礎上追加額外的路徑。

6. 相關插件插件

Git Parameter:

Git plugin:是 Jenkins 中的核心插件,允許在 Jenkins 作業配置中指定 Git 倉庫的 URL、分支、標籤等信息,並提供了拉取、克隆和檢出 Git 代碼的功能。Git plugin 還可以與其他插件(如構建觸發器、構建環境變量、構建步驟等)結合使用,以實現與 Git 倉庫的集成。

Git client plugin:更關注於與 Git 客戶端工具的交互和管理。允許在 Jenkins 中安裝和配置不同版本的 Git 客戶端工具,並在構建過程中使用特定版本的 Git 客戶端工具來執行與 Git 相關的操作。Git client plugin 提供了更高級的配置選項,如代理設置、路徑管理等,以便更靈活地管理和使用 Git 客戶端工具。

GitLab API Plugin:允許在 Jenkins 作業中使用 GitLab 的 REST API,以實現與 GitLab 的集成和自動化。

GitLab Plugin:與 GitLab 版本控制系統進行集成的功能。

7. Jenkinsfile 編寫

pipeline {
    agent any
    environment {
        LANG = 'en_US.UTF-8'
        LANGUAGE = 'en_US.UTF-8'
        LC_ALL = 'en_US.UTF-8'
        PUB_HOSTED_URL = 'https://pub.flutter-io.cn/'
        FLUTTER_STORAGE_BASE_URL = 'https://storage.flutter-io.cn/'
    }
    parameters {
        gitParameter(
            name: 'Branch',
            type: 'PT_BRANCH',
            branchFilter: 'origin/(.*)',
            defaultValue: 'master',
            selectedValue: 'DEFAULT',
            sortMode: 'DESCENDING_SMART',
            description: '選擇你的分支,默認master.'
        )
        choice(choices: ['test', 'production'], description: '構建類型,值爲 test 和 production', name: 'BUILD_TYPE')
        choice(choices: ['apk', 'ipa', 'all'], description: 'APP的類型,值爲 ipa、apk 和 all', name: 'APP_TYPE')
        booleanParam(name: 'IS_UPLOAD', defaultValue: false, description: '是否上傳,值爲 1 表示上傳,0 表示不上傳')
    }
    stages {
        stage('比較環境監測') {
            steps {
                // 當前運行時的用戶
                sh 'whoami'
                // flutter 版本
                sh 'flutter --version'
                // java SDK 版本
                sh 'java --version'
                // 檢查是否安裝 ruby
                sh 'ruby --version'
                // pod 版本
                sh 'pod --version'
                // xcode 版本
                sh 'xcodebuild -version'
            }
        }
        stage('拉取代碼') {
            steps {
                checkout scm
            }
        }
        stage('構建APP') {
            steps {
                script {
                    try {
                        def result = sh(
                            returnStatus: true,
                            script: "/bin/bash ./build.sh --buildType=\"${params.BUILD_TYPE}\" --appType=\"${params.APP_TYPE}\" --isUpload=\"${params.IS_UPLOAD ? 1 : 0}\""
                        )
                        if (result != 0) {
                            error("腳本執行失敗")
                        } else {
                            echo "構建成功"
                        }
                    } catch (Exception e) {
                        currentBuild.result = 'FAILURE'
                        error("❌ 腳本執行失敗,出錯步驟:${e.getMessage()}")
                    }
                }
            }
        }
    }
}

8. 成果展示

參考鏈接

https://www.ruby-lang.org/en/documentation/installation/#homebrew

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/084a6NkYI_mvFiCodirhfQ