-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathIPABuildShell.sh
More file actions
executable file
·1686 lines (1343 loc) · 50.8 KB
/
IPABuildShell.sh
File metadata and controls
executable file
·1686 lines (1343 loc) · 50.8 KB
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# ----------------------------------------------------------------------
# name: IPABuildShell.sh
# version: 3.0.4(225)
# createTime: 2018-07-30
# description: iOS 自动打包
# author: itx
# email: 335418265@qq.com
# github: https://github.com/aa335418265/IPABuildShell
# ----------------------------------------------------------------------
CMD_PlistBuddy="/usr/libexec/PlistBuddy"
CMD_Xcodebuild=$(which xcodebuild)
CMD_Security=$(which security)
CMD_Lipo=$(which lipo)
CMD_Codesign=$(which codesign)
##历史备份目录
Package_Dir=~/Desktop/PackageLog
##脚本工作目录
Shell_Work_Path=$(pwd)
##脚本文件目录
Shell_File_Path=$(cd `dirname $0`; pwd)
## 用户配置
# Shell_User_Xcconfig_File="$Shell_File_Path/user.xcconfig"
## 脚本临时生成最终用于构建的配置
Tmp_Build_Xcconfig_File="$Package_Dir/build.xcconfig"
Tmp_Log_File="$Package_Dir/`date +"%Y%m%d%H%M%S"`.txt"
##临时文件目录
Tmp_Options_Plist_File="$Package_Dir/optionsplist.plist"
#############################################基本功能#############################################
function usage
{
# setAliasShortCut
echo ""
echo "Usage:$(basename $0) -[abcdptvhx] [--enable-bitcode] [--auto-buildversion] ..."
echo "可选项:"
echo " -a | --archs <armv7|arm64|armv7 arm64> 指定构建架构集,例如:-a 'armv7'或者 -a 'arm64' 或者 -a 'armv7 arm64' 等"
echo " -b | --bundle-id <bundleId> 设置Bundle Id"
echo " -c | --channel <development|app-store|enterprise|ad-hoc> 指定分发渠道,development 内部分发,app-store商店分发,enterprise企业分发, ad-hoc 企业内部分发"
echo " -d | --provision-dir <dir> 指定描述文件目录,默认会在~/Library/MobileDevice/Provisioning Profiles 中寻找"
echo " -g | --git-versions 查看当前git版本数量"
echo " -p | --keychain-password <passoword> 指定访问证书时解锁钥匙串的密码,即开机密码"
echo " -t | --target <targetName> 指定构建的target。默认当项目是单工程(非workspace)或者除Pods.xcodeproj之外只有一个工程的情况下,自动构建工程的第一个Target"
echo " -v | --verbose 输出详细的构建信息"
echo " -h | --help 帮助."
echo " -s | --show-sign 显示签名配置"
echo " -x 脚本执行调试模式."
echo " --non-dsym 不生成dsym文件"
echo " --show-profile-detail <provisionfile> 查看描述文件的信息详情(development、enterprise、app-store、ad-hoc)"
echo " --debug Debug和Release构建模式,默认Release模式,"
echo " --enable-bitcode 开启BitCode, 默认不开启"
echo " --auto-buildversion 自动修改构建版本号(设置为当前项目的git版本数量),默认不开启"
echo " --xcconfig <filename> 构建的时候,会去加载在该文件中定义的Build Setting 的配置。注:如果出现与其他参数配置相同的功能,这里的配置会覆盖其他所有设置。它与xcodebuild 命令的\" -xcconfig <filename> \" 参数用法一致。文件格式为:key=value"
echo " --env-filename <filename> 指定开发和生产环境的配置文件"
echo " --env-varname <varname> 指定开发和生产环境的配置变量"
echo " --env-production <YES/NO> YES 生产环境, NO 开发环境(只有指定filename和varname都存在时生效)"
echo ""
echo "================================================"
echo "作者:fenglh"
echo "Email:335418265@qq.com"
echo "GitHub:https://github.com/fenglh/IPABuildShell"
echo "================================================"
exit 0
}
## 日志格式化输出
function logit() {
echo -e "\033[32m [IPABuildShell] \033[0m $@"
echo "$@" >> "$Tmp_Log_File"
}
## 日志格式化输出
function errorExit(){
echo -e "\033[31m【IPABuildShell】$@ \033[0m"
exit 1
}
## 日志格式化输出
function warning(){
echo -e "\033[33m【警告】$@ \033[0m"
}
##字符串版本号比较:大于等于
function versionCompareGE() { test "$(echo "$@" | tr " " "\n" | sort -rn | head -n 1)" == "$1"; }
## 备份历史数据
function historyBackup() {
## 备份上一次的打包数据
if [[ -d "$Package_Dir" ]]; then
for name in "${Package_Dir}"/* ; do
if [[ "$name" == "${Package_Dir}/History" ]] && [[ -d "$name" ]]; then
continue;
fi
cp -rf "$name" "${Package_Dir}/History"
rm -rf "$name"
done
else
mkdir -p "${Package_Dir}/History"
fi
}
## 获取xcpretty安装路径
function getXcprettyPath() {
xcprettyPath=$(which xcpretty)
echo $xcprettyPath
}
## 初始化build.xcconfig配置文件
function initBuildXcconfig() {
local xcconfigFile=$Tmp_Build_Xcconfig_File
if [[ -f "$xcconfigFile" ]]; then
## 清空
> "$xcconfigFile"
else
## 生成文件
touch "$xcconfigFile"
fi
echo $xcconfigFile
}
# function initUserXcconfig() {
# if [[ -f "$Shell_User_Xcconfig_File" ]]; then
# local allKeys=(CONFIGRATION_TYPE ARCHS CHANNEL ENABLE_BITCODE DEBUG_INFORMATION_FORMAT AUTO_BUILD_VERSION UNLOCK_KEYCHAIN_PWD API_ENV_FILE_NAME API_ENV_VARNAME API_ENV_PRODUCTION PROVISION_DIR )
# for key in ${allKeys[@]}; do
# local value=$(getXcconfigValue "$Shell_User_Xcconfig_File" "$key")
# # echo "===$value====="
# if [[ "$value" ]]; then
# eval "$key"='$value'
# logit "【初始化用户配置】${key} = `eval echo "$value"`"
# fi
# done
# fi
# }
function checkOpenssl() {
local opensslInfo=$(openssl version)
local opensslName=$(echo $opensslInfo | cut -d " " -f1)
local opensslVersion=$(echo $opensslInfo | cut -d " " -f2)
if [[ "$opensslName" == "LibreSSL" ]] || ! versionCompareGE "${opensslVersion%\.*}" "1.0"; then
errorExit "${opensslInfo} 版本过旧,请更新 OpenSSL 版本"
fi
logit "【构建信息】OpenSSL 版本:$opensslVersion"
}
# function getXcconfigValue() {
# local xcconfigFile=$1
# local key=$2
# if [[ ! -f "$xcconfigFile" ]]; then
# exit 1
# fi
# ## 去掉//开头 ; 查找key=特征,去掉双引号
# local value=$(grep -v "[ ]*//" "$xcconfigFile" | grep -e "[ ]*$key[ ]*=" | tail -1| cut -d "=" -f2 | grep -o "[^ ]\+\( \+[^ ]\+\)*" | sed 's/\"//g' | sed "s/\'//g" )
# echo $value
# }
## 解锁keychain
function unlockKeychain(){
$CMD_Security unlock-keychain -p "$UNLOCK_KEYCHAIN_PWD" "$HOME/Library/Keychains/login.keychain" 2>/dev/null
if [[ $? -ne 0 ]]; then
return 1
fi
$CMD_Security unlock-keychain -p "$UNLOCK_KEYCHAIN_PWD" "$HOME/Library/Keychains/login.keychain-db" 2>/dev/null
if [[ $? -ne 0 ]]; then
return 1
fi
return 0
}
## 添加一项配置
function setXCconfigWithKeyValue() {
local key=$1
local value=$2
local xcconfigFile=$3
if [[ ! -f "$xcconfigFile" ]]; then
exit 1
fi
if grep -q "[ ]*$key[ ]*=.*" "$xcconfigFile";then
## 进行替换
sed -i "_bak" "s/[ ]*$key[ ]*=.*/$key = $value/g" "$xcconfigFile"
else
## 进行追加(重定位)
echo "$key = $value" >>"$xcconfigFile"
fi
}
##获取Xcode 版本
function getXcodeVersion() {
local xcodeVersion=`$CMD_Xcodebuild -version | head -1 | cut -d " " -f 2`
echo $xcodeVersion
}
##xcode 8.3之后使用-exportFormat导出IPA会报错 xcodebuild: error: invalid option '-exportFormat',改成使用-exportOptionsPlist
function generateOptionsPlist(){
local provisionFile=$1
local bundleId=$2
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
local provisionFileTeamID=$(getProvisionfileTeamID "$provisionFile")
local provisionFileType=$(getProfileType "$provisionFile")
local provisionFileName=$(getProvisionfileName "$provisionFile")
# local provisionFileBundleID=$(getProfileBundleId "$provisionFile")
local compileBitcode='<false/>'
if [[ "$ENABLE_BITCODE" == 'YES' ]]; then
compileBitcode='<true/>'
fi
local plistfileContent="
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n
<plist version=\"1.0\">\n
<dict>\n
<key>teamID</key>\n
<string>$provisionFileTeamID</string>\n
<key>method</key>\n
<string>$provisionFileType</string>\n
<key>stripSwiftSymbols</key>\n
<true/>\n
<key>provisioningProfiles</key>\n
<dict>\n
<key>$bundleId</key>\n
<string>$provisionFileName</string>\n
</dict>\n
<key>compileBitcode</key>\n
$compileBitcode\n
</dict>\n
</plist>\n
"
## 重定向
echo -e "$plistfileContent" > "$Tmp_Options_Plist_File"
echo "$Tmp_Options_Plist_File"
}
## 例如分割"E52A5D3E1ED7B40100D658B7:BMOnlineManagement:/Users/itx/BMOnlineManagement/BMOnlineManagement.xcworkspace/../BMOnlineManagement.xcodeproj"
function getTargetInfoValue(){
local targetInfo="$1"
local key="$2"
if [[ "$targetInfo" == "" ]] || [[ "$key" == "" ]]; then
errorExit "getTargetInfoValue 参数不能为空"
fi
## 更换数组分隔符
OLD_IFS="$IFS"
IFS=":"
local arr=($targetInfo)
IFS="$OLD_IFS"
if [[ ${#arr[@]} -lt 3 ]]; then
errorExit "getTargetInfoValue 函数出错"
fi
local value=''
if [[ "$key" == "id" ]]; then
value=${arr[0]}
elif [[ "$key" == "name" ]]; then
value=${arr[1]}
elif [[ "$key" == "xcproj" ]]; then
value=${arr[2]}
fi
echo "$value"
}
## 获取workspace的项目路径列表
function getAllXcprojPathFromWorkspace() {
local xcworkspace=$1;
local xcworkspacedataFile="$xcworkspace/contents.xcworkspacedata";
if [[ ! -f "$xcworkspacedataFile" ]]; then
echo "xcworkspace 文件不存在";
exit 1;
fi
local list=($(grep "location =" "$xcworkspacedataFile" | cut -d "\"" -f2 | cut -d ":" -f2))
## 补充完整路径
local completePathList=()
for xcproj in ${list[*]}; do
local path="${xcworkspace}/../${xcproj}"
## 数组追加元素括号里面第一个参数不能用双引号,否则会多出一个空格
completePathList=(${completePathList[*]} "$path")
done
echo "${completePathList[*]}"
}
## 获取xcproj的所有target
## 比分数组元素本身带有空格,所以采用字符串用“;”作为分隔符,而不是用数组。
function getAllTargetsInfoFromXcprojList() {
## 转换成数组
local xcprojList=$1
## 因在mac 系统下 在for循环中无法使用map ,所以使用数组来代替,元素格式为 targetId:targetName:xcprojPath
local wrapXcprojListStr='' ##
## 获取每个子工程的target
for (( i = 0; i < ${#xcprojList[*]}; i++ )); do
local xcprojPath=${xcprojList[i]};
local pbxprojPath="${xcprojPath}/project.pbxproj"
if [[ -f "$pbxprojPath" ]]; then
# echo "$pbxprojPath"
local rootObject=$($CMD_PlistBuddy -c "Print :rootObject" "$pbxprojPath")
local targetIdList=$($CMD_PlistBuddy -c "Print :objects:${rootObject}:targets" "$pbxprojPath" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//')
#括号用于初始化数组,例如arr=(1,2,3),括号用于初始化数组,例如arr=(1,2,3)
local targetIds=($(echo $targetIdList));
for targetId in ${targetIds[*]}; do
local targetName=$($CMD_PlistBuddy -c "Print :objects:$targetId:name" "$pbxprojPath")
local info="${targetId}:${targetName}:${xcprojPath}"
if [[ "$wrapXcprojListStr" == '' ]]; then
wrapXcprojListStr="$info";
else
wrapXcprojListStr="${wrapXcprojListStr};${info}";
fi
done
fi
done
echo "$wrapXcprojListStr"
}
##查找xcodeproj工程启动文件
function findXcodeproj() {
local xcodeprojPath=$(find "$Shell_Work_Path" -maxdepth 1 -type d -iname "*.xcodeproj")
if [[ ! -d "$xcodeprojPath" ]] || [[ ! -f "${xcodeprojPath}/project.pbxproj" ]]; then
exit 1
fi
echo $xcodeprojPath
}
##查找xcworkspace工程启动文件
function findXcworkspace() {
local xcworkspace=$(find "$Shell_Work_Path" -maxdepth 1 -type d -iname "*.xcworkspace")
if [[ -d "$xcworkspace" ]] || [[ -f "${xcworkspace}/contents.xcworkspacedata" ]]; then
echo $xcworkspace
fi
}
##检查podfile是否存在
function checkPodfileExist() {
local podfile=$(find "$Shell_Work_Path" -maxdepth 1 -type f -iname "Podfile")
if [[ ! -f "$podfile" ]]; then
exit 1
fi
echo $podfile
}
function getProjectVersion() {
local infoPlistFile=$1
local configurationId=$2
local pbxproj=$3/project.pbxproj
if [[ ! -f "$infoPlistFile" ]]; then
exit 1
fi
local projectVersion=$($CMD_PlistBuddy -c "Print :CFBundleShortVersionString" "$infoPlistFile")
## 兼容xcode 11 版本号显式显式在info.plist 而是用$(MARKETING_VERSION)
if [[ "projectVersion"=='$(MARKETING_VERSION)' ]]; then
projectVersion=$($CMD_PlistBuddy -c "Print :objects:$configurationId:buildSettings:MARKETING_VERSION" "$pbxproj" )
fi
echo $projectVersion
}
function getBuildVersion() {
local infoPlistFile=$1
if [[ ! -f "$infoPlistFile" ]]; then
exit 1
fi
local buildVersion=$($CMD_PlistBuddy -c "Print :CFBundleVersion" "$infoPlistFile")
echo $buildVersion
}
## 获取git仓库版本数量
function getGitRepositoryVersionNumbers (){
## 是否存在.git目录
local gitRepository=$(find "$Shell_Work_Path" -maxdepth 1 -type d -iname ".git")
if [[ ! -d "$gitRepository" ]]; then
exit 1
fi
local gitRepositoryVersionNumbers=$(git -C "$Shell_Work_Path" rev-list HEAD 2>/dev/null | wc -l | grep -o "[^ ]\+\( \+[^ ]\+\)*")
if [[ $? -ne 0 ]]; then
## 可能是git只有在本地,而没有提交到服务器,或者没有网络
exit 1
fi
echo $gitRepositoryVersionNumbers
}
#设置Info.plist文件的构建版本号
function setBuildVersion () {
local infoPlistFile=$1
local buildVersion=$2
if [[ ! -f "$infoPlistFile" ]]; then
exit 1
fi
$CMD_PlistBuddy -c "Set :CFBundleVersion $buildVersion" "$infoPlistFile"
}
function finalIPAName ()
{
local targetName=$1
local apiEnvFile=$2
local apiEnvVarName=$3
local infoPlistFile=$4
local channelName=$5
local configurationId=$6
local xcodeprojPath=$7
if [[ ! -f "$infoPlistFile" ]]; then
return;
fi
## IPA和日志重命名
local curDatte=`date +"%Y%m%d_%H%M%S"`
local ipaName=${targetName}_${curDatte}
local apiEnvValue=$(getIPAEnvValue "$apiEnvFile" "$apiEnvVarName")
local projectVersion=$(getProjectVersion "$infoPlistFile" "$configurationId" "$xcodeprojPath")
local buildVersion=$(getBuildVersion "$infoPlistFile")
if [[ "$apiEnvValue" ]]; then
local apiEnvName=''
if [[ "$apiEnvValue" == 'YES' ]]; then
apiEnvName='生产环境'
elif [[ "$apiEnvValue" == 'NO' ]]; then
apiEnvName='开发环境'
else
apiEnvName='未知环境'
fi
ipaName="$ipaName""_${apiEnvName}"
fi
ipaName="${ipaName}""_${channelName}""_${projectVersion}""(${buildVersion})"
echo "$ipaName"
}
##获取签名方式,##设置手动签名,即不勾选:Xcode -> General -> Signing -> Automatically manage signning
## 在xcode 9之前(不包含9),只有在General这里配置是否手动签名,在xcode9之后,多加了一项在setting中
function getCodeSigningStyle ()
{
local pbxproj=$1/project.pbxproj
local targetId=$2
local rootObject=$($CMD_PlistBuddy -c "Print :rootObject" "$pbxproj")
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
##没有勾选过Automatically manage signning时,则不存在ProvisioningStyle
signingStyle=$($CMD_PlistBuddy -c "Print :objects:$rootObject:attributes:TargetAttributes:$targetId:ProvisioningStyle " "$pbxproj" 2>/dev/null)
echo $signingStyle
}
##设置签名方式(手动/自动),注意:如果项目存在中文文件名,使用PlistBuddy 命令对pbxproj文件进行修改导致乱码!该方法已被抛弃!
function setManulCodeSigning ()
{
local pbxproj=$1/project.pbxproj
local targetId=$2
local rootObject=$($CMD_PlistBuddy -c "Print :rootObject" "$pbxproj")
##如果需要设置成自动签名,将Manual改成Automatic
$CMD_PlistBuddy -c "Set :objects:$rootObject:attributes:TargetAttributes:$targetId:ProvisioningStyle Manual" "$pbxproj"
}
function setManulCodeSigningRuby()
{
local project=$1
local targetId=$2
local pbxproj=$1/project.pbxproj
local codeSigningStyle=$(getCodeSigningStyle "$xcodeprojPath" "$targetId")
if [[ ! "$codeSigningStyle" ]] || [[ "$codeSigningStyle" != "Manual" ]]; then
logit "【签名信息】设置签名方式:Manual"
ruby "$Shell_File_Path/set_codesign_style.rb" "$project" "$targetId" 2>/dev/null
## 这里会报错 :如果c [Xcodeproj] Unknown object version. (RuntimeError),但是实际可以修改成功,暂时不做下面的逻辑处理
# if [[ $? -ne 0 ]]; then
# local rootObject=$($CMD_PlistBuddy -c "Print :rootObject" "$pbxproj")
# local compatibilityVersion=$($CMD_PlistBuddy -c "Print :objects:$rootObject:compatibilityVersion" "$pbxproj")
# if [[ "$compatibilityVersion"=="Xcode 9.3" ]]; then
# errorExit "设置手动签名失败,cocoapod 不兼容Xcode 9.3。版本请在【项目】- xxxTarget】- Show the File inspector】- Project Document】-【Project Format】 中选中小于Xcode 9.3-compatible的一项"
# else
# errorExit "设置手动签名失败,请在【项目】-【General】-【Signing】中去掉勾选Automatically manage signning"
# fi
# fi
fi
}
function addManulCodeSigning ()
{
local pbxproj=$1/project.pbxproj
local targetId=$2
local rootObject=$($CMD_PlistBuddy -c "Print :rootObject" "$pbxproj")
##如果需要设置成自动签名,将Manual改成Automatic
$CMD_PlistBuddy -c "Add :objects:$rootObject:attributes:TargetAttributes:$targetId:ProvisioningStyle string Manual" "$pbxproj"
}
#获取,会在当前脚本执行目录以及5级内的子目录下自动寻找
function findIPAEnvFile () {
local fileName=$1
## 如果直接是全路径文件,直接返回
if [[ -f "$fileName" ]]; then
echo $fileName
else
local apiEnvFile=`find "$Shell_Work_Path" -maxdepth 5 -path "./.Trash" -prune -o -type f -name "$fileName" -print| head -n 1`
if [[ ! -f "$apiEnvFile" ]]; then
exit 1
fi
echo $apiEnvFile
fi
}
## 获取接口环境的值
function getIPAEnvValue () {
local apiEnvFile=$1
local apiEnvVarName=$2
if [[ ! -f "$apiEnvFile" ]]; then
exit 1
fi
local apiEnvValue=$(grep "$apiEnvVarName" "$apiEnvFile" | grep -v '^//' | cut -d ";" -f 1 | cut -d "=" -f 2 | sed 's/^[ \t]*//g' | sed 's/[ \t]*$//g')
echo $apiEnvValue
}
function setIPAEnvFile () {
local apiEnvFile=$1
local apiEnvVarName=$2
local apiEnvVarValue=$3
if [[ ! -f "$apiEnvFile" ]]; then
exit 1
fi
sed -i ".bak" "/[ ]*$apiEnvVarName[ ]*=/s/=.*/= $apiEnvVarValue;\/\/脚本自动设置/" "$apiEnvFile" && rm -rf ${apiEnvFile}.bak
}
##获取描述文件过期天数
function getExpiretionDays()
{
local expireTimestamp=$1
local nowTimestamp=`date +%s`
local r=$[expireTimestamp-nowTimestamp]
local days=$[r/60/60/24]
echo $days
}
## 将描述文件的签名数据封装成证书
function wrapProvisionSignDataToCer {
local provisionFile=$1
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
## 获取DeveloperCertificates 字段
local data=$($CMD_Security cms -D -i "$provisionFile" | grep data | head -n 1 | sed 's/.*<data>//g' | sed 's/<\/data>.*//g' )
if [[ $? -ne 0 ]]; then
exit 1
fi
## 使用openssl进行解码 1. 构建cer证书 2. 解码证书
## 1.
local tmpCerFile='/tmp/tmp.cer'
echo "-----BEGIN CERTIFICATE-----" > "$tmpCerFile"
echo "${data}" >> "$tmpCerFile"
echo "-----END CERTIFICATE-----" >> "$tmpCerFile"
echo "${tmpCerFile}"
}
## 获取描述文件中的签名id
function getProvisionCodeSignIdentity
{
local provisionFile=$1
local cerFile=$(wrapProvisionSignDataToCer "$provisionFile")
##去掉前后空格
local codeSignIdentity=$(openssl x509 -noout -text -in "$cerFile" | grep Subject | grep "UID" | awk -F ", OU" '{print $1}' | cut -d "=" -f3 | awk '{sub(/^ */, "");sub(/ *$/, "")}1' |sed 's/\"//g' )
##必须使用"${}"这种形式,否则连续的空格会被转换成一个空格
## 这里使用-e 来解决中文签名id的问题
echo -e "${codeSignIdentity}"
}
function getProvisionfileCreateTimestmap {
local provisionFile=$1
##切换到英文环境,不然无法转换成时间戳
export LANG="en_US.UTF-8"
##获取描述文件的过期时间
local createTime=`$CMD_PlistBuddy -c 'Print :CreationDate' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/tmp/log.txt)`
local timestamp=`date -j -f "%a %b %d %T %Z %Y" "$createTime" "+%s"`
# echo $(date -r `expr $timestamp ` "+%Y年%m月%d" )
echo "$timestamp"
}
function getProvisionfileExpireTimestmap {
local provisionFile=$1
##切换到英文环境,不然无法转换成时间戳
export LANG="en_US.UTF-8"
##获取描述文件的过期时间
local expirationTime=`$CMD_PlistBuddy -c 'Print :ExpirationDate' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/tmp/log.txt)`
local timestamp=`date -j -f "%a %b %d %T %Z %Y" "$expirationTime" "+%s"`
# echo $(date -r `expr $timestamp ` "+%Y年%m月%d" )
echo "$timestamp"
}
## 获取描述文件中指定证书的创建时间
function getProvisionCodeSignCreateTimestamp {
local provisionFile=$1
local cerFile=$(wrapProvisionSignDataToCer "$provisionFile")
##切换到英文环境,不然无法转换成时间戳
export LANG="en_US.UTF-8"
## 得到字符串: Not Before: Sep 7 07:21:52 2017 GMT
local startTimeStr=$( openssl x509 -noout -text -in "$cerFile" | grep "Not Before" )
## 截图第一个:之后的字符串,得到:Sep 7 07:21:52 2017 GMT
startTimeStr=$(echo ${startTimeStr#*:}) ## 截取,echo 去掉前后空格
## 格式化
local startTimestamp=$(date -j -f "%b %d %T %Y %Z" "$startTimeStr" "+%s")
# echo $(date -r `expr $startTimestamp ` "+%Y年%m月%d" )
echo "$startTimestamp"
}
## 获取描述文件中指定证书的过期时间
function getProvisionCodeSignExpireTimestamp {
local provisionFile=$1
local cerFile=$(wrapProvisionSignDataToCer "$provisionFile")
##切换到英文环境,不然无法转换成时间戳
export LANG="en_US.UTF-8"
## 得到字符串: Not Before: Sep 7 07:21:52 2017 GMT
local endTimeStr=$( openssl x509 -noout -text -in "$cerFile" | grep "Not After" )
## 截图第一个:之后的字符串,得到:Sep 7 07:21:52 2017 GMT
endTimeStr=$(echo ${endTimeStr#*:}) ## 截取,echo 去掉前后空格
## 格式化
local expireTimestamp=$(date -j -f "%b %d %T %Y %Z" "$endTimeStr" "+%s")
# echo $(date -r `expr $expireTimestamp + 86400` "+%Y年%m月%d" )
echo "$expireTimestamp"
}
function getProvisionCodeSignSerial {
local provisionFile=$1
local cerFile=$(wrapProvisionSignDataToCer "$provisionFile")
## 去掉空格
local serial=$( openssl x509 -noout -text -in "$cerFile" | grep "Serial Number" | cut -d ':' -f2 | sed 's/^[ ]//g')
echo "$serial"
}
## 获取描述文件UUID
function getProvisionfileUUID()
{
local provisionFile=$1
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
provisonfileUUID=$($CMD_PlistBuddy -c 'Print :UUID' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/dev/null))
echo $provisonfileUUID
}
## 获取描述文件TeamName
function getProvisionfileTeamName()
{
local provisionFile=$1
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
provisonfileTeamName=$($CMD_PlistBuddy -c 'Print :TeamName' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/dev/null))
echo $provisonfileTeamName
}
## 获取描述文件TeamID
function getProvisionfileTeamID()
{
local provisionFile=$1
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
provisonfileTeamID=$($CMD_PlistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/dev/null))
echo $provisonfileTeamID
}
## 获取描述文件名称
function getProvisionfileName()
{
local provisionFile=$1
if [[ ! -f "$provisionFile" ]]; then
exit 1
fi
provisonfileName=$($CMD_PlistBuddy -c 'Print :Name' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/dev/null))
echo $provisonfileName
}
##这里只取第一个target
function getTargetName()
{
local pbxproj=$1/project.pbxproj
local targetId=$2
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
local targetName=$($CMD_PlistBuddy -c "Print :objects:$targetId:name" "$pbxproj")
echo $targetName
}
## 获取配置ID,主要是后续用了获取bundle id
function getConfigurationIds() {
##配置模式:Debug 或 Release
local targetId=$2
local pbxproj=$1/project.pbxproj
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
local buildConfigurationListId=$($CMD_PlistBuddy -c "Print :objects:$targetId:buildConfigurationList" "$pbxproj")
local buildConfigurationList=$($CMD_PlistBuddy -c "Print :objects:$buildConfigurationListId:buildConfigurations" "$pbxproj" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//')
##数组中存放的分别是release和debug对应的id
local configurationTypeIds=$(echo $buildConfigurationList)
echo $configurationTypeIds
}
function getConfigurationIdWithType(){
local configrationType=$3
local targetId=$2
local pbxproj=$1/project.pbxproj
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
local configurationTypeIds=$(getConfigurationIds "$1" $targetId)
for id in ${configurationTypeIds[@]}; do
local name=$($CMD_PlistBuddy -c "Print :objects:$id:name" "$pbxproj")
if [[ "$configrationType" == "$name" ]]; then
echo $id
fi
done
}
function getInfoPlistFile()
{
configurationId=$2
local pbxproj=$1/project.pbxproj
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
local infoPlistFileName=$($CMD_PlistBuddy -c "Print :objects:$configurationId:buildSettings:INFOPLIST_FILE" "$pbxproj" )
## 替换$(SRCROOT)为.
infoPlistFileName=${infoPlistFileName//\$(SRCROOT)/.}
### 完整路径
infoPlistFilePath="$1/../$infoPlistFileName"
echo $infoPlistFilePath
}
## 获取bundle Id,分为Releae和Debug
function getProjectBundleId()
{
local configurationId=$2
local pbxproj=$1/project.pbxproj
if [[ ! -f "$pbxproj" ]]; then
exit 1
fi
local bundleId=$($CMD_PlistBuddy -c "Print :objects:$configurationId:buildSettings:PRODUCT_BUNDLE_IDENTIFIER" "$pbxproj" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//')
echo $bundleId
}
function checkCodeSignIdentityValid()
{
local codeSignIdentity=$1
local content=$($CMD_Security find-identity -v -p codesigning | grep "$codeSignIdentity")
echo "$content"
}
##匹配签名身份--方法已被替换
# function matchCodeSignIdentity()
# {
# local provisionFile=$1
# local channel=$2
# local channelFilterString=''
# local startSearchString=''
# local endSearchString='1\\0230\\021\\006\\003U\\004'
# if [[ ! -f "$provisionFile" ]]; then
# exit 1;
# fi
# if [[ "$channel" == 'enterprise' ]] || [[ "$channel" == 'app-store' ]]; then
# channelFilterString='iPhone Distribution: '
# startSearchString='003U\\004\\003\\0142'
# else
# channelFilterString='iPhone Developer: '
# startSearchString='003U\\004\\003\\014&'
# fi
# profileTeamId=$($CMD_PlistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' /dev/stdin <<< $($CMD_Security cms -D -i "$provisionFile" 2>/dev/null))
# codeSignIdentity=$($CMD_Security dump-keychain 2>/dev/null | grep "\"subj\"<blob>=" | cut -d '=' -f 2 | grep "$profileTeamId" | awk -F "[\"\"]" '{print $2}' | grep "$channelFilterString" | sed "s/\(.*\)$startSearchString\(.*\)$endSearchString\(.*\)/\2/g" | head -n 1)
# echo "$codeSignIdentity"
# }
##匹配描述文件
function matchMobileProvisionFile()
{
##分发渠道
local channel=$1
local appBundleId=$2
##描述文件目录
local mobileProvisionFileDir=$3
if [[ ! -d "$mobileProvisionFileDir" ]]; then
exit 1
fi
##遍历
local provisionFile=''
local maxExpireTimestmap=0
for file in "${mobileProvisionFileDir}"/*.mobileprovision; do
local bundleIdFromProvisionFile=$(getProfileBundleId "$file")
local wildcard=${bundleIdFromProvisionFile:0-2} ##从右边取2个字符串
local orginPrefix=$(echo ${bundleIdFromProvisionFile%.*})
local targetPrefix=$(echo "${appBundleId%.*}")
if [[ "$appBundleId" == "$bundleIdFromProvisionFile" || (( "$wildcard" == '.*' && "$orginPrefix" == "$targetPrefix" )) ]] ; then
# echo "$bundleIdFromProvisionFile : $appBundleId ,$orginPrefix : $targetPrefix "
local profileType=$(getProfileType "$file")
if [[ "$profileType" == "$channel" ]]; then
local timestmap=$(getProvisionfileExpireTimestmap "$file")
## 匹配到有效天数最大的描述文件
if [[ $timestmap -gt $maxExpireTimestmap ]]; then
provisionFile=$file
maxExpireTimestmap=$timestmap
fi
fi
fi
done
echo $provisionFile
}
function getProfileBundleId()
{
local profile=$1
local applicationIdentifier=$($CMD_PlistBuddy -c 'Print :Entitlements:application-identifier' /dev/stdin <<< "$($CMD_Security cms -D -i "$profile" 2>/dev/null )")
if [[ $? -ne 0 ]]; then
exit 1;
fi
##截取bundle id,这种截取方法,有一点不太好的就是:当applicationIdentifier的值包含:*时候,会截取失败,如:applicationIdentifier=6789.*
local bundleId=${applicationIdentifier#*.}
echo $bundleId
}
function getProfileInfo(){
if [[ ! -f "$1" ]]; then
errorExit "指定描述文件不存在!"
fi
provisionFileTeamID=$(getProvisionfileTeamID "$1")
provisionFileType=$(getProfileType "$1")
channelName=$(getProfileTypeCNName $provisionFileType)
provisionFileName=$(getProvisionfileName "$1")
provisionFileBundleID=$(getProfileBundleId "$1")
provisionfileTeamName=$(getProvisionfileTeamName "$1")
provisionFileUUID=$(getProvisionfileUUID "$1")
provisionfileCreateTimestmap=$(getProvisionfileCreateTimestmap "$1")
provisionfileCreateTime=$(date -r `expr $provisionfileCreateTimestmap ` "+%Y年%m月%d" )
provisionfileExpireTimestmap=$(getProvisionfileExpireTimestmap "$1")
provisionfileExpireTime=$(date -r `expr $provisionfileExpireTimestmap ` "+%Y年%m月%d" )
provisionFileExpirationDays=$(getExpiretionDays "$provisionfileExpireTimestmap")
provisionfileCodeSign=$(getProvisionCodeSignIdentity "$1")
provisionfileCodeSignSerial=$(getProvisionCodeSignSerial "$1")
provisionCodeSignCreateTimestmap=$(getProvisionCodeSignCreateTimestamp "$1")
provisionCodeSignCreateTime=$(date -r `expr $provisionCodeSignCreateTimestmap ` "+%Y年%m月%d" )
provisionCodeSignExpireTimestamp=$(getProvisionCodeSignExpireTimestamp "$1")
provisionCodeSignExpireTime=$(date -r `expr $provisionCodeSignExpireTimestamp + 86400` "+%Y年%m月%d" )
provisionCodesignExpirationDays=$(getExpiretionDays "$provisionCodeSignExpireTimestamp")
logit "【描述文件】名字:$provisionFileName "
logit "【描述文件】类型:${provisionFileType}(${channelName})"
logit "【描述文件】TeamID:$provisionFileTeamID "
logit "【描述文件】Team Name:$provisionfileTeamName "
logit "【描述文件】BundleID:$provisionFileBundleID "
logit "【描述文件】UUID:$provisionFileUUID "
logit "【描述文件】创建时间:$provisionfileCreateTime "
logit "【描述文件】过期时间:$provisionfileExpireTime "
logit "【描述文件】有效天数:$provisionFileExpirationDays "
logit "【描述文件】使用的证书签名ID:[${provisionfileCodeSign}]"
logit "【描述文件】使用的证书序列号:$provisionfileCodeSignSerial"
logit "【描述文件】使用的证书创建时间:$provisionCodeSignCreateTime"
logit "【描述文件】使用的证书过期时间:$provisionCodeSignExpireTime"
logit "【描述文件】使用的证书有效天数:$provisionCodesignExpirationDays "
if [[ $provisionFileExpirationDays -lt 0 ]]; then
errorExit "描述文件:${provisionFileName} 已过期,请更新描述文件!"
fi
if [[ $provisionCodesignExpirationDays -lt 0 ]]; then
errorExit "证书:${provisionfileCodeSign} 已过期,请更新证书!"
fi
}
##获取描述文件类型
function getProfileType()
{
local profile=$1
local profileType=''
if [[ ! -f "$profile" ]]; then
exit 1
fi
##判断是否存在key:ProvisionedDevices
local haveKey=$($CMD_Security cms -D -i "$profile" 2>/dev/null | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//' | grep ProvisionedDevices)
if [[ "$haveKey" ]]; then
local getTaskAllow=$($CMD_PlistBuddy -c 'Print :Entitlements:get-task-allow' /dev/stdin <<< $($CMD_Security cms -D -i "$profile" 2>/dev/null ) )
if [[ $getTaskAllow == true ]]; then
profileType='development'
else
profileType='ad-hoc'
fi
else
local haveKeyProvisionsAllDevices=$($CMD_Security cms -D -i "$profile" 2>/dev/null | grep ProvisionsAllDevices)
if [[ "$haveKeyProvisionsAllDevices" != '' ]]; then