Jenkins搭建Facebook,Android,IOS自动化打包

从0-1搭建Jenkins,实现Facebook,Android,IOS自动化打包

前言

文章仅是一个初学者的记录,欢迎各位佬交流。
目前项目测试包需要发布Web-mobile,正式包需要发布Facebook Instant Game,Android和IOS。

最终



目前功能实现

  • 项目构建
  • 生成产物
    • Android ( AAB & APK )
    • IOS ( IPA )
  • 产物归档
    • Android ( AAB & APK )
    • IOS ( IPA )
    • Facebook ( Zip )
    • Web-mobile ( Zip )
  • 安装
    • Android
  • 发布
    • Android – ftp服务器
    • IOS – Appstore
    • Facebook – facebook instant game 后台
    • Web-mobile – ftp服务器
  • 通知
    • 飞书

环境,脚本,Cocos版本

Mac,Shell,CocosCreator3.8

安装与配置Jenkins

  • 通过Homebrew安装 安装Jenkins

    brew install jenkins-lts
    
  • 配置

    • 启动配置(在/opt/homebrew/Cellar/jenkins-lts/2.462.1/homebrew.mxcl.jenkins-lts.plist 文件中)

      1. 因为要用本地仓库做测试,需要添加
        -Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true

      2. 因为要局域网访问,需要修改
        httpListenAddress 改成0.0.0.0
        就可以通过http://本机ip地址:端口访问

    • Jenkins系统配置

      1. URL(Jenkins-System-Jenkins URL)

      2. 配置Git Credentials 使用 credentials

      3. 配置环境变量(Jenkins-System-Environment Variables)

        ANDROID_HOME(CocosCreator打包需要)
        CocosCreator(命令行调用CocosCreator打包)
        HOME_BREW(命令行第三方库管理)
        PATH+RUBY+GEM(IOS构建)
        PATH+NPM(项目需要用到的npm管理第三方库,如果没有,则可以忽略)

    • 安装插件
      - Parameter Separator Plugin (parameter显示优化)
      - ThinBackup (备份Jenkins)
      - Git Parameter Plug-In (允许按Tag惯例Pipeline)
      - Pipeline Utility Steps (扩展steps的功能,如zip压缩,findFiles查找文件)

新建项目

  • 配置
    • Branch Source(数据源,配置远程Git地址/本地Git目录)
      1. Behaviours
        Discover tags(Branch就可以按照分支或tag导出)
        Filter by name(通过名字过滤)
      2. Property strategy
        Suppress automatic SCM triggering (禁止git更新后,自动触发Build)
    • Build Configuration
      Script Path (配置Jenkinsfile在仓库的相对路径)

扫描仓库分支(或Tag)的Jenkinsfile。Scan Multibranch Pipeline Now

编写Jenkinsfile

  • vscode
    插件:
    jenkins-pipeline-linter-connector 检测Jenkinsfile是否正确
    Jenkins Doc 代码补全

  • 语法
    有2种方式,Declarative Pipeline 和 Scripted Pipeline,这里用Declarative Pipeline

        //pipeline开始
        pipeline {
            //agent 定义代理节点
            agent any
            //parameters 定义参数,在构建面板显示并应用在构建中
            parameters {
                ...  
            }
            //stages 所有的阶段 在pipeline下,也可以在stage下
            stages {
                //stage 在界面显示出一个节点阶段
                stage('STAGE1') {
                    //when 条件判断,是否执行该阶段
                    when {
                        ...
                    }
                    //steps 阶段执行的步骤
                    steps {
                        //echo 打印xxx
                        echo "..."
                        //script 执行groovy代码块
                        script {
                        ...
                        }
                        //sh 用命令行执行文件
                        sh "..."
                        //input 获取用户输入才继续进行下面步骤
                        input ...
                    }
                    //post 后处理阶段
                    post {
                        //success 成功后的处理
                        success {
                        ...
                        }
                        ...
                    }
                }
                stage('STAGE2') {
                    //stage下可以用parallel内嵌多个stage,使得他们并发执行
                    parallel {
                        stage('SUB STAGE1') {
                        ...
                        }
                        stage('SUB STAGE2') {
                        ...
                        }
                        ...
                    }
                }
            }

源码

  • 参数

    • 公共参数
      JENKIN_BUILD_PLATFORM 选择构建平台
      JENKIN_AUTO_INSTALL 是否自动安装
      JENKIN_AUTO_DEPLOY 是否自动发布

    • 构建平台参数

      • Android
        JENKIN_ANDROID_DEBUG_FLAG 是否构建debug包
        JENKIN_ANDROID_VERSION_CODE android的Version Code
        JENKIN_ANDROID_VERSION_NAME android的Version Name
        JENKIN_ANDROID_ARTIFACT_TYPE 构建android产物类型APK/AAB

      • IOS
        JENKIN_IOS_INTERNAL_TESTING_ONLY_FLAG 是否testflight internal testing only
        JENKIN_IOS_BUNDLE_VERSION_SHORT IOS的Version Short
        JENKIN_IOS_BUNDLE_VERSION IOS的Version

      • Facebook
        JENKIN_FB_COMMENT fb发布的comment

    Jenkinsfile

         parameters {
                 //公共参数
                 choice(name:'JENKIN_BUILD_PLATFORM', choices: ['FB', 'WEB-MOBILE', 'ANDROID', 'IOS'], description: '选择构建平台')
                 booleanParam(name:'JENKIN_AUTO_INSTALL', defaultValue: false, description: '是否自动安装')
                 booleanParam(name:'JENKIN_AUTO_DEPLOY', defaultValue: false, description: '是否自动发布')
                 
                 //FB参数
                 string(name:'JENKIN_FB_COMMENT', defaultValue: 'no comment', description: 'fb发布的comment')
    
                 //Android参数
                 booleanParam(name:'JENKIN_ANDROID_DEBUG_FLAG', defaultValue: false, description: '是否构建android debug包')
                 string(name:'JENKIN_ANDROID_VERSION_CODE', defaultValue: '3', description: 'android的Version Code')
                 string(name:'JENKIN_ANDROID_VERSION_NAME', defaultValue: '1.0.7.2', description: 'android的Version Name')
                 choice(name:'JENKIN_ANDROID_ARTIFACT_TYPE',choices: ['APK', 'AAB'], description: '构建android产物类型')
    
                 //IOS参数
                 booleanParam(name:'JENKIN_IOS_INTERNAL_TESTING_ONLY_FLAG', defaultValue: false, description: '是否testflight internal testing only')
                 string(name:'JENKIN_IOS_BUNDLE_VERSION_SHORT', defaultValue: '1.0.0', description: 'IOS的Version Short')
                 string(name:'JENKIN_IOS_BUNDLE_VERSION', defaultValue: '1.0.0.0', description: 'IOS的Version')
             }
    
  • 流程

    • Print Information 打印参数

    • Build 构建

      • 构建Cocos项目
        npm install
        CocosCreator命令行构建 参考

        Jenkinsfile

                    steps {
                        echo 'Building FB..'
                        sh "$WORKSPACE/jenkins/common-build-cocos.sh"
                   }
        

        common-build-cocos.sh

                    # 安装项目引用node库
                    npm cache clean --force
                    npm i
                    npm run install:core_yz
        
                    # Cocos命令行构建
                    CocosCreator \
                    --project \
                    --build \
                    "configPath=指定对应平台Build Config;"
        
                    # 36是构建成功
                    if [ $? -eq 36 ]; then
                        exit 0
                    else
                        exit 1
                    fi
        
      • 构建产物

        • Android 参考

          Jenkinsfile

                      steps {
                          echo 'Building ANDROID AAB..'
                          //构建项目
                          sh "$WORKSPACE/jenkins/common-build-cocos.sh"
                          //打包aab
                          sh "$WORKSPACE/jenkins/android/build_aab.sh"
                      }
          

          build_aab.sh

                        #ANDROID_VERSION_CODE,ANDROID_VERSION_NAME就是
                        gradlew \
                        --project-dir \
                        -PversionCode=$ANDROID_VERSION_CODE \
                        -PversionName=$ANDROID_VERSION_NAME \
                        clean :app:bundleGpDebug
          
        • IOS

          • IOS目前用Cocoapods管理,类似Android的Gradle 参考

          • xcodebuild 导出ipa 参考

          Jenkinsfile

          略,参考Android
          

          build_ipa.sh

                #执行pod install
                echo "==========开始pod install=========="
                export LANG=en_US.UTF-8
                pod install --repo-update
                echo "==========结束pod install=========="
                
                #执行xcode clean
                echo "==========开始项目清理=========="
                xcodebuild clean \
                -workspace $XCODEBUILD_PARAM_WORKSPACE_FILE \
                -scheme $XCODEBUILD_PARAM_SCHEME_NAME \
                -configuration $XCODEBUILD_PARAM_CONFIGURATION_TYPE \
                -destination $XCODEBUILD_PARAM_DESTINATION \
                | xcpretty
                echo "==========结束项目清理=========="
          
                #执行对plist版本号的修改 使用/usr/libexec/PlistBuddy
                echo "==========开始修改info.plist=========="
                /usr/libexec/PlistBuddy -c "set CFBundleShortVersionString $IOS_BUNDLE_VERSION_SHORT" $PROJECT_INFO_PLIST_FILE
                /usr/libexec/PlistBuddy -c "set CFBundleVersion $IOS_BUNDLE_VERSION" $PROJECT_INFO_PLIST_FILE
                echo "==========结束修改info.plist=========="
          
                #执行xcode archive
                echo "==========开始archive=========="
                xcodebuild archive \
                    -workspace $XCODEBUILD_PARAM_WORKSPACE_FILE \
                    -scheme $XCODEBUILD_PARAM_SCHEME_NAME \
                    -sdk $XCODEBUILD_PARAM_SDK \
                    -configuration $XCODEBUILD_PARAM_CONFIGURATION_TYPE \
                    -destination $XCODEBUILD_PARAM_DESTINATION \
                    -archivePath $XCODEBUILD_PARAM_ARCHIVE_OUTPUT_FILE \
                    | xcpretty
                echo "==========结束archive=========="
          
                #执行xcode 导出ipa
                echo "==========开始导出ipa=========="
                xcodebuild -exportArchive \
                    -allowProvisioningUpdates \
                    -archivePath $XCODEBUILD_PARAM_ARCHIVE_OUTPUT_FILE \
                    -exportPath $XCODEBUILD_PARAM_IPA_OUTPUT_DIR \
                    -exportOptionsPlist $EXPORT_OPTIONS_PLIST_FILE \
                    | xcpretty
                echo "==========结束导出ipa=========="
          
        1. 新版Cocoapods要求Terminal是UTF-8 encoding。如果报错就在前面加上

                          export LANG=en_US.UTF-8
          
        2. xcpretty 参考 可以美化输出的日志

        3. 因为要指定版本号,就要修改info.plist里参数,使用PlistBuddy 参考

      • Facebook 跳过

      • Web-mobile 跳过

    • 产物归档(做备份)参考

      • Android

        Jenkinsfile

         post {
                  success {
                      //产物归档
                      echo 'Save Artifacts'
                      archiveArtifacts artifacts: '**/build/android/proj/build/app/outputs/bundle/**/*.aab', fingerprint: true
                  }
              }
        
      • IOS
        略,参考Android

      • Web-mobile

        Jenkinsfile

          post {
                      success {
                      //zip压缩
                      zip archive: false, zipFile: "./build/web-mobile/archive-web-mobile-$env.BUILD_TAG-${getCurrentTime()}.zip", dir: './build/web-mobile'
                      
                      //存储产物
                      echo 'Zip Artifacts'
                      archiveArtifacts artifacts: '**/build/web-mobile/archive-web-mobile-*.zip', fingerprint: true
                      }
                  }
        
      • Facebook
        略,参考Android

    • Install 安装

      • Android

        Jenkinsfile

                    steps {
                        echo 'Installing ANDROID..'
                        sh "$WORKSPACE/jenkins/android/install.sh"
                    }
        

        install.sh

                    # 安装apk
                    if [ "$ANDROID_DEBUG_FLAG" = true ]; then
                        echo "==========开始安装Debug=========="
                        $ANDROID_PROJ_DIR/gradlew --project-dir $ANDROID_PROJ_DIR installGpDebug
                        echo "==========结束安装Debug=========="
                    else
                        echo "==========开始安装Release=========="
                        $ANDROID_PROJ_DIR/gradlew --project-dir $ANDROID_PROJ_DIR installGpRelease
                        echo "==========结束安装Release=========="
                    fi
        
    • Deploy 发布

      • 发布

        • Android

          Jenkinsfile

                   steps {
                       echo 'Deploying Android....'
                       script {
                           //查找产物文件名和路径
                           def artifact_name = ""
                           def artifact_file_path = ""
                           if (params.JENKIN_ANDROID_ARTIFACT_TYPE == 'APK') {
                               //apk
                               def files = findFiles(glob: "**/build/android/proj/build/app/outputs/apk/**/*.apk")
                               if (files[0] != null) {
                               artifact_name = files[0].name
                               artifact_file_path = files[0].path
                               }
                           }else {
                               //aab
                               def files = findFiles(glob: "**/build/android/proj/build/app/outputs/bundle/**/*.aab")
                               if (files[0] != null) {
                               artifact_name = files[0].name
                               artifact_file_path = files[0].path
                               }
                           }
                           if(artifact_name && artifact_file_path){
                               def remote_dir_path = "/artifacts/blokus-king/android"
          
                               //发布ftp
                               sh "$WORKSPACE/jenkins/android/deploy.sh ${artifact_file_path} ${remote_dir_path}" 
                               
                               //生成远程下载链接
                               ANDROID_ARTIFACT_REMOTE_URL="$(ftp_server_url)/${remote_dir_path}/${artifact_name}"
          
                               echo "Remote Url : ${ANDROID_ARTIFACT_REMOTE_URL}"
                           }
                       }
                   }
          

          deploy.sh

                   #第一个参数传入文件相对路径
                   RELATIVE_FILE=$1
                   #第二个参数传入上传文件夹
                   FTP_REMOTE_DIR=$2
          
                   # 上传Ftp
                   echo "==========开始上传FTP=========="
                   lftp -u "$USER_NAME","$PASSWORD" "$FTP_SERVER" <<EOF
                   cd "$FTP_REMOTE_DIR"
                   put "$PROJECT_DIR/$RELATIVE_FILE"
                   bye
                   EOF
                   echo "==========结束上传FTP=========="
          
        • IOS 参考

          Jenkinsfile

                  steps {
                      echo 'Deploying IOS....'
                      sh "$WORKSPACE/jenkins/ios/deploy.sh"
                  }
          

          deploy.sh

                   #验证ipa
                   echo "==========开始验证ipa=========="
                   xcrun altool \
                       --verbose \
                       --show-progress \
                       --validate-app \
                       -f $ALTOOL_PARAM_IPA_OUTPUT_FILE \
                       -t ios \
                       -u "$ALTOOL_PARAM_USER" \
                       -p "$ALTOOL_PARAM_PASSWORD" \
                       --output-format xml
          
                   result=$(echo $?)
                   echo "==========结束验证ipa 结果:$result=========="
          
                   if [ $result -ne 0 ]; then
                       echo "FAIL"
                       echo '======DONE======'
                       exit 1
                   fi
          
          
                   #上传appstore
                   echo "==========开始上传appstore=========="
                   xcrun altool \
                       --verbose \
                       --show-progress \
                       --upload-package $ALTOOL_PARAM_IPA_OUTPUT_FILE \
                       -t ios \
                       --apple-id $ALTOOL_PARAM_APPLE_ID \
                       --team-id $ALTOOL_PARAM_TEAM_ID \
                       --bundle-id $ALTOOL_PARAM_BUNDLE_ID \
                       --bundle-short-version-string $IOS_BUNDLE_VERSION_SHORT \
                       --bundle-version $IOS_BUNDLE_VERSION \
                       -u "$ALTOOL_PARAM_USER" \
                       -p "$ALTOOL_PARAM_PASSWORD" \
                       --output-format xml
          
                   result=$(echo $?)
                   echo "==========结束上传appstore 结果:$result=========="
          
                   if [ $result -ne 0 ]; then
                       echo "FAIL"
                       echo '======DONE======'
                       exit 1
                   fi
          
          
                   echo '======DONE======'
                   exit 0
          
        • Facebook 参考

          Jenkinsfile

                      略,参考IOS
          

          deploy.sh

                      # 获取 access_token
                      echo '======开始获取ACCESS_TOKEN======'
                      resp=$(curl -s -X GET "https://graph.facebook.com/oauth/access_token?client_id=$APP_ID&client_secret=$APP_SECRET&grant_type=client_credentials")
                      echo "======结束获取ACCESS_TOKEN 结果:$resp======"
          
                      # 提取 access_token 的值(假设返回的格式是 access_token:your_token_here)
                      access_token=$(echo $resp | jq -r '.access_token')
          
                      echo $access_token
          
                      # 开始上传
                      echo '======开始上传======'
                      resp=$(curl -X POST https://graph-video.facebook.com/$APP_ID/assets \
                      -F 'access_token='$access_token \
                      -F 'type=BUNDLE' \
                      -F "asset=@$PROJECT_DIR/build/fb-instant-games/blokusking.zip" \
                      -F 'comment='$COMMENT)
          
                      result=$(echo $resp | jq -r '.success')
          
                      echo "======结束上传 结果:$result======"
          
          
                      if [ "$result" = true ]; then
                          echo '======DONE======'
                          exit 0
                      else
                          error=$(echo $resp | jq -r '.error')
                          echo $error
                          echo '======DONE======'
                          exit 1
                      fi
          
        • Web-mobile

             略,参考Android
          
      • 发送通知

        • 飞书机器人 参考

          Jenkinsfile

                    post {
                        success {
                            //用户决定是否发送
                            input message:'是否发送飞书通知', ok:'发送'
                            echo "发送"
                            script {
                                def webhookUrl = "$(feishu_server_url)"
                                //获取Git最新一次提交的信息
                                def gitLog = sh(script: 'git log -1 --pretty=oneline ${GIT_COMMIT}', returnStdout: true).trim()
                                def title = "${env.BRANCH_NAME} ${params.JENKIN_BUILD_PLATFORM}发布"
                                def downloadLink = ""
          
                                if (params.JENKIN_BUILD_PLATFORM == 'ANDROID') {
                                    //Android才是生成下载链接
                                    downloadLink = ANDROID_ARTIFACT_REMOTE_URL
                                }
          
                                def json = """
                                {
                                    "msg_type":"text",
                                    "content":{
                                        "title":"${title}",
                                        "text":"${gitLog}",
                                        "archive_url":"${downloadLink}"
                                    }
                                }
                                """
                                sh "curl -X POST -H 'Content-Type: application/json' -d '${json}' ${webhookUrl}"
                            }
                        }
                    }
          

完整的Jenkinsfile

Jenkinsfile.zip (2.5 KB)

呼,最后吐槽一下,不知道是否是我操作问题,明明在vscode预览没问题的Markdown代码,粘贴过来格式错乱了,要重新排版,有大佬可以解答一下吗?

28赞

Mark留名!!!

markmark

进来学习了,

mark mark

干货。给楼主点赞

jenkins相关,有几点分享一下:

1.在windows运行jenkins服务,要注意运行服务的用户,是SYSTEM还是自己的管理员账户。
如果有一些命令在windows本地能用但jenkins调用失败的情况下。首先考虑的就是切换jenkins服务的账户,从SYSTEM切换到自己的管理员账户,切换账户的时候需要拷贝用户目录下的配置文件,否则会让你重新配置jenkins。其次就是考虑环境变量的设置,jenkins的环境变量和实际用户的不一定一样,特别是PATH。

2.一般我在做打包系统的时候,大部分步骤都会先用一个脚本封装,例如python脚本。jenkins只用于传参以及一些外部交互(svn,rsync等)。
这样做有几个好处,

  • 可以先建立本地的打包系统脚本,再以本地打包系统脚本为基础,来配置jenkins。这样的话本机和打包机的打包都可用。
  • jenkins 上使用的shell 脚本功能上没有python等脚本强大,例如很多处理库和三方库都是没有的,所以用封装脚本会比较好。
  • 跨平台,python 脚本在windows和macos上都可以运行,如果是shell的话,要写2套适配不同系统。

3.jenkins 有2种工程类型,freestyle 和 pipeline。 lz这里没仔细写,freestyle不需要写jenkinsfile,直接使用网页配置。
我主要是建立了封装脚本,一般快速配置用freestyle就可以了。正式一点的用pipeline(就是jenkinsfile),比较好归档。新人可以先试试freestyle,把打包环境在freestyle上调通以后,再走pipeline的jenkinsfile,传到svn或者git上。

5赞

markmark,干货

markmark

mark~

mark mark

mark!!

干货慢慢 Mark!!

收藏了。。。。。。

好东西,感谢分享

mark.