IOS项目脚本自动化批量打包并上传第三方平台

由于公司项目需求然后想要一个项目打包多个包,然后app名字、bundleId、服务器连接URL、appIocn、app项目内容渠道参数不一样,针对网上看了一些教程和方式,现在总结一下。

如果想要知道单个项目脚本打包请查看IOS脚本单独打包

一、bash脚本

bash 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。bash 既是一种命令语言,又是一种程序设计语言。bash脚本基础学习

项目目录结构

pp97uJs.png

结构说明:

MutaPackageScript.sh : 为脚本文件 (批量时部分配置修改)

Plist:为打包4种打包模式 AdHoc、AppStore、Development、Enterprise (不需要动)

1
2
3
4
5
6
7
8
9
10
11
12
<?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>method</key>
<string>development</string>//这儿development、enterprise、ad-hoc、app-store四种
<key>signingStyle</key>
<string>automatic</string>
<key>compileBitcode</key>
<false/>
</dict>
</plist>

AppIcon: 为要打包的替换的AppIcon图系列,这儿首先要用工具生成好所有图,名字必须是AppIcon~系列的,原因是同一个包脚本会替换覆盖操作。我是使用的一个Asset Catalog Creator 你自己使用其他的也行,能生成就行。(需要使用工具生成)

pp9bZCj.md.png

目录子目录文件夹就是当打包多个的时候AppIcon文件目录,名字对应的脚本文件的里的配置文件appIconArr参数的名字,修改app文件配置参数,所以如果新增APP时请修改新增此脚本参数,不然会找不到APPIcon文件图片替换目录。

1
2
3
4
appIconArr=(
"yqb_AppIcon"
"lebzs_AppIcon"
)

这儿启动图和引导图都可以放在这个里面,然后会同一替换,主要图片名字记得保持一致,例如在项目中启动图叫

launch_s.png,重新打包的时候也请命名为launch_s.png放在AppIcon文件夹包里。

ppCpfpT.png

二、开始打包操作

  • 操作脚本
1
2
cd /Users/mac/Desktop/您的项目名称/MutaPackageScript
sh MutaPackageScript.sh

上面要cd到当前项目文件夹下的MutaPackageScript脚本目录,原因是为了当其他项目在不同的目录路径下时,获取到项目层级会不一致,不同的电脑上可能不是同一个,所以cd项目时需要先项目目录文件下MutaPackageScript目录下。

所有脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/bin/bash

# Script.sh
# Package
# Created by wws on 2023/02/25.
# Copyright © 2023 年 wws. All rights reserved.

# 1.Configuration Info

# 是否编译工作空间 (例:若是用Cocopods管理的.xcworkspace项目,赋值true;用Xcode默认创建的.xcodeproj,赋值false)
is_workspace="true"
# 指定要打包编译的方式 : Release,Debug...
build_configuration="Release"

# 返回上一级目录,进入项目工程目录
cd ..
# 获取项目名称
projectName=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
# Scheme Name
schemeName=${projectName}
#cd ~/Desktop/${schemeName}
# 图标路径
iconPath="./MutaPackageScript/AppIcon"
# 打包所有生成路径
ipaPath=~/Desktop/IPAS
# 临时生成APP路径
buildDir="${ipaPath}/build/${schemeName}.xcarchive"

plistPath="./MutaPackageScript/Plist"
# 导出ipa所需要的plist文件路径 (默认为AdHocExportOptionsPlist.plist)
exportOptionsPlistPath="${plistPath}/AdHocExportOptionsPlist.plist"

#app名字
displayNameArr=(
"app1的名字"
"app2的名字"
)
#app的bundleId
bundleIdentifierArr=(
"com.qyf.qyf"
"com.qyf.ysf"
)
#获取URL链接的主机地址编号
hostNumberArr=(
"A022"
"A021"
)
#修改app文件地址
appIconArr=(
"yqb_AppIcon"
"lebzs_AppIcon"
)
#打包渠道号
channelCodeArr=(
"112"
"115"
)
#版本号
versionArr=(
"1.0.3"
"1.0.4"
)
#服务器默认连接的地址
serverArr=(
"https://www.xxx.com/app1/"
"https://www.xxx.com/app2/"
)

# 开始时间
beginTime=`date +%s`

# 清除缓存
rm -rf mkdir ${ipaPath}
# 创建打包目录
mkdir ${ipaPath}

# 蒲公英分发参数 不分发可忽略 默认不分发 下面的两个KEY是默认测试的网址对应KEY 换成你自己的
isUploadPgyer=0
USERKEY="xxx"
APIKEY="xxx"

# fir分发参数 不分发可忽略 默认分发 Token换成你自己的
isUploadFir=1
FirApiToken="931b41960fd413455496b9465ec910adb97"

# AdHoc,AppStore,Enterprise,Development四种打包方式
echo "\033[36;1m请选择打包方式(输入序号,按回车即可) \033[0m"
echo "\033[33;1m1. AdHoc \033[0m"
echo "\033[33;1m2. AppStore \033[0m"
echo "\033[33;1m3. Enterprise \033[0m"
echo "\033[33;1m4. Development \033[0m"
#读取用户输入并存到变量里
read parameter
sleep 0.5
method="$parameter"

# 判读用户是否有输入
if [ -n "$method" ]
then
if [ "$method" = "1" ] ; then
exportOptionsPlistPath="${plistPath}/AdHocExportOptionsPlist.plist"
elif [ "$method" = "2" ] ; then
exportOptionsPlistPath="${plistPath}/AppStoreExportOptionsPlist.plist"
elif [ "$method" = "3" ] ; then
exportOptionsPlistPath="${plistPath}/EnterpriseExportOptionsPlist.plist"
elif [ "$method" = "4" ] ; then
exportOptionsPlistPath="${plistPath}/DevelopmentExportOptionsPlist.plist"
else
echo "输入的参数无效!!!"
exit 1
fi
fi

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

path="${ipaPath}/build"
# 删除临时旧的xcarchive文件和打包项目
rm -rf mkdir ${path}
mkdir ${path}

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

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

xcodebuild archive -workspace ${projectName}.xcworkspace \
-scheme ${schemeName} \
-configuration ${build_configuration} \
-archivePath ${buildDir}
else
# 编译前清理工程
xcodebuild clean -project ${projectName}.xcodeproj \
-scheme ${schemeName} \
-configuration ${build_configuration}

xcodebuild archive -project ${projectName}.xcodeproj \
-scheme ${schemeName} \
-configuration ${build_configuration} \
-archivePath ${buildDir}
fi

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

appPackNum=${#displayNameArr[*]}
# ----全部打包----
for (( i=0; i<appPackNum; i++ )); do
# App DisPlay Name 打包名字
displayName=${displayNameArr[${i}]}
# App BundleIdentifier 打包标识ID
bundleIdentifier=${bundleIdentifierArr[${i}]}
# App Icon File Name 打包AppIocn文件路径
appIcon=${appIconArr[${i}]}
# App channelCode 打包渠道
channelCode=${channelCodeArr[${i}]}
# App hostNumber 请求的主机地址编号
hostNumber=${hostNumberArr[${i}]}
# App version 版本号
version=${versionArr[${i}]}
# App hostUrl 服务器地址
hostUrl=${serverArr[${i}]}

# 创建不同 app ipa 目录
rm -rf $ipaPath/${displayName}
mkdir $ipaPath/${displayName}

echo "33[31m appName:$displayName appIconName:$appIcon appChannelName:$channelCode bundleID:$bundleIdentifier n 33[0m"

# 将对应的 icon 复制到需要修改的 app 的目录下
# .xcarchive 文件下对应的Applications文件路径
Applications_Path=${buildDir}/Products/Applications
cp -Rf `${iconPath}/${appIcon}/*` `$Applications_Path/*.app`

if [[ $? = 0 ]]; then
echo "33[31m 修改 icon 成功33[0m"
else
echo "33[31m 修改 icon 失败33[0m"
fi
# 修改 Plist
# plist路径
infoPlist_File_Path=`$Applications_Path/*.app/info.plist`

/usr/libexec/PlistBuddy -c "Set :CFBundleName $displayName" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $displayName" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $bundleIdentifier" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :ChannelCode $channelCode" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :HostNumber $hostNumber" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $version" $infoPlist_File_Path
/usr/libexec/PlistBuddy -c "Set :HostUrl $hostUrl" $infoPlist_File_Path

if [[ $? = 0 ]]; then
echo "33[31m 修改 Plist 成功33[0m"
else
echo "33[31m 修改 Plist 失败33[0m"
fi

# 重签名
#codesign -f -s "iPhone Distribution: Beijing Waming Environmental Technology Co., Ltd" --entitlements $exportOptionsPlistPath ${ipaPath}/Payload/${schemeName}.app
#if [[ $? = 0 ]]; then
#echo " 签名成功n "
#else
#echo " 签名失败n "
#fi

# 生成 ipa
xcodebuild -exportArchive -archivePath ${ipaPath}/build/${schemeName}.xcarchive -exportOptionsPlist ${exportOptionsPlistPath} -exportPath ${ipaPath}/${displayName} -allowProvisioningUpdates ${YES} -allowProvisioningDeviceRegistration ${YES}

if [[ $? = 0 ]]; then
echo "33[31m n 生成 IPA 成功 nnnnn33[0m"
else
echo "33[31m n 生成 IPA 失败 nnnnn33[0m"
fi

rm -rf ${ipaPath}/${displayName}/${projectName}.xcarchive

# 上传fir
if [[ $isUploadFir = 1 ]]; then
echo "正在上传fir..."
fir p "${ipaPath}/${displayName}/${displayName}.ipa" -T ${FirApiToken}
fi
# 移动
#mv ${ipaPath}/$displayName/$displayName.ipa ${allIPAPackPath}/$appName

# 上传蒲公英分发平台
if [[ $ISUPLOAD = 1 ]]; then
echo "正在上传蒲公英..."
curl -F "file=@$ipaPath/$displayName/$displayName.ipa" -F "uKey=$USERKEY" -F "_api_key=$APIKEY" http://www.pgyer.com/apiv1/app/upload

fi
done

# 结束时间
endTime=`date +%s`
echo -e "打包时间$[ endTime - beginTime ]秒"

判读用户是否有输入 1、AdHoc 2、AppStore 3、Enterprise 4、Development

如果打包出现证书匹配问题,如果选择的auto 脚本里需要配置 ,上面脚本已经配置。

-allowProvisioningUpdates ${YES}

-allowProvisioningDeviceRegistration ${YES}

如果打包报其他错误,请先在Xcode项目上保证能archive,先解决掉代码上的错误才能保证脚本打包。

等待生成ipa其中这儿生成替换的参数在APP中会读取,这儿也可以根据自己项目需求自定参数到info.plist下,然后在项目代码里实际的功能需求来操作。

  • 参数说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#app名字
displayNameArr=(
"渝钱宝管家"
"开益宝助手"
)
#app的bundleId
bundleIdentifierArr=(
"com.qyf.qyf"
"com.qyf.qyf"
)
#获取URL链接的主机地址编号
hostNumberArr=(
"A022"
"A021"
)
#修改app文件地址
appIconArr=(
"yqb_AppIcon"
"lebzs_AppIcon"
)
#打包渠道号
channelCodeArr=(
"112"
"115"
)

这儿举个简单的例子例如hostNumberArr参数,在实际项目中是为了请求URL服务器地址的,然后在项目里获取到info.plist文件读取出来过后,然后在去请求服务器请求地址代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//获取开关配置和切换IP地址数据
+(void)updateHostUrlSysCnfBlock:(void(^)(void))block{
AFHTTPSessionManager *manger =[AFHTTPSessionManager manager];
[manger.requestSerializer willChangeValueForKey:@"timeoutInterval"];
manger.requestSerializer.timeoutInterval = 20.f;
[manger.requestSerializer didChangeValueForKey:@"timeoutInterval"];
NSString *hostNumber = [NSBundle mainBundle].infoDictionary[@"HostNumber"];
NSString *getURL = [NSString stringWithFormat:@"http://sys.xxxx.cn:10271/geturl?v=%@",hostNumber];
[manger GET:getURL parameters:nil headers:nil progress:^(NSProgress *downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *info = (NSDictionary *)responseObject;
if (info) {
DataInstance.hideFlag = ((NSString *)info[@"hideFlag"]).integerValue;
NSString *hostUrl = info[@"url"];
if(![AppUtils isEmptyStr:hostUrl]){
hostUrl =[NSString stringWithFormat:@"%@/",info[@"url"]];
if (![DataInstance.hostUrl isEqualToString:hostUrl]) {
#ifdef DEBUG
#else
DataInstance.hostUrl = hostUrl;
[UserMgn cleanUser];
#endif
}
}
}
//进入控制器
if (block) {block();}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error.localizedDescription);
if (block) {block();}
}];
}

三、生成IPAS产品系列包

  • 打包生成所有产品下的包

pp9qYTS.png

  • 如果需要上传fir平台

如果没有安装fir插件,请先安装

1
sudo gem install fir-cli –no-ri –no-rdoc

执行成功会生成对应的项目文件目录

1
2
echo "fir平台..."
fir p "${ipaPath}/${displayName}/${displayName}.ipa" -T 931b41960f7496b946ei2s13c910pdb97

上面-T后面的token需要替换成你自己,上面只是例子随便的值,如果想要知道如果获取fir的token登陆fir官网看以下图
pp9q2Y4.png

  • 如果需要上传蒲公英pgyer分发平台
1
2
echo "正在上传蒲公英..."
curl -F "file=@$ipaPath/$displayName/$displayName.ipa" -F "uKey=$USERKEY" -F "_api_key=$APIKEY" http://www.pgyer.com/apiv1/app/upload

方法不累述 USERKEY和APIKEY,上面默认的脚本不上传fir和pgyer,你可以根据自己需求,打开注释,和修改是否需要上传蒲公英的状态开关。

pp9Lp0f.png

如果上传fri,对应的二维码就是生成在fir平台的安装包