由于公司要求构建自己的CICD流程,基于公司内部搭建的服务平台去搭建自动打包流程,在这个过程中遇到了几个节点记录一下。
公司内部号称有自己的一套软管平台,实际内核都是基于Jekins的一套机制,不同的是有些参数可以自定义,由于公司是硬件起家,而App打包又没有既成的案例借鉴,所以这一套逻辑针对App还是有些难以入手的。
首先App的包是安装包,不同于前端和其他服务,直接生成运行环境供用户使用,而整体的软管平台是将所有的生成物打包成zip压缩文件,所以还是需要将成果物上传到测试环境的发包平台,以及testflight,最后再将成果物打包成zip供version记录。
其次打包的脚本实现,需要从svn拉去代码,然后配置服务器的打包环境,再使用脚本执行打包。
整个流程都是使用脚本实现,包括软管平台脚本和打包脚本
软管平台脚本如下:
#!/bin/bash
source /etc/profile
#***********************************************************************
#FileName: buildPCI.sh
#Note:
#1、jenkins调用命令为./buildPCI.sh ${buildType} ${WORKSPACE}/${outputDir} ${userDefined}
#2、${WORKSPACE}/${outputDir}为绝对路径;${userDefined}为可选自定义参数,例子:userDefined="a b c" 则${3}=a、${4}=b、${5}=c
#3、组件下载参数:CI_COMMON_PATH为组件存放地址;modelList为组件名列表,调用copyDependent拷贝组件文件到源代码对应位置进行构建
#4、最终成果物为${OUTPUT_PATH}/*.zip 和${OUTPUT_PATH}/*.ini,其中OUTPUT_PATH=${2}
#升级包命名规则:请参考部门升级包命名规范
#5、ini文件内容如下:
#[prop]
#config=配置(STD、NEU、GM)
#language=语言(CN、EN、GML、ML)
#softVersion=Vx.y.z_yymmdd
#infileName=发布zip包名
#6、!!!开发仅需修改字符x所在处!!!
#**********************************自定义方法*************************************
#入参的含义
function usage()
{
echo
echo "################### buildPCI.sh usage: ###################"
echo "./buildPCI.sh \${buildType} \${outputDir} \${userDefined}"
echo "@param [in] \$1: [release|debug] buildType"
echo "@param [in] \$2: outputDir abs path,if not exist will create.if exist,the file will be added "
echo "@param [in] \$3: [Produce] product type"
echo "@param [in] \$4-..: product defined"
echo -e "EGG:"
echo -e " ./buildPCI.sh release $(pwd) 1:$1-2:$2-3:$3-4:$4"
echo "[ERROR]:***************************unknow param(three param least) ******************************"
echo
}
# 执行构建
function build()
{
BUILD_PATH="${1}"
BUILD_CMD="${2}"
# copyDependentxx
cd ${BUILD_PATH}
echo "build_path:${BUILD_PATH}"
chmod -R +x *.sh
${BUILD_CMD}
judgeResult "${BUILD_PATH} ${BUILD_CMD} build failed"
cd -
}
# 判断命令执行结果
function judgeResult()
{
if [ $? -ne 0 ] ; then
judgeResultPrint="${1}"
echo "[ERROR]:***************************${judgeResultPrint}******************************"
exit 1
fi
}
# generate_package_config_init config language softVersion infileName
function generate_package_config_init()
{
#[prop]
#config=配置(STD、NEU、GM)
#language=语言(CN、EN、GML、ML)
#softVersion=Vx.y.z_yymmdd
#infileName=发布zip包名
# 创建压缩包名称
Product_File_ini="$1".ini
echo "[prop]" >"$Product_File_ini"
echo config="$2" >> "$Product_File_ini"
echo language="$3" >> "$Product_File_ini"
echo softVersion="$4">> "$Product_File_ini"
echo infileName="$1.zip" >> "$Product_File_ini"
}
# 遍历所有$1文件夹下所有文件拷贝到$2
function read_dir_cp_dst(){
for file in `ls $1`
do
if [ -d $1"/"$file ]
then
read_dir_cp_dst $1"/"$file $2
else
file_path=$1"/"$file
cp $file_path $2
fi
done
}
#************************************脚本开始执行***********************************
# 打印所有的入参
echo "0:$0-1:$1-2:$2-3:$3-4:$4-5:$5"
# 小于3个参数返回失败
if [ $# -le 3 ];then
usage
exit 1
fi
# 平台传递必选参数:buildType为release或debug;OUTPUT_PATH为绝对路径;
# commonPath为组件存放路径;component为依赖组件json信息,可解析成modelList组件名列表
buildType="${1}"
OUTPUT_PATH="${2}"
build_language="${3}"
ROOT_PATH=$(cd "$(dirname "$0")"/..;pwd)
softVersion="V${4}"
PRODUCT_WORK_PATH=${ROOT_PATH}
echo "PRODUCT_WORK_PATH:$PRODUCT_WORK_PATH"
# 检查目标产品路径是否存在,如果不存在直接退出。
if [ ! -d "${PRODUCT_WORK_PATH}/build" ]; then
echo ${PRODUCT_WORK_PATH}" not exist exit"
exit 1
fi
# 使用平台配置的组件打包
# 定义运行脚本路径及参数
BUILD_PATH=${PRODUCT_WORK_PATH}"/build"
if [ ${buildType} == "release" ]; then
shift 3
BUILD_CMD="./build.sh ${build_tool_prefix} $@"
elif [ ${buildType} == "debug" ]; then
shift 3
BUILD_CMD="./buildDebug.sh ${build_tool_prefix} $@"
else
echo "[ERROR]:***************************unknow buildType ${buildType}******************************"
usage
exit 1
fi
echo "build ${BUILD_PATH} ${BUILD_CMD}"
# 执行构建
build "${BUILD_PATH}" "${BUILD_CMD}"
#上述构建需生成同名的zip和ini文件,并且拷贝升级包到OUTPUT_PATH目录下
mkdir -p "$BUILD_PATH/xxx/"
# 进入打包的文件夹
cd "$BUILD_PATH/xxx/"
# 创建压缩包名称
resultName="NB_$build_language""_STD_$softVersion""_$(date +%y%m%d)"
# 创建ini文件
generate_package_config_init "${resultName}" "STD" "$build_language" "$softVersion" "$resultName.zip"
zipName="${resultName}.zip"
iniName="${resultName}.ini"
# 将所有文件进行压缩
zip -q -r $zipName "${BUILD_PATH}/xxx/"
# 将压缩包剪切到output文件夹
mv -f "${BUILD_PATH}/xxx/$iniName" "${ROOT_PATH}/output/"
mv -f "${BUILD_PATH}/xxx/$zipName" "${ROOT_PATH}/output/"
## 将打包的成果物和ini文件复制到output文件夹中
#rm -rf "${ROOT_PATH}/output/"
#mkdir -p "${ROOT_PATH}/output/"
#read_dir_cp_dst "${BUILD_PATH}/xxx/" "${ROOT_PATH}/output/"
### 定位到outpu文件夹
#cd "$ROOT_PATH/output/"
# 获取当前目录下时间最新的zip文件名
#file_name=$(ls -lt "${BUILD_PATH}/xxx/" | grep '\.zip' | head -n 1 | awk '{print $NF}'|awk -F '.zip' '{print$1}')
#echo "mkdir file_name:$file_name+build_language:$build_language+softVersion:$softVersion+infileName:$infileName"
#generate_package_config_init "xxx" "STD" "$build_language" "$softVersion" "$infileName.zip"
## 分析最新的zip文件名中的日期是否和当日的日期一致
#echo "fileDate:$file_name-$(echo $file_name|awk -F '_' '{print $NF}')"
#if [[ $(date +%y%m%d) == $(echo $file_name|awk -F '_' '{print $NF}') ]]; then
# cp -rvf "$BUILD_PATH/xxx/${file_name}.zip" "${ROOT_PATH}/output/"
# # generate ini
# config_name_prefix=${ROOT_PATH}/$file_name
# package_config=$(echo $file_name | awk -F "_" '{print$(NF-2)}')
# language=$(echo $file_name | awk -F "_" '{print$(NF-3)}')
# softVersion=$(echo $file_name | awk -F "_" '{print"V"$(NF-1)"_"$(NF)}' )
# generate_package_config_init ${config_name_prefix} ${package_config} ${language} ${softVersion} ${file_name}.zip
# # add debug ezapp_nostrip
# cp ${ROOT_PATH}/build/ezapp_nostrip ${OUTPUT_PATH}
# cd ${ROOT_PATH}
# zip ${file_name}_ELF.zip ezapp_nostrip
# rm ezapp_nostrip
# tree ${ROOT_PATH}
#else
# echo "build failed,not found generate file"
#fi
exit 0
# 遍历所有$1文件夹下所有文件拷贝到$2
function read_dir_cp_dst(){
for file in `ls $1`
do
if [ -d $1"/"$file ]
then
read_dir_cp_dst $1"/"$file $2
else
file_path=$1"/"$file
cp $file_path $2
fi
done
}
# 使用平台配置的组件打包,$1平台组件目录 $2平台产品目录
function use_component_file(){
temp_dir=$(pwd)/temp_compent
if [ ! -d "${temp_dir}" ]; then
echo "${temp_dir} not found and mkdir "
mkdir -p ${temp_dir}
else
echo "${temp_dir} exist delete"
rm -rf ${temp_dir}
fi
read_dir_cp_dst $1 ${temp_dir}
for file in `ls ${temp_dir}`
do
product_file=$(find $2 -name $file)
if [ ${#product_file} -gt 0 ];then
echo "cp ${temp_dir}/$file ${product_file}"
cp ${temp_dir}/$file ${product_file}
if [ $? -ne 0 ] ; then
echo "replace ${file} and use platform file failed"
exit 1
else
echo "replace ${file} and use platform file success"
fi
fi
done
echo "clean tempdir rm -rf ${temp_dir}"
rm -rf ${temp_dir}
}
#平台传递必选参数:buildType为release或debug;OUTPUT_PATH为绝对路径;commonPath为组件存放路径;component为依赖组件json信息,可解析成modelList组件名列表
buildType="${1}"
OUTPUT_PATH="${2}"
build_product="${3}"
ROOT_PATH=$(cd "$(dirname "$0")"/../..;pwd)
PRODUCT_WORK_PATH=${ROOT_PATH}/ci/${build_product}
# 检查目标产品路径是否存在,如果不存在直接退出。
if [ ! -d "${PRODUCT_WORK_PATH}" ]; then
echo ${PRODUCT_WORK_PATH}" not exist exit"
exit 1
fi
# 使用平台配置的组件打包
if [ ${buildType} == "release" ]; then
use_component_file ../common ${PRODUCT_WORK_PATH}
fi
# 根据不同产品设置不同的编译
source ${PRODUCT_WORK_PATH}/build.sh
build_tool_prefix=${CROSS_TOOL_PREFIX}
if [[ ${#build_tool_prefix} == 0 ]]; then
echo "empty build toold prefix"
exit 1
fi
echo "build ${build_product} cross tool prefix ${CROSS_TOOL_PREFIX}"
#定义运行脚本路径及参数
if [ ${buildType} == "release" ]; then
BUILD_PATH=${PRODUCT_WORK_PATH}
shift 3
BUILD_CMD="./build.sh ${build_tool_prefix} $@"
elif [ ${buildType} == "debug" ] then
BUILD_PATH=${PRODUCT_WORK_PATH}
shift 3
BUILD_CMD="./buildDebug.sh ${build_tool_prefix} $@"
else
echo "[ERROR]:***************************unknow buildType ${buildType}******************************"
usage
exit 1
fi
echo "build ${BUILD_PATH} ${BUILD_CMD}"
#执行构建
build "${BUILD_PATH}" "${BUILD_CMD}"
打包脚本如下:
#!/bin/sh
export LANG=en_US.UTF-8
#计时
SECONDS=0
# 项目名称
#project_name=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
project_name='xxx'
echo "工程名是: $project_name"
# 当前时间(用于区分目录名打包时间)
now=$(date +"%Y-%m-%d_%H-%M-%S")
echo "打包时间是: $now"
# scheme名称
scheme_name=${project_name}
echo "scheme名称是: $scheme_name"
# 项目路径
# ~/NB-IOS/v1.0.0/ci/build
build_path=$(pwd)
ci_path=$(dirname "$(pwd)")
podfile_path=$(dirname "$ci_path")
echo "build_path:$build_path"
echo "podfile_path:$podfile_path"
version=$(xcodebuild -version | awk 'NR == 1 { print $2 }')
major_version=$(echo $version | cut -d'.' -f1)
if [ "$major_version" == "14" ]; then
echo "Xcode version is 14"
cd ${build_path}
echo "进入${build_path}文件夹"
echo '=============正在移除-ld_classic配置============='
ruby ./remove.rb
else
echo "Xcode is not 14"
fi
#导出目录
#export_path="./ci/build/${project_name}_${now}"
export_path="${build_path}/${project_name}"
echo "导出目录是: $export_path"
#ipa包路径(用于检验是否导出成功)
export_ipa_path="${export_path}/${project_name}.ipa"
echo "ipa包路径是: $export_ipa_path"
#编译build路径
archive_path="${export_path}/${project_name}.xcarchive"
echo "编译build路径是: $archive_path"
# 打包配置plist文件路径【这个文件需要先创建】
plist_path="${build_path}/archive_release.plist"
#打包方式
#build_type="project"
# workspace/xcodeproj 路径(根据项目是否使用cocoapod,确定打包的方式)
#if [ -e "${podfile_path}${project_name}-iOS.xcworkspace" ];then
workspace_path="${project_name}-iOS.xcworkspace"
build_type="workspace"
# 执行pod
cd ${podfile_path}
pod install
#else
#workspace_path="${podfile_path}/${project_name}.xcodeproj"
echo "工程路径是: $workspace_path"
#build_type="project"
#fi
#echo ${workspace_path}
# scheme名称
scheme_name=${project_name}
# 配置打包样式:Release/ad-hoc/Debug
configuration='Release'
echo "打包样式是: $configuration"
echo '=============正在清理工程============='
echo $configuration
xcodebuild \
clean -${build_type} ${workspace_path} \
-scheme ${scheme_name} \
-configuration ${configuration} -quiet || exit
echo '清理完成-->>>--正在编译工程:'${workspace_path}
# build
if [ -d ${workspace_path} ];then
xcodebuild archive -${build_type} ${workspace_path} \
-scheme ${scheme_name} \
-configuration ${configuration} \
-archivePath ${archive_path} -quiet || exit
else
echo 'workspace 不存在'
fi
# 检查是否构建成功(build)
if [ -d ${archive_path} ] ; then
echo '=============项目 build 成功============='
else
echo '=============项目 build 失败============='
exit 1
fi
# exprot
echo '编译完成-->>>--开始ipa打包'
xcodebuild -exportArchive -archivePath ${archive_path} \
-configuration ${configuration} \
-exportPath ${export_path} \
-exportOptionsPlist ${plist_path} \
-quiet || exit
if [ -e ${export_ipa_path} ]; then
#删除编译包文件
rm -rf $archive_path
echo '=============ipa包导出成功============='
else
echo '=============ipa包导出失败============'
echo "${export_ipa_path}"
exit 1
fi
#打包完成,打开目录
#open ${export_path}
# 输出总用时
echo "执行耗时: ${SECONDS}秒"
exit 0
注意:
由于Xcode15采用了新的链接器(
Linker
),被称作ld_prime
。新的连接器有诸多好处,尤其是对合并库的支持方面。然而,链接器的升级可能会出现不兼容老库的情况出现。遇到这种情况,可以通过恢复旧的连接器来解决这个问题。从Other Linker Flags
添加-ld_classic
选择使用旧的链接器,而不是默认的新的-ld_prime
链接器。同时由于服务器还是使用xcode 14
,所以目前只能在release
环境下去掉-ld_classic
,用来暂时支持自动打包,后续再研究其他兼容性方案。
后续:
为了解决上述
注意:
中遗留的问题,首先思考能否使用脚本的方式移除xcode
的某些配置项,因为我们的打包流程全部使用的脚本来实现的,所以采用脚本也在可行性的考虑范围内。最后找到了用脚本的方式动态移除-ld_classic
的配置项的解决方案(该脚本是.rb
文件)。上述文章中的脚本也是添加完移除配置项的最新脚本
具体代码如下:
#!/usr/bin/env ruby
require 'xcodeproj'
taget_name='xxx'
current_folder_path = __dir__
ci_folder_path = File.dirname(__dir__)
podfile_folder_path = File.dirname(ci_folder_path)
#xcodeproj_path="$podfile_path/$project_name.xcodeproj"
xcodeproj_path="#{podfile_folder_path}/#{taget_name}.xcodeproj"
puts "xcodeproj_path:#{xcodeproj_path}"
project = Xcodeproj::Project.open(xcodeproj_path)
target=project.targets.find { |t| t.name == "#{taget_name}" }
target.build_configurations.each do |config|
flags = config.build_settings['OTHER_LDFLAGS']
flags.reject! { |flag| flag == '-ld_classic' }
config.build_settings['OTHER_LDFLAGS'] = flags
end
project.save
puts "移除 -ld_classic 成功"