iOS 自动化打包上传 AppleStore、fir.im,并发邮件通知测试人员

workflow

从上图可以看出使用自动化出包,可以优化掉好几个需要人工介入的步骤,优化开发流程,节省开发者的时间。

自动打包的原理概不累述了,测试过程中如果有问题,可以用终端 cd 到项目目录,单独执行出问题的语句,再把错误的输出丢到搜索引擎,只要相信你遇到的问题别人肯定也会遇到,就一定能得到解决。如果有例外,那我相信——能弄出史无前例的问题的你,一定也厉害到可以史无前例的予以解决~当然也可以试着给我留言,说不定三个臭皮匠果真赛过诸葛亮。

脚本内容基本是基于网络上内容,做了整合,修复了一些可能会导致打包失败的错误。将打印添加颜色输出,以及添加了一些注释。

一、参考

延伸知识:多渠道自动打包

二、脚本源码

已做脱敏处理。

自动打包并上传

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
#打包前需要在 xcode 工程中证书选项设置 `Automatically manage signing` 否则会报错

#-------------项目自定义部分(自定义好下列参数后再执行该脚本)
# 计时
SECONDS=0

#工程名 将XXX替换成自己的工程名
project_name=xxxxx

#scheme名 将XXX替换成自己的sheme名
scheme_name=xxxxx

# 工程中Target对应的配置plist文件名称, Xcode默认的配置文件为Info.plist
info_plist_name="Info"

#打包模式 Debug/Release
development_mode=Release

#firToken 替换成自己账号的 token
fir_token="xxxxx"

#上传到 AppleStore 替换成自己的开发者账号
#密码需要到 https://appleid.apple.com/account 生成 App 专用密码
apple_account="xxxxx"
apple_password="xxxxx"

# ----------------自动打包部分(无特殊情况不用修改)#
#工程绝对路径
project_path=$(cd `dirname $0`; pwd)

archive_path=${project_path}/PackageArchived

#build文件夹路径
build_path=${project_path}/AutoBuild

#plist文件所在路径
exportOptionsPlistPath=${project_path}/exportTest.plist

# 时间
DATE=`date '+%Y-%m-%d-%H-%m-%S'`

#导出.ipa文件所在路径
exportIpaPath=${archive_path}/${development_mode-$DATE}

# 获取版本号
version=$(sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ${project_name}.xcodeproj/project.pbxproj)

# 指定输出ipa名称
ipa_name="$scheme_name-v$version"

#输出包路径,如果不存在创建,否则删除
if [ ! -d "$archive_path" ];
then
mkdir -p "$archive_path";
else
rm -rf "$archive_path"
mkdir -p "$archive_path"
fi

if [ -d "$build_path" ]; then
#statements
rm -rf "$build_path"
mkdir -p "$build_path"
fi

echo "\033[34;1m请选择打包方式(输入序号,按回车即可) \033[0m"
echo "\033[32;1m1:app-store \033[0m"
echo "\033[32;1m2:ad-hoc \033[0m"
##
read number
while([[ $number != 1 ]] && [[ $number != 2 ]])
do
echo "\033[31m错误! 请输入 1 或者 2\033[0m"
echo "\033[34;1m请选择打包方式(输入序号,按回车即可) \033[0m"
echo "\033[32;1m1:app-store \033[0m"
echo "\033[32;1m2:ad-hoc \033[0m"
read number
done

if [ $number == 1 ];then
development_mode=Release
exportOptionsPlistPath=${project_path}/exportAppstore.plist
else
development_mode=Debug
exportOptionsPlistPath=${project_path}/exportTest.plist
fi

echo '\033[33;1m///-----------\033[0m'
echo '\033[33;1m/// 正在清理工程\033[0m'
echo '\033[33;1m///-----------\033[0m'

xcodebuild \
clean -configuration ${development_mode} -quiet || exit

echo '\033[32;1m///--------\033[0m'
echo '\033[32;1m/// 清理完成\033[0m'
echo '\033[32;1m///--------\033[0m'
echo ''

echo '\033[33;1m///-------------\033[0m'
echo "\033[33;1m/// 正在编译工程: ${development_mode}\033[0m"
echo '\033[33;1m///-------------\033[0m'
xcodebuild \
archive -workspace ${project_path}/${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${development_mode} \
-archivePath ${build_path}/${project_name}.xcarchive -quiet || exit

echo '\033[32;1m///-------------\033[0m'
echo '\033[32;1m/// 编译完成 \033[0m'
echo '\033[32;1m///-------------\033[0m'
echo ''

echo '\033[33m///-------------\033[0m'
echo '\033[33m/// 开始ipa打包\033[0m'
echo '\033[33m///-------------\033[0m'
xcodebuild -exportArchive -archivePath ${build_path}/${project_name}.xcarchive \
-configuration ${development_mode} \
-exportPath ${exportIpaPath} \
-exportOptionsPlist ${exportOptionsPlistPath} \
-allowProvisioningUpdates \
-quiet || exit

# 修改ipa文件名称
mv $exportIpaPath/$scheme_name.ipa $exportIpaPath/$ipa_name.ipa

if [ -e $exportIpaPath/$ipa_name.ipa ]; then
echo '\033[32;1m///-------------\033[0m'
echo "\033[32;1m导出 ${ipa_name}.ipa 包成功 🎉🎉🎉 \033[0m"
echo '\033[32;1m///-------------\033[0m'
open $exportIpaPath
#移除构建文件夹
rm -rf "$build_path"
else
echo '\033[31;1m///-------------\033[0m'
echo "\033[31;1m导出 ${ipa_name}.ipa 包失败 💔💔💔 \033[0m"
echo '\033[31;1m///-------------\033[0m'
exit 1
fi
echo '\033[36;1m///-------------\033[0m'
echo "\033[36;1m/// 打包 ipa 完成。总耗时:${SECONDS}s \033[0m"
echo '\033[36;1m///-------------\033[0m'
echo ''

echo '\033[33m///-------------\033[0m'
echo '\033[33m/// 开始发布ipa包 \033[0m'
echo '\033[33m///-------------\033[0m'

if [ $number == 1 ];then

#验证并上传到App Store
# 将 -u 后面填入账号,-p后面填入密码
altoolPath="/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework/Versions/A/Support/altool"
"$altoolPath" --validate-app -f ${exportIpaPath}/${ipa_name}.ipa -u $apple_account -p $apple_password -t ios --output-format xml
"$altoolPath" --upload-app -f ${exportIpaPath}/${ipa_name}.ipa -u $apple_account -p $apple_password -t ios --output-format xml
else

#上传到 Fir
fir login -T $fir_token
fir publish $exportIpaPath/$ipa_name.ipa --skip-update-icon

echo '\033[33m///-------------\033[0m'
echo '\033[33m/// 开始发送邮件给测试人员 \033[0m'
echo '\033[33m///-------------\033[0m'

#如需变更发件人和收件人邮箱,请到工程目录底下 sendEmail.py 文件内修改
python sendEmail.py "测试版本 (iOS)xxxxx v(${version}) 上传成功" "赶紧下载体验吧:xxxxx"
fi

exit 0

上传成功成功后发送邮件

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
# -*- coding: utf-8 -*
#!/usr/bin/env python3
# coding=utf-8

# sendEmail title content
import sys
import smtplib
import io
from email.mime.text import MIMEText
from email.header import Header
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

sender = 'lvkai@cutecatos.com;'
#单个邮箱 改为 to_receiver = 'xxxxx'
#群发邮箱
to_receiver = ['xxxxx','xxxxx']
#抄送
cc_receiver = ['xxxxx']
smtpserver = 'smtp.exmail.qq.com'
#smtpserver = 'smtp.exmail.qq.com'
smtpport = 465

//smtp 配置,需要到邮箱账户设置界面自行获取
username = 'xxxxx'
password = 'xxxxx'

# 待优化内容,发送网页形式 邮件
# SendHtml = """
# <html>
# <h1>赶紧下载体验吧:<a href="http://d.firim.vip/jpy7">点击跳转</a></h1>
# </html>
# """

def send_mail(title, content):

try:
subject = title

msg = MIMEMultipart("related")

# msg = MIMEText(content,'plain','utf-8')

if not isinstance(title,unicode):
title = unicode(title, 'utf-8')
msg['Subject'] = title
msg['From'] = sender
msg['To'] = ";".join(to_receiver)
msg['Cc'] = ";".join(cc_receiver)
msg["Accept-Language"]="zh-CN"
msg["Accept-Charset"]="ISO-8859-1,utf-8"

# msgText = MIMEText(SendHtml,"html","utf-8")
msgText = MIMEText(content,'plain','utf-8')
msg.attach(msgText)

# fp = open("Ta_Pro_adHoc.png", 'rb')
# msgImage = MIMEImage(fp.read())
# fp.close()

# # 定义图片 ID,在 HTML 文本中引用
# msgImage.add_header('Content-ID', '<image>')
# msg.attach(msgImage)

to_addrs=to_receiver+cc_receiver
smtp = smtplib.SMTP_SSL(smtpserver,smtpport)
smtp.login(username, password)
smtp.sendmail(sender, to_addrs, msg.as_string())
smtp.quit()
return True
except Exception, e:
print str(e)
return False

if send_mail(sys.argv[1], sys.argv[2]):
print "🎉🎉🎉发送成功!"
else:
print "❌❌❌发送失败!"

三、问题

上传工具路径不对问题

解决方法来源:xcode11 构建上传问题

从原来的

1
/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool

变为了

1
/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework/Versions/A/Support/altool

$(MARKETING_VERSION) 获取版本号失败

解决方法来源:How to handle $(MARKETING_VERSION) of Xcode 11 by CI

使用一下语句获取

1
2
3
4
5
6
7
# get release number from Xcode build settings
version=$(sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' {your project name}.xcodeproj/project.pbxproj)

if [ -z "${version}" ]; then
echo "[ERROR] failed to get MARKETING_VERSION"
exit 1
fi

xcrun altool –upload-app -f path -u username -p password 上传失败

错误信息:

1
2
3
4
5
Error: Errors uploading 'xxx.ipa': (
"Error Domain=ITunesSoftwareServiceErrorDomain Code=-22014 \"We are unable to create an authentication session.\"
UserInfo={NSLocalizedDescription=We are unable to create an authentication session., NSLocalizedFailureReason=Unable to
validate your application.}"
)

解决方式:到 https://appleid.apple.com 生成一个专用密码就行了,然后用这个密码改到上面去,再次执行。即:xcrun altool --upload-app -f a/b/xxx.ipa -u [xxxx@qq.com](mailto:xxxx@qq.com) -p 生成的专用密码