iOS shell 脚本打包

!/bin/bash

export LANG=en_US.UTF-8

报错后退出执行

set -o errexit

要区分 scheme_name

UAT 开发+测试

Prd ECDH服务端新公钥

APPSTORE 上线的target

要区分 导出method

ad-hoc

release-testing

app-store-connect

要区分 上传 (加上加固 - 默认上传)

http://****:7979/app/ios

sit(test)|dev(开发联调提供给服务端开发使用) 测试环境

|uat| 正式提测(是测试人员拿包自行上传)

preprod 预生产(app人员上传)

pro (不使用)

production (不上传)

appstore/testflight (加上加固 - 默认上传)

脚本放在工程目录下

原 导出的包放在桌面/IPA/scheme_name/

新 导出的包放在桌面/xcodeBulid/scheme_name

最新加固工具放在

桌面/bulid/shield/doc

桌面/bulid/shield/SDK

桌面/bulid/shield/Shielder

旧版本归档

桌面/bulid/旧版本加固工具/Shield_iOS7.0.1.140843

定义 display_help 函数

display_help() {
echo ""
echo -e "\033[32m🍏欢迎使用iOS打包脚本, 请按照指引使用🍏\033[0m"
echo -e " 脚本名:\033[32mxcodebuildscript.sh\033[0m"
echo -e " 模式:\033[32m iOS打包主脚本\033[0m"
echo ""
echo -e "\033[33m 使用:\033[0m"
echo -e "\033[33m [脚本文件] [选项] [选项]\033[0m (使用打包脚本并使用对应配置)"
echo ""
echo -e "\033[33m 方式一:\033[31m[key] [value]\033[0m 选项:\033[0m"
echo -e " -t, --target【选项】指定项目的scheme名称(也就是工程的target名称)后面加【选项】"
echo -e " uat对应UAT"
echo -e " prd对应Prd"
echo -e " appstore对应APPSTORE"
echo ""
echo -e " -m, --method【选项】打包方式 后面加【选项】"
echo -e " dev对应release-testing"
echo -e " dis对应app-store-connect"
echo ""
echo -e "\033[33m 示例:\033[0m"
echo -e " 脚本目录/xcodebuild_only_modify.sh -t uat -m dev"
echo ""
echo -e " 脚本目录/xcodebuild_only_modify.sh -t appstore -m dis"
echo ""
echo -e " 脚本目录/xcodebuild_only_modify.sh -target appstore -method dis"
echo ""
echo ""
echo -e "\033[33m 方式二:\033[31m[value]\033[0m 选项:\033[0m"
echo -e " -uat, \033[33muat对应UAT\033[0m"
echo -e " -prd, \033[33mprd对应Prd\033[0m"
echo -e " -appstore, \033[33mappstore对应APPSTORE\033[0m"
echo ""
echo -e " -dev, -adhoc, \033[33m打测试安装包,adhoc模式\033[0m"
echo -e " -dis, -appstore, \033[33m打生产/testflight包,appstore模式\033[0m"
echo ""
echo -e "\033[33m 示例:\033[0m"
echo ""
echo -e " 脚本目录/xcodebuild_only_modify.sh -prd -dis"
echo ""
echo -e " 脚本目录/xcodebuild_only_modify.sh -appstore -dis"
echo ""
echo -e "\033[32m如不输入参数,则会在后续过程中输入选择\033[0m"
echo ""
echo ""
}

if [[ "1" == "-h" || "1" == "-help" ]]; then
display_help
exit 0
fi

echo -e "\033[32m =================================================== \033[0m"
echo -e "\033[32m ============= 开始执行 iOS打包脚本 ============= \033[0m"
echo -e "\033[32m =================================================== \033[0m"

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

is_workspace="true"

.xcworkspace的名字,如果is_workspace为true,则必须填。否则可不填

workspace_name="*****"

.xcodeproj的名字,如果is_workspace为false,则必须填。否则可不填

project_name="*****"

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

build_configuration="Release"

签名方式,目前****都是手动管理 manual/automatic

signing_style="manual"

team_id="9595***569"

项目的scheme名称(也就是工程的target名称)

scheme_name=""

导出ipa方式

旧方式分别为: development, ad-hoc, app-store, enterprise,

新方式为:release-testing,

method=""

描述文件

mobileprovision_name=""

bundleid

bundle_identifier=""

function input_error() {
echo -e "\033[31m❗️❗️❗️脚本格式错误, 请使用 [-h] 或 [-help]查看使用方法 \033[0m"
echo ""
display_help
exit 1
}

基于参数配置target、描述文件、bundleid

function set_targetname() {
if [[ "1" == "uat" ]]; then scheme_name="UAT" elif [[ "1" == "prd" ]]; then
scheme_name="Prd"
elif [[ "$1" == "appstore" ]]; then
scheme_name="APPSTORE"
else
input_error
fi
}

基于参数配置导出方法

function set_method() {
if [[ "$1" == "dev" ]]; then
method="release-testing"

    if [[ "$scheme_name" == "UAT" ]]; then
        
        mobileprovision_name="***.uat.adhoc"
        bundle_identifier="***.uat"

    elif [[ "$scheme_name" == "Prd" ]]; then
        
        mobileprovision_name="***.uat.adhoc"
        bundle_identifier="***.uat"

    elif [[ "$scheme_name" == "APPSTORE" ]]; then

        mobileprovision_name="***.prod.adhoc"
        bundle_identifier="***.prod"
    else
        input_error
    fi

elif [[ "$1" == "dis" ]]; then
    method="app-store-connect"

    if [[ "$scheme_name" == "UAT" ]]; then
        
        mobileprovision_name="***.uat.dis"
        bundle_identifier="***.uat"

    elif [[ "$scheme_name" == "Prd" ]]; then
        
        mobileprovision_name="***.uat.dis"
        bundle_identifier="***.uat"

    elif [[ "$scheme_name" == "APPSTORE" ]]; then

        mobileprovision_name="***.prod.dis"
        bundle_identifier="***.prod"
    else
        input_error
    fi
    
else
    input_error
fi

}

选择target

function choose_target() {

if [ "$scheme_name" = "" ]; then

    echo -e "\033[35m 请选择打包target:\033[0m"
    echo -e "\033[33m 1)UAT"
    echo -e " 2)Prd"
    echo -e " 3)APPSTORE"
    echo -e "\033[0m"
    read choice

    case $choice in
    1) set_targetname "uat" ;;

    2) set_targetname "prd" ;;

    3) set_targetname "appstore" ;;

    *) {
        echo -e "\033[31m 无效输入,重新选择 \033[0m"
        echo -e ""
        choose_target
    } ;;
    esac
fi

}

选择method,打包的方式

function choose_method() {

if [ "$method" = "" ]; then

    echo -e "\033[35m 请选择打包的方式method:\033[0m"
    echo -e "\033[33m 1)release-testing"
    echo -e " 2)app-store-connect"
    echo -e "\033[0m"
    read choice

    case $choice in
    1) set_method "dev" ;;
    2) set_method "dis" ;;
    *)
        echo -e "\033[31m 无效输入,重新选择 \033[0m"
        choose_method
        ;;
    esac
fi

}

判断参数key是否存在

function print_msg() {
if [ "2" = "null" ]; then echo -e "\033[31m(printf "%-20s = %s\n" "1" "空")\033[0m" elif [ "2" = "" ]; then
echo -e "\033[33m(printf "%-20s = %s\n" "1" """")\033[0m"
else
echo -e "\033[33m(printf "%-20s = " "1")\033[0m\033[31m(printf "%s\n" "2")\033[0m"
fi
}

if [[ "1" == "-t" || "1" == "--target" ]]; then
set_targetname "2" if [[ "3" == "-m" || "3" == "--method" ]]; then set_method "4"
else
input_error
fi
elif [[ "1" == "-uat" || "1" == "-prd" || "1" == "-appstore" ]]; then target_name_param="1"
set_targetname "{target_name_param:1}" if [[ "2" == "-dev" || "2" == "-dis" ]]; then method_param="2"
set_method "{method_param:1}" else input_error fi elif [[ "1" == "" ]]; then
echo -e ""
echo -e "\033[35m 未输入配置,需手动选择配置 \033[0m"
echo -e ""
choose_target
choose_method
else
input_error
fi

echo -e ""
echo -e "\033[32m ============= 脚本配置参数检查 ============= \033[0m"
print_msg is_workspace is_workspace print_msg workspace_nameworkspace_name
print_msg project_name project_name print_msg scheme_namescheme_name
print_msg build_configuration build_configuration print_msg bundle_identifierbundle_identifier
print_msg mobileprovision_name mobileprovision_name print_msg methodmethod

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

获取当前脚本所在目录

script_dir="(cd "(dirname "$0")" && pwd)"

工程根目录

project_dir=$script_dir

进入项目工程目录

cd ${project_dir}

读取旧的version版本号 原脚本代码中需要修改buildVerison 但不是改的xcodeproject文件中,而是改的plist文件

oldBuildNumber=(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "infoPlist_path")

echo "打包build号:$oldBuildNumber"

打包版本号

versions=(xcodebuild -project "project_dir/project_name.xcodeproj" -scheme "scheme_name" -showBuildSettings | grep MARKETING_VERSION | awk '{print 3}') echo "打包版本号:versions"

读取旧的版本号

oldBuildNumber=(xcodebuild -project "project_dir/project_name.xcodeproj" -scheme "scheme_name" -showBuildSettings | grep CURRENT_PROJECT_VERSION | awk '{print 3}') echo "打包build号:oldBuildNumber"

获取当前设备当前用户的根路径

adm_path=$HOME

时间

now_date=$(date '+%Y-%m-%d_%H-%M-%S')

指定输出导出文件夹路径

package_path="$adm_path/Desktop/XcodeBuild"

例如~/Desktop/XcodeBuild/****/UAT/ad-hoc/2024-10-30_10-44-18

export_path="package_path/project_name/scheme_name/method/$now_date"

判断是否为目录,创建空目录

if [ ! -d "export_path" ]; then mkdir -vp "export_path"
fi

指定输出.xcarchive归档文件路径

export_archive_path="export_path/scheme_name.xcarchive"

指定输出ipa文件夹路径

export_ipa_path="{export_path}/"scheme_name"_v"versions"_"method""

指定导出ipa包需要用到的plist配置文件的路径

export_options_plist_path="$export_path/ExportOptions.plist"

echo ""
echo -e "\033[32m ============= infoPlist修改参数检查(buildVersion修改) ============= \033[0m"
echo ""

查找info.plist 文件的路径(得到的是相对路径)

infoPlist_path_relative=(find . -path './Package' -a -prune -o -iname "{scheme_name}*.plist" -print)

info.plist 的绝对路径

infoPlist_path_absolute={script_dir}{infoPlist_path_relative:1}

info.plist 的路径

infoPlist_path=$infoPlist_path_absolute

新build 版本号

buildNumber=$(date '+%Y%m%d%H%M')

指定输出ipa名称 未加固

ipa_name=""scheme_name"_v{versions}_${buildNumber}_noshield"

已加固

shield_ipa_name=""scheme_name"_v{versions}_${buildNumber}_shield"

dSYM路径

package_dsym_path="export_archive_path/dSYMs/scheme_name.app.dSYM"

dSYM导出路径

new_dsym_path="export_ipa_path/ipa_name.dSYM"

if [ ! "oldBuildNumber" ]; then echo "旧buildVersion不存在,新增..." /usr/libexec/PlistBuddy -c "Add :CFBundleVersion String{buildNumber}" "infoPlist_path" echo "新buildVersion={buildNumber}"
else
echo "旧buildVersion存在={oldBuildNumber},修改..." /usr/libexec/PlistBuddy -c "Set :CFBundleVersion{buildNumber}" "infoPlist_path" echo "新buildVersion={buildNumber}"
fi

echo ""
echo -e "\033[32m ============= 脚本固定参数检查 ============= \033[0m"
echo ""
print_msg project_dir {project_dir} print_msg now_date{now_date}
print_msg export_path {export_path} print_msg export_archive_path{export_archive_path}
print_msg export_ipa_path {export_ipa_path} print_msg export_options_plist_path{export_options_plist_path}
print_msg ipa_name ${ipa_name}

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

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

指定输出文件目录不存在则创建

if [ -d "export_path" ]; then echoexport_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}
-destination 'generic/platform=iOS'
-quiet || exit # -quiet只有warn和error才会输出

xcodebuild archive -allowProvisioningUpdates \
    -workspace ${workspace_name}.xcworkspace \
    -scheme ${scheme_name} \
    -configuration ${build_configuration} \
    -archivePath ${export_archive_path} \
    -destination 'generic/platform=iOS' \
    -quiet || exit # -quiet只有warn和error才会输出

else
# 编译前清理工程
xcodebuild clean -project {project_name}.xcodeproj \ -scheme{scheme_name}
-configuration ${build_configuration} -destination 'generic/platform=iOS'

xcodebuild archive -project ${project_name}.xcodeproj \
    -scheme ${scheme_name} \
    -configuration ${build_configuration} \
    -archivePath ${export_archive_path} -destination 'generic/platform=iOS'

fi

检查是否构建成功

xcarchive 实际是一个文件夹不是一个文件所以使用 -d 判断

if [ -d "$export_archive_path" ]; then
echo -e "\033[32m项目构建成功 🚀 🚀 🚀 \033[0m"
else
echo -e "\033[31m项目构建失败 😢 😢 😢 \033[0m"
exit 1
fi

echo ""
echo -e "\033[32m =============== 正在导出ipa: "${export_ipa_path}" =============== \033[0m"
echo ""

先删除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 :destination string export" "export_options_plist_path" /usr/libexec/PlistBuddy -c "Add :compileBitcode Bool false"export_options_plist_path
/usr/libexec/PlistBuddy -c "Add :method String {method}"export_options_plist_path
/usr/libexec/PlistBuddy -c "Add :signingStyle string {signing_style}" "export_options_plist_path"
/usr/libexec/PlistBuddy -c "Add :stripSwiftSymbols bool true" "export_options_plist_path" /usr/libexec/PlistBuddy -c "Add :thinning string <none>" "export_options_plist_path"
/usr/libexec/PlistBuddy -c "Add :teamID string {team_id}" "export_options_plist_path"
/usr/libexec/PlistBuddy -c "Add :provisioningProfiles dict" export_options_plist_path /usr/libexec/PlistBuddy -c "Add :provisioningProfiles:{bundle_identifier} String {mobileprovision_name}"export_options_plist_path

xcodebuild -exportArchive
-archivePath {export_archive_path} \ -exportPath{export_ipa_path}
-exportOptionsPlist ${export_options_plist_path}
-allowProvisioningUpdates -destination 'generic/platform=iOS'

检查ipa文件是否存在

if [ -f "export_ipa_path/scheme_name.ipa" ]; then
echo -e "\033[32mexportArchive ipa包成功,准备进行重命名\033[0m"
else
echo -e "\033[31mexportArchive 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 -e "\033[32m导出 {ipa_name}.ipa 包成功 🎉 🎉 🎉 \033[0m" openexport_ipa_path
else
echo -e "\033[31m导出 ${ipa_name}.ipa 包失败 😢 😢 😢 \033[0m"
exit 1
fi

开始加固

echo -e "\033[32m ==============开始执行加固命令================== \033[0m"
echo ""
shield_tool_path=""package_path"/shield/Shielder/Shielder.jar" shield_config_path=""package_path"/shield/Shielder/config-release-template.xml"

java -jar shield_tool_path --standalone --configshield_config_path export_ipa_path/ipa_name.ipa --out export_ipa_path/shield_ipa_name.ipa
echo ""
echo -e "\033[32m ==============加固成功!================== \033[0m"
echo ""

输出打包总用时

echo -e "\033[32m 🚀🚀🚀打包脚本执行总用时为 ${SECONDS} 秒 \033[0m"

选择上传到哪个环境

function choose_uploadToQR() {
echo "\033[32m 请选择上传环境:\033[0m"
echo "\033[33m 1)上传 dev 环境二维码"
echo " 2)上传 uat 环境二维码"
echo " 3)上传 preprod 环境二维码"
echo " *)不上传\033[0m"

read choice

case $choice in
1) system="dev" ;;
2) system="uat" ;;
3) system="preprod" ;;
*)
    echo "\033[31m 无效选择 \033[0m"
    exit 1
    ;;
esac

}

上传到二维码

uploadToQR() {
echo -e "\033[32m =============== 执行app上传到内部平台脚本 =============== \033[0m"

osType="ios"
appName="***_*****"
bundleId=$bundle_identifier
versionCode=$1 # Assume the first argument to the function is the versionCode
ipa_name=$2    # Assume the second argument is the ipa name
system=$3      # Assume the third argument is the environment (dev/sit/uat/preprod/pro/production)

# Derived variables
app_file_name="$ipa_name.ipa"
UPLOAD_URL="http://192.168.1.19:7979/app/ios"
APP_FILE_PATH="$export_ipa_path/$ipa_name.ipa"

print_msg "Uploading package" $app_file_name
print_msg "bundleId" $app_file_name
print_msg "Environment" $system
print_msg "Version number" $versionCode
print_msg "Local package path" $APP_FILE_PATH

upload_response=$(curl -X POST "$UPLOAD_URL" \
    -F "osType=$osType" \
    -F "appName=$appName" \
    -F "system=$system" \
    -F "versionCode=$versionCode" \
    -F "bundleId=$bundleId" \
    -F "fileName=@$APP_FILE_PATH")

# Extract and print the link
link=$(echo $upload_response | sed -n 's/.*<p>\(https*:\/\/[^<]*\)<\/p>.*/\1/p')
echo -e "\033[32miOS ipa file upload result:\033[0m\033[31m$link\033[0m"

}

function uploadToAppstore() {

# app-store-connect,可以选择是否上传Appstore
echo -e "\033[32m =============== 执行app包上传Appstore脚本 =============== \033[0m"
# echo ""
# echo -e "\033[0;35m生产包/testflight,可以选择是否上传Appstore(请输入Y/N): \033[0m"
# read is_upload

# # 判断 is_upload 是否为 "Y" 或 "y"
# if [[ "$is_upload" = "Y" ]] || [[ "$is_upload" = "y" ]]; then
#     echo ""
# else
#     echo -e "\033[0;32m 不上传包,上传脚本执行完成 \033[0m"
#     exit 0
# fi

# upload_app_appstore_ipa文件路径
uas_appFile="$1"

apple_username="****@qq.com"
apple_password="*******"

# 提示用户输入
echo -e "\033[32m =============== 执行app验证脚本 =============== \033[0m"
echo ""
xcrun altool --validate-app --type ios --file "$uas_appFile" --username "$apple_username" --password "$apple_password" --show-progress
echo ""
echo -e "\033[32m =============== 执行app包上传Appstore脚本 =============== \033[0m"
echo ""
xcrun altool --upload-app --type ios --file "$uas_appFile" --username "$apple_username" --password "$apple_password" --show-progress
echo ""

}

ad-hoc 则上次到内部

if [[ "method" == "release-testing" ]]; then # 选择上传到哪个环境 choose_uploadToQR # 上传ipa到内部二维码 uploadToQRversions shield_ipa_namesystem

elif [[ "$method" == "app-store-connect" ]]; then

echo ""
echo -e "\033[32m =============== 执行导出dSYMs文件 =============== \033[0m"
echo ""
cp -R "$package_dsym_path" "$new_dsym_path"
echo -e "\033[0;32m已提取dSYM文件到ipa目录文件夹:"$new_dsym_path"\033[0m"
echo ""

# 上传ipa到appstore/testflight
uploadToAppstore "$export_ipa_path/$shield_ipa_name.ipa"

fi

exit 1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容