jenkins是什么自不多说。用了都说好。
jenkins之前一直用别人配置好的。最近入职新公司,没有jenkins,打算自己动手解决这个问题。
在网上找了很多文章, 对我来讲,没有一篇能按照步骤最后成功使用的,在中间过程中,都出现了问题。
大多数的文章都是通过【Keychains and Provisioning Profiles Management】这个插件来管理证书。但是在最新版本的jenkins中,这个插件点击无反应。如果一定要用这个插件管理证书,就只能通过其他方式安装旧版本,而不能直接通过brew命令安装jenkins。【明确的是,2.263.4这个旧版本可以用这个插件,具体哪个版本开始不支持,未知】。
在安装过程中,也采用Docker安装了2.263.4版本,但是最终使用【Keychains and Provisioning Profiles Management】这个插件还是出现了另外一个异常,没有找到解决方案,最终放弃,转而采用脚本的方法。
后面有最终配置完成和脚本内容,如果对于jenkins安装和脚本安装比较熟悉的话, 可以直接看最后的 【最终配置】
jenkins安装路径:
/opt/homebrew/Cellar/jenkins-lts/
jenkins工作路径:~/.jenkins
jenkins工作的工程路径:~/.jenkins/workspace
jenkins密码路径:~/.jenkins/secrets/initialAdminPassword
jenkins安装
进入jenkins官网下载页。
jenkins分为稳定版本【LTS】 和定期发布版本(可以理解为开发版本)。官方推荐稳定版本(LTS). 具体区别可自行查看。
支持多种安装方式,点击有具体的指引
1、brew安装【推荐】
安装brew
如果有安装 可跳过
-
确定有没有安装brew:命令行输入
brew -v,已安装,将会输出类似以下内容:
Homebrew 3.3.5-28-g3ab140e Homebrew/homebrew-core (git revision 4c5cbc9cd34; last commit 2021-11-24)未安装,会输出
-bash: brew: command not found -
未安装,需要先安装brew,安装方法如下:
-
1、官方 --【不建议尝试】,下载速度KB为单位
输入
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"。等待自动下载。如果出现
curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused表示网络环境有问题,可以切换不同的网络试试 -
2、镜像方式 强烈推荐 NB的飞起
安装过程如有问题,自行百度。
2025/12/16补充: MAC安装brew保姆级教程
-
安装jenkins
命令行直接安装即可
# 更新 Homebrew
brew update
# 或者安装 Jenkins LTS(长期支持版本)
brew install jenkins-lts
# brew install jenkins //开发版
安装完成之后 直接启动服务
# 启动 Jenkins
brew services start jenkins-lts
// brew services start jenkins
// 重启
//brew services restart jenkins-lts
//brew services restart jenkins
以下内容供了解
jenkins分为稳定版本【LTS】 和定期发布版本(可以理解为开发版本)。官方推荐稳定版本(LTS). 具体区别可自行查看。
安装jenkins,往下拉,找到MacOS,
我们要安装的是稳定版本【LTS】,选择左边的。在命令行中,按照指导输入:brew install jenkins-lts
静静等待安装完成:
==> Caveats
Note: When using launchctl the port will be 8080.
To start jenkins-lts now and restart at login:
brew services start jenkins-lts
Or, if you don't want/need a background service you can just run:
/opt/homebrew/opt/openjdk@21/bin/java -Dmail.smtp.starttls.enable\=true -jar /opt/homebrew/opt/jenkins-lts/libexec/jenkins.war --httpListenAddress\=127.0.0.1 --httpPort\=8080
==> Summary
🍺 /opt/homebrew/Cellar/jenkins-lts/2.528.1: 9 files, 102.7MB
==> Running `brew cleanup jenkins-lts`...
Disable this behaviour by setting `HOMEBREW_NO_INSTALL_CLEANUP=1`.
Hide these hints with `HOMEBREW_NO_ENV_HINTS=1` (see `man brew`).
==> Caveats
==> jenkins-lts
Note: When using launchctl the port will be 8080.
To start jenkins-lts now and restart at login:
brew services start jenkins-lts
Or, if you don't want/need a background service you can just run:
/opt/homebrew/opt/openjdk@21/bin/java -Dmail.smtp.starttls.enable\=true -jar /opt/homebrew/opt/jenkins-lts/libexec/jenkins.war --httpListenAddress\=127.0.0.1 --httpPort\=8080
这里面2个有用信息:
1、jenkins 安装目录:/opt/homebrew/Cellar/jenkins-lts/
2、默认端口号:8080
配置jenkins[脚本]
在浏览器中输入:http://localhost:8080/
第一次进入的时候需要输入一个本地的密码。这个密码就是上面步骤中说到的。找到复制进去。
路径:~/.jenkins/secrets/initialAdminPassword 网页上也有展示
插件选推荐,下一步之后,等待下载完成。

进入jenkins之后,什么都不用管,先建个任务再说:
输入名称,选择自由风格的任务
点击确定之后, 进入配置界面:
描述:自己写
源码管理:选择git,

构建触发器(Triggers)不用管
构建环境(Environment)不用管
构建(Build Steps) 这个是重点,选择执行脚本
输入框中输入:
#bin/bsah - l
export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# 这里的TEST是你的工程xxxx.xcworkspace 上一级目录
cd $WORKSPACE/TEST #进入工程目录
# pod install #这里根据自己实际情况,决定要不要pod install
cd AutoPacking
./autopacking.sh
保存。完工。
保存。完工。
保存。完工。
这样jenkins配置就基本完成.
点击刚才创建的任务,
先不要慌
先不要慌
先不要慌
此时还不能构建成功。还差2个文件。
1、其中一个是脚本文件,刚才构建那一步中,最后输入的一行是:cd AutoPacking ./autopacking.sh。
2、工程导出配置文件
创建一个文本文件:autopacking.sh,将后面[脚本文件]的内容复制进去按照自己的工程修改。
创建一个文件夹:名称:AutoPacking,放在工程目录下

脚本文件
偶然看到iOS脚本自动化打包这篇文章,也记录一下, 内容没有尝试,不确定是否正常。
-------------以下为实测OK
这个脚本的作用是自动打包并上传pgyer/fir的,该脚本来源于iOS自动打包脚本
在这个脚本里, 需要在控制台进行选择,用于jenkins肯定是不合适的。 而且对于我来说,一切都是明确的(Releas版本、adHoc证书、上传pgyer),进行了小改动,如下:
#!/bin/sh
# 该脚本使用方法
# 源码地址:https://github.com/stackhou
# step 1. 在工程根目录新建AutoPacking文件夹,在该文件夹中新建文件autopacking.sh,将该脚本复制到autopacking.sh文件并保存(或者直接复制该文件);
# step 2. 设置该脚本;
# step 2. cd 该脚本目录,运行chmod +x autopacking.sh;
# step 3. 终端运行 sh autopacking.sh;
# step 4. 选择不同选项....
# step 5. Success 🎉 🎉 🎉!
# 注意:可以全文搜索“配置”,看相关注释选择配置,因为不同的项目配置不同,最好有相关的基础知识
# ************************* 需要配置 Start ********************************
# 【配置上传到蒲公英相关信息】(可选)
__PGYER_U_KEY="xxxxxxx"
__PGYER_API_KEY="xxxxx"
# 【配置上传到 Fir】(可选)
__FIR_API_TOKEN="xxxx"
# 【配置证书】(如果只有一个证书时该项 可选)
#__CODE_SIGN_DISTRIBUTION="iPhone Distribution: xxxxxxxxxxxCo., Ltd."
#__CODE_SIGN_DEVELOPMENT="iPhone Developer: xxxx xxxx (5xxxxxxxxxx2V)"
# 发布APP Store 账号密码
#__IOS_SUBMIT_ACCOUNT="apple id"
#__IOS_SUBMIT_PASSWORD="xxxxxx"
# ==================== 公共部分 =====================
# ######### 脚本样式 #############
__TITLE_LEFT_COLOR="\033[36;1m==== "
__TITLE_RIGHT_COLOR=" ====\033[0m"
__OPTION_LEFT_COLOR="\033[33;1m"
__OPTION_RIGHT_COLOR="\033[0m"
__LINE_BREAK_LEFT="\033[32;1m"
__LINE_BREAK_RIGHT="\033[0m"
# 红底白字
__ERROR_MESSAGE_LEFT="\033[41m ! ! ! "
__ERROR_MESSAGE_RIGHT=" ! ! ! \033[0m"
# xcode version
XCODE_BUILD_VERSION=$(xcodebuild -version)
echo "-------------- Xcode版本: $XCODE_BUILD_VERSION -------------------"
# 等待用户输入时间
__WAIT_ELECT_TIME=0.2
# 选择项输入方法 接收3个参数:1、选项标题 2、选项数组 3、选项数组的长度(0~256)
function READ_USER_INPUT() {
title=$1
options=$2
maxValue=$3
echo "${__TITLE_LEFT_COLOR}${title}${__TITLE_RIGHT_COLOR}"
for option in ${options[*]}; do
echo "${__OPTION_LEFT_COLOR}${option}${__OPTION_RIGHT_COLOR}"
done
read
__INPUT=$REPLY
expr $__INPUT "+" 10 &> /dev/null
if [[ $? -eq 0 ]]; then
if [[ $__INPUT -gt 0 && $__INPUT -le $maxValue ]]; then
return $__INPUT
else
echo "${__ERROR_MESSAGE_LEFT}输入越界了,请重新输入${__ERROR_MESSAGE_RIGHT}"
READ_USER_INPUT $title "${options[*]}" $maxValue
fi
else
echo "${__ERROR_MESSAGE_LEFT}输入有误,请输入0~256之间的数字序号${__ERROR_MESSAGE_RIGHT}"
READ_USER_INPUT $title "${options[*]}" $maxValue
fi
}
# 打印信息
function printMessage() {
pMessage=$1
echo "${__LINE_BREAK_LEFT}${pMessage}${__LINE_BREAK_RIGHT}"
}
# 1. 请选择 SCHEME
#__SELECT_TARGET_OPTIONS=("1.AutoPackingDemo")
#READ_USER_INPUT "请选择对应的Target: " "${__SELECT_TARGET_OPTIONS[*]}" ${#__SELECT_TARGET_OPTIONS[*]}
#
#__SELECT_TARGET_OPTION=$?
#if [[ $__SELECT_TARGET_OPTION -eq 1 ]]; then
# __BUILD_TARGET="AutoPackingDemo"
# __SCHEME_NAME="AutoPackingDemo"
#else
# printMessage "这里请填写好你工程的所有target, 如果只有一个建议写死如下"
# # __BUILD_TARGET="AutoPackingDemo"
# exit 1
#fi
__SCHEME_NAME="您工程scheme"
# 2.Debug 或者 Release 选项
#__PACK_ENV_OPTIONS=("1.Release" "2.Debug")
#READ_USER_INPUT "请选择打包类型: " "${__PACK_ENV_OPTIONS[*]}" ${#__PACK_ENV_OPTIONS[*]}
#
#__PACK_ENV_OPTION=$?
#if [[ $__PACK_ENV_OPTION -eq 1 ]]; then
# __BUILD_CONFIGURATION="Release"
#elif [[ $__PACK_ENV_OPTION -eq 2 ]]; then
# __BUILD_CONFIGURATION="Debug"
#fi
__BUILD_CONFIGURATION="Release"
# 3. 工程类型(.xcworkspace项目,赋值true; .xcodeproj项目, 赋值false)
__IS_WORKSPACE_OPTIONS=("1.是" "2.否")
#READ_USER_INPUT "请选择是否是.xcworkspace项目: " "${__IS_WORKSPACE_OPTIONS[*]}" ${#__IS_WORKSPACE_OPTIONS[*]}
#__IS_WORKSPACE_OPTION=$?
__IS_WORKSPACE_OPTION=1
# 4.# AdHoc, AppStore, Enterprise, Development
#__PACK_TYPES=("1.AdHoc" "2.AppStore" "3.Enterprise" "4.Development")
#READ_USER_INPUT "请选择打包环境类型(输入序号,直接回车): " "${__PACK_TYPES[*]}" ${#__PACK_TYPES[*]}
#__PACK_TYPE=$?
#if [[ $__PACK_TYPE -eq 1 ]]; then
# __EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/AdHocExportOptionsPlist.plist"
# __BUILD_METHOD_NAME="AdHoc"
#elif [[ $__PACK_TYPE -eq 2 ]]; then
# __EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/AppStoreExportOptionsPlist.plist"
# __BUILD_METHOD_NAME="AppStore"
#elif [[ $__PACK_TYPE -eq 3 ]]; then
# __EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/EnterpriseExportOptionsPlist.plist"
# __BUILD_METHOD_NAME="Enterprise"
#elif [[ $__PACK_TYPE -eq 4 ]]; then
# __EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/DevelopmentExportOptionsPlist.plist"
# __BUILD_METHOD_NAME="Development"
#fi
__EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/ExportOptions.plist"
__BUILD_METHOD_NAME="AdHoc"
# 5.上传安装包到指定位置
__UPLOAD_IPA_OPTIONS=("1.None" "2.Pgyer" "3.Fir" "4.Pgyer")
#READ_USER_INPUT "请选择ipa上传位置: " "${__UPLOAD_IPA_OPTIONS[*]}" ${#__UPLOAD_IPA_OPTIONS[*]}
#__UPLOAD_IPA_OPTION=$?
__UPLOAD_IPA_OPTION=2
# 6. 成功出包后是否自动打开文件夹
__IS_AUTO_OPENT_FILE_OPTIONS=("1.是" "2.否")
#READ_USER_INPUT "是否自动打开文件夹: " "${__IS_AUTO_OPENT_FILE_OPTIONS[*]}" ${#__IS_AUTO_OPENT_FILE_OPTIONS[*]}
#__IS_AUTO_OPENT_FILE_OPTION=$?
__IS_AUTO_OPENT_FILE_OPTION=2
# 7. 是否立即开始打包
__IS_NOW_STAR_PACKINGS=("1.是" "2.否")
#READ_USER_INPUT "是否立即开始打包: " "${__IS_NOW_STAR_PACKINGS[*]}" ${#__IS_NOW_STAR_PACKINGS[*]}
#__IS_NOW_STAR_PACKING=$?
__IS_NOW_STAR_PACKING=1
if [[ $__IS_NOW_STAR_PACKING -eq 1 ]]; then
printMessage "已开始打包"
elif [[ $__IS_NOW_STAR_PACKING -eq 2 ]]; then
printMessage "您退出了自动打包脚本"
exit 1
fi
# ===============================自动打包部分=============================
# 打包计时
__CONSUME_TIME=0
# 回退到工程目录
cd ../
__PROGECT_PATH=`pwd`
# 获取项目名称
__PROJECT_NAME=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
# 已经指定Target的Info.plist文件路径 【配置Info.plist的名称】
__CURRENT_INFO_PLIST_NAME="Info.plist"
# 获取 Info.plist 路径 【配置Info.plist的路径】
__CURRENT_INFO_PLIST_PATH="${__PROJECT_NAME}/${__CURRENT_INFO_PLIST_NAME}"
\
# ✅:确保此路径正确 printMessage "info 路径 = ${__CURRENT_INFO_PLIST_PATH}"
# 获取版本号
__BUNDLE_VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${__CURRENT_INFO_PLIST_PATH}`
# 获取编译版本号
__BUNDLE_BUILD_VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${__CURRENT_INFO_PLIST_PATH}`
# Xcode11 以上版本
if [[ $XCODE_BUILD_VERSION =~ "Xcode 11" || $XCODE_BUILD_VERSION =~ "Xcode11" ]]; then
__BUNDLE_VERSION_TAG="MARKETING_VERSION"
__BUNDLE_BUILD_VERSION_TAG="CURRENT_PROJECT_VERSION"
__PROJECT_ROOT_PATH=`find . -name *.xcodeproj`
__PBXPROJ_PATH="$__PROJECT_ROOT_PATH/project.pbxproj"
__BUNDLE_VERSION_11=$(grep "${__BUNDLE_VERSION_TAG}" $__PBXPROJ_PATH | head -1 | awk -F '=' '{print $2}' | awk -F ';' '{print $1}' | sed s/[[:space:]]//g)
__BUNDLE_BUILD_VERSION_11=$(grep "${__BUNDLE_BUILD_VERSION_TAG}" $__PBXPROJ_PATH | head -1 | awk -F '=' '{print $2}' | awk -F ';' '{print $1}' | sed s/[[:space:]]//g)
if [[ -n "$__BUNDLE_VERSION_11" ]]; then
__BUNDLE_VERSION="$__BUNDLE_VERSION_11";
fi
if [[ -n "$__BUNDLE_BUILD_VERSION_11" ]]; then
__BUNDLE_BUILD_VERSION="$__BUNDLE_BUILD_VERSION_11";
fi
fi
# 编译生成文件目录
__EXPORT_PATH="./build"
# 指定输出文件目录不存在则创建
if test -d "${__EXPORT_PATH}" ; then
rm -rf ${__EXPORT_PATH}
else
mkdir -pv ${__EXPORT_PATH}
fi
# 归档文件路径
__EXPORT_ARCHIVE_PATH="${__EXPORT_PATH}/${__SCHEME_NAME}.xcarchive"
# ipa 导出路径
__EXPORT_IPA_PATH="${__EXPORT_PATH}"
# 获取时间 如:201706011145
__CURRENT_DATE="$(date +%Y%m%d_%H%M%S)"
# ipa 名字
__IPA_NAME="${__SCHEME_NAME}_V${__BUNDLE_BUILD_VERSION}_${__CURRENT_DATE}"
function print_packing_message() {
printMessage "打包类型 = ${__BUILD_CONFIGURATION}"
printMessage "打包导出Plist路径 = ${__EXPORT_OPTIONS_PLIST_PATH}"
printMessage "工程目录 = ${__PROGECT_PATH}"
printMessage "当前Info.plist路径 = ${__CURRENT_INFO_PLIST_PATH}"
}
print_packing_message
if [[ $__IS_WORKSPACE_OPTION -eq 1 ]]; then
# pod install --verbose --no-repo-update
if [[ ${__BUILD_CONFIGURATION} == "Debug" ]]; then
# step 1. Clean
xcodebuild clean -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION}
# step 2. Archive
xcodebuild archive -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-archivePath ${__EXPORT_ARCHIVE_PATH} \
CFBundleVersion=${__BUNDLE_BUILD_VERSION} \
-destination generic/platform=ios \
#CODE_SIGN_IDENTITY="${__CODE_SIGN_DEVELOPMENT}"
elif [[ ${__BUILD_CONFIGURATION} == "Release" ]]; then
# step 1. Clean
xcodebuild clean -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION}
# step 2. Archive
xcodebuild archive -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-archivePath ${__EXPORT_ARCHIVE_PATH} \
CFBundleVersion=${__BUNDLE_BUILD_VERSION} \
-destination generic/platform=ios \
#CODE_SIGN_IDENTITY="${__CODE_SIGN_DISTRIBUTION}"
fi
else
if [[ ${__BUILD_CONFIGURATION} == "Debug" ]] ; then
# step 1. Clean
xcodebuild clean -project ${__PROJECT_NAME}.xcodeproj \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
#-alltargets
# step 2. Archive
xcodebuild archive -project ${__PROJECT_NAME}.xcodeproj \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-archivePath ${__EXPORT_ARCHIVE_PATH} \
CFBundleVersion=${__BUNDLE_BUILD_VERSION} \
-destination generic/platform=ios \
#CODE_SIGN_IDENTITY="${__CODE_SIGN_DEVELOPMENT}"
elif [[ ${__BUILD_CONFIGURATION} == "Release" ]]; then
# step 1. Clean
xcodebuild clean -project ${__PROJECT_NAME}.xcodeproj \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-alltargets
# step 2. Archive
xcodebuild archive -project ${__PROJECT_NAME}.xcodeproj \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-archivePath ${__EXPORT_ARCHIVE_PATH} \
CFBundleVersion=${__BUNDLE_BUILD_VERSION} \
-destination generic/platform=ios \
#CODE_SIGN_IDENTITY="${__CODE_SIGN_DISTRIBUTION}"
fi
fi
# 检查是否构建成功
# xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断
if test -d "${__EXPORT_ARCHIVE_PATH}" ; then
printMessage "项目构建成功 🚀 🚀 🚀"
else
printMessage "项目构建失败 😢 😢 😢"
exit 1
fi
printMessage "开始导出ipa文件"
xcodebuild -exportArchive -archivePath ${__EXPORT_ARCHIVE_PATH} \
-exportPath ${__EXPORT_IPA_PATH} \
-destination generic/platform=ios \
-exportOptionsPlist ${__EXPORT_OPTIONS_PLIST_PATH} \
-allowProvisioningUpdates
# 修改ipa文件名称
mv ${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa ${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa
# 检查文件是否存在
if test -f "${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa" ; then
printMessage "导出 ${__IPA_NAME}.ipa 包成功 🎉 🎉 🎉"
if [[ $__UPLOAD_IPA_OPTION -eq 1 ]]; then
printMessage "您选择了不上传到内测网站"
elif [[ $__UPLOAD_IPA_OPTION -eq 2 ]]; then
curl -F "file=@${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa" \
-F "uKey=$__PGYER_U_KEY" \
-F "_api_key=$__PGYER_API_KEY" \
"http://www.pgyer.com/apiv1/app/upload"
printMessage "上传 ${__IPA_NAME}.ipa 包 到 pgyer 成功 🎉 🎉 🎉"
elif [[ $__UPLOAD_IPA_OPTION -eq 3 ]]; then
fir login -T ${__FIR_API_TOKEN}
fir publish "${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa"
printMessage "上传 ${__IPA_NAME}.ipa 包 到 fir 成功 🎉 🎉 🎉"
elif [[ $__UPLOAD_IPA_OPTION -eq 4 ]]; then
fir login -T ${__FIR_API_TOKEN}
fir publish "${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa"
printMessage "上传 ${__IPA_NAME}.ipa 包 到 fir 成功 🎉 🎉 🎉"
curl -F "file=@{${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa}" \
-F "uKey=$__PGYER_U_KEY" \
-F "_api_key=$__PGYER_API_KEY" \
"http://www.pgyer.com/apiv1/app/upload"
printMessage "上传 ${__IPA_NAME}.ipa 包 到 pgyer 成功 🎉 🎉 🎉"
fi
# 自动打开文件夹
if [[ $__IS_AUTO_OPENT_FILE_OPTION -eq 1 ]]; then
open ${__EXPORT_IPA_PATH}
fi
else
printMessage "导出 ${__IPA_NAME}.ipa 包失败 😢 😢 😢"
exit 1
fi
# 输出打包总用时
printMessage "使用YJShell脚本打包总耗时: ${SECONDS}s"
工程导出配置文件
这个文件可以自己写,但是新手我也不会,不要紧,让xcode帮你写。
按照你正常Product->Archive进行打包。最终导出的时候,按照adhoc、develop等方式导出到桌面,在导出的这个文件夹里 ,就存在了一个 ExportOptions.plist,按照脚本中的目录复制进去:
./AutoPacking/Plist/ExportOptions.plist.
测试脚本是否正常。
让jenkins自动构建之前,要先看看这个脚本能否按照你的预期工作。
按照刚才的[脚本文件]上面一点的那张图结构放好各个文件。通过命令行之行此脚本
1、cd 该脚本目录,运行chmod +x autopacking.sh;
2、终端运行 sh autopacking.sh;
如果能执行正常,就OK了。此时将AutoPacking文件放在你的jenkins的工程目录下
/Users/<name>/.jenkins/workspace/jenkins创建的任务名/工程名
此时 通过jenkins创建的任务,立即构建应该就ok了。
如果自动脚本构建失败,确认你的工程info文件是不是和脚本里面的路径相同
自主选择分支构建
开发人员一般都会创建多个分支开发任务,测试也需要针对不同的分支进行打包。
安装插件
这里需要用jenkins的脚本:Git Parameter
插件配置
插件安装完并重启服务后,进入 “我的视图”-> "点击刚才创建的任务",进入配置

需要改2个地方:
-
1、参数化构建过程
-
2、源码管理里面的指定分支要和参数化构建里面的名称一致,否则构建时,选择了分支1,但是实际打包的不是所选择的分支
参数化构建里面的名称:
name那么源码管理里面就应该是${name} -
3、构建->执行脚本里面添加一行, 用于打印当前执行的分支名,方便区分
echo ${branchName}
保存。完工。

最终配置
配置图
配置脚本:
- 确认
__SCHEME_NAME=是工程的Target - 确认打包导出配置文件
__EXPORT_OPTIONS_PLIST_PATH=的路径正确 - 确认info.plist的路径
__CURRENT_INFO_PLIST_PATH正确
# 修改:__SCHEME_NAME="改为工程的Target"
# 确认:__EXPORT_OPTIONS_PLIST_PATH="./AutoPacking/Plist/ExportOptions.plist"。确保__EXPORT_OPTIONS_PLIST_ABSPATH正确
# 确认:__CURRENT_INFO_PLIST_PATH info.plist的路径是否正确
# ************************* ⬇️配置Start ********************************
# 【配置上传到蒲公英相关信息】(可选)
__PGYER_U_KEY="xxxxxx"
__PGYER_API_KEY="xxxxx"
__SCHEME_NAME="AIChat"
__EXPORT_OPTIONS_PLIST_PATH="./Plist/ExportOptions.plist"
__EXPORT_OPTIONS_PLIST_ABSPATH=$(realpath "${__EXPORT_OPTIONS_PLIST_PATH}")
echo "-------导出文件路径: ${__EXPORT_OPTIONS_PLIST_ABSPATH}"
# 回退到工程目录 xxx.xcworkspace上一级文件夹
cd ../../AIChat/AIChat
# cd ../../Jenkins工程名/xcode工程文件夹名
# 按需 -- 构建一次PMG
xcodebuild -resolvePackageDependencies -workspace AIChat.xcworkspace -scheme AIChat -scmProvider system
# ************************* ⬆️配置Start ********************************
# ==================== ⬇️公共部分 =====================
# ######### 脚本样式 #############
__TITLE_LEFT_COLOR="\033[36;1m==== "
__TITLE_RIGHT_COLOR=" ====\033[0m"
__OPTION_LEFT_COLOR="\033[33;1m"
__OPTION_RIGHT_COLOR="\033[0m"
__LINE_BREAK_LEFT="\033[32;1m"
__LINE_BREAK_RIGHT="\033[0m"
# 红底白字
__ERROR_MESSAGE_LEFT="\033[41m ! ! ! "
__ERROR_MESSAGE_RIGHT=" ! ! ! \033[0m"
# xcode version
XCODE_BUILD_VERSION=$(xcodebuild -version)
echo "-------------- Xcode版本: $XCODE_BUILD_VERSION -------------------"
# 打印信息
function printMessage() {
pMessage=$1
echo "${__LINE_BREAK_LEFT}${pMessage}${__LINE_BREAK_RIGHT}"
}
# ==================== ⬆️公共部分 =====================
# ===============================自动打包部分=============================
# 打包计时
__CONSUME_TIME=0
__PROGECT_PATH=`pwd`
# 获取项目名称
__PROJECT_NAME=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
# 已经指定Target的Info.plist文件路径 【配置Info.plist的名称】
__CURRENT_INFO_PLIST_NAME="Info.plist"
# 获取 Info.plist 路径 【配置Info.plist的路径】
__CURRENT_INFO_PLIST_PATH="${__PROJECT_NAME}/${__CURRENT_INFO_PLIST_NAME}"
# 获取版本号
__BUNDLE_VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${__CURRENT_INFO_PLIST_PATH}`
# 获取编译版本号
__BUNDLE_BUILD_VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${__CURRENT_INFO_PLIST_PATH}`
# Xcode11 以上版本
if [[ $XCODE_BUILD_VERSION =~ "Xcode 11" || $XCODE_BUILD_VERSION =~ "Xcode11" ]]; then
__BUNDLE_VERSION_TAG="MARKETING_VERSION"
__BUNDLE_BUILD_VERSION_TAG="CURRENT_PROJECT_VERSION"
__PROJECT_ROOT_PATH=`find . -name *.xcodeproj`
__PBXPROJ_PATH="$__PROJECT_ROOT_PATH/project.pbxproj"
__BUNDLE_VERSION_11=$(grep "${__BUNDLE_VERSION_TAG}" $__PBXPROJ_PATH | head -1 | awk -F '=' '{print $2}' | awk -F ';' '{print $1}' | sed s/[[:space:]]//g)
__BUNDLE_BUILD_VERSION_11=$(grep "${__BUNDLE_BUILD_VERSION_TAG}" $__PBXPROJ_PATH | head -1 | awk -F '=' '{print $2}' | awk -F ';' '{print $1}' | sed s/[[:space:]]//g)
if [[ -n "$__BUNDLE_VERSION_11" ]]; then
__BUNDLE_VERSION="$__BUNDLE_VERSION_11";
fi
if [[ -n "$__BUNDLE_BUILD_VERSION_11" ]]; then
__BUNDLE_BUILD_VERSION="$__BUNDLE_BUILD_VERSION_11";
fi
fi
# 编译生成文件目录
__EXPORT_PATH="./build"
# 指定输出文件目录不存在则创建
if test -d "${__EXPORT_PATH}" ; then
rm -rf ${__EXPORT_PATH}
else
mkdir -pv ${__EXPORT_PATH}
fi
# 归档文件路径
__EXPORT_ARCHIVE_PATH="${__EXPORT_PATH}/${__SCHEME_NAME}.xcarchive"
# ipa 导出路径
__EXPORT_IPA_PATH="${__EXPORT_PATH}"
# 获取时间 如:201706011145
__CURRENT_DATE="$(date +%Y%m%d_%H%M%S)"
# ipa 名字
__IPA_NAME="${__SCHEME_NAME}_V${__BUNDLE_BUILD_VERSION}_${__CURRENT_DATE}"
function print_packing_message() {
printMessage "打包导出Plist路径 = ${__EXPORT_OPTIONS_PLIST_ABSPATH}"
printMessage "工程目录 = ${__PROGECT_PATH}"
printMessage "当前Info.plist路径 = ${__CURRENT_INFO_PLIST_PATH}"
}
print_packing_message
__BUILD_CONFIGURATION="Release"
# step 1. Clean
xcodebuild clean -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION}
# step 2. Archive
xcodebuild archive -workspace ${__PROJECT_NAME}.xcworkspace \
-scheme ${__SCHEME_NAME} \
-configuration ${__BUILD_CONFIGURATION} \
-archivePath ${__EXPORT_ARCHIVE_PATH} \
CFBundleVersion=${__BUNDLE_BUILD_VERSION} \
-destination generic/platform=ios \
# 检查是否构建成功
# xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断
if test -d "${__EXPORT_ARCHIVE_PATH}" ; then
printMessage "项目构建成功 🚀 🚀 🚀"
else
printMessage "项目构建失败 😢 😢 😢"
exit 1
fi
printMessage "开始导出ipa文件"
xcodebuild -exportArchive -archivePath ${__EXPORT_ARCHIVE_PATH} \
-exportPath ${__EXPORT_IPA_PATH} \
-destination generic/platform=ios \
-exportOptionsPlist ${__EXPORT_OPTIONS_PLIST_ABSPATH} \
-allowProvisioningUpdates
echo "=== 调试信息 ==="
echo "导出路径: ${__EXPORT_IPA_PATH}"
echo "Scheme名称: ${__SCHEME_NAME}"
echo "IPA名称: ${__IPA_NAME}"
echo "完整源路径: ${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa"
echo "完整目标路径: ${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa"
# 检查文件是否存在
if [ -f "${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa" ]; then
echo "✅ 源文件存在,准备重命名..."
mv "${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa" "${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa"
echo "✅ 重命名成功"
else
echo "❌ 错误: 源文件不存在"
echo "目录 ${__EXPORT_IPA_PATH} 中的文件:"
ls -la "${__EXPORT_IPA_PATH}"
# 查找目录下所有 .ipa 文件
echo "=== 查找 .ipa 文件 ==="
IPA_FILES=$(find "${__EXPORT_IPA_PATH}" -name "*.ipa" -type f)
if [ -n "$IPA_FILES" ]; then
echo "找到以下 .ipa 文件:"
echo "$IPA_FILES"
# 获取第一个 .ipa 文件
FIRST_IPA_FILE=$(echo "$IPA_FILES" | head -1)
echo "将重命名第一个找到的 .ipa 文件: $FIRST_IPA_FILE"
# 重命名为 a.ipa
mv "$FIRST_IPA_FILE" "${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa"
echo "✅ 已重命名为: ${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa"
else
echo "❌ 未找到任何 .ipa 文件"
fi
fi
# 修改ipa文件名称
mv ${__EXPORT_IPA_PATH}/${__SCHEME_NAME}.ipa ${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa
# 检查文件是否存在
if test -f "${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa" ; then
printMessage "导出 ${__IPA_NAME}.ipa 包成功 🎉 🎉 🎉"
printMessage "开始上传到pgy"
curl -F "file=@${__EXPORT_IPA_PATH}/${__IPA_NAME}.ipa" \
-F "uKey=$__PGYER_U_KEY" \
-F "_api_key=$__PGYER_API_KEY" \
"http://www.pgyer.com/apiv1/app/upload"
printMessage "上传 ${__IPA_NAME}.ipa 包 到 pgyer 成功 🎉 🎉 🎉"
else
printMessage "导出 ${__IPA_NAME}.ipa 包失败 😢 😢 😢"
exit 1
fi
# 输出打包总用时
printMessage "使用YJShell脚本打包总耗时: ${SECONDS}s"
脚本放置位置结构
Jenkins的工作路径
~/.jenkins/workspace
workspace/
├── AIChat/ jenkins的工程目录
│ ├── .git/
│ └── AIChat/ Git下来的工程文件
│ └── xxxx.xcworkspace/
├── AIChat@tmp/
└── Shell/ 脚本文件夹
└── AIChat/ 针对这个工程的脚本文件夹
├── autopacking.sh 自动打包脚本
└── Plist/
└── ExportOptions.plist ipa导出配置文件
添加通知
钉钉通知
使用插件。可参考这篇文章
飞书通知
创建群机器人
必要条件: 在飞书群中,新建一个【群机器人】-> 【自定义机器人】 并获取到Webhook 签名校验先暂时不要
通知方式1-插件:post build task --这个脚本有问题
-
- 安装插件
详细安装方式往上翻【安装插件】的位置
-
- 开启插件
在配置中,拉到最底下,【构建后操作】-> 【增加构建后操作步骤】 -> 选择下载好的组件
post build task -
- 输入脚本
1、脚本中的user和passwd 需要输入有效的jenkins账号,
2、
https://open.feishu.cn/open-apis/bot/v2/hook/xxx那一串替换成自己的机器人webHook#!/bin/bash JOB_URL="${JENKINS_URL}job/${JOB_NAME}" getBuildState(){ buildNr=$1 result=$2 user=Jenkins passwd=Jenkins curl -u $user:$passwd ${JOB_URL}/${buildNr}/api/json |grep -Po $result } state=$(getBuildState $BUILD_NUMBER '"result":\s*"\K\w+') des=$(getBuildState $BUILD_NUMBER 'msg[":]+\K[^"]+') pro=$(getBuildState $BUILD_NUMBER 'fullName[":]+\K[^"]+') string1=$BUILD_DISPLAY_NAME string2=$JOB_BASE_NAME nowTime=$(date "+%Y-%m-%d %H:%M:%S") echo ${state} echo ${des} echo ${pro} changlog=$(awk 'NR==FNR {a[FNR]=$1; next} {print $0, a[FNR]}' <(echo "$pro") <(echo "$des") |tr ' ' '_' ) scm=$(echo $changlog | awk '{gsub(/ /,";")}1') if [[ "${state}" == "SUCCESS" ]] ; then curl -X POST -H "Content-Type: application/json" \ -d '{"msg_type":"post","content": {"post": {"zh_cn": {"title": "编译结果通知","content": [[{"tag": "text","text": "'"项目名称:$string2\n构建编号:第$BUILD_NUMBER次构建\n远程分支:$GIT_BRANCH\n构建状态:成功\n构建日期:$nowTime\n提交记录:$scm"'"}]]} } }}' \ https://open.feishu.cn/open-apis/bot/v2/hook/ else curl -X POST -H "Content-Type: application/json" \ -d '{"msg_type": "interactive","card": {"elements": [{"tag": "div","text": {"content": "'"项目名称: $string2\n构建编号: 第$BUILD_NUMBER次构建\n远程分支: $GIT_BRANCH\n构建状态: 失败\n构建日期: $nowTime\n提交记录:$scm\n"'","tag": "lark_md"}}, {"actions": [{"tag": "button","text": {"content": "点击查看错误日志","tag": "lark_md"},"url": "'"$JOB_URL/$BUILD_NUMBER/consoleText"'","type": "default","value": {}}],"tag": "action"}],"header": {"title": {"content": " 编译结果通知","tag": "plain_text"}}}}' \ https://open.feishu.cn/open-apis/bot/v2/hook fi
通知方式2-插件 lark-notice-plugin 【✅】
-
1、安装:
这个插件不能直接在线安装,需要通过地址或下载后安装 官网
复制链接地址:
在插件管理中部署
部署完成之后重启jenkins
-
2、设置 找到安装的插件,在系统管理页面的最底下
-
3、 在配置中开启
在描述下面一点点:
自定义消息输入
📋 **任务名称**:[${PROJECT_NAME}](${JENKINS_URL}/job/${PROJECT_NAME}/) 🔢 **任务编号**:[${JOB_NAME}](${JENKINS_URL}/job/${PROJECT_NAME}/${BUILD_NUMBER}/) 🌟 **构建状态**: <text_tag color='blue'>${JOB_STATUS}</text_tag> 🕐 **构建用时**: ${JOB_DURATION} 🎶**构建分支**: ${GIT_BRANCH} 🌐**下载地址**: https://www.pgyer.com/xxxxx
局域网内访问
参考来源
正常来说,安装完成之后,只能通过http://localhost:8080/来访问。但是在开发中,自动打包,其实大多数是又测试人员来用,如何让局域网内,其他电脑也可以使用jenkins?
特别提醒:先记住你的管理员账号和密码,修改配置后,不能通过localhost访问了,只能通过ip访问。我的账号密码是通过浏览器自动保存的。修改完成之后,由于是通过ip访问,host变了,账号密码,不能自动匹配上,最后我在浏览器的记录中重新找回来的。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>homebrew.mxcl.jenkins-lts</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/opt/openjdk@11/bin/java</string>
<string>-Dmail.smtp.starttls.enable=true</string>
<string>-jar</string>
<string>/usr/local/opt/jenkins-lts/libexec/jenkins.war</string>
<string>--httpListenAddress=127.0.0.1</string>
<string>--httpPort=8080</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
我是通过brew安装的jenkins。安装版本是:jenkins-lts。
-
步骤一、修改以下2个配置文件:将里面的内容
127.0.0.1修改为:0.0.0.0
(路径中,安装的稳定版:jenkins-lts 开发版本:jenkins)/opt/homebrew/Cellar/jenkins-lts/版本号/homebrew.mxcl.jenkins-lts.plist
~/Library/LaunchAgents/版本号/homebrew.mxcl.jenkins-lts.plist -
步骤二、重启jenkins服务
brew services restart jenkins-lts
多账号分配及权限
局域网内其他电脑能访问之后,比如测试人员,他们需要账号登陆,不可能把管理账号给他们用,只能分配账号/匿名账号。
允许匿名用户打包
在这种情况下,用户不需要登陆,就可以对其进行一些简单的基本配置/打包
在系统管理- 全局安全配置 中进行设置
不同角色不同权限
1、下载Role-based Authorization Strategy插件。
2、开启插件功能:在 "系统管理" ->"安全" -> "安全全局配置" -> "授权策略" 选中 “Role-based Strategy” - 保存。
3、配置权限。
先了解一个流程: 要给用户分配权限,如果很多人都需要相同的权限,每个人分别分配是比较麻烦的,简单的方法的是: 先确认这个人所属的部门,然后将人安排到相应的部门。
上面保存完成之后,回到 "系统管理" ->"安全",多了一个 "Manage and Assign Roles"。
1)、进入 管理角色(Manage Roles)。
添加一个测试组 (这个组是给测试人员用,只需要能构建任务即可)
保存返回。
2)、进入 分配角色(Assign Roles)。
保存。搞定!