Jenkins自动化构建Android、RN混合apk打包+乐固加固+分渠道打包(需要开发配合用哪种分包方式)+推送打包通知到企业微信
要在Jenkins上做自动化打包,首先得把环境搞好。所需环境,包括JDK,Jenkins,gradle,AndroidSDK,nodejs,git,python。环境的安装就不赘述了。做成三个工程组成上下游工程构建,打包一个工程,加固签名一个工程,推送通知一个工程。下面粗糙的做了一个自动化打apk包的工程组合。直接上Jenkins自动化apk打包工程的配置:
(一)、打包工程
构建页面如下:
下面是配置:
首先设置参数--git 参数,由于是RN混合Android原生,所以gitlab仓库的项目是rn项目加子模块android的形式。所以设置两个git参数:项目分支参数,子模块的分支
还有其他如环境参数Env,是否要推送到企业微信,是否要加固后签名,是否要分渠道打包,如下图:
参数设置后,接下来设置源码管理,目前所用的是git,因为是混合项目,有子模块,所有选择Multiple SCMs
接下来配置构建环境:
下面是构建步骤
一、由于我是在Linux环境下的Jenkins,所以打包所用的build-tools与windows不一样,所以打包前需要修改build.gradle里面的build-tools版本,动态的修改Versioncode和Versionname,以及添加打包后检查流程报错的过滤。
修改项目下app里面的build.gradle的地方如下:
1、修改build-tools,Versioncode,Versionname
2、修改输出地址:
3、增加三行代码,避免执行lint的时候报错
4、修改配置文件Power.kt文件,按照测试环境或生产环境进行修改
下面是做出修改的脚本
originoutput="C:\\\\\\\\Users\\\\\\\\dell\\\\\\\\Desktop\\\\\\\\" newoutput="\/data\/jenkins\/workspace\/LuckyPanda" powerpath=$WORKSPACE/android/app/src/main/java/com/wtsd/luckypanda/pack/config/Power.kt gradlebuilepath=/data/jenkins/workspace/LuckyPanda/android/app/build.gradle sed -i "s#$originoutput#$newoutput#g" $gradlebuilepath sed -i "s/30.0.3/30.0.0-rc2/g" $gradlebuilepath row=`sed -n '/compileOptions/=' $gradlebuilepath` sed -i "${row}i\ }" $gradlebuilepath sed -i "${row}i\ abortOnError false" $gradlebuilepath sed -i "${row}i\ lintOptions \{" $gradlebuilepath Code=versionCode\ $versionCode Name=versionName\ \'$versionName\' #cp $JENKINS_HOME/workspace/build.gradle $gradlebuilepath cp $JENKINS_HOME/workspace/local.properties $WORKSPACE/android/local.properties if [ $Env == test ];then echo --------------------Env为test,打测试包-------------------- :<<! echo 复制测试环境配置文件Power.kt到utils目录下并替换目录下的Power.kt cp $JENKINS_HOME/workspace/Power.kt ${powerpath} ! echo 修改文件${powerpath}的配置,改为测试环境配置 sed -i 's/const val Log =.*$/const val Log = true \/\/发布APP 时 false/g' ${powerpath} sed -i 's/const val Product =.*$/const val Product = false \/\/发布APP 时 true/g' ${powerpath} sed -i 's/^.*const val Root = "https\:\/\/panda.wtsd.cn".*$/ \/\/const val Root = "https\:\/\/panda.wtsd.cn"/g' ${powerpath} sed -i 's/^.*const val Root = "https\:\/\/test.szwtsd.com".*$/ const val Root = "https\:\/\/test.szwtsd.com"/g' ${powerpath} elif [ $Env == product ];then echo --------------------Env为product,打生产包--------------------- :<<! echo 复制生产环境配置文件Power-pro.kt到utils目录下重命名为Power.kt并替换目录下原有的Power.kt cp $JENKINS_HOME/workspace/Power-pro.kt ${powerpath} ! echo 修改文件${powerpath}的配置,改为测试环境配置 sed -i 's/const val Log =.*$/const val Log = false \/\/发布APP 时 false/g' ${powerpath} sed -i 's/const val Product =.*$/const val Product = true \/\/发布APP 时 true/g' ${powerpath} sed -i 's/^.*const val Root = "https\:\/\/panda.wtsd.cn".*$/ const val Root = "https\:\/\/panda.wtsd.cn"/g' ${powerpath} sed -i 's/^.*const val Root = "https\:\/\/test.szwtsd.com".*$/ \/\/const val Root = "https\:\/\/test.szwtsd.com"/g' ${powerpath} fi #sed -i 's#versionCode [[:digit:]]*#versionCode\ $versionCode#g' $gradlebuilepath #sed -i 's/versionName \"[[:digit:]].*\"/versionName\ \"$versionName\"/g' $gradlebuilepath echo ---------------------修改versionCode为$versionCode,修改versionName为$versionName-------------------- sed -i "/./{s/versionCode [[:digit:]]*/$Code/;s#versionName '[[:digit:]].*'#$Name#}" $gradlebuilepath :<<! crows=(`sed -n '/versionCode/=' $gradlebuilepath`) crow=${crows[0]} cmdc="sed -i '${crow},${crow}s#[0-9]\+#${versionCode}#g' $gradlebuilepath" eval $cmdc vrows=(`sed -n '/versionName/=' $gradlebuilepath`) vrow=${vrows[0]} cmd="sed -i '${vrow},${vrow}s#[0-9.]\+#${versionName}#g' $gradlebuilepath" eval $cmd !
二、RN环境依赖下载,需要修改一个node_modules里面react-native-fs/android里面的build.gradle里面的版本配置,要不然会报错“react-native-fs:verifyReleaseResources FAILED”,需要修改的地方如下图
下面截图是想以的shell脚本
#!/bin/bash export NODE_HOME=/opt/nodejs/node-v14.15.3-linux-x64/bin export PATH=${PATH}:${NODE_HOME} yarn if [ $Env == test ];then yarn ${Env} elif [ $Env == product ];then yarn prod fi reactfs_buildegradle=$WORKSPACE/node_modules/react-native-fs/android/build.gradle sed -i "s#compileSdkVersion', [[:digit:]]*#compileSdkVersion\'\,\ 28#g" $reactfs_buildegradle sed -i "s#buildToolsVersion', '[[:digit:]].*'#buildToolsVersion\'\,\ \'28.0.3\'#g" $reactfs_buildegradle sed -i "s#targetSdkVersion', [[:digit:]]*#targetSdkVersion\'\,\ 28#g" $reactfs_buildegradle export GRADLE_HOME=/opt/gradle export PATH=$PATH:$GRADLE_HOME/bin cd android/ gradle clean build
如果只单独构建release包或者debug,替换命令行即可:
- gradle assembleDebug 只打debug
- gradle assembleRelease 只打release
构建后操作:
记录生产版本的Versioncode
if(manager.build.buildVariables.get("Env").equals("product")) {manager.addShortText(manager.build.buildVariables.get("Env")) manager.addShortText("versionCode:${manager.build.buildVariables.get("versionCode")}")}
每次触发后构建历史会显示如下:
传递参数到下游项目:
(二)、加固签名工程
构建页面如下:
配置如下:
参数出需要设置接收的传递参数:
设置构建步骤:
1、使用腾讯云乐固加固,加固的依赖jar包可以在腾讯云社区下载https://cloud.tencent.com/developer/article/1193406?from=10680或依据以下步骤下载:
查看jar包更新历史:
https://leguimg.qcloud.com/ms-client/java-tool/version.json
可以根据最近日期获取到最新版本号
将上面获取到的版本号替换下面的版本号进行下载:
如下下载最新版本1.0.3版本
https://leguimg.qcloud.com/ms-client/java-tool/1.0.3/ms-shield.jar
cp ${JENKINS_HOME}/workspace/${prj}/android/app/build/outputs/apk/release/*.apk ${JENKINS_HOME}/workspace/${prj}/android/LuckyPanda-$Env-$versionName.apk cp ${JENKINS_HOME}/workspace/ms-shield.jar ${JENKINS_HOME}/workspace/${prj}/android/ leguapp=${JENKINS_HOME}/workspace/${prj}/result/LuckyPanda-${Env}-${versionName}_legu.apk if [ $ShieldAndSigner == Y ];then if [ ! -f "$leguapp" ];then #乐固加固处理 cd ${JENKINS_HOME}/workspace/${prj}/android/ echo --------------------------------------------乐固加固处理-------------------------------------------------- java -Dfile.encoding=utf-8 -jar ms-shield.jar -sid AKIDLVP3RZR4SbU8etsb3FBsJUP2OUJq65pI -skey D9pFOFqBE0UieJgndeNp54nFRN2RaR1J -uploadPath ${JENKINS_HOME}/workspace/${prj}/android/LuckyPanda-${Env}-${versionName}.apk -downloadPath ${JENKINS_HOME}/workspace/${prj}/result/ else echo 已做加固处理~ fi else echo 不做加固处理~ fi
2、签名并做分渠道打包处理,并且把渠道包到成zip包,(wall-cli-all.jar下载地址:https://github.com/Meituan-Dianping/walle/releases)
#重新签名 leguapp=LuckyPanda-${Env}-${versionName}_legu.apk buildToolsVersion=30.0.3 #编译工具版本 keystore=${JENKINS_HOME}/workspace/${prj}/android/keystore/lucky.jks storePwd="wtsd888888" # 默认的签名文件密码 keyPwd="wtsd666666" # 默认的签名别名密码 alias=luckypanda #别名 signFile=LuckyPanda-${Env}-${versionName}_legu-signed.apk signCmd="/opt/android-sdk-linux/build-tools/${buildToolsVersion}/apksigner sign \ --ks ${keystore} \ --v1-signing-enabled true \ --v2-signing-enabled true \ --ks-key-alias ${alias} \ --ks-pass pass:${storePwd} \ --key-pass pass:${keyPwd} \ --out ${JENKINS_HOME}/workspace/${prj}/result/${signFile} ${leguapp}" if [ $ShieldAndSigner == Y ];then cd ${JENKINS_HOME}/workspace/${prj}/result/ if [ ! -f "$signFile" ];then echo '正在签名' ${signCmd} echo '签名完成' else echo '已有签名完成的apk包' fi else echo '不做加固处理不需要签名~' fi #多渠道打包 if [ $MultiChannel == Y ];then #使用美团wall分渠道打包,把分渠道的依赖包复制到工作空间 cp $JENKINS_HOME/workspace/walle-cli-all.jar ${JENKINS_HOME}/workspace/${prj}/ #进入工作空间根目录 cd ${JENKINS_HOME}/workspace/${prj}/ if [ ! -f "${JENKINS_HOME}/workspace/${prj}/result/LuckyPanda"*".zip" ];then #多渠道分包 /opt/java/jdk1.8.0_181/bin/java -jar walle-cli-all.jar batch -f ${JENKINS_HOME}/workspace/${prj}/android/channel.txt ${JENKINS_HOME}/workspace/${prj}/result/$signFile time=$(date "+%Y%m%d%H%M%S") #分包后重新进入分包所在目录,并把所得渠道包压缩为一个gz包 cd ${JENKINS_HOME}/workspace/${prj}/result/ #tar --warning=no-file-changed -zcvf ${JOB_NAME}${time}.tar.gz * --exclude=${leguapp} --exclude=${signFile} --exclude=${signFile}.idsig zip ${prj}${time}.zip * -x "${leguapp}" "${signFile}" "${signFile}.idsig" else echo '已做好分渠道打包' fi else echo '不做分渠道打包~' fi
构建后操作:
设置下游项目的传递参数
(三)推送企业微信
构建页面如下:
配置如下:
参数处设置接受上游项目传递过来的参数
构建步骤
#!/bin/bash cd /data/jenkins/workspace/ if [ $isPushToQYWX == Y ];then echo isPushToQYWX参数为Y,把apk包推送到企业微信 if [ $ShieldAndSigner == Y ];then if [ $MultiChannel == Y ];then filepath=$JENKINS_HOME/workspace/${prj}/result/LuckyPanda*.zip else filepath=$JENKINS_HOME/workspace/${prj}/result/LuckyPanda-${Env}-${versionName}_legu-signed.apk fi else filepath=$JENKINS_HOME/workspace/${prj}/android/LuckyPanda-${Env}-${versionName}.apk fi echo $filepath python apkbuildresult.py $prj $filepath else echo 不推送到企业微信~ fi
推送的脚本apkbuildresult.py放在$JENKINS_HOME/workspace/下面,脚本如下:
#coding=utf-8 import requests import json import urllib #import urllib.error import time import urllib2 import re import os import sys jobname=sys.argv[1] filepath=sys.argv[2] # 获取构建结果 a="--select--" def getProName(): fname = pathGitLab with open(fname, 'r') as f: first_line = str(f.readline()) # 读第一行 s = f.read() urls = re.findall(r'http://192.168.0.174/[a-zA-Z0-9._/\-:]*@[A-Z0-9]*', s) # 用正则获取svn路径的url #print(first_line) builders=first_line.split(" ") #print(builders) while builders[0].isalpha(): builders.remove(builders[0]) builder=builders[0].strip("\n") builder=re.findall(ur'[^a-zA-Z0-9+\^:/\[\]]+',builder) #print(builder) builder=builder[-1] print(builder) f.closed if urls == []: urls = re.findall(r'http://192.168.0.174/[a-zA-Z0-9._/\-:]*', s) if urls == []: urls = re.findall(r'192.168.0.210[a-zA-Z0-9._/\-:]*.git', s) print urls branchs = re.findall(r'\(refs/remotes/origin/[a-zA-Z0-9._/\-:]*\)', s) print branchs if branchs==[]: branchs=re.findall(r'\(detached\)', s) if branchs==['(detached)']: print branchs branch="master" else: B = branchs[0].split("/origin/") print B branchs0 = B[-1] B1 = branchs0.split(")") branch = B1[0] print branch branch = "/branches/"+branch url=urls[0].replace(".git",branch) print url else: url = urls[0] # 取第一个url else: url = urls[0] # 取第一个url #print urls print url L = url.split("/branches/")#以branches作为分割点,得到分支 print L a = L[0] branch=L[-1] print branch L1 = a.split('/')#以/分割得到项目名 prj = L1[-1] if prj=="source": prj=L1[-2] if prj=="BMS" or prj=="MPS": prj = L1[-3]+L1[-2] elif prj=="BMS" or prj=="MPS": pb=L1[-2].split(':') prj = pb[-1]+L1[-1] print prj return prj,branch,builder def getResult(): fname = pathGitLab with open(fname, 'rb') as f: # 打开文件 first_line = f.readline() # 读第一行 #print (first_line) off = -50 # 设置偏移量 while True: f.seek(off, 2) # seek(off, 2)表示文件指针:从文件末尾(2)开始向前50个字符(-50) lines = f.readlines() # 读取文件指针范围内所有行 # print (lines) if len(lines) >= 2: # 判断是否最后至少有两行,这样保证了最后一行是完整的 last_line = lines[-1] # 取最后一行 print (last_line) break off *= 2 if 'FAILURE' in last_line.decode(): return 1 elif 'SUCCESS' in last_line.decode(): return 0 else: return 2 # 获取下一次构建的Number和当前构建的number def getNextNumber(jobname): f = open(r'/data/jenkins/jobs/%s/nextBuildNumber'% jobname, 'r') currentNumber = int(f.read()) - 1 return currentNumber # 网络模块,用于企业微信发送信息 def jenkins(result,builder,proname,branch,jobname,jobnum): url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=5eac012b-20ea-43d0-a9bd-504b85c02eca' #url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=f088b2d7-6cd0-488d-a28d-2e4c9babb78b' # 企业微信机器人的webhook if result == 1: con = {"msgtype": "text", "text": {"content": "项目构建提醒\r\n构建者:%s\r\n项目名及分支:%s,%s\r\n构建结果:FAILURE\r\n打包失败,请查看Jenkins构建具体日志!\r\nhttp://192.168.0.154:6689/job/%s/%s/console"% (builder,proname,branch,jobname,jobnum)}, } elif result == 0: con = {"msgtype": "text", "text": {"content": "项目构建提醒\r\n构建者:%s\r\n项目名及分支:%s,%s\r\n构建结果:SUCCESS\r\n打包成功!"% (builder,proname,branch)}, } else : con={"msgtype": "text","text": {"content":"项目构建提醒\r\n构建者:%s\r\n项目名及分支: %s,%s\r\n构建结果:UNSTABLE\r\n打包失败,请查看Jenkins构建具体日志!\r\nhttp://192.168.0.154:6689/job/%s/%s/console"% (builder,proname,branch,jobname,jobnum)}, } jd = json.dumps(con).encode('utf-8') print jd req=urllib2.Request(url, jd) #req = urllib.request.Request(url, jd) req.add_header('Content-Type', 'application/json') response = urllib2.urlopen(req) def apk_upload(file): name = os.path.basename(file) #id_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=5eac012b-20ea-43d0-a9bd-504b85c02eca&type=file' # 上传文件接口地址 wx_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=5eac012b-20ea-43d0-a9bd-504b85c02eca' # 发送消息接口地址 if result == 0: #data = {'file': open(file, 'rb')} # post jason #response = requests.post(url=id_url, files=data) # post 请求上传文件 #json_res = response.json() # 返回转为json #media_id = json_res['media_id'] # 提取返回ID # #data = {"msgtype": "file", "file": {"media_id": media_id}} # post json if ".zip" in name: data = {"msgtype": "text", "text": { "content": "因分渠道打包后压缩包较大,请点击下方链接下载\r\nhttp://192.168.0.154:6689/job/%s/ws/result/%s" % (jobname,name)}} # data = json.dumps(data).encode('utf-8') #r = requests.post(url=wx_url, json=data) # post请求消息 elif "signed" in name: data = {"msgtype": "text", "text": { "content": "因apk包较大,请点击下方链接下载\r\nhttp://192.168.0.154:6689/job/%s/ws/result/%s" % (jobname,name)}} else: data = {"msgtype": "text", "text": { "content": "因apk包较大,请点击下方链接下载\r\nhttp://192.168.0.154:6689/job/%s/ws/android/%s" % (jobname,name)}} else: return False r = requests.post(url=wx_url, json=data) # post请求消息 return r # 返回请求状态 if __name__ == '__main__': jobCurrentNumber = getNextNumber(jobname) # 获取当前构建number print (jobCurrentNumber) # 获取当前构建的目录比如D:\Jenkins\jobs\gk_check\builds\153, path = "/data/jenkins/jobs/%s/builds/" % jobname + str(jobCurrentNumber) + "/" # pathGitLab = path + "log" # 获取构建日志的路径 #pathGitLab = "E:\\log" pathAuthor = path + "changelog.xml" # 获取递交者信息的文件路径 tup=getProName() proname=tup[0] print proname branch=tup[1] print branch builder=tup[2] result = getResult() # 读取构建结果 print (result) jenkins(result,builder,proname,branch,jobname,str(jobCurrentNumber)) # 最后执行函数 apk_upload(filepath) getResult()
配置结束。。。