Jenkins持续集成iOS项目

May 10, 2016


fastlane - gym 命令

export LC_ALL="en_US.UTF-8"
export PATH="$PATH:/usr/local/opt/coreutils/libexec/gnubin:/usr/local/bin:/usr/local/bin:/opt/X11/bin:/usr/local/sbin$:/usr/local/share/npm/bin:/opt/codereview/phabricator/bin:/opt/codereview/arcanist/bin"

# 项目名字
projectName="name"

# 计时
SECONDS=0

# 当前时间
now=$(date +"%Y_%m_%d_%H_%M_%S")

# 指定项目的scheme名称
scheme="${projectName}"

# 指定要打包的配置名 Release Debug
configuration="Release"

# 指定打包所使用的输出方式,目前支持app-store, package, ad-hoc, enterprise, development, 和developer-id,即xcodebuild的method参数
export_method='ad-hoc'

# 工程路径
path=${WORKSPACE}/${projectName}.xcworkspace

# 指定项目地址
workspace_path="${path}"

# 指定输出路径
output_path="${HOME}/Pictures/${JOB_NAME}/${projectName}_${now}"
# 创建文件夹
mkdir -p ${output_path}

# 指定输出归档文件地址
archive_path="${output_path}/${projectName}_${now}.xcarchive"

# 指定输出ipa名称
ipa_name="${projectName}_${now}.ipa"

# 指定输出ipa地址
ipa_path="${output_path}/${ipa_name}"

cd ${WORKSPACE}
#pod update
pod install
#pod update --no-repo-update
#pod update --verbose --no-repo-update

# 先清空前一次build
gym --workspace ${workspace_path} --scheme ${scheme} --clean --configuration ${configuration} --archive_path ${archive_path} --export_method ${export_method} --output_directory ${output_path} --output_name ${ipa_name}

# 输出总用时
echo "===Finished. Total time: ${SECONDS}s==="

xcodebuild + xcrun命令

# 工程名
APP_NAME="YourProjectName"
# 证书
CODE_SIGN_DISTRIBUTION="iPhone Distribution: Shanghai ******* Co., Ltd."
# info.plist路径
project_infoplist_path="./${APP_NAME}/Info.plist"

#取版本号
bundleShortVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${project_infoplist_path}")

#取build值
bundleVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${project_infoplist_path}")

DATE="$(date +%Y%m%d)"
IPANAME="${APP_NAME}_V${bundleShortVersion}_${DATE}.ipa"

#要上传的ipa文件路径
IPA_PATH="$HOME/${IPANAME}"
echo ${IPA_PATH}
echo "${IPA_PATH}">> text.txt

//下面2行是没有Cocopods的用法
echo "=================clean================="
xcodebuild -target "${APP_NAME}"  -configuration 'Release' clean

echo "+++++++++++++++++build+++++++++++++++++"
xcodebuild -target "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)'

//下面2行是集成有Cocopods的用法
echo "=================clean================="
xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}"  -configuration 'Release' clean

echo "+++++++++++++++++build+++++++++++++++++"
xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)'

xcrun -sdk iphoneos PackageApplication "./Release-iphoneos/${APP_NAME}.app" -o ~/"${IPANAME}"

pipeline

Build:

pipeline {
    agent {
        label 'one_label'
    }

    options {
        disableConcurrentBuilds()
    }

    parameters {
        string defaultValue: 'defaultValue', description: '''description''', name: 'parameter_key', trim: true
        
        choice choices: ['one', 'two', 'three'], description: 'description', name: 'BUILD_SCHEME'
        
        booleanParam defaultValue: false, description: 'description', name: 'IS_DELETE_WORKSPACE'
    }
    
    environment {
        APP_REPO_REMOTE = "address"
        LC_ALL = "en_US.UTF-8"
        PATH = "$PATH:/usr/local/opt/coreutils/libexec/gnubin:/usr/local/bin:/usr/local/bin:/opt/X11/bin:/usr/local/sbin\$:/usr/local/share/npm/bin:/opt/codereview/phabricator/bin:/opt/codereview/arcanist/bin"
        http_proxy = "address"
        https_proxy = "address"
    }

    stages {

        stage('oneStage') {
            steps {
                script {
                    def DISPLAY_NAME_PREFIX = ""
                    if (env.BUILD_SCHEME == "one_custom1") {
                        DISPLAY_NAME_PREFIX = "aa"
                    } else if (env.BUILD_SCHEME == "one_custom2") {
                        DISPLAY_NAME_PREFIX = "cc"
                    }
                    
                    currentBuild.displayName = "#${BUILD_NUMBER} - ${DISPLAY_NAME_PREFIX} ${CUSTOM_BUILD_NUMBER}"
                    
                    wrap([$class: 'BuildUser']) {
                        currentBuild.description = "${env.BUILD_USER}<br/>"
                    }

                    if (env.IS_DELETE_WORKSPACE == "true") {
                        deleteDir()
                    } else {
                        echo "noting"
                    }
                }

                echo sh(returnStdout: true, script: 'env')
            }
        }

        stage('twoStage') {
            steps {
                checkout(...)//查询相关文档

                dir('app') {
                    script {
                        env.COMMIT_MESSAGE = sh(returnStdout: true, script: 'git log --format=oneline -n 1 $CIRCLE_SHA1').trim()
                        env.COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
                        env.COMMIT_SHORT= sh(returnStdout: true, script: 'echo ${COMMIT:0:7}').trim()
                    }
                }
            }
        }

        stage('threeStage') {
            steps {
                sh script: '''
                pod --version
                '''

                sh script: '''
                pod repo update
                '''

                dir('app') {
                    sh script: '''
                    pod install
                    '''
                }
            }
        }

        stage('fourStage') {
            steps {
                script {
                    def APP_FILENAME_SUFFIX = ""
                    if (env.BUILD_SCHEME == "xx") {
                        change...
                    } else if (env.BUILD_SCHEME == "dd") {
                        change...
                    }

                    env.PROJECT_INFOPLIST_PATH = "..."
                    
                    env.BUNDLE_SHORT_VERSION = sh(returnStdout: true, script: '''
                    echo $(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${PROJECT_INFOPLIST_PATH}")
                    ''').trim()

                    def DATE_TIME = sh(returnStdout: true, script: '''
                    date +%Y%m%d-%H%M%S
                    ''').trim()

                    env.APP_FILENAME = "..."
                    echo "APP_FILENAME -> ${APP_FILENAME}"
                }

                withCredentials([string(credentialsId: 'credentialsId', variable: 'LOGIN_PASSWORD')]) {
                    sh script: '''
                    security unlock-keychain -p "${LOGIN_PASSWORD}" /Users/username/Library/Keychains/login.keychain
                    '''
                }
            }
        }

        stage('fiveStage') {
            steps {
                script {
                    sh script: '''
                    /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${CUSTOM_BUILD_NUMBER}" "${PROJECT_INFOPLIST_PATH}"
                    /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${BUNDLE_SHORT_VERSION}" "${SIRI_INFOPLIST_PATH}"
                    '''
                }
            }
        }

        stage('sixStage') {
            steps {
                sh script: '''
                /usr/bin/xcodebuild -version
                clang -v
                '''

                dir('app/xxx') {
                    sh script: '''
                    xcodebuild -quiet -workspace xxx.xcworkspace  -scheme ${BUILD_SCHEME} -configuration ${BUILD_CONFIGURATION} -archivePath my_build/${BUILD_SCHEME}.xcarchive archive
                    '''
                }
            }
        }

        stage('sevenStage') {
            steps {
                dir('app') {
                    sh script: '''
                    xcodebuild -quiet -exportArchive -archivePath my_build/${BUILD_SCHEME}.xcarchive -exportOptionsPlist ${EXPORT_OPTIONS_PLIST} -exportPath ./my_build/ipa

                    mv my_build/ipa/${BUILD_SCHEME}.ipa "my_build/ipa/${APP_FILENAME}.ipa"
                    '''
                }

                dir("app/my_build/${BUILD_SCHEME}.xcarchive") {
                    sh script: '''
                    zip -q -r temp.dSYMs.zip dSYMs/
                    '''
                }

                dir("app/my_build") {
                    sh script: '''
                    mv "${BUILD_SCHEME}.xcarchive/temp.dSYMs.zip" "${APP_FILENAME}.dSYMs.zip"
                    '''
                }
            }
        }

        stage('eightStage') {
            steps {
                dir("app/my_build") {
                    archiveArtifacts artifacts:"${APP_FILENAME}.dSYMs.zip", fingerprint: true
                }

                dir("app/my_build/ipa") {
                    archiveArtifacts artifacts:"${APP_FILENAME}.ipa", fingerprint: true
                }
                
                script {
                    def OUTPUT_JSON = readJSON text: '{}'
                    OUTPUT_JSON["CUSTOM_BUILD_NUMBER"] = CUSTOM_BUILD_NUMBER
                    OUTPUT_JSON["APP_IPA_NAME"] = "${APP_FILENAME}.ipa" as String
                    writeJSON(file: 'OUTPUT.json', json: OUTPUT_JSON)
                    archiveArtifacts artifacts:"OUTPUT.json", fingerprint: true
                }
            }
        }
        
    }
}

distribution:

pipeline {
    
    stages {
        stage('BuildOtherJob') {
            steps {
                script {
                    def buildResult = build job: 'job_name'
                    env.BUILD_ID = "${buildResult.getId()}"
                    env.PROJECT_URL = "${buildResult.getAbsoluteUrl()}"
                    env.RTIFACT_URL = "${PROJECT_URL}artifact/"
                }
            }
            
            post {
                success {
                    step([$class: 'CopyArtifact', filter: 'file_name'...])
                }
            }
        }
    }
    
    
    post {
        email...
    }
}

参考资料

Updating environment global variable