Jenkins Pipeline系列(三)—— 使用扩展共享库构建微信小程序项目

Jenkins Pipeline 微信小程序

手动上传不是挺方便的吗

在初期,小程序开发者1-2人时,通过Win和Mac小程序开发者工具,进行上传确实比较省时省力,但是随着小程序业务代码增多,并行开发任务增多,开发者增多, 在管理各种版本上,都需要牵扯精力,而且上传发布很可能是多个人都会进行的事情了。我们并不能保证每个上传人的环境统一、AppID每次替换都不出错误等等。随着这些问题的发生,自然而然,我们会把频繁做且流程化的事情,做成自动化。

环境说明

硬件

  • Mac Mini(OR Windows本,这里我使用的是Mac Mini)

软件

  • Jenkins(Docker):Pipeline构建平台
  • Node(v12.13.0) & Npm(6.12.0):编译工具
  • 微信开发者工具:主要依赖工具
  • Nginx(Docker):内网图片服务
  • Gitlab(Docker):代码库

关于Jenkins、Gitlab,以及Jenkins的扩展共享库,请参考下面的链接
Jenkins Pipeline系列(一)-- 如何配置扩展共享库

流程说明

Jenkins Pipeline 微信小程序 c63d3b7cb57b422d9a0e784fc60593bd.jpg
  • 编译
  • 预览(按需登录)
  • 上传(按需登录)

Pipeline实现

这里还是使用的是Jenkins的扩展共享库来实现的,在上面有共享扩展库的文章说明,请参考
在共享扩展库的Git Repo的vars目录下新建wechat_mini_program_build.groovy,内容如下:

/**
配置参数说明:
--------------------------------------------------------------
参数名称: REPO_URL
参数类型: 文本参数
默认值: ssh://git@gitlab.xxx.com/group_name/repo_name.git
描述: 项目代码仓库地址
--------------------------------------------------------------
参数名称: BUILD_BRANCH
参数类型: 字符参数
默认值: some-branch
描述: 构建分支<br>
     some-branch<br>
     develop<br>
     release<br>
     master
--------------------------------------------------------------
参数名称: BUILD_ENV
参数类型: Active Choices Parameter
Groovy Script: return["请选择","dev","test","prod"]
描述: dev开发环境<br>
     test测试环境<br>
     prod生产环境
--------------------------------------------------------------
参数名称: BUILD_TYPE
参数类型: 选型参数
选型: preview
     upload
描述: preview: 预览生成二维码<br>
     upload: 上传代码
--------------------------------------------------------------
参数名称: UPLOAD_VERSION
参数类型: 字符参数
默认值: 
描述: 上传代码版本号: 如 2.0.0<br>
     只有upload任务需要
*/
def call(Map config) {
    node('front-end') {
        properties([
            buildDiscarder(
              logRotator(
                  daysToKeepStr: '30',
                  numToKeepStr: '50'
              )
          )
        ])
        stage('清理工作区') {
            log.info '清理工作区'
            deleteDir()
        }
        stage('获取代码') {
            log.info "获取代码地址:${REPO_URL},获取代码分支: ${BUILD_BRANCH}"
            fetch_code "${REPO_URL}"
        }
        stage('编译') {
            log.info "构建:${BUILD_ENV}"
            sh "source ~/.bash_profile && npm install && npm run build:${BUILD_ENV}"
        }
        stage('替换APPID'){
            miniProgramAppId = getMiniProgramAppId("${BUILD_ENV}",config.projectName)
            log.info "替换数据:${BUILD_ENV},小程序APPID:${miniProgramAppId}"
            sh "sed -i '' 's/\"appid\":.*/\"appid\": \"${miniProgramAppId}\",/g' ${WORKSPACE}/dist/build/mp-weixin/project.config.json"
        }
        stage('预览') {
            if(BUILD_TYPE == 'preview'){
                log.info "构建分支: ${BUILD_BRANCH}"
                wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
                sh "${wechatBinPath}/cli preview --project ${WORKSPACE}/dist/build/mp-weixin -f image -o ${WORKSPACE}/${BUILD_ID}.jpg > ${WORKSPACE}/build.log"
                errlog = sh(returnStdout: true, script: "grep -i error: ${WORKSPACE}/build.log || echo").trim()
                log.info "errlog:${errlog}"
                if ("${errlog}"==""){
                    log.info '执行成功'
                    sh "cp ${WORKSPACE}/${BUILD_ID}.jpg /Users/guoguo/workspace/nginx/image"
                    buildDesc = "代码分支: ${BUILD_BRANCH}<br>构建环境: ${BUILD_ENV}<br>构建类型: ${BUILD_TYPE}<br>请使用微信扫描以下二维码进行预览: <br><img src=\"http://图片服务器地址/${BUILD_ID}.jpg\" width=\"200\" height=\"200\">"
                }else{
                    log.error '执行失败'
                    closeTool()
                    buildDesc = '微信开发者工具登录失效,请登录后再执行<br>登录工具:<a href=\"${JENKINS_URL}/job/WECHAT_LOGIN_TOOL\">'
                    sh "exit 1"
                }
            }else{
                log.info '不是预览任务,跳过'
            }
        }
        stage('上传') {
            if(BUILD_TYPE == 'upload'){
                if(BUILD_ENV == 'prod' && BUILD_BRANCH != 'master'){
                    log.error 'PROD环境只能允许master分支上传'
                    sh "exit 1"
                }
                log.info "构建分支: ${BUILD_BRANCH}"
                wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
                now = getTimestamp()
                uploadDesc = "CI 在 ${now} 提交上传"
                sh "${wechatBinPath}/cli upload --project ${WORKSPACE}/dist/build/mp-weixin -v '${UPLOAD_VERSION}' -d '${uploadDesc}' > ${WORKSPACE}/upload.log"
                errlog = sh(returnStdout: true, script: "grep -i error: ${WORKSPACE}/upload.log || echo").trim()
                log.info "errlog:${errlog}"
                if ("${errlog}"==""){
                    log.info '上传成功'
                    buildDesc = "代码分支: ${BUILD_BRANCH}<br>构建环境: ${BUILD_ENV}<br>构建类型: ${BUILD_TYPE}"
                }else{
                    log.error '上传失败'
                    closeTool()
                    buildDesc = '微信开发者工具登录失效,请登录后再执行<br>登录工具:<a href=\"${JENKINS_URL}/job/登录工具JOB\">'
                    sh "exit 1"
                }
            }else{
                log.info '不是上传任务,跳过'
            }
        }
        stage('关闭工具'){
            closeTool()
        }
        stage('通知') {
            dingding.notice("${BUILD_BRANCH}")
        }
        currentBuild.description = "${buildDesc}"
    }
}

def getMiniProgramAppId(buildEnv,projectEnglishName){
    if(projectEnglishName == '应用A'){
        if(buildEnv == 'dev'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'test'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'prod'){
            miniProgramAppId = 'xxx'
        }
    }
    if(projectEnglishName == '应用B'){
        if(buildEnv == 'dev'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'test'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'prod'){
            miniProgramAppId = 'xxx'
        }
    }
    if(projectEnglishName == '应用C'){
        if(buildEnv == 'dev'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'test'){
            miniProgramAppId = 'xxx'
        }else if(buildEnv == 'prod'){
            miniProgramAppId = 'xxx'
        }
    }
    return miniProgramAppId
}

def closeTool(){
    log.info "关闭工具"
    sh "${wechatBinPath}/cli quit"
}

def getTimestamp() {
    return new Date().format('yyyy-MM-dd HH:mm:ss')
}

在Jenkins新建JOB,类型选择Pipeline,按照上面脚本注释内的“配置参数说明”,录入参数,并在最下面,Pipeline script填写如下内容,点击保存:

@Library('jenkins-shared-libraries@master') _

node {
    script {
        wechat_mini_program_build(
            projectName:'你的项目名称'
        )
    }
}

打开任务执行,参数如下:


20201126171008.jpg

预览任务执行后的样子如下图,打包任务,基本一致,就是没有二维码而已:


20201126171901.jpg

小程序的自动化任务,都是需要有登录态的(上面的任务,如果登录失效,会提示,用登录工具先登录),这里登录的动作必须由人工完成,下面是登录工具的Pipeline,vars 下面新建wechat_mini_program_login_tool.groovy

def call() {
    node('front-end') {
        properties([
            buildDiscarder(
              logRotator(
                  daysToKeepStr: '30',
                  numToKeepStr: '50'
              )
          )
        ])
        stage('登录') {
            log.info "登录"
            imagePath = '/你的图片Nginx目录'
            wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
            sh "${wechatBinPath}/cli login -f image -o ${imagePath}/login-${BUILD_ID}.jpg > ${WORKSPACE}/run.log 2>&1 &"
            log.info "请在60秒内打开链接并扫码(请使用专用微信): http://图片服务器地址/login-${BUILD_ID}.jpg"
            sh 'sleep 60'
        }
    }
}

在Jenkins新建JOB,类型选择Pipeline,并在最下面,Pipeline script填写如下内容,点击保存:

@Library('jenkins-shared-libraries@master') _

node {
    script {
        wechat_mini_program_login_tool()
    }
}

构建过程中,点击控制台打印的 http://图片服务器地址/login-${BUILD_ID}.jpg链接,进行扫描动作

总结

已经解决问题

  • 自动化编译
  • 自动化预览
  • 自动化上传代码

TODO

  • 自动从package.json读取版本号并做自动新增
  • 自动MR / 自动Tag

Reference

微信小程序命令行说明

Jenkins Pipeline系列(一)—— 如何配置扩展共享库
Jenkins Pipeline系列(二)—— 使用扩展共享库构建Maven项目
Jenkins Pipeline系列(三)—— 使用扩展共享库构建微信小程序项目

推荐阅读更多精彩内容