研发效能 客户端打包体系

xinxi · 2020年10月08日 · 最后由 HelloHalo 回复于 2020年10月09日 · 3132 次阅读

背景

目前我司使用的是 jenkins 开源工具来持续集成构建客户端安装包

以下是之前构建测试 app 的流程图:

image

在构建后会把构建好的产物 (安装包) 上传到阿里云 oss 平台保存,最终在内部 App 应用商城展示构建详细信息.

jenkins 构建测试包

jenkins 搭建

我们使用的 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 权限管理

其实最开始我们是打算在 jenkins 上封一层 UI 页面,不让测试同学直接操作 jenkins,其目的是怕测试同学误删除、修改配置,影响打包.

但是发现 jenkins 自带了权限管理功能,可以开通给账号赋予只读权限,还是使用了 jenkins 直接构建.

在 Manage Roles 设置不同角色权限

image

在 Manage and Assign Roles 赋予人员权限

image

只读账号只能看到构建按钮,这样就解决了权限问题

image

构建脚本

Android

关于构建 Android 应用这块,没有使用 pipline 来构建,还是使用 shell 脚本构建,主要原因是构建前需要设置一些变量还有一些前置操作.

主要几个步骤:

(1)、根据分支 clone 代码

这里我们是全量来 clone 代码,先删除本地所有代码,之前发现过一些缓存文件导致测试包有问题.

(2)、设置环境变量

因为先需要通过 “buildSystem.gradle” 文件接受外部参数,比如 env 参数、构建模式参数.

buildSystem.gradle 是使用 groovy 语法编程的文件,在编译前会先执行这个文件.

image

(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 ==========  ")

iOS

(1)、执行打包

iOS 打包使用的是 fastlane 构建打包,相比原生 xcodebuild 命令可扩展能力比较强、需要编写 fastlane 脚本.

fastlane 文档

https://docs.fastlane.tools

fastlane 脚本如下

image

打包命令如下

执行 fastlane 命令后,会自动执行 pod update、xcodebuild 操作

fastlane adhoc

(2)、保存构建信息

这里和 Android 基本差不多,只不过 iOS 额外存了 dsym 文件,方便发生 crash 的时候还原堆栈信息.

代码变更日志

在构建完 app 后,可以获取本次代码的变更日志,方便测试同学有效测试变更内容.

image

实现代码变更日志功能,通过本次构建代码的 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 测试包通知群,有新构建测试包会给这个群发通知.

image

内部 App 应用商城

我们想通过围绕测试包开发一个平台,缩短找测试包、下载测试包时间.

使用 vue+flask 技术栈开发的,相对比较容易上手.

App 应用商城 1.0 版本

1.0 版本仅仅给测试通过提供一个可以下载测试包的网页地址.

image

App 应用商城 2.0 版本

在 1.0 升级到 2.0 版本中,我们想把内部 App 应用商城开发成一个开发平台、提供给整个产研团队使用、不仅是 QA 内部使用.

主要增加了几个功能:

(1)、Android 渠道包管理

(2)、批量构建渠道包

(3)、历史包存放

(4)、打通热修复平台

(5)、dsym 查询下载

(6)、提供外部查询 app 接口

image

image

结语

本次介绍客户端打包体系,是一个在公司内部不断摸索的过程.了解测试同学的痛点、打通现有自动化体系、提高研发测试效率.

共收到 6 条回复 时间 点赞

做的越来越不错了

看起來很厲害的樣子,請問是一開始就採用的 fastlane 還是之後遷移過去的

gyyfifafans 回复

后来移过去的

和我们公司类似,在服务器上面编译成包,然后上传到公司内部的后台服务器并记录一些信息到后台服务器 (渠道/升级也在上面),最后再推送下载链接和二维码到钉钉,ios 还做了一键发布到 appstore/testflight

图片挂了,能看看么

想做一个类似的,但是有跨部门合作,先借鉴下你这个

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册