构建CICD

由于公司要求构建自己的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 成功"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值