通过xcodebuild自动构建并发布Ad Hoc测试包

通过xcodebuild自动构建并发布Ad Hoc测试包

目标是通过一个shell脚本,完成构建及发布。

Ad Hoc发布证书及Provisioning Profile

在『钥匙串访问』中执行:

证书助理->从证书颁发机构请求证书:

证书助理->从证书颁发机构请求证书

填写证书信息:

此处,为了方便识别证书,其常用名称,被设置为了”Ad Hoc”。

填写证书信息

点击『继续』按钮:

会弹出certSigningRequest文件的保存路径。

生成certSigningRequest文件后,『钥匙串访问』中会多出名为”Ad Hoc”的专用密钥名为”Ad Hoc”的公用密钥,然后登录苹果开发者账号,进入『Certificates, Identifiers & Profiles』,在『Certificates』中添加证书,在第一步中会让用户选择需要创建哪种类型的证书,这里我们需要选择『Ad Hoc』。

  • 如果是个人开发者账号,界面会是这样:

个人开发者

  • 如果是企业开发者账号,界面会是这样:

企业开发者

注意:一个是App Store and Ad Hoc,一个是In-House and Ad Hoc。也就是说,发布到App Store用的证书也可用于Ad Hoc发布。我这里的不可用(显示为灰色),是因为之前已经创建了相关的证书。

后面会上传之前生成的certSigningRequest文件,然后生成证书,下载并双击证书,证书就会与『钥匙串访问』中之前多出的Ad Hoc专用密钥进行配对。

接着,需要生成Ad Hoc所用的Provisioning Profile

  • 如果是个人开发者账号,界面会是这样:

个人开发者

  • 如果是企业开发者账号,界面会是这样:

企业开发者

在随后的步骤中,会让选择App ID,对于Ad Hoc发布,可以使用明确的App ID,也可以使用包含通配符(*)的App ID。如果没有相应的App ID,可以在Identifiers->App IDs中进行添加:

Snip20161122_13.png

选择App ID后,会让选择certificates,勾选在之前生成的证书,然后就可以生成Provisioning Profile文件,下载并双击,系统会自动将这个文件复制到~/Library/MobileDevice/Provisioning Profiles目录,并为其重新命名,新名称会类似这样:86ceff27-1dff-40eb-8fd7-6072af8cb03b.mobileprovision,这样的名称会在xcodebuild命令中使用到,但不会使用到.mobileprovision扩展名。

使用xcodebuild构建项目

xcodebuild简介

用途

xcodebuild最主要的用途就是『构建Xcode项目及工作空间』,也有其它用途:

  • 列举信息(项目配置、SDK、xcodebuild版本)
  • 导出存档(从xcarchive存档导出ipamanifest.plist
  • 导入/导出本地化文件(localization

xcodebuild命令的详情使用说明,请自行在命令行中执行man xcodebuildxcodebuild -help进行查看。这里给出的用法示例,基本能够满足日常的使用。

项目构建

项目构建主要会使用xcodebuild的三种构建形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1
xcodebuild [-project name.xcodeproj]
[[-target targetname] ... | -alltargets]
[-configuration configurationname]
[-sdk [sdkfullpath | sdkname]] [action ...]
[buildsetting=value ...] [-userdefault=value ...]

# 2
xcodebuild [-project name.xcodeproj] -scheme schemename
[[-destination destinationspecifier] ...]
[-destination-timeout value]
[-configuration configurationname]
[-sdk [sdkfullpath | sdkname]] [action ...]
[buildsetting=value ...] [-userdefault=value ...]

# 3
xcodebuild -workspace name.xcworkspace -scheme schemename
[[-destination destinationspecifier] ...]
[-destination-timeout value]
[-configuration configurationname]
[-sdk [sdkfullpath | sdkname]] [action ...]
[buildsetting=value ...] [-userdefault=value ...]

如果没有使用workspace,也就是没有使用xcworkspace文件,就使用前两种形式。

如果使用了workspace,就使用第三种形式(比如,项目使用了CocoaPods)。

生成xcarchive存档

为了构建xcarchive,需要将action指定为archive,此时必须指定一个schemescheme名称可在Xcode菜单的Product->Scheme->Manage Schemes中查找到。

比如,你的项目名称为Ad-Hoc-Demo,Scheme为Ad-Hoc-Demo-Scheme且没有使用workspace,就可以使用如下命令,对项目进行archive

1
xcodebuild -project Ad-Hoc-Demo.xcodeproj -scheme Ad-Hoc-Demo-Scheme archive

但有可能出错。因为项目的配置可能是不正确的(比如证书、Provisioning ProfileBundle Identifier配置错误)。

有三种途径可以查看项目的配置,:

  1. 直接在Xcode中进行查看(最直观)
  2. XXX.xcodeproj/project.pbxproj文件中进行查看
  3. 通过xcodebuild-showBuildSettings选项进行查看(建议使用这种方式)。

建议通过-showBuildSettings选项进行查看,主要原因是之前遇到的一个坑,我在Xcode中已经将证书、Provisioning Profile正确地设置了(对于Xcode来说是正确的,对于xcodebuild来说就不一定是正确的),但在使用xcodebuild时还是报出错误:Provisioning profile "XXXDis" doesn't include signing certificate "iPhone Distribution: XXX.。之所以报出这样的错误,只是因为Xcode中的配置并没有与项目的XXX.xcodeproj/project.pbxproj文件中的配置完全保持一致,该文件中的PROVISIONING_PROFILE_SPECIFIERPROVISIONING_PROFILE所对应的Provisioning Profile文件并不是同一个。

为了让构建进行得更加顺利,需要指定一些[buildsetting=value ...],比如,指定前面提到的PROVISIONING_PROFILE_SPECIFIERPROVISIONING_PROFILE

PROVISIONING_PROFILE_SPECIFIER的值可以这样找到:Xcode->Preferences...->Accounts,然后选择一个Apple ID,再点击右下角的View Details,进入详情界面,其中的Provisioning Profiles那一列就是PROVISIONING_PROFILE_SPECIFIER

PROVISIONING_PROFILE_SPECIFIER

在这个界面中,右键点击某个Provisioning Profile,可以看到Show In Finder项,选择该项,可以直接进入~/Library/MobileDevice/Provisioning Profiles目录。

如果Provisioning Profile文件名为86ceff27-1dff-40eb-8fd7-6072af8cb03b.mobileprovision,就将PROVISIONING_PROFILE的值指定为”86ceff27-1dff-40eb-8fd7-6072af8cb03b”(不带后面的.mobileprovision):

1
xcodebuild -project Ad-Hoc-Demo.xcodeproj -scheme Ad-Hoc-Demo-Scheme PROVISIONING_PROFILE="86ceff27-1dff-40eb-8fd7-6072af8cb03b" PROVISIONING_PROFILE_SPECIFIER="Ad Hoc" archive

这样就可以构建出xcarchive存档,其默认的存放目录为~/Library/Developer/Xcode/Archives,我们可以修改存放目录,只需要指定-archivePath选项,并在其后面指定xcarchive存档存放路径(需要包含xcarchive存档的文件名)就可以了:

1
xcodebuild -project Ad-Hoc-Demo.xcodeproj -scheme Ad-Hoc-Demo-Scheme PROVISIONING_PROFILE="86ceff27-1dff-40eb-8fd7-6072af8cb03b" PROVISIONING_PROFILE_SPECIFIER="Ad Hoc" archive -archivePath ~/Ad-Hoc-Demo.xcarchive

自己指定xcarchive存档的存放路径,可以方便后续处理

actionarchive时,xcodebuild默认使用的Build ConfigurationRelease,不过我们可以根据实际情况进行设置(比如,使用生产环境还是测试环境,因为在默认情况下,Release配置中是没有设置DEBUG宏的,有些团队会根据这个宏,去连接不同的服务器),此时,需要使用到-configuration选项,可以指定ReleaseDebug

1
xcodebuild -project Ad-Hoc-Demo.xcodeproj -scheme Ad-Hoc-Demo-Scheme PROVISIONING_PROFILE="86ceff27-1dff-40eb-8fd7-6072af8cb03b" PROVISIONING_PROFILE_SPECIFIER="Ad Hoc" archive -archivePath ~/Ad-Hoc-Demo.xcarchive -configuration Debug

导出ipa

使用-exportArchive就可以从xcarchive存档导出ipamanifest.plist

不过需要指定其它3个选项,使用方式如下:

1
2
xcodebuild -exportArchive -archivePath xcarchivepath -exportPath
destinationpath -exportOptionsPlist path
配置-exportOptionsPlist选项所需的plist文件

对于-exportOptionsPlist,需要创建一个plist文件,通过xcodebuild -help可以查看到该文件中可用的键及其描述,常用的有:

  • compileBitcode
  • manifest
  • method
  • teamID
  • uploadBitcode
  • uploadSymbols

其中,manifestmethodteamID是最重要的。

manifest对应的值是一个字典,这个字典需要包含appURLdisplayImageURLfullSizeImageURL这三个键。通过它,就可以生成一个名为manifest.plist文件,通过网页下载应用程序时,会使用这个manifest.plist文件。

method用于指明应该导出什么样的存档。

teamID可以在『钥匙串访问』中查看到:

teamID

导出ipamanifest.plist

创建并配置好-exportOptionsPlist选项所需的plist文件后,就可以通过xcodebuild -exportArchive导出ipamanifest.plist

生成下载页

在下载页面中,最重要的就是下载链接的href属性,该属性值的形式如下:

itms-services://?action=download-manifest&url=xxx/manifest.plist

其中的xxxmanifest.plist所在URL的某一部分,需要根据具体情况进行设置。

对于iOS 7.1及更高版本,manifest.plist所在URL必须使用https协议

将文件上传到服务器

只需要将下载页面、manifest.plistipa文件上传到服务器的某个目录下,并确保可以通过网站访问到该目录下的文件。

将以上步骤整合为脚本

需要将脚本保存在XXX.xcodeproj所在目录下(记得加执行权限)

脚本示例:

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
#!/bin/sh

#
# Created by Daniate.
#

# 项目名称
PROJECT_NAME="xxx" # 请替换
# 工作空间名称
WS_NAME="xxx.xcworkspace" # 请替换
# Scheme
SCHEME_NAME="xxx" # 请替换
# 版本号
BUNDLE_VERSION=`grep -A1 'CFBundleShortVersionString' "./$PROJECT_NAME/Info.plist" | grep '<string>' | awk -F '<\/string>' '{print $1}' | awk -F '<string>' '{print $2}'`
# 当前日期时间
CURRENT_DATE_TIME=`date +%Y.%m.%d.%H.%M.%S`
# 由版本号及当前日期时间组成的目录名
EXPORT_FOLDER_NAME="v$BUNDLE_VERSION_$CURRENT_DATE_TIME"
# 文件导出目录
EXPORT_ROOT="./ad_hoc_production"
EXPORT_FULL_FOLDER_NAME="$EXPORT_ROOT/$PROJECT_NAME/$EXPORT_FOLDER_NAME"
# 归档路径
ARCHIVE_PATH="$EXPORT_FULL_FOLDER_NAME/$PROJECT_NAME.xcarchive"
# 域名或IP地址
DL_DOMAIN="https://xxx.cn" # 请替换
# 下载页面的名称
DL_HTML="dl.html"
# 下载页面所在的URL
DL_URL="$DL_DOMAIN/$PROJECT_NAME/$EXPORT_FOLDER_NAME/$DL_HTML"

# STEP 1. 清理以前构建所留下的文件
echo "正在执行 xcodebuild clean ..."
xcodebuild -workspace "$WS_NAME" -scheme "$SCHEME_NAME" clean 1>/dev/null
echo "xcodebuild clean 执行完毕"

# STEP 2. 构建归档
PROFILE="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # 请替换
PROFILE_SPECIFIER="xxx" # 请替换
TEAM_ID="xxxxxxxxxx" # 请替换
echo "正在执行 xcodebuild archive ..."
xcodebuild -workspace "$WS_NAME" -scheme "$SCHEME_NAME" -configuration Release PROVISIONING_PROFILE="$PROFILE" PROVISIONING_PROFILE_SPECIFIER="$PROFILE_SPECIFIER" archive -archivePath "$ARCHIVE_PATH" 1>/dev/null

if [ -d "$ARCHIVE_PATH" ] # 构建归档成功
then
echo "执行 xcodebuild archive 成功"
else
echo "执行 xcodebuild archive 失败"
exit 1
fi

# STEP 3. 生成 exportOptionsPlist 选项所需的 plist 文件

EXPORT_OPTS_PLIST_NAME="export_opts.plist"
EXPORT_OPTS_PLIST_PATH="$EXPORT_FULL_FOLDER_NAME/$EXPORT_OPTS_PLIST_NAME"

echo "<?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>compileBitcode</key>\n\
<false/>\n\
<key>method</key>\n\
<string>ad-hoc</string>\n\
<key>teamID</key>\n\
<string>$TEAM_ID</string>\n\
<key>manifest</key>\n\
<dict>\n\
<key>appURL</key>\n\
<string>$DL_DOMAIN/$PROJECT_NAME/$EXPORT_FOLDER_NAME/$SCHEME_NAME.ipa</string>\n\
<key>displayImageURL</key>\n\
<string>$DL_DOMAIN/display.png</string>\n\
<key>fullSizeImageURL</key>\n\
<string>$DL_DOMAIN/fullSize.png</string>\n\
</dict>\n\
<key>uploadBitcode</key>\n\
<false/>\n\
<key>uploadSymbols</key>\n\
<false/>\n\
</dict>\n\
</plist>" > "$EXPORT_OPTS_PLIST_PATH"

if [ -s "$EXPORT_OPTS_PLIST_PATH" ]
then
echo "成功生成 exportOptionsPlist 选项所需的 plist 文件"
else
echo "生成 exportOptionsPlist 选项所需的 plist 文件失败"
rm -rdf "$EXPORT_ROOT"
exit 1
fi

# STEP 4. 导出 ipa、manifest.plist
echo "正在由归档导出ipa、manifest.plist文件 ..."
xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" -exportPath "$EXPORT_FULL_FOLDER_NAME" -exportOptionsPlist "$EXPORT_OPTS_PLIST_PATH" 1>/dev/null

# 删除归档
rm -rdf "$ARCHIVE_PATH"
# 删除生成的export options plist
rm "$EXPORT_OPTS_PLIST_PATH"

if [ -s "$EXPORT_FULL_FOLDER_NAME/$SCHEME_NAME.ipa" -a -s "$EXPORT_FULL_FOLDER_NAME/manifest.plist" ]
then
echo "成功导出 ipa、manifest.plist"
else
echo "导出 ipa、manifest.plist 失败"
rm -rdf "$EXPORT_ROOT"
exit 1
fi

# STEP 5. 生成 ipa 下载页面
echo "正在生成 ipa 下载页面 ..."
echo "<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<title>Ad-Hoc 内部测试</title>\n\
<meta charset=\"UTF-8\">\n\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\
<style>\n\
p {\n\
text-align: center;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<p>\n\
<img src=\"../../Icon.png\">\n\
</p>\n\
<p>\n\
$PROJECT_NAME\n\
</p>\n\
<p>\n\
$EXPORT_FOLDER_NAME\n\
</p>\n\
<p>\n\
<a href=\"itms-services://?action=download-manifest&url=$DL_DOMAIN/$PROJECT_NAME/$EXPORT_FOLDER_NAME/manifest.plist\">\n\
点击安装\n\
</a>\n\
</p>\n\
</body>\n\
</html>" > "$EXPORT_FULL_FOLDER_NAME/$DL_HTML"

if [ -s "$EXPORT_FULL_FOLDER_NAME/$DL_HTML" ]
then
echo "成功生成 ipa 下载页面"
else
echo "生成 ipa 下载页面失败"
rm -rdf "$EXPORT_ROOT"
exit 1
fi

echo "正在将相关文件上传至服务器 ..."

SERVER_USER="xxx" # 服务器用户名,请替换
SERVER_IP="xxx.xxx.xxx.xxx" # 服务器IP,请替换
SERVER_FOLDER="~/xxx" # 服务器某目录,请替换

scp -r "$EXPORT_ROOT/$PROJECT_NAME" "$SERVER_USER@$SERVER_IP:$SERVER_FOLDER"

# 上传完成后,删除目录
rm -rdf "$EXPORT_ROOT"

echo "成功上传,请使用 iPhone Safari 打开:$DL_DOMAIN/$PROJECT_NAME/$EXPORT_FOLDER_NAME/$DL_HTML 进行下载安装"


通过xcodebuild自动构建并发布Ad Hoc测试包
https://daniate.github.io/2016/11/27/通过xcodebuild自动构建并发布Ad Hoc测试包/
作者
Daniate
发布于
2016年11月27日
许可协议