jenkins 趟坑记

363 阅读5分钟

装逼之前,先容我吐槽一番。

网上对 jenkins 的介绍,大多是按部就班的授之于鱼,没后从原理上讲这个东西,而且千篇一律,不知道抄别人的博客有啥意义,谷歌出来的都是一摸一样。所以我觉得有必要把我三天的趟坑结果记录一下了。如果理解有偏差,还望不吝指正。

底层逻辑

现在似乎很流行“底层逻辑”这个词,这里咱也用一下。

jenkins 的配置教程随便一搜就是一大堆,这里就不讲了。

当我们点击配置好的项目的“开始构建”按钮后,jenkins 在背后到底做了什么?在说这个之前,先介绍下 iOS 打包过程。

iOS 打包,在一般情况下直接通过 xcode 实现,操作简单而且人性化,不过苹果也提供了一套通过命令行方式的打包方法,这两者没有本质区别,只是表现形式不同,而这里要用到的就是后一种方式。所以,jenkins 的最本质的东西,其实就是把通过命令行打包的方式搬到服务器去操作,并把打包生成的 ipa 文件上传到指定服务器(这个可以自定义也可以放到 fir 或者蒲公英平台),而关键点就是脚本的编写。到此其实 jenkins 打包已经很好理解了,具体流程如下。

  • 当我们点击配置后的项目的“开始构建”按钮是,jenkins 首先会拉去配置好的 git 远程仓库的最新代码,也就是我们的工程项目。
  • 自动执行编写的脚本,这一步也是提前配置好的,一般情况下,脚本包括 pod 更新、所需资源文件下载、打包命令执行以及 ipa 包上传服务器等。
  • 就是后续操作了,依旧可以配置,比如发邮件告知相关人员“我已经打包好了,可以来下载了”。

ok,原理搞清楚了,我们拓展一下,事实上 jenkins 这个东西正如我刚开始趟坑时所想的,可以完全不需要,也就是说完全能可以自己实现一套服务更个性化的实现我们的打包需求。

真正的底层逻辑

其实实现起来也大概相当简单吧

首先脚本是必须的,也完全可以用和 jenkins 相同的那一套。只需要在脚本中加一句 git clone ssh://git@...即可,当然我们也可以做的类似于 jenkins 一样,构建相应的数据库、服务器以及 web 页面,web 页面整个开始按钮,点击事件即执行脚本,然后把每次的 log 日志以及其他的所有的打包操作记录到数据库中,以便以后访问。

当然我是很不建议重复造轮子的,不过 jenkins 的配置真的很麻烦,这句话只有趟过坑的才懂。

最后

我的理解是打包服务器必须是 Mac 系统,而且 pod、xcode 等相关的环境最好不低于自己本地测试的版本,不然会有各种莫名其妙的问题。

传一份网上的打包脚本吧,根据自己的项目配一下即可(替换脚本中的xxx)。

#!/bin/sh

export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8

#pod install
pod install --verbose --no-repo-update

# 使用方法:
# step1: 将该脚本放在工程的根目录下(跟.xcworkspace文件or .xcodeproj文件同目录)
# step2: 根据情况修改下面的参数
# step3: 打开终端,执行脚本。(输入sh,然后将脚本文件拉到终端,会生成文件路径,然后enter就可)

# =============项目自定义部分(自定义好下列参数后再执行该脚本)=================== #

# 是否编译工作空间 (例:若是用Cocopods管理的.xcworkspace项目,赋值true;用Xcode默认创建的.xcodeproj,赋值false)
is_workspace="true"

# .xcworkspace的名字,如果is_workspace为true,则必须填。否则可不填
workspace_name="xxx"

# .xcodeproj的名字,如果is_workspace为false,则必须填。否则可不填
project_name="xxx"

# 指定项目的scheme名称(也就是工程的target名称),必填
scheme_name="xxx"

# 指定要打包编译的方式 : Release,Debug。一般用Release。必填
build_configuration="Release"

# method,打包的方式。方式分别为 development, ad-hoc, app-store, enterprise 。必填
method="development"


#  下面两个参数只是在手动指定Pofile文件的时候用到,如果使用Xcode自动管理Profile,直接留空就好
# (跟method对应的)mobileprovision文件名,需要先双击安装.mobileprovision文件.手动管理Profile时必填
mobileprovision_name=""

# 项目的bundleID,手动管理Profile时必填
bundle_identifier=""

echo "--------------------脚本配置参数检查--------------------"
echo "\033[33;1mis_workspace=${is_workspace} "
echo "workspace_name=${workspace_name}"
echo "project_name=${project_name}"
echo "scheme_name=${scheme_name}"
echo "build_configuration=${build_configuration}"
echo "bundle_identifier=${bundle_identifier}"
echo "method=${method}"
echo "mobileprovision_name=${mobileprovision_name} \033[0m"


# =======================脚本的一些固定参数定义(无特殊情况不用修改)====================== #

# 获取当前脚本所在目录
script_dir="$( cd "$( dirname "$0"  )" && pwd  )"
# 工程根目录
project_dir=$script_dir

# 时间
DATE=`date '+%Y%m%d_%H%M%S'`
# 指定输出导出文件夹路径
export_path="$project_dir/Package/$scheme_name-$DATE"
# 指定输出归档文件路径
export_archive_path="$export_path/$scheme_name.xcarchive"
# 指定输出ipa文件夹路径
export_ipa_path="$export_path"
# 指定输出ipa名称
ipa_name="${scheme_name}_${DATE}"
# 指定导出ipa包需要用到的plist配置文件的路径
export_options_plist_path="$project_dir/ExportOptions.plist"
# 服务器路径 
server_url=""

echo "--------------------脚本固定参数检查--------------------"
echo "\033[33;1mproject_dir=${project_dir}"
echo "DATE=${DATE}"
echo "export_path=${export_path}"
echo "export_archive_path=${export_archive_path}"
echo "export_ipa_path=${export_ipa_path}"
echo "export_options_plist_path=${export_options_plist_path}"
echo "ipa_name=${ipa_name} \033[0m"

# =======================自动打包部分(无特殊情况不用修改)====================== #

echo "------------------------------------------------------"
echo "\033[32m开始构建项目  \033[0m"

# security unlock-keychain -p "591213" "/Users/gaozemin/Library/Keychains/login.keychain-db"
# 进入项目工程目录
cd ${project_dir}


# 指定输出文件目录不存在则创建
if [ -d "$export_path" ] ; then
    echo $export_path
else
    mkdir -pv $export_path
fi

# 判断编译的项目类型是workspace还是project
if $is_workspace ; then
# 编译前清理工程
xcodebuild clean -workspace ${workspace_name}.xcworkspace \
                 -scheme ${scheme_name} \
                 -configuration ${build_configuration}

xcodebuild archive -workspace ${workspace_name}.xcworkspace \
                   -scheme ${scheme_name} \
                   -configuration ${build_configuration} \
                   -archivePath ${export_archive_path}
else
# 编译前清理工程
xcodebuild clean -project ${project_name}.xcodeproj \
                 -scheme ${scheme_name} \
                 -configuration ${build_configuration}

xcodebuild archive -project ${project_name}.xcodeproj \
                   -scheme ${scheme_name} \
                   -configuration ${build_configuration} \
                   -archivePath ${export_archive_path}
fi

#  检查是否构建成功
#  xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断
if [ -d "$export_archive_path" ] ; then
    echo "\033[32;1m项目构建成功 🚀 🚀 🚀  \033[0m"
else
    echo "\033[31;1m项目构建失败 😢 😢 😢  \033[0m"
    exit 1
fi
echo "------------------------------------------------------"

echo "\033[32m开始导出ipa文件 \033[0m"


# 先删除export_options_plist文件
if [ -f "$export_options_plist_path" ] ; then
    #echo "${export_options_plist_path}文件存在,进行删除"
    rm -f $export_options_plist_path
fi
# 根据参数生成export_options_plist文件
/usr/libexec/PlistBuddy -c  "Add :method String ${method}"  $export_options_plist_path
# /usr/libexec/PlistBuddy -c  "Add :provisioningProfiles:"  $export_options_plist_path
# /usr/libexec/PlistBuddy -c  "Add :provisioningProfiles:${bundle_identifier} String ${mobileprovision_name}"  $export_options_plist_path
/usr/libexec/PlistBuddy -c  "Add :signingStyle String automatic"  $export_options_plist_path
/usr/libexec/PlistBuddy -c  "Add :destination String export"  $export_options_plist_path
/usr/libexec/PlistBuddy -c  "Add :compileBitcode bool false"  $export_options_plist_path


xcodebuild  -exportArchive \
            -archivePath ${export_archive_path} \
            -exportPath ${export_ipa_path} \
            -exportOptionsPlist ${export_options_plist_path} \
            -allowProvisioningUpdates

# 检查ipa文件是否存在
if [ -f "$export_ipa_path/$scheme_name.ipa" ] ; then
    echo "\033[32;1mexportArchive ipa包成功,准备进行重命名\033[0m"
else
    echo "\033[31;1mexportArchive ipa包失败 😢 😢 😢     \033[0m"
    exit 1
fi

# 修改ipa文件名称
mv "$export_ipa_path/$scheme_name.ipa" "$export_ipa_path/$ipa_name.ipa"

# 检查文件是否存在
if [ -f "$export_ipa_path/$ipa_name.ipa" ] ; then
    echo "\033[32;1m导出 ${ipa_name}.ipa 包成功 🎉  🎉  🎉   \033[0m"
    open $export_path
else
    echo "\033[31;1m导出 ${ipa_name}.ipa 包失败 😢 😢 😢     \033[0m"
    exit 1
fi

# 删除export_options_plist文件(中间文件)
if [ -f "$export_options_plist_path" ] ; then
    #echo "${export_options_plist_path}文件存在,准备删除"
    rm -f $export_options_plist_path
fi



# 输出打包总用时
echo "\033[36;1m使用AutoPackageScript打包总用时: ${SECONDS}s \033[0m"



echo "------------------------------------------"
echo "开始上传"
echo "上传文件:$export_ipa_path/$ipa_name.ipa"
echo "服务器路径:${server_url}"

scp "$export_ipa_path/$ipa_name.ipa" ${server_url}
echo "上传完成"

fmfile="$project_dir/Package/"
# 删除export_path文件(中间文件)
if [ -d "$fmfile" ] ; then
    echo "${fmfile}文件存在,准备删除"
    rm -rf dir "$fmfile"
fi

exit 0