背景
目前项目功能开发已告一段落,后续进入项目实施阶段。考虑到后续有大量的测试和交付场景,有必要对项目搭建持续集成打包平台,方便后续测试交付。
由于公司已经搭建有Jenkins,而且作者以前也使用Jenkins搭建过Android持续集成打包平台,对于Jenkins的安装和配置就不多做讲解,如有需要可以参考使用Jenkins搭建iOS/Android持续集成打包平台。这里主要讲解如何在Jenkins上配置和构建Flutter工程,以及遇到的问题。
首先,给大家展示下平台搭建完成后的整体效果:
该平台主要实现的功能:
- 可选择branch分支打包
- 支持自定义版本号
- 支持指定打包类型
- 根据git提交sha命名文件
- 打包生成二维码网页
- 构建完成后通过钉钉推送到指定群通知
流程
开始搭建之前,我们需要先整理下我们的搭建思路:
- 在打包服务器(linux服务器)上安装Flutter、Android、JDK等构建所需SDK,安装Python3、amzqr用于生成二维码
- 在Jenkins中构建项目,配置打包参数
- 添加项目构建命令,构建打包生成APK
- 根据下载地址生成二维码,并写入html文件生成扫码下载网页
- 上传APK、二维码、扫码下载html文件到指定仓库提供下载
- 整个流程结束后通知开发人员构建完成
Linux常用命令
由于用到了linux服务器,很多linux服务器是不支持远程图形化界面展示的,所以我们需要了解一些常用的linux操作命令:
ls(英文全拼:list files): 列出目录及文件名
cd(英文全拼:change directory):切换目录
pwd(英文全拼:print work directory):显示目前的目录
mkdir(英文全拼:make directory):创建一个新的目录
rmdir(英文全拼:remove directory):删除一个空的目录
cp(英文全拼:copy file): 复制文件或目录
rm(英文全拼:remove): 删除文件或目录
mv(英文全拼:move file): 移动文件与目录,或修改文件与目录的名称
Tip:如果大家不熟悉Linux系统可以参考Linux教程
Shell常用命令
Flutter项目构建不同于Android原生可以采用Gradle直接构建,需要用到shell脚本,所以我们对于一些常用的shell命令也需要熟悉:
变量
传递参数
Shell echo命令
Shell 流程控制
Shell 输入/输出重定向
Tip:先关资料可以参考Shell教程
Linux上安装Flutter
- 下载Flutter安装包
wget https://storage.flutter-io.cn/flutter_infra_release/releases/stable/linux/flutter_linux_2.2.3-stable.tar.xz
- 解压
cd ~/development
tar xf ~/Downloads/flutter_linux_2.2.3-stable.tar.xz
- 环境变量配置
vi /etc/profile
添加环境变量
source /etc/profile
- 测试环境是否正确
flutter doctor
在Jenkins中构建项目
在Jenkins新建一个项目,进入构建配置页面
1. 源码管理
- 选择Git管理填入git地址
- 添加项目的用户凭证
- 指定分支为:$branch,这里是引用的其他变量,用于分支的选择
2. 构建
在Execute shell中添加项目构建命令:
cd /data/jenkins/workspace/xxx
source /etc/profile
flutter clean
#执行打包脚本
sh app.sh
#执行图片二维码脚本
sh qr.sh
#复制安装包、图片、html到指定目录
cp -rf build/app/outputs/flutter-apk/xxx* /data/ftp/apps/xxx/
#钉钉配置
PRO_NAME=xxx
PRO_NAME_CN=xxx【移动端APP】开发环境
DINGDING_PATH=/data/dingdingpusher
DINGDING_JSON=$DINGDING_PATH/$PRO_NAME.json
SERVER_IP=xxx
#编写钉钉通知脚本
echo {\"msgtype\": \"markdown\", \
\"markdown\": { \
\"title\":\"jenkins编译提示\", \
\"text\": \"# **jenkins编译通知**\\n#### jenkins编译《$PRO_NAME_CN》已经发布到[$SERVER_IP]中,访问地址为:http://xxx:1080/apps/xxx/ \\n## 变动内容:${SCM_CHANGELOG}\" \
} \
} > $DINGDING_JSON
echo "打印$DINGDING_JSON内容:"
cat $DINGDING_JSON
3. 构建后的操作
上述思路有提到,构建成功后需要在钉钉上通知开发者,这里是用的是Jenkins中的Dingding Json Pusher插件:
填入钉钉的Access token
项目的json文件路径
4. 配置可选参数
- 在General中勾选This project is parameterized,新增Git Paramter插件用于git分支的选择:
- 选择Choice Pramter新增buildType配置可以打包的类型:
- 选择String Paramter配置应用的版本号,默认使用代码中的版本号
5. App打包脚本
#!/bin/bash -ilex
#获取Git SHA1
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
#默认打包类型
DEFAULT_BUILD_TYPE=release
#打包类型
if [ ! $buildType ]; then
type=$DEFAULT_BUILD_TYPE
else
type=$buildType
fi
#获取版本号
versionCode=""
version=""
versionCodeSpe=""
pubspecFile="pubspec.yaml"
if [ -e $pubspecFile ]
then
if [ -r $pubspecFile ]
then
if [ -w $pubspecFile ]
then
#修改文件
#webview
version=`sed -n '18p' $pubspecFile`
else
echo "文件不可写"
fi
else
echo "文件不可读"
fi
else
echo "文件不存在"
fi
#对IFS变量 进行替换处理 拆分分号
OLD_IFS="$IFS"
IFS=": "
array=($version)
IFS="$OLD_IFS"
#拆分加号
OLD_IFS="$IFS"
IFS="+"
array=(${array[1]})
IFS="$OLD_IFS"
versionName=${array[0]}
#versionCode=`expr "${versionName}"|sed "s/\.*//g"`
versionCode=${array[1]}
#指定版本号
if [ ! $buildVersionName ]; then
echo "没有指定版本,默认使用配置文件的版本"
else
versionName=$buildVersionName
fi
time=$(date "+%m%d")
test=Test
#执行打包
flutter build apk --$type --obfuscate --split-debug-info=xx --build-name=$versionName --build-number=$versionCode
#修改包名称
mv "build/app/outputs/flutter-apk/app-${type}.apk" "build/app/outputs/flutter-apk/hn_lottery_${versionName}+${versionCode}_${GIT_SHA1}_${type}.apk"
6. 图片二维码脚本
#!/bin/bash -ilex
#获取Git SHA1
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
#默认打包类型
DEFAULT_BUILD_TYPE=release
#打包类型
if [ ! $buildType ]; then
type=$DEFAULT_BUILD_TYPE
else
type=$buildType
fi
#amzqr路径配置
amzqr=/usr/local/bin/amzqr
#获取版本号
versionCode=""
version=""
versionCodeSpe=""
pubspecFile="pubspec.yaml"
#pubspecFile="utils/web_socket_utils.dart"
if [ -e $pubspecFile ]
then
if [ -r $pubspecFile ]
then
if [ -w $pubspecFile ]
then
#修改文件
#webview
version=`sed -n '18p' $pubspecFile`
else
echo "文件不可写"
fi
else
echo "文件不可读"
fi
else
echo "文件不存在"
fi
#对IFS变量 进行替换处理 拆分分号
OLD_IFS="$IFS"
IFS=": "
array=($version)
IFS="$OLD_IFS"
#拆分加号
OLD_IFS="$IFS"
IFS="+"
array=(${array[1]})
IFS="$OLD_IFS"
versionName=${array[0]}
#versionCode=`expr "${versionName}"|sed "s/\.*//g"`
versionCode=${array[1]}
#指定版本号
if [ ! $buildVersionName ]; then
echo "没有指定版本,默认使用配置文件的版本"
else
versionName=$buildVersionName
fi
baseUrl="http://xxx:1080/apps/xxx_app"
fileName="xxx_${versionName}+${versionCode}_${GIT_SHA1}_${type}"
apkUrl="${baseUrl}/${fileName}.apk"
apkPicUrl="${baseUrl}/${fileName}.png"
$amzqr "$apkUrl" -n "${fileName}.png" -d "build/app/outputs/flutter-apk/"
echo "<!doctype html>
<html>
<head>
<meta http-equiv=\"content-type\" content=\"text/html;\">
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width; initial-scale=1.0\">
<meta name=\"keywords\" content=\"test\" />
<meta name=\"description\" content=\"\">
<title>Android测试包下载页</title>
<link rel=\"stylesheet\" type=\"text/css\" href=\"style/css/mobile.css\" /></head>
<body>
<div class=\"doc\">
<br/>
<br/>
<p align = \"center\">
<a id=\"ipaPathHref\" href=\"$apkUrl\">$apkUrl</a>
</p>
<br/>
<br/>
<p align=\"center\">
<a id=\"plistPathHref\" href=\"$apkUrl\">
<img alt=\"\" src=\"$apkPicUrl\" style=\"height:300px; width:300px\" /></a>
</p>
<br/>
<br/>
<br/>
<p align=\"center\">请使用Android系统扫一扫</p>
<p align=\"center\">或使用应用浏览器扫码进行下载</p>
<br/></div>
</body>
</html>
" > "build/app/outputs/flutter-apk/${fileName}.html"
7. 图片二维码的生成
在上述图片二维码的脚本中我们可以看到如下命令:
$amzqr "$apkUrl" -n "${fileName}.png" -d "build/app/outputs/flutter-apk/"
这个命令是amazing-qr(github上搜索,简书被屏蔽)的使用,这个一个开源的Python 二维码生成器,基于Python3来运行,因此我们需要先安装Python3然后再安装amazing-qr
1. Linux中安装Python3
Linux系统中的安装方式有很多种,由于服务器上很多运行插件都没有安装,所以我采用下载解压的方式来安装:
wget https://www.python.org/ftp/python/3.9.7/Python-3.9.7.tgz
tar -zxvf Python-3.9.7.tgz
cd Python-3.9.7
./configure
make && make install
2. 安装amazing-qr
amazing-qr是基于Python来安装的,这里用到命令:
python3 -m pip install amzqr
问题(遇到的坑)
总体流程看下来比较清晰,操作也比较简单,基本是一步一步的往下走,但是事情往往没有想象中的顺利,中途也遇到的很多问题
1. Jenkins读取不到系统环境变量的问题
在第2步构建的Execute shell中,我们用到如下命令:
source /etc/profile
主要是因为Jenkins在运行时读取不到系统环境变量,需要我们在shell中设置生效。
还有另外一种方式,通过shell头文件定义,但是在Jenkins运行时会看不到执行日志
#!/bin/bash -i1
2. Python3安装失败问题
linux上Python安装失败主要是因为相关插件、环境缺失导致,这里列举我遇到的几个问题:
- 缺少gcc
configure: error: no acceptable C compiler found in $PATH
该错误是因为本机缺少gcc编译环境,只需安装gcc即可
# 安装命令
yum install -y gcc
- 缺少zlib
错误代码
zipimport.ZipImportError: can't decompress data; zlib not available
该错误是因为本机缺少zlib解压缩类库,只需安装zlib即可
# 安装命令
yum install -y zlib*
- 缺少libffi-devel
错误代码
ModuleNotFoundError: No module named '_ctypes'
该错误是因为本机缺少libffi-devel包,只需安装此包即可
# 安装命令
yum install -y libffi-devel
注意在安装完缺少的依赖包后,仍需重新运行对应所在的配置、编译和执行安装命令
3. Pip3安装失败问题
- SSL模块不存在
错误代码
Can't connect to HTTPS URL because the SSL module is not available.
说明ssl安装有问题,或者没有安装ssl。解决方法去python3 的安装目录下的/usr/local/python3/Python-3.6.8/Modules/Setup文件里,去掉下面四行的注释
SSL=/user/local/ssl
_ssl _ssl.c \
-DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-L$(SSL)/lib -lssl -lcrypto
4. amzing-qr安装失败问题
Pip无法在环境变量中定义。由于Python和Pip都是采用的下载tag文件解压的方式,并且Pip是基于Python进行安装,导致直接运行Pip命令系统无法识别,我们也无法在环境中配置Pip,解决方式是通过Python来运行Pip
python3 -m pip install amzqr
5. amzing-qr命令无法在shell中运行问题
amzing-qr安装完成后,服务器本地使用amzqr运行正常,但是在shell中运行一直识别不到该命令,即使是在系统环境变量中配置了也不行,需要指定amzqr的执行文件目录才可以
#amzqr路径配置
amzqr=/usr/local/bin/amzqr
由于amzing-qr是基于Pip进行安装,而Pip又是基于Python运行,所以出现该问题目前无法得知明确的原因,目前的解决方式是在Jenkins中配置amzing-qr的环境变量以此来运行
总结
平台的搭建从流程思路的明确、操作命令的学习、环境的安装、Jenkins项目的搭建、脚本命令的编写五个步骤来完成,在实施过程中也遇到很多问题,踩过了很多的坑,同时也获得了很多收获和感悟:
- Linux操作系统很重要。Linux操作系统在非常多的地方都用到,还是非常有必要了解和熟悉的
- 遇到问题时要了解问题的本质。安装失败或者存在环境问题时,多查询相关资料,同时了解问题出现的原因和解决处理的本质是什么
- 思路很重要。做一件复杂的事情之前,有必要先明确自己的思路,先拆解任务后逐步实现
- 实践出真知。很多事情看起来比较简单,但我们不能忽视,只有在实践之后我们才能得出结论,获得更多感悟和提升