文章主要介绍Jenkins主从节点配置,mac机配置slave节点。从机已经搭建android和ios编译环境为例,介绍Jenkins节点配置。
环境介绍
- 主机环境介绍:主机Jenkins运行在tomcat中。Jenkins本身安装的环境仅包括java环境和gradle环境。
# set java environment export JAVA_HOME=/usr/java export JRE_HOME=/usr/java/jre export CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin # set gradle export GRADLE_HOME=/usr/local/gradle/gradle-3.3 export PATH=$PATH:$GRADLE_HOME/bin # set tomcat export TOMCAT_HOME=/usr/local/tomcat/apache-tomcat-7.0.79 # set jenkins export JENKINS_HOME=/usr/local/tomcat/apache-tomcat-7.0.79/webapps/jenkins
- 从机环境介绍:从机mac可以不安装jenkins,只是配置好android和ios的编译环境,此处的IOS编译环境尽量是已经可以通过 xcode 编译项目。
# set android sdk export ANDROID_HOME=/Users/aorise/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools # set gradle GRADLE_HOME=/Users/aorise/.gradle/wrapper/dists/gradle-4.1-all/bzyivzo6n839fup2jbap0tjew/gradle-4.1; export GRADLE_HOME export PATH=$PATH:$GRADLE_HOME/bin # set tomcat export TOMCAT_HOME=/users/aorise/Library/ApacheTomcat export JENKINS_HOME=/users/aorise/Library/ApacheTomcat/webapps/jenkins
打开MAC电脑远程登录
MAC电脑进入 ‘系统偏好设置’ -> ‘共享’ -> ‘远程登录’ 打开远程登录。对应的账号设置可以被主节点访问后,mac 节点的 jenkins 工作区间要配置到对应的账号名下。本文中 mac 账号是 aorise ,对应配置为从节点的 jenkins 工作区间是 ‘/Users/aorise/Library/ApacheTomcat/webapps/jenkins’
主机配置
- 安装 Xcode integration:ios编译(貌似不安装也可以),本文采用 shell 脚本编译,没有采用 xcode 的工具
- 安装 description setter plugin 插件:生成二维码(同步打开 jenkins -> 系统配置 -> 全局安全配置 -> Markup Formatter -> Safe HTML)
.*qrcodeHistory\\/(\S{64}) <img src='http://www.pgyer.com/app/qrcodeHistory/\1' alt="二维码解析失败"/>
- 安装 Git Parameter Plug-In 插件:参数构建配置git参数
- 使用 Archive the artifacts 插件:存档构建输出物
platform/build/outputs/apk/sample/release/*.apk build/Education/*.ipa
节点配置
在系统管理 / 节点管理 创建新节点。
编译命令配置
记住ios工程一定要是已经可以直接通过xcode编译,也即相关的签名文件已经记录在工程文件里面。
使用凭证管理账户号密码
实际编译脚本 ‘jenins-build.sh’
#!/bin/sh set -x echo "\n" echo "================= jenkins-build start =================" #项目名字 PRODUCT="cf_rn_base" #SDK版本信息 SDK="iphoneos17.0" #ipa导出目录 IPADIR="ipaDir" echo "MAC HOME:${HOME}" echo "WORKSPACE:${WORKSPACE}" echo "GIT_BRANCH:${GIT_BRANCH}" echo "JOB_NAME:${JOB_NAME}" echo "SIGNATURE:${SIGNATURE}" # 解锁对login.keychain的访问,codesign会用到(此处为关键) set +x security unlock-keychain -p "xxxx" $HOME/data/soft/key/login.keychain set -x export YARN_HOME=/opt/homebrew/bin/ export PATH=$YARN_HOME:$PATH export PATH=/Users/cfhy/.nvm/versions/node/v18.12.0/bin/:$PATH node -v cd /Users/cfhy/data/soft/Specs git pull cd ${WORKSPACE} # npm install -g react-native-update-cli yarn gitInit yarn install cd ios export LANG=en_US.UTF-8 rm -rf Podfile.lock sed -i -e '1i\'$'\nsource \'/Users/cfhy/data/soft/Specs\'\n' Podfile sed -i -e "s|:http => 'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2'|:http => 'file:///Users/cfhy/data/soft/boost_1_76_0.tar.bz2'|g" ../node_modules/react-native/third-party-podspecs/boost.podspec #安装三方库 /opt/homebrew/bin/pod repo update --verbose #/opt/homebrew/bin/pod install #HERMES_ENGINE_TARBALL_PATH="/Users/cfhy/data/soft/hermes-runtime-darwin-v0.70.8.tar.gz" /opt/homebrew/bin/pod install --verbose HERMES_ENGINE_TARBALL_PATH="/Users/cfhy/data/soft/react-native-artifacts-0.71.11-hermes-ios-debug.tar.gz" /opt/homebrew/bin/pod install --verbose #清除 #xcodebuild clean -workspace ${WORKSPACE}/ios/${PRODUCT}.xcworkspace -scheme ${PRODUCT} -configuration ${CONFIG} #模拟器编译 #xcodebuild -workspace ${WORKSPACE}/ios/${PRODUCT}.xcworkspace -scheme ${PRODUCT} -configuration debug -sdk iphonesimulator16.2 # ../node_modules/react-native/packager/packager.sh clean #判断是否更新版本号 if [ ! -n "${MARKETING_VERSION}" ]; then echo "IS NULL" echo "未修改版本号构建!" else echo "NOT NULL,版本号为: ${MARKETING_VERSION}" sed -i '' 's|MARKETING_VERSION = .*;|MARKETING_VERSION = '${MARKETING_VERSION}';|g' cf_rn_base.xcodeproj/project.pbxproj fi sh ./release_prod.sh # 打包签名 xcodebuild -archivePath ${WORKSPACE}/ios/buildDir/${PRODUCT}.xcarchive -workspace ${WORKSPACE}/ios/${PRODUCT}.xcworkspace -sdk ${SDK} -scheme ${PRODUCT} -configuration ${CONFIG} archive if [ $? -ne 0 ]; then echo "=====failed=====" exit 1 else echo "=====succeed=====" fi # 导出ipa xcodebuild -exportArchive -archivePath ${WORKSPACE}/ios/buildDir/${PRODUCT}.xcarchive -exportPath ${WORKSPACE}/ios/${IPADIR} -exportOptionsPlist ${WORKSPACE}/ios/buildDir/${PRODUCT}.xcarchive/Info.plist -allowProvisioningUpdates if [ $? -ne 0 ]; then echo "=====failed=====" exit 1 else echo "=====succeed=====" fi echo "\n" echo "================= jenkins-build end =================" #判断是否热更 if [ ${Exchange} == "Y" ];then # 上传热更新服务 echo "----------- 上传热更新服务 --------------" cd ${WORKSPACE} #echo "开始任务" set +x # 提示 “请在项目目录中运行`pushy login`命令来登录'” 时,打开下面注释进行登录 pushy login $PUSHY_USERNAME $PUSHY_PASSWORD if [ $? -eq 0 ];then echo "登录成功~" else echo "用户名密码错误!" exit 1 fi set -x sh ./release_prod.sh pushy uploadIpa ${WORKSPACE}/ios/${IPADIR}/${PRODUCT}.ipa if [ $? -ne 0 ]; then echo "=====failed=====" exit 1 else echo "=====succeed=====" fi else echo "原生包构建完成 !" fi currentDate=`date '+%Y%m%d%H%M'` cd ${WORKSPACE}/ios/${IPADIR}/ scp ${PRODUCT}.ipa [email protected]:/data/jenkins/prod-ios-ipa/hz/${PRODUCT}-${currentDate}.ipa /Users/cfhy/data/soft/ossutil/ossutil/ossutilmac64 cp -f ${WORKSPACE}/ios/${IPADIR}/${PRODUCT}.ipa oss://cfky-download/ios/hz/ #git clean -xdf
发送消息脚本
import java.util.*; import java.text.SimpleDateFormat; //构建结果 def buildResult = manager. getResult() //构建用户 def buildUser= manager.getEnvVariable("BUILD_USER") //项目名称 def jobName= manager.getEnvVariable("JOB_NAME") //构建结果页面 def buildUrl= manager.getEnvVariable("BUILD_URL") //构建说明 //def buildDes = manager.getEnvVariable("description") def buildDes = manager.getEnvVariable("Build_Description") //构建环境 def buildEnv= manager.getEnvVariable("BUILD_ENV") //构建类型 def buildType= manager.getEnvVariable("CONFIG") //GIT分支 def gitBranch = manager.getEnvVariable("GIT_BRANCH") // 应用版本号 apiAppVersion = manager.getEnvVariable("MARKETING_VERSION") manager.listener.logger.println("项目名称:"+ jobName) manager.listener.logger.println("构建分支:"+ gitBranch) manager.listener.logger.println("构建环境:"+ buildEnv) manager.listener.logger.println("构建类型:"+ buildType) manager.listener.logger.println("构建用户:"+ buildUser) manager.listener.logger.println("构建结果:"+ buildResult) if(buildResult == "SUCCESS"){ //解析蒲公英上传返回数据 //ipa下载地址 ipaDownloadUrl = "https://www.pgyer.com/"+ manager.getEnvVariable("buildShortcutUrl") ossDownloadUrl = "oss地址" //ipa二维码 ipaQrCode = manager.getEnvVariable("buildQRCodeURL") //ipa应用程序包 ipabuildIdentifier = manager.getEnvVariable("buildIdentifier") //ipa 版本号 ipabuildVersion = manager.getEnvVariable("buildVersion") manager.listener.logger.println("ipa下载地址"+ipaDownloadUrl) manager.listener.logger.println("ipa二维码地址:"+ipaQrCode) manager.listener.logger.println("ipa应用程序包名:"+ipabuildIdentifier) manager.listener.logger.println("ipa版本号:"+ipabuildVersion) manager.listener.logger.println("应用版本号:"+apiAppVersion) dingding("iOS打包构建","### [ "+jobName+" ] 构建成功" + "\n\n构建分支:" + gitBranch + "\n\n构建类型:"+ buildType + "\n\n三端类型:" + "hz" + "\n\n应用版本号:"+ apiAppVersion+ "\n\n提审包下载地址:"+ ossDownloadUrl + "\n\n构建日期:" + getNowTime() + "构建 " + "\n\n构建用户:"+ buildUser + "\n\n查看详情:[项目地址]("+buildUrl+")" ) }else if(buildResult == "ABORTED"){ dingding("prod-ios-hz-rn","### [ prod-ios-hz-rn ] 构建被终止\n" + " " + getNowTime() + " 终止\n\n[查看jenkins任务详情]("+buildUrl+")") }else{ dingding("prod-ios-hz-rn","### [ prod-ios-hz-rn ] 构建失败\n分支:" + gitBranch + "\n\n" + " " + getNowTime() + " 终止\n\n[查看jenkins任务详情]("+buildUrl+")") } //发送钉钉消息 def dingding(p_title,p_text){ manager.listener.logger.println("--------------------------"+p_title+p_text) def json= new groovy.json.JsonBuilder() json { msgtype "markdown" markdown { title p_title text p_text } at { atMobiles([]) isAtAll false } } manager.listener.logger.println("发送钉钉数据:"+json) def connection = new URL("https://oapi.dingtalk.com/robot/send?access_token=xxxx").openConnection() connection.setRequestMethod('POST') connection.doOutput = true connection.setRequestProperty('Content-Type', 'application/json') def writer = new OutputStreamWriter(connection.outputStream) writer.write(json.toString()); writer.flush() writer.close() connection.connect() def respText = connection.content.text manager.listener.logger.println("钉钉返回结果:"+respText ) } //获取当前时间 def getNowTime(){ def str = ""; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Calendar lastDate = Calendar.getInstance(); str = sdf.format(lastDate.getTime()); return str; }
注意事项
- 从机sh脚本可以独立编译通过,通过主机编译就不可以?
打开系统钥匙串,解锁 ‘系统’ 钥匙串一次。如果还不行,对登录用户的证书里面 MAC 编译对应的证书访问属性开启 ‘允许所有应用访问此项目’