Mac 安装jenkins +脚本构建+上传pgyer+构建完成钉钉/飞书通知+局域网内访问

4,390 阅读13分钟

1.jpg 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). 具体区别可自行查看。

支持多种安装方式,点击有具体的指引

image.png

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的飞起

      简单五步安装brew

    安装过程如有问题,自行百度。

    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). 具体区别可自行查看。

TLS和普通版本的区别

安装jenkins,往下拉,找到MacOS,

image.png

我们要安装的是稳定版本【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 网页上也有展示

image.png

插件选推荐,下一步之后,等待下载完成。

image.png

进入jenkins之后,什么都不用管,先建个任务再说:

image.png

输入名称,选择自由风格的任务

image.png

点击确定之后, 进入配置界面:
描述:自己写
源码管理:选择git,

image.png

image.png

构建触发器(Triggers)不用管
构建环境(Environment)不用管
构建(Build Steps) 这个是重点,选择执行脚本

image.png

输入框中输入:

#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配置就基本完成.

image.png

点击刚才创建的任务,

image.png

先不要慌
先不要慌
先不要慌

此时还不能构建成功。还差2个文件。

1、其中一个是脚本文件,刚才构建那一步中,最后输入的一行是:cd AutoPacking ./autopacking.sh。
2、工程导出配置文件

创建一个文本文件:autopacking.sh,将后面[脚本文件]的内容复制进去按照自己的工程修改。 创建一个文件夹:名称:AutoPacking,放在工程目录下

image.png

脚本文件

偶然看到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

image.png

image.png

image.png

插件配置

插件安装完并重启服务后,进入 “我的视图”-> "点击刚才创建的任务",进入配置

image.png

需要改2个地方:

  • 1、参数化构建过程

    image.png

  • 2、源码管理里面的指定分支要和参数化构建里面的名称一致,否则构建时,选择了分支1,但是实际打包的不是所选择的分支

    参数化构建里面的名称:name 那么源码管理里面就应该是${name}

    image.png

  • 3、构建->执行脚本里面添加一行, 用于打印当前执行的分支名,方便区分

    echo ${branchName}

    image.png

保存。完工。

image.png

最终配置

配置图

Jenkins打包配置.psd

配置脚本:

  • 确认 __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 签名校验先暂时不要

image.png

通知方式1-插件:post build task --这个脚本有问题

    1. 安装插件

    详细安装方式往上翻【安装插件】的位置

    image.png

    1. 开启插件

    配置中,拉到最底下,【构建后操作】-> 【增加构建后操作步骤】 -> 选择下载好的组件post build task

    image.png

    1. 输入脚本

    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、安装:

    这个插件不能直接在线安装,需要通过地址或下载后安装 官网

    复制链接地址:

    image.png

    在插件管理中部署

    image.png

    部署完成之后重启jenkins

  • 2、设置 找到安装的插件,在系统管理页面的最底下

    image.png

    image.png

  • 3、 在配置中开启

    在描述下面一点点:

    image.png

    自定义消息输入

    📋 **任务名称**:[${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

多账号分配及权限

局域网内其他电脑能访问之后,比如测试人员,他们需要账号登陆,不可能把管理账号给他们用,只能分配账号/匿名账号。

允许匿名用户打包

在这种情况下,用户不需要登陆,就可以对其进行一些简单的基本配置/打包

在系统管理- 全局安全配置 中进行设置

image.png

image.png

不同角色不同权限

详细信息参考以下文章。
参考文章1
参考文章2

1、下载Role-based Authorization Strategy插件。

2、开启插件功能:在 "系统管理" ->"安全" -> "安全全局配置" -> "授权策略" 选中 “Role-based Strategy” - 保存。

3、配置权限。

先了解一个流程: 要给用户分配权限,如果很多人都需要相同的权限,每个人分别分配是比较麻烦的,简单的方法的是: 先确认这个人所属的部门,然后将人安排到相应的部门。

上面保存完成之后,回到 "系统管理" ->"安全",多了一个 "Manage and Assign Roles"。

1)、进入 管理角色(Manage Roles)。
添加一个测试组 (这个组是给测试人员用,只需要能构建任务即可)

image.png 保存返回。

2)、进入 分配角色(Assign Roles)。

image.png 保存。搞定!