[凡文]Docker+Jenkins+Gradle+GitLab在Linux服务端自动化构建Android包

概述

在linux服务器上安装docker和jenkins,项目仓库在搭建的gitlab私服上,然后在windows或者mac上编写项目,最后push代码到gitlab指定分支时,触发jenkins去gitlab上拉取项目,进行构建。这里主要记录一下配置和构建的过程,而不是怎么在linux中安装docker和jenkins。

jenkins配置

在安装好的jenkins中,创建一个新任务,名称一般和项目名称一致。然后选中pipeline,点击“确定”,就创建了一个新任务。 创建新任务后,会进入配置页面:

  1. General:GitLab Repository Name栏目,输入项目组名+项目名,如clients/android_client
  2. Build Triggers:选择Build when a change is pushed to GitLab,意思是当push代码到指定的分支时,就触发构建。Allowed branches:允许触发构建的分支,选择的是Filter branches by name,然后填写允许触发构建的分支名称,必须是项目里存在的分支名称。
  3. Advanced Project Options:不用管
  4. Pipeline:选择pipeline script frm SCM(source code manage);SCM(源代码管理)选择Git,Repository URL填写项目所在仓库地址,是https而不是ssh;Credentials填写仓库的账号密码;Branches to build填写需要构建的分支名称; Script Path,是jenkins脚本的路径,脚本放在项目下,如buildsystem/ci/jenkins/Jenkinsfile,是一个相对于项目的相对路径。

最后点击保存,这样jenkins里就配置好了。这里配置了去哪个仓库地址拉取源代码,但是还得有人告诉你什么时间该拉源代码进行构建,这就需要在gitlab里配置。

gitlab配置

这里主要配置一个webhook,可以简单理解成一个通知器,当项目发生某些事件时,就通知jenkins,然后jenkins再根据配置的事件类型和传来的事件类型是否一致来决定是否需要拉取源代码进行构建。比如,我们在jenkins里配置了push到master就会拉取源代码进行构建,那么如果通知过来的是push到develop分支的事件,jenkins就不会拉取源代码进行构建,如果通知过来的是push到master分支的事件,jenkins就会拉取源代码进行构建。

你的账号必须要是这个项目的管理者,不然是没有权限进行配置webhook的。点项目,在左侧栏里找到Settings,选择integrations进入到webhook的配置页面:

  1. URL:这个是在jenkins里创建的任务的地址,紧跟在Build Triggers的Build when a change is pushed to GitLab后面,当事件发生时,会向这个地址发送事件(是一个post请求)。
  2. Secret Token:这个是token认证,验证是不是合法请求;在jenkins配置中的Build Triggers->Build when a change is pushed to GitLab->高级->Secret token,点击Generate会自动生成一个token。
  3. Trigger:配置哪些事件会通知Jenkins,根据需要,这里选择了Push events。
  4. 点Add webhook保存配置

至此,gitlab里也配置好了,现在push源代码到指定的分支话,gitlab会通知jenkins,jenkins会执行构建。但是用什么来构建呢?就是用在jenkins的Pipeline里配置的script path脚本来执行构建。它指向的是一份Jenkinsfile脚本。

Jenkinsfile脚本

两个jenkins基础概念node和stage。node是一个节点,可以有多个节点,一般执行耗时操作时才需要另开一个节点;stage是一个阶段,每个阶段干一件具体的事情;可以一个node下有一个或多个stage,也可以一个stage下有一个或多个node。没有特别需要一般都是一个node下多个stage。

当一个正确的事件触发时,jenkins会执行这份Jenkinsfile脚本。去配置的gitlab仓库中拉取源代码,拉取来时默认是master分支,这个都知道。接下来的实现就需要在Jenkinsfile脚本中来做:

  1. 构建一个docker镜像,就是一个Android环境,因为项目需要在Android环境下才能够执行。可以在Docker Hub上找一个Android基镜像文件,然后自己的Dockerfile继承这个基镜像(FROM beevelop/android:latest)。Dockerfile示例如下:
FROM beevelop/android:latest
MAINTAINER KuickDeal "xiaoge@kuick.cn"
#安装expect
RUN apt-get update && apt-get install -y --force-yes expect
# 设置环境变量
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
# 创建目录
RUN mkdir -p /opt/workspace
# 拷贝所有文件到目录下
COPY . /opt/workspace
# 切换目录
WORKDIR /opt/workspace
# 配置环境变量
ENV PATH ${PATH}:/opt/workspace/buildsystem/ci/tools/
# 更新sdk,要和项目的编译版本一致
RUN echo y | android update sdk --all --no-ui --filter build-tools-27.0.3,extra-android-m2repository,android-27
# Cleaning
RUN apt-get clean

复制代码

BuildPushDocker.sh(构建、推送docker镜像的shell脚本):使用docker build -t ${image_name} -f ${Dockerfile_path} ${workspace}来构建镜像。构建好后用docker push ${image_name}命令把镜像push到公共的仓库,以便在不同的终端可以使用这个镜像。${image_name}是由在Docker Hub注册的用户名/tag组成的,例如注册的用户名为test.docker,tag为1.5,则${image_name}为test.docker/1.5。至此已经构建好一个Android镜像,并且推到了公共仓库。

  1. PullDocker.sh(拉取docker镜像的shell脚本):通过docker pull ${image_name}拉取镜像,因为不需要在每次构建Android包的时候都构建一次docker镜像,所以第1步一般情况下只执行一次,只需要去公共仓库拉取镜像就可以。在项目编译版本改变时,需要重新构建一个安装了对应AndroidSDK版本的镜像。

  2. AssembleApk.sh(构建apk的shell脚本):

docker run -v ${workspace}:/opt/workspace ${container_name} ./gradlew build --info
复制代码

在物理机的{workspace}目录和docker的/opt/workspace目录做一个映射,创建一个新的docker容器{container_name},在容器中执行命令./gradlew build 来构建apk。{workspace}是gradlew和gradlew.bat所在的目录,也就是项目的根目录,在物理机中,但是没有Android环境。所以需要把这个目录和有Android环境的docker镜像的目录/opt/workspace做一个映射。至此,debug包和release包就构建出来了。

现在都希望能更好的保护公司的源代码,所以会对apk做一些加固。市面上加固平台有很多,有阿里安全、腾讯加固宝、爱加密、梆梆加固、360加固等。两年前用的是梆梆加固,但是发现加固后在小米note手机上会出现崩溃,所以果断放弃了。后来一直用爱加密到现在(2018.6),手动上传到平台去加密。后面需要做自动化,所以咨询了上面各家加固平台是否有自动化脚本加固。梆梆加固、腾讯加固宝没有,放弃。阿里安全、爱加密有,但是收费,费用还不便宜,爱加密单买自动化加固就得2w一年,所以也暂时不考虑。最后只有360加固有自动化脚本加固,还免费,所以采纳了它。

  1. 360加固,需要他们的加固工具,所以需要先下载加固工具。这一步涉及到多个脚本,JiaGu360Exist.sh、DownloadUnzip360.sh、JiaGu360.sh。第一个脚本是检测有没有下载360的加固工具,如果没有下载,则用第二个脚本下载并解压加固工具,最后用第三个脚本对apk进行加固。根据有没有目录来判断有没有下载加固工具,比如把加固工具解压到./jg目录下,那只需要判断./jg这个目录是否存在就可以了:
   for file in $(ls ${curPath})
   do
       zipFile=`echo ${file} | grep 'jg'`
       if [[ ${zipFile} = "jg" ]]
       then
           echo ${zipFile}
           break
   fi
   done
复制代码

如果不存在,则说明没有下载加固工具,下载的命令是直接写在了Jenkinsfile里,也可以写成一个shell脚本,Jenkinsfile是用groovy语言编写:

def download = sh(script: "wget -O jiagubao.zip -P ./ http://down.360safe.com/360Jiagu/360jiagubao_linux_64.zip", returnStdout: true).toString().trim()
echo "download=${download}"
// 解压到./jg目录下
sh "sudo unzip -d ./jg ./jiagubao.zip"
// 删除加固工具压缩包
sh "sudo rm ./jiagubao.zip"
复制代码

最后是加固:

//登录360开放平台
loginR=`sudo ${base} -login ${name} ${pw2}`
echo "loginR=${loginR}"
//导入签名信息,加固后自动签名
keystoreR=`sudo ${base} -importsign ${ccPath} ${pw} ${alias} ${pw}`
echo "keystoreR=${keystoreR}"
//修改需要的加固服务
updateR=`sudo ${base} -config -x86`
echo "updateR=${updateR}"

chmod +x ${curScriptDir}/FindDebugApk.sh
chmod +x ${curScriptDir}/FindReleaseApk.sh
//创建加固后的debug和release包保存路径
sudo mkdir ${debugEnhancePath} ${releaseEnhancePath}
//找到需要加固的debug包
debugApk=`${curScriptDir}/FindDebugApk.sh`
echo "debugApk=${debugApk}"
//加固debug包
enhanceDebugR=`sudo ${base} -jiagu ${debugPath}${debugApk} ${debugEnhancePath} -autosign`
echo "enhanceDebugR=${enhanceDebugR}"
找到需要加固的release包
releaseApk=`${curScriptDir}/FindReleaseApk.sh`
echo "releaseApk=${releaseApk}"
//加固release包
enhanceReleaseR=`sudo ${base} -jiagu ${releasePath}${releaseApk} ${releaseEnhancePath} -autosign`
echo "enhanceReleaseR=${enhanceReleaseR}"
复制代码

至此,加固后的apk就出来了,但是现在apk是在服务器上的,测试组或者其他人员都无法拿到这个apk,所以需要一个公共的地址,来存放这个apk。可以是自己的服务器下载地址,也可以是其他开放的下载地址。这里选择的是蒲公英

  1. UploadDebugApk.sh和UploadReleaseApk.sh,上传加固包到蒲公英的脚本。 上传命令
uploadResult=`curl -F "file=@${apk_path}" -F "uKey=${userKey}" -F "_api_key=${apiKey}" "http://www.pgyer.com/apiv1/app/upload"`
复制代码

上传完后,我们需要做最后一步,就是通过邮件通知相关人员,比如测试人员。把蒲公英下载地址通过邮件发送给他们。

  1. 发送邮件,直接在Jenkinsfile里定义函数,jenkins自带发邮件功能:
// 发邮件
def sendMail(String to,String title, String body){
    mail([
            bcc: '',
            body: body,
            cc: '',
            from: '',
            replyTo: '',
            subject: title,
            to: to
         ]);
}
复制代码

到此,整个自动化构建、打包、加固apk就完成了。 Jenkinsfile的结构如下:

node('node_name'){

    stage("删除build目录"){
        //切换到jenkins里配置的需要构建的分支,默认拉取的是master分支
        checkout scm
        //删除build目录,避免资源更换后,缓存未更新,导致构建错误
        sh "sudo rm -rf app/build"
    }

    stage("准备镜像") {
    // 不用每次都构建镜像
//        sh "buildDocker.sh"
        sh "pullDocker.sh"
    }

    stage("构建包") {
        try{
            sh "assembleAPK.sh"
        }catch(Exception e){
            sendMail(mySelfEmail, 'Android安装包构建失败', e.message)
        }
    }

    stage("加密包"){
        try{
            // 检测是否下载了360加固工具
            def exist360 = sh(script: "JiaGu360Exist.sh", returnStdout: true).toString().trim()
            if ("jg" != exist360){
                // 查看wget下载工具版本,未安装会抛异常
                def result = sh(script: "wget -V", returnStdout: true).toString().trim()
                // 下载360加固工具,重命名为jiagubao.zip,下载到当前目录./
                def download = sh(script: "wget -O jiagubao.zip -P ./ http://down.360safe.com/360Jiagu/360jiagubao_linux_64.zip", returnStdout: true).toString().trim()
                // 解压到./jg目录下
                sh "sudo unzip -d ./jg ./jiagubao.zip"
                // 删除加固工具压缩包
                sh "sudo rm ./jiagubao.zip"
            }
            sh "JiaGu360.sh"
        }catch(Exception e){
            sendMail(mySelfEmail, '360加密包失败', e.message)
        }
    }
    
    stage("确认上传测试包"){
        input message: "确认上传测试包?"
    }
    
    stage("上传测试包") {
        try{

            def code = sh(script: "uploadTestApk.sh", returnStdout: true).toString().trim()
            if(code == null || code != "0"){
                sendMail(mySelfEmail, 'Android测试包上传失败', "code=${code}")
            }else{
                //从readme文件中读取apk的更新内容
                def content = sh(script: "ReadUpgradeInfo.sh", returnStdout: true).toString().trim()
                sendMail(testGroupEmail, 'Android测试包下载地址', "https://www.pgyer.com ${content}")

            }
        }catch(e){
            sendMail(mySelfEmail, 'Android测试包上传失败', e.message)

        }
    }

    stage("确认上传正式包"){
        input message: "确认上传正式包?"
    }
    
    stage("上传正式包") {
  
    }

}
// 发邮件
def sendMail(String to,String title, String body){
    mail([
            bcc: '',
            body: body,
            cc: '',
            from: '',
            replyTo: '',
            subject: title,
            to: to
         ]);
}
复制代码

完结。

Tips:

  1. 如里出现Permission Denied,在执行脚本前用 chmod +x {shell_path}给脚本加上执行权限。
  2. 有问题,可以给xiaoge@kuick.cn发邮件,一起探讨
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值