目前我司使用的是 jenkins 开源工具来持续集成构建客户端安装包
以下是之前构建测试 app 的流程图:
在构建后会把构建好的产物 (安装包) 上传到阿里云 oss 平台保存,最终在内部 App 应用商城展示构建详细信息.
我们使用的 docker 部署 jenkins,相比 war 包部署简单、灵活
使用阿里云加速镜像下载镜像
docker pull kfwkfulq.mirror.aliyuncs.com/jenkinsci/jenkins:2.150.1
启动命令
docker run -p 8001:8080 -p 50000:50000 -d -v /data/jenkins/jenkins_home:/var/jenkins_home kfwkfulq.mirror.aliyuncs.com/jenkinsci/jenkins:2.150.1
启动后会提示没有权限
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
需要修改下目录权限, 因为当映射本地数据卷时,/home/docker/jenkins 目录的拥有者为 root 用户,而容器中 jenkins user 的 uid 为 1000
sudo chown -R 1000:1000 /data/jenkins/jenkins_home
然后用 nginx 反向代理 ip+ 端口就可以,通过域名解析到 ip 上.
其实最开始我们是打算在 jenkins 上封一层 UI 页面,不让测试同学直接操作 jenkins,其目的是怕测试同学误删除、修改配置,影响打包.
但是发现 jenkins 自带了权限管理功能,可以开通给账号赋予只读权限,还是使用了 jenkins 直接构建.
在 Manage Roles 设置不同角色权限
在 Manage and Assign Roles 赋予人员权限
只读账号只能看到构建按钮,这样就解决了权限问题
关于构建 Android 应用这块,没有使用 pipline 来构建,还是使用 shell 脚本构建,主要原因是构建前需要设置一些变量还有一些前置操作.
主要几个步骤:
(1)、根据分支 clone 代码
这里我们是全量来 clone 代码,先删除本地所有代码,之前发现过一些缓存文件导致测试包有问题.
(2)、设置环境变量
因为先需要通过 “buildSystem.gradle” 文件接受外部参数,比如 env 参数、构建模式参数.
buildSystem.gradle 是使用 groovy 语法编程的文件,在编译前会先执行这个文件.
(3)、执行 gradle 打包命令
先 clean 缓存,再执行打包命令.目前我们通过打包优化后,构建时间大概 7 分钟左右.
./gradlew clean
./gradlew :app:assembleIgetcoolRelease
(4)、执行上传脚本
1)、测试应用上传 oss
使用 python 脚本把 apk 上传到阿里云的 oss 服务器上.这样有几个好处.
私有化测试应用 (不使用蒲公英这类的)、oss 下载地址可以提供给用户下载、减少服务器磁盘空间
python 版的 oss 上传代码
需要 ENDPOINT、ACCESSID、ACCESSKEY、BUCKET 参数,建议把参数写到配置文件中,防止硬编码泄密.
import oss2
def upload_oss(file_path,project_name):
"""
上传到阿里云oss储存服务
:return:
"""
try:
config = get_config()
ENDPOINT = config.get('oss', 'ENDPOINT')
ACCESSID = config.get('oss', 'ACCESSID')
ACCESSKEY = config.get('oss', 'ACCESSKEY')
BUCKET = config.get('oss', 'BUCKET')
file_name = str(file_path).split('/')[-1]
current_time = time.strftime("%Y%m%d%H%M%S")
ObjectName = "t/{}/{}{}".format(project_name,current_time,file_name)
auth = oss2.Auth(ACCESSID, ACCESSKEY)
bucket = oss2.Bucket(auth, ENDPOINT, BUCKET)
bucket.put_object_from_file(ObjectName, file_path)
oss_save_url = "https://xxxx.oss-cn-beijing.aliyuncs.com/{}".format(ObjectName)
results = {}
results['code'] = '0'
results['message'] = '保存oss成功。'
results['oss_url'] = oss_save_url
except Exception as e:
print(e)
results = {}
results['code'] = '10000'
results['message'] = '保存oss失败,请检查后再试。'
print(results)
finally:
return results
上传文件速度主要看网速和服务器带宽,最终生成上传后地址如下:
https://xxxx.oss-cn-beijing.aliyuncs.com/t/apk/2020093011522820200930115xxxx.apk
2)、生成测试包二维码
我们为了测试通过安装测试包方便,把测试包等相关信息生成二维码.
代码片段如下:
import qrcode
import PIL.Image
def gen_qrcode(file_url,project_name):
"""
文件URL生成二维码URL
"""
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=4,
border=1
)
print("qr.box_size = " + str(qr.box_size))
image_name = str(file_url).split('/')[-1].split('.')[0] + '.png'
save_image_path = "/".join(file_url.split("/")[0:-1]) + "/" + image_name
print("file url : " + file_url)
print("qrcode url : " + save_image_path)
qr.add_data(file_url)
qr.make(fit=True)
img = qr.make_image()
base_path = os.path.dirname(__file__) # 当前文件所在路径
dir_path = os.path.join(base_path, 'files')
if not os.path.exists(dir_path):
os.makedirs(dir_path)
create_time = time.strftime("%Y%m%d%H%M%S")
# temp_path = os.path.join(base_path,create_time + "_" + image_name)
temp_path = os.path.join(base_path, create_time + ".png")
img.save(temp_path)
results = upload_oss(temp_path,project_name)
return results
except Exception as e:
print(e)
results = {}
results['code'] = '10001'
results['message'] = '生成oss二维码失败,请检查后再试。'
print(results)
print(e)
finally:
os.remove(temp_path)
return results
3)、保存构建信息
需要保存下载地址、构建时间、构建人、分支、版本、变更日志,这里我们实现了后端接口来保存构建信息.
代码如下:
qrcode_url = info['oss_url']
logger.info("========== save appinfo to sql ==========")
data = {'platform': 'Android',
'version': version,
'env': env,
'apk_path': app_url,
'qrcode_path': qrcode_url,
'tag': tag,
'info': commit_info,
'project': project
}
print(json.dumps(data,indent=4))
response = requests.post(save_apk_info_api, json=data,timeout=timeout_time)
result = response.json()
if result['status'] == 'ok':
logger.info("========== send_sucess_notice ========== ")
(1)、执行打包
iOS 打包使用的是 fastlane 构建打包,相比原生 xcodebuild 命令可扩展能力比较强、需要编写 fastlane 脚本.
fastlane 文档
https://docs.fastlane.tools
fastlane 脚本如下
打包命令如下
执行 fastlane 命令后,会自动执行 pod update、xcodebuild 操作
fastlane adhoc
(2)、保存构建信息
这里和 Android 基本差不多,只不过 iOS 额外存了 dsym 文件,方便发生 crash 的时候还原堆栈信息.
在构建完 app 后,可以获取本次代码的变更日志,方便测试同学有效测试变更内容.
实现代码变更日志功能,通过本次构建代码的 commit id 查询变更信息.
def create_diff_report(apk_id,platform,commit_id,branch):
"""
生成diff报告
:return:
"""
gl = gitlab.Gitlab('http://gitlab.xxxx.com', private_token=gitlab_access_token)
if platform == 'Android':
projects = gl.projects.get(android_project_id)
elif platform == 'iOS':
projects = gl.projects.get(ios_project_id)
else:
raise Exception
commits = projects.commits.list()
commit = projects.commits.get(commit_id)
CURRENT_BRANCH = branch
CURRENT_AUTHOR = commit.author_name
CURRENT_MESSAGE = commit.message
CURRENT_DATE = commit.committed_date
CURRENT_EMAIL = commit.committer_email
diff_list = commit.diff()
diff_str = 'diff --git a\/ '
new_diff = ''
for i in diff_list:
diff = i['diff']
new_path = i['new_path']
new_diff = new_diff + "{} {}".format(diff_str,new_path) + '\\n' + diff.replace('\n',"\\n' + '") + '\\n'
new_diff = "'{}'".format(new_diff)
base_path = os.path.dirname(os.path.abspath(__file__))
try:
template_file = base_path + u"/temp/code_diff_temp.html"
with open(template_file) as f:
template_str = f.read()
template = Template(template_str)
except Exception as e:
print(e)
out = template.render({"CURRENT_BRANCH": CURRENT_BRANCH,"CURRENT_AUTHOR": CURRENT_AUTHOR,
"CURRENT_MESSAGE": CURRENT_MESSAGE,"CURRENT_DATE": CURRENT_DATE,
"CURRENT_EMAIL": CURRENT_EMAIL,
"DIFF": new_diff})
report_file_name = time.strftime("%Y%m%d%H%M%S") + "-diff-results.html"
dest_file = base_path + u"/diff/" + report_file_name
try:
with open(dest_file, 'w') as f:
f.write(out)
except Exception as e:
print(e)
finally:
oss_path = upload_report(dest_file)
params = {}
params['apk_id'] = apk_id
params['commit_branch'] = branch
params['commit_author'] = CURRENT_AUTHOR
params['commit_id'] = commit_id
params['commit_date'] = CURRENT_DATE
params['commit_message'] = CURRENT_MESSAGE
params['diff_report_path'] = oss_path
save_diff(params)
return oss_path
我们在企业微信专门创建了一个叫 App 测试包通知群,有新构建测试包会给这个群发通知.
我们想通过围绕测试包开发一个平台,缩短找测试包、下载测试包时间.
使用 vue+flask 技术栈开发的,相对比较容易上手.
1.0 版本仅仅给测试通过提供一个可以下载测试包的网页地址.
在 1.0 升级到 2.0 版本中,我们想把内部 App 应用商城开发成一个开发平台、提供给整个产研团队使用、不仅是 QA 内部使用.
主要增加了几个功能:
(1)、Android 渠道包管理
(2)、批量构建渠道包
(3)、历史包存放
(4)、打通热修复平台
(5)、dsym 查询下载
(6)、提供外部查询 app 接口
本次介绍客户端打包体系,是一个在公司内部不断摸索的过程.了解测试同学的痛点、打通现有自动化体系、提高研发测试效率.