使用Jenkins搭建Flutter持续集成打包平台

背景

目前项目功能开发已告一段落,后续进入项目实施阶段。考虑到后续有大量的测试和交付场景,有必要对项目搭建持续集成打包平台,方便后续测试交付。

由于公司已经搭建有Jenkins,而且作者以前也使用Jenkins搭建过Android持续集成打包平台,对于Jenkins的安装和配置就不多做讲解,如有需要可以参考使用Jenkins搭建iOS/Android持续集成打包平台。这里主要讲解如何在Jenkins上配置和构建Flutter工程,以及遇到的问题。

首先,给大家展示下平台搭建完成后的整体效果:

WX20210903-113445.png
WX20210902-142950.png

该平台主要实现的功能:

  • 可选择branch分支打包
  • 支持自定义版本号
  • 支持指定打包类型
  • 根据git提交sha命名文件
  • 打包生成二维码网页
  • 构建完成后通过钉钉推送到指定群通知

流程

开始搭建之前,我们需要先整理下我们的搭建思路:

  1. 在打包服务器(linux服务器)上安装Flutter、Android、JDK等构建所需SDK,安装Python3、amzqr用于生成二维码
  2. 在Jenkins中构建项目,配置打包参数
  3. 添加项目构建命令,构建打包生成APK
  4. 根据下载地址生成二维码,并写入html文件生成扫码下载网页
  5. 上传APK、二维码、扫码下载html文件到指定仓库提供下载
  6. 整个流程结束后通知开发人员构建完成

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

  1. 下载Flutter安装包
wget https://storage.flutter-io.cn/flutter_infra_release/releases/stable/linux/flutter_linux_2.2.3-stable.tar.xz
  1. 解压
cd ~/development
tar xf ~/Downloads/flutter_linux_2.2.3-stable.tar.xz
  1. 环境变量配置
vi /etc/profile
添加环境变量
source /etc/profile
  1. 测试环境是否正确
flutter doctor

在Jenkins中构建项目

在Jenkins新建一个项目,进入构建配置页面

1. 源码管理

  • 选择Git管理填入git地址
  • 添加项目的用户凭证
  • 指定分支为:$branch,这里是引用的其他变量,用于分支的选择
WX20210902-152431.png

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文件路径
WX20210902-153306.png

4. 配置可选参数

  1. 在General中勾选This project is parameterized,新增Git Paramter插件用于git分支的选择:
WX20210902-153720.png
  1. 选择Choice Pramter新增buildType配置可以打包的类型:
WX20210902-153730.png
  1. 选择String Paramter配置应用的版本号,默认使用代码中的版本号
WX20210902-153739.png

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安装失败主要是因为相关插件、环境缺失导致,这里列举我遇到的几个问题:

  1. 缺少gcc
configure: error: no acceptable C compiler found in $PATH
该错误是因为本机缺少gcc编译环境,只需安装gcc即可
# 安装命令
yum install -y gcc
  1. 缺少zlib
错误代码
zipimport.ZipImportError: can't decompress data; zlib not available
该错误是因为本机缺少zlib解压缩类库,只需安装zlib即可
# 安装命令
yum install -y zlib*
  1. 缺少libffi-devel
错误代码
ModuleNotFoundError: No module named '_ctypes'
该错误是因为本机缺少libffi-devel包,只需安装此包即可
# 安装命令
yum install -y libffi-devel
注意在安装完缺少的依赖包后,仍需重新运行对应所在的配置、编译和执行安装命令

3. Pip3安装失败问题

  1. 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项目的搭建、脚本命令的编写五个步骤来完成,在实施过程中也遇到很多问题,踩过了很多的坑,同时也获得了很多收获和感悟:

  1. Linux操作系统很重要。Linux操作系统在非常多的地方都用到,还是非常有必要了解和熟悉的
  2. 遇到问题时要了解问题的本质。安装失败或者存在环境问题时,多查询相关资料,同时了解问题出现的原因和解决处理的本质是什么
  3. 思路很重要。做一件复杂的事情之前,有必要先明确自己的思路,先拆解任务后逐步实现
  4. 实践出真知。很多事情看起来比较简单,但我们不能忽视,只有在实践之后我们才能得出结论,获得更多感悟和提升
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容