[TOC]
经常在开发的时候,测试/产品/运营等人员会来要求安装一下软件,这时候不得不停下手中的事情来打包安装,但终归不是长久之计:
- 自己开发时被经常打断思路;
- 停下来手头的工作来打包,每次怎么也得浪费个几分钟,长期下来不划算;
- 开发中的代码经常不是稳定或者未经过自测的代码,冒然给人安装总是容易发生问题,徒增bug;
作为一个 '懒人' ,这种重复性的工作肿么能每次都自己动手呢,另外最好再有个地方能提供稳定分支代码的安装包供人下载安装,省得有人说不会安装apk ==!...
话说前公司就提供有自动打包功能,以前不觉得有什么,等到没有的时候才发觉它的好...无奈,只能自己动手搭一套;
P.S. jenkins会去gitlab仓库中读取最新的分支代码到本地,具体地址也就是其环境变量
WORKSPACE
指代的位置;
基于:
系统: mac 10.12.4(Sierra)
Jenkins: 2.46.1
Git: 2.10.0
Python: 3.x
Jenkins的基本使用
安装运行Jenkins
- 到 官网 下载软件,应该是一个
jenkins.war
单文件; - 运行:
// 方式1: 假如系统中安装有Tomcat,就把它当做普通的war包放置到 webapps/ 下运行即可;
// 方式2: (假设 jenkins.war 放置在 ~/Downloads/ 目录下)
cd ~/Downloads/
nohup java -jar jenkins.war & // 后台运行jenkins.war程序,默认使用8080端口
// 指定端口
--httpPort=8080 // 用来设置jenkins运行时的web端口,避免冲突
- 安装运行成功后在浏览器中打开
localhost:8080
(端口号请按需修改),第一次会要求输入账号密码,jenkins
提供了一个初始密码,可以根据页面提示在文件initialAdminPassword
中获取:
sudo cat /Users/***/initialAdminPassword // 提示路径可能不同,根据页面提示修改
- 初始化后就会弹出添加账号的界面,按需设置一个新的账号密码,也可以后续在 Manage Users 中进行创建或修改;
- 登录成功后会有个安装插件提示页面,选择
Install suggested plugins
:
后续也可在 jenkins首页 -
Manage Jenkins
-Manage Plugins
中安装插件,主要是Gradle Plugin
Android Signing Plugin
Git Parameter Plug-In
GitHub Authentication plugin
Gitlab Authentication plugin
Git plugin
等
- 安装完重启就可以在首页添加任务
New Item
,添加完成后会在右侧显示已添加的 job 列表:
初始化配置
开始打包发布Android应用前需要进行如下环境的设置:Android/Git/Gradle/Python等
指定ANDROID_HOME全局变量
==! 不知道咋回事,没有识别到我配置在
~/.bash_profile
中的ANDROID_HOME环境变量,最后折腾了好久才发现要在jenkins中手动指定一个
在 Manage Jenkins
- Configure System
- Global properties
勾选 Environment variables
,并添加一个:
Name : ANDROID_HOME
Value : /Users/***/Applications/AndroidSDK
配置JDK/Git/Gradle
在 Manage Jenkins
- global tool configuration
中按需选择工具 , name
随意指定,其他的参考下图:
// 配置JDK主目录
name: MAC_JDK
path: /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home
// 配置Git运行路径
name: Default
path: /usr/bin/git
// 配置Gradle主目录
name: gradle3.5
path: /Users/lynxz/.sdkman/candidates/gradle/current/bin
// 注意: 在 `gradle /current/bin/bin`中需存在 `gradle` 可执行文件;
// 备注: 我之前是使用 `sdkman` 来安装的 `gradle` ,所以路径比较奇怪
curl -s https://get.sdkman.io | bash
sdk install gradle 3.5
创建和配置job
- 在jenkins首页左侧导航栏中点击
new item
, 输入名称, 选择Freestyle project
,点击ok
按钮即可; - 创建完成后,在首页右侧的 Job 列表中选择刚才创建的job,然后选择
Configure
进行配置; - 在
General
中按需输入project name
和Description
;
另外,这个tab页面中比较常用的还有参数化设置(This project is parameterized
), 后续会讲到; - 在
Source Code Management
中指定版本管理类型/仓库地址/认证信息等;
- 在
Build
中选择Invoke Gradle Script
,在Gradle Version
下拉列表中选择自定义的本机Gradle版本;并在Tasks
中输入打包命令:
// 默认未做多渠道多版本配置时,打包release类型的命令:
clean assembleRelease --stacktrace --debug
如果有需要将 general
标签也中定义的变量注入到项目中让 gradle
脚本使用,则请勾选 Pass job parameters as Gradle properties
,则gradle脚本中用到的同名自定义变量就会使用jenkins中指定的值;
参数化构建
有时候需要在构建的时候进行一些定制化操作,比如指定编译的代码分支,增加打包版本说明等,又或者项目中使用了私有仓库,而仓库的登录账号名(如mavenUser
)以及密码(mavenPassword
)存储于 gradle.properties
(不同步到gitlab仓库中),这时就需要添加参数,并将该参数注入到Android项目中了,以便gradle脚本能获取到正确的值,具体操作如下:
- 在job页面的
Configure
-General
面板中勾选This project is parameterized
; - 在
Add Parameter
下拉列表中就可以选择对应的类型变量
注意: 若参数需要注入到Android项目构建脚本中,则需要勾选 configure
- Build
- Pass job parameters as Gradle properties
;
- 比如选择添加一个
String Parameter
- 比如增加一个
Choice Parameter
,用于指定要打包的版本:
图中指定的三个choice是我在Android项目app/build.gradle
中定义过的,用于后续的打包命令:
android{
buildTypes {
release {
signingConfig signingConfigs.release
}
//不能以"test"开头
tstEnv {
debuggable true
signingConfig signingConfigs.release
}
debug {
versionNameSuffix "-dev"
debuggable true
signingConfig signingConfigs.release
}
}
}
上面指定的 choice parameter
类型参数 buildTypes
便可用在 Build
命令中:
签名
还没去研究过jenkins签名插件,一般直接在Android项目中配置好脚本即可:
- 将签名文件
*.jks
放置于app/
目录下; - 在
app/build.gradle
中配置签名参数,这样jenkins打包出来的apk就是签名过的:
android{
signingConfigs {
release {
keyAlias '***'
keyPassword '***'
storeFile file('*.jks')
storePassword '***'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.release
}
}
}
P.S. 不过总觉得这样直接公开签名文件到仓库中不太好,在打包服务器上进行控制会更合适点,毕竟知晓的人更少,这个后续再研究,先占个位;
获取当前用户的名称等信息
- 需要安装插件
user build vars plugin
,可在插件列表中直接获取安装 或者到 这里 下载插件包; - 安装后等待jenkins重启,并找到
job
-configure
-Build Environment
-Set jenkins user build variables
,勾选此项后才生效; - 在 shell 中便可像jenkins自带的变量那样使用,如
echo "$BUILD_USER"
:
插件可用的变量名 | 变量描述 |
---|---|
BUILD_USER | Full name (first name + last name) |
BUILD_USER_FIRST_NAME | First name |
BUILD_USER_LAST_NAME | Last name |
BUILD_USER_ID | Jenkins user ID |
BUILD_USER_EMAIL | Email address |
Build History 定制
默认的job构建历史命名( 如 #35 Apr 27, 2017 11:15 AM
)不容易理解记忆,我们可以自定义,加入构建者姓名,版本等信息;
定制包括"build name" 和 "build description" 两部分的定制,效果如下
构建名称定制
- 下载 Build Name Setter Plugin 插件;
- 手动安装:
manage jenkins
-manage plugins
-Advanced
在upload plugin
中选择刚才下载的插件,提交后重启jenkins即可; - 选择一个 job 进入
Configure
-Build Environment
,就会多出一个Set Build Name
复选项,勾选后即可定制;
构建描述定制
安装插件
description setter plugin
(可以在jenkins的manage plugins
中找到);-
重启jenkins后,进入 job 的
Configure
-post-build Actions
,选择Add post-build action
-set build description
即可定制;
-
为了显示蒲公英的二维码图片,需要先在
manage jenkins
-Configure global security
,找到Markup Formatter
,将默认的plain text
改为safe html
;
在 job -
configure
-post-build actions
-set build description
中就可以输入html标签了,比如我设置了:
// 这里的${pgyerNotes}是我在general中添加的参数
${pgyerNotes}
![](https://static.pgyer.com/app/qrcode/Cb6T)
Download Directly
构建完成后打包记录并显示在 job 首页面,便于下载
- 进入 job -
Configure
-Post-Build Actions
- 在
Add post-build action
列表中选择Archive the artifacts
输入要存档的文件路径,可使用通配符,比如我输入的是app/build/outputs/apk/Sb*.apk
,就会在job项目首页看到存档记录,可以直接下载;
注意:这里不能使用${WORKSPACE}
等变量,貌似就是直接以当前工作空间为根目录的;
使用shell上传apk到蒲公英
请首先到 蒲公英 上注册账号,并认证,若不认证则上传失败;
蒲公英上传文件接口
蒲公英的key值可在 账户设置
- API信息
中查看到
由于我是mac系统,因此在 Job - Configure
- Build
- Add build step
列表中选择 Execute shell
,运行shell脚本
P.S. 若是对shell不熟悉,可参考 教程
另外,由于我在项目中根据版本号(vesionName)重命名了生成的apk,因此需要在shell脚本中提取版本号以便获得apk全称,进而上传蒲公英
# build -> Add build step -> Execute shell
pgyerApiKey="******"
pgyerUKey=="******"
echo "获取apk版本号..."
# ${WORKSPACE} 是jenkins提供的环境变量,表示当前项目跟目录路径
# 下面的命令是获取 app/build.gradle 的第17行内容,然后按照双引号进行切换,提取第2部分内容,即上面图示中的 1.1.4
versionName=`sed -n '17p' ${WORKSPACE}/app/build.gradle | cut -d \" -f 2`
echo "获取apk所在路径..."
# _360 是项目中定义了多渠道,但由于之前在 Build - Task 中设置的打包命令,直接指定了渠道号,因此这里也直接固定写好就可以;
apkAbsPath="${WORKSPACE}/app/build/outputs/apk/SonicMoving_${buildTypes}_[_360]_v${versionName}.apk"
echo "上传apk到蒲公英进行发布..."
response=$(curl -F "file=@${apkAbsPath}" -F "uKey=${pgyerUKey}" -F "_api_key=${pgyerApiKey}" https://qiniu-storage.pgyer.com/apiv1/app/upload)
echo "上传结束"
# 原本上传结束后想要使用 jq 工具 (`brew install jq`) 对蒲公英上传时返回的response进行json处理的,结果在电脑的shell中测试可行,但写到这里就一直不成功,无奈,只好放弃
# 提取蒲公英返回的json数据中的 appShortcutUrl 字段值,可拼接成下载地址
#responseCode=$(echo -E "${response}" | jq .code)
#if [ $((responseCode)) == 0 ]
#then
# echo "上传结束,处理返回相应..."
# appShortcutUrl=$(echo -E "${response}" | jq ".data.appShortcutUrl" | cut -d \" -f 2)
# apkOnlineUrl="https://www.pgyer.com/${appShortcutUrl}"
#else
# echo "上传失败,返回码为: ${responseCode} ,具体请看日志"
#fi
使用python上传apk到蒲公英
最早之前我也是尝试直接使用python插件的,操作如下:
- 安装
Python Plugin
; - 在 job 的
Configure
-Build
中就会多一个选项Execute python script
;
import os
# 获取jenkins变量 'BUILD_NUMBER'
print("build_number is ==> ",os.getenv("BUILD_NUMBER"))
但是由于我是mac,系统中默认的python是2.7.x,而我又装了其他版本的python,虽然在jenkins的全局变量中指定了python版本,但实际执行的时候却用的不是它,大致的错误如下:
可以发现jenkins把我们写在
python script
中的生成了一份位于
/var/.../*.py
的文件,然后使用系统默认的
python
版本来执行,而我 mac 默认的python是python2.7.*,导致里面写的很多基于python3.x的代码出错:
解决方案
使用python多版本问题的常用方法 virtualenv
,最后演变成通过shell来启用版本隔离,然后手动调用python命令加载脚本:
在 Build
- add build step
- execute shell
中写入如下脚本:
# 如果当前无指定的环境目录存在,则创建,并指定python版本
if [ ! -d ".env" ]; then
virtualenv -p /usr/local/bin/python3 .env
fi
# 启动virtualenv
source .env/bin/activate
echo "当前操作的用户是 : $BUILD_USER "
#requestsLibName="requests"
#isInstallRequest=$(pip freeze | grep $requestsLibName)
#if [[ $isInstallRequest =~ "*requests*" ]];then
# pip install requests
#fi
# 安装所需要的第三方库,若已安装,则不会重新安装
pip install requests
# 运行指定路径下的python脚本
python3 ~/Desktop/upload.py
上面shell脚本中的 upload.py
内容如下(可考虑将其放在Android项目中,上传gitlab):
#!/usr/local/bin/python3.5
# -*- coding: utf-8 -*-
'''
jenkins 打包线上代码生成apk后发布到蒲公英
本脚本路径: ~/Desktop/upload.py
'''
import os
import io
import re
import requests
import json
WORKSPACE = os.getenv("WORKSPACE") # 获取jenkins环境变量
userName = os.getenv("BUILD_USER") # 获取用户名
buildTypes = os.getenv("buildTypes") # 获取用户选择的编译版本
pgyerNotes = os.getenv("pgyerNotes") # 获取用户填写的版本说明
# 重置默认编码为utf8
import sys
default_encoding = 'utf-8'
if sys.getdefaultencoding() != default_encoding:
reload(sys)
sys.setdefaultencoding(default_encoding)
# 确认下当前python版本
print("当前编译器版本: %s " % sys.version)
print("python编译器详细信息: ", sys.version_info)
# 获取版本号
# guild.gralde文件所在路径
buildGradleFilePath = "%s/app/build.gradle" % (WORKSPACE) # 指定文件所在的路径
print("build.gradle路径是: ", buildGradleFilePath)
# 读取build.gradle并获取versionName值
with open(buildGradleFilePath, 'r', encoding='utf-8') as buildGradleFile:
line = buildGradleFile.readlines()[16:17][0] # 读取第17行数据, 切片从0开始;
print("line 17 is .... ", line)
versionName = re.split(r'\"', line)[1]
print("版本号为: %s" % versionName)
# "获取apk所在路径..."
apkAbsPath = "%s/app/build/outputs/apk/SonicMoving_%s_[_360]_v%s.apk" % (WORKSPACE, buildTypes, versionName)
print("准备上传apk到蒲公英进行发布,apk所在路径为: %s" % apkAbsPath)
# 上传结束后发出请求通知服务端,进而由服务端发送钉钉消息
def notify_upload_result(msg):
headers = {'user-agent': 'jenkins_upload_pgyer'}
params = {'userName': userName, 'msg': msg} # 字段中值为None的字段不会被添加到url中
response = requests.get('http://btcserver.site:8080/WebHookServer_war', params=params, headers=headers)
#response = requests.get('http://localhost:8081', params=params, headers=headers)
print("通知webhook服务器结果: ", response.text)
# 蒲公英账号信息
pgyerApiKey = "******"
pgyerUKey = "******"
# response=$(curl -F "file=@${apkAbsPath}" -F "uKey=${pgyerUKey}" -F "_api_key=${pgyerApiKey}" -F "updateDescription=${pgyerNotes}" https://qiniu-storage.pgyer.com/apiv1/app/upload | jq .)
# post请求中所需携带的信息
data = {
'_api_key': pgyerApiKey,
'uKey': pgyerUKey,
'updateDescription': pgyerNotes
}
files = {'file': open(apkAbsPath, 'rb')}
uploadUrl = 'https://qiniu-storage.pgyer.com/apiv1/app/upload'
response = requests.post(uploadUrl, data=data, files=files)
print(response.status_code, response.text)
if response.status_code == 200:
print("上传成功,通知webhook服务器...")
notify_upload_result(response.text)
else:
print("上传失败,状态码为: %s" % (response.status_code))
蒲公英发布成功后通知钉钉
参考 打通Gitlab与钉钉之间的通讯
这里有两种方式通知服务器后台:
-
使用蒲公英项目中自带的webhook功能,但是这个通知无法具体得知是谁进行的这次打包发布,并且若上传蒲公英失败的话也不会进行webhook通知,因此不太方便:
- 如上面python脚本中写那样,在上传返回后,主动调用服务器接口,将response和jenkins相关信息上传,然后后台有针对性的发送消息;
碰到的异常
1. Failed to connect to repository : Command "git ls-remote -h https://git.***.git HEAD" returned status code 143:
需要在仓库中添加公钥;
//获取公钥
ssh-keygen -t rsa -f ~/.ssh/id_rsa.pub
测试时用的是 coding.net
, 因此在 coding.net
对应项目的 设置
- 部署公钥
- 新建部署公钥
将刚才输出的公钥粘贴进去即可;
2. token-macro v1.5.1 is missing. To fix, install v1.5.1 or later
token-macro-plugin
到插件管理页面中搜索 Token Macro Plugin
安装即可;