Flutter混合工程CI/CD最佳实践
2021-12-17 本文已影响0人
jackyshan
背景
项目处于混合开发状态,native开发的同学没有装flutter环境,无法编译flutter的代码,工程无法跑起来。
官方推荐方案
源码依赖
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