Flutter混合工程CI/CD最佳實踐

背景

項目處于混合開發狀態,native開發的同學沒有裝flutter環境,無法編譯flutter的代碼,工程無法跑起來。

官方推薦方案

將 Flutter module 集成到 iOS 項目

源碼依賴

flutter_application_path = './my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end

Flutter工程以submodule方式引入native工程

優點

源碼依賴,方便調試,分支管理方便。

缺點

沒有flutter環境的同學無法編譯運行工程,對native侵入性比較強。

產物依賴

流程圖

iOS工程持續集成產物依賴

優點

侵入性低,產物依賴提升打包速度

缺點

開發迭代比較麻煩,打包產物步驟麻煩,生成產物需要托管管理,產物體積比較大

我的方案

APP.xcframework和Flutter.xcframework是以產物依賴,其他的插件是以源碼形式依賴

產物和源碼混合依賴

流程圖

iOS持續集成依賴

優點

只把dart代碼編譯成產物,其他使用源碼方式依賴,產物體積很小,侵入性低,打包速度快。

缺點

開發調試比較麻煩,需要托管產物,要花時間實現一套全網沒有參考的新方案

比較

方案實現

制作

打包產物腳本

打包app.xcframework產物,然后把flutter.podspec、FlutterPluginRegistrant、plugins復制到binary目錄,壓縮binary.zip目錄

#環境變量
function exportFlutterEnv() {
        export PUB_HOSTED_URL=https://pub.flutter-io.cn
        export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
}

#打包app.xcframework
function buildiOSFramework() {
        if [ $BUILD_MODE == "Debug" ]
        then
                $FLUTTER_PATH/bin/flutter build ios-framework --no-release --no-profile --output=./ios-framework --verbose --cocoapods --no-tree-shake-icons
        else
          $FLUTTER_PATH/bin/flutter build ios-framework --no-debug --no-profile --output=./ios-framework --verbose --cocoapods --no-tree-shake-icons
        fi

}

#創建目錄binary
function createBinaryDir() {
        mkdir -v binary
}

#移動plugins
function movePluginsDir() {
        cp -r -v .ios/.symlinks/plugins/. binary/plugins
}

#移動 app.xcframework、flutter.podspec、FlutterPluginRegistrant
function moveFlutterDir() {
        mkdir -p binary/flutter/FlutterPluginRegistrant
        cp -r -v .ios/Flutter/FlutterPluginRegistrant binary/flutter
        cp -v ios-framework/$BUILD_MODE/Flutter.podspec binary/flutter/Flutter.podspec
        cp -r -v ios-framework/$BUILD_MODE/App.xcframework/. binary/flutter/App.xcframework
}

#創建App.podspec
function createAppPodspec() {
        touch binary/flutter/App.podspec
        echo """Pod::Spec.new do |s|
  s.name                  = 'App'
  s.version               = '1.0.0'
  s.summary               = 'fast apps.'
  s.description           = <<-DESC
Business Code
DESC
  s.homepage              = 'https://flutter.cn'
  s.license               = { :type => 'BSD' }
  s.author                = { 'Jacky' => 'shanhaoqiang@lizhi.fm' }
  s.source                = { :path => '.' }
  s.documentation_url     = 'https://flutter.cn/docs'
  s.platform              = :ios, '9.0'
  s.vendored_frameworks   = 'App.xcframework'
end
        """>binary/flutter/App.podspec
}

#打zip包
function zipBinaryDir() {
        zip -r binary-$BUILD_MODE.zip binary
}

#執行
exportFlutterEnv
buildiOSFramework
createBinaryDir
movePluginsDir
moveFlutterDir
createAppPodspec
zipBinaryDir

jenkins任務

拉取flutter工程代碼,執行上面的shell腳本,生成binary.zip,歸檔zip到jenkins的artifacts目錄中,獲取zip鏈接env.BUILD_URL + "artifact/binary-${BUILD_MODE}.zip"

    stage('\u261D 使用分支名稱作為任務名稱') {
        currentBuild.displayName = "#${BUILD_NUMBER}_${BRANCH_NAME}"
    }

    stage('\u262D 拉取代碼') {
        checkout([$class: 'GitSCM', branches: [[name: '*/' + BRANCH_NAME]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '25b61d91-a240-4eaa-8390-9ae2655fb969', refspec: '+refs/heads/' + BRANCH_NAME + ':refs/remotes/origin/' + BRANCH_NAME, url: GIT_URL]]])
        withCredentials([sshUserPrivateKey(credentialsId: '25b61d91-a240-4eaa-8390-9ae2655fb969', keyFileVariable: 'SSH_KEY')]) {
            sh """
                git checkout ${BRANCH_NAME}
                git pull origin ${BRANCH_NAME}
            """
        }

    }
    stage('\u261D 拉取腳本') {
        sh 'rm -rf ./' + 'flutterbinary'
        sh 'git clone ' + 'git@gitlab.xx.fm:ocean/xx.git' + ' -b master --depth=1 ./' + 'flutterbinary'
    }

    stage('\u2615 編譯產物') {
        sh """
        cp flutterbinary/build.sh build.sh
        sh build.sh ${BUILD_MODE} ${BUILD_NUMBER} ${BUILD_MODEL} ${FLUTTER_PATH} ${JDK_PATH}
        """
    }

    stage('\u26B0 保存成品') {
            archiveArtifacts artifacts: "binary-${BUILD_MODE}.zip", fingerprint: true
    }

    stage('\u2709 發送通知') {
        //iOS產物地址
        url = env.BUILD_URL + "artifact/binary-${BUILD_MODE}.zip"

        wrap([$class: 'BuildUser']) {
            USER_ID = BUILD_USER_ID
            USER_NAME = BUILD_USER
        }

        def updateLog = "${env.UPDATELOG}".trim()

        String content = "請相關同事知悉。本次Flutter產物發布信息如下:\\n 操作人:${USER_NAME}" + "\\n 打包類型:${BUILD_MODE}" + "\\n 任務名:${env.JOB_NAME}" + "\\nFlutter iOS產物地址:${url}"+ "\\nFlutter Android aar包地址:${aarUrl}"+"\\n對應分支:${BRANCH_NAME}\\nFlutter地址:${GIT_URL}\\n更新內容:${updateLog.replace("\n", "\\n")}\\n"

        def contentall = """
    {"content":{"text": "${content}"},"msg_type":"text"}
"""
        println("contentall:" + contentall)
        def command = """
    curl -X POST -H "Content-Type: application/json"\
      -d '${contentall}' \
      "https://open.feishu.cn/open-apis/bot/v2/hook/${env.NOTIFY_KEY}"
  """
        sh(script: command)
    }

集成

一行代碼集成flutter,Podfile中填寫jenkins打包的flutter項目的zip鏈接

def pod_flutter
  puts "=== 集成flutter sdk ==="
  install_remote_flutter_binary('https://jksclient.xx.fm/job/%E8%8D%94%E6%9E%9D-flutter/106/artifact/binary-Release.zip')
end

編寫Podfile插件

  • 收到傳進來的url,對url做md5,創建Flutter緩存目錄,下載zip包,解壓
  • 安裝flutter引擎,flutter指向podspec,podspec的source zip是官方地址
  • 安裝plugins,各個plugin包含FlutterPluginRegistrant,指向解壓后端path地址
  • 安裝App.xcframework,指向App所在的path路徑
## author:Jacky
## desc:install binary pods in Podfile
#!/usr/bin/env ruby

require 'digest'
require 'fileutils'
require 'uri'
require 'net/http'
require 'net/https'

module Pod
  class Podfile
    module DSL

      #下載
      def install_remote_flutter_binary(url = nil)
        #md5
        md5 = Digest::MD5.new               # =>#<Digest::MD5>
        md5 << url
        md5value = md5.hexdigest                        # => "78e73102..."
        flutter_f_home = Dir.home+'/Library/Caches/CocoaPods/Flutter/'
        flutter_binary_home = flutter_f_home+md5value
        flutter_binary_path = flutter_binary_home+'/binary'
        
        #創建目錄
        FileUtils.mkdir_p(flutter_binary_home)

        #清除超過30天的緩存
        xxxx
        
        #判斷緩存
        if File::directory?(flutter_binary_path) == false
          #下載
          puts "開始下載 "+url

        #集成
        install_all_lzflutter_pods(flutter_binary_path)
      end

      #安裝
      def install_all_lzflutter_pods(flutter_binary_path)
        install_lzflutter_engine_pod(flutter_binary_path)
        install_lzflutter_plugin_pods(flutter_binary_path)
        install_lzflutter_application_pod(flutter_binary_path)
      end

      # 安裝flutter引擎
      def install_lzflutter_engine_pod(flutter_binary_path)
          xxx
      end

      # Install Flutter plugin pods.
      def install_lzflutter_plugin_pods(flutter_binary_path)
        # Keep pod path relative so it can be checked into Podfile.lock.
        # Process will be run from project directory.

        #FlutterPluginRegistrant
        xxx
        
        #插件目錄
        xxx

        #plugins遍歷
        xxx
      end

      # Install Flutter application pod.
      def install_lzflutter_application_pod(flutter_binary_path)
          xxx
      end
    end
  end
end
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容