文章主要介绍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 root@10.3.1.20:/data/jenkins/prod-ios-ipa/hz/${PRODUCT}-${currentDate}.ipa
/Users/cfhy/data/soft/ossutil/ossutil/ossutilmac64 cp -f ${WORKSPACE}/ios/${IPADIR}/${PRODUCT}.ipa oss://xxxx/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 编译对应的证书访问属性开启 ‘允许所有应用访问此项目’