xcodebuild 和 xcrun 是 Xcode 为开发者提供的一套构建打包的命令。使用它们编写脚本,可以实现通过脚本自动化打包的功能。本文基于 Xcode9 ,xcrun 的打包工具被移除,因此笔者主要使用 xcodebuild 来完成打包工作。

在日常开发中,无论是提交给测试部测试还是最终打包上线,都需要将工程打包成 ipa 文件,通常我们是选定 Scheme ,然后在 Xcode 中点击『 Project - Archive 』,当整个工程 archive 后,在自动弹出的 『 Organizer 』 中点击『 Export 』,选择 Ad Hoc 或 App Store ,选择证书或自动签名,等待圈圈转完,导出到指定位置,才能得到里面的 ipa 文件。虽然 Xcode 完美地帮我们完成这一项工作,但其中我们还是需要点击 5 ~ 6 次。而使用脚本来构建打包,就只需要一个命令运行脚本,在等待脚本运行结束的时候,我们可以同时进行其他的工作。

同时,使用构建脚本也能更好地集成 CI 。

xcodebuild

xcodebuild builds one or more targets contained in an Xcode project, or builds a scheme contained in an Xcode workspace or Xcode project.

xcodebuild是构建工具,可以将工程中的 target 或 scheme 编译、链接成 .app 文件。要使用xcodebuild,需要在.xcodeproj存放的文件夹中执行。

可以使用man来查看xcodebuild,可以看到该命令提供了一些常用概要( SYNOPSIS ):

xcodebuild [-project name.xcodeproj]
           [[-target targetname] ... | -alltargets]
           [-configuration configurationname]
           [-sdk [sdkfullpath | sdkname]] [action ...]
           [buildsetting=value ...] [-userdefault=value ...]

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

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

xcodebuild -version [-sdk [sdkfullpath | sdkname]] [infoitem]

xcodebuild -showsdks

xcodebuild -showBuildSettings
           [-project name.xcodeproj | [-workspace name.xcworkspace -scheme schemename]]

xcodebuild -list [-project name.xcodeproj | -workspace name.xcworkspace]

xcodebuild -exportArchive -archivePath xcarchivepath -exportPath
                destinationpath -exportOptionsPlist path

xcodebuild -exportLocalizations -project name.xcodeproj -localizationPath path [[-exportLanguage language] ...]

xcodebuild -importLocalizations -project name.xcodeproj -localizationPath path

前三条指令是比较常用的,其中涉及很多参数:

  • -project:指定要构建的 project ,当文件夹中有多个.xcodeproj文件时需要指定。
  • -target:指定构建哪一个 target 。默认地,若不指定此参数,xcodebuild 构建工程中的第一个 target 。
  • -alltargets:构建所有 target 。
  • -workspace:如果要构建一个 workspace (例如使用 cocoapods 管理第三方依赖文件),则需要指定 workspace ,当然后缀就是.xcworkspace
  • -scheme:构建 workspace ,还必须指定 scheme ,使用此参数。
  • -destination:指定设备,如:'platform=OS X,arch=x86_64'指代当前 Mac 或generic/platform=iOS指定 Generic iOS device 。
  • -destination-timeout:搜索指定设备超时时间,默认 30S 。
  • -configuration:如果工程中没有添加其他配置,默认就是 Debug 和 Release 这两个版本。没有指定此参数和 scheme 参数,则默认为 Release 版本。
  • -arch:指定架构 armv7、armv7s、arm64 等。
  • -sdk:指定构建使用的 sdk ,后跟 sdk 的绝对路径或 sdk 名称,可通过xcodebuild -showsdks查看。
  • -list:查看工程的 target、configuration、scheme 。

我们常用xcodebuild来构建项目,得到 .app 文件,但其实,xcodebuild也能打包出我们需要的 .ipa 文件,这就需要了解多以下几个比较重要的参数:

  • -exportArchive:archive 后导出,需要-exportFormat-archivePath-exportPath三个参数
  • -exportFormat:指定格式为 pkg ( macOS ) 或 ipa ( iOS ) 的一种
  • -archivePath:指定 archive 文件的地址,将此.xcarchive打包成 ipa
  • -exportPath:最后输出文件 ipa 所在的文件夹
  • -exportOptionsPlist:plist 文件,其中定义了将.xcarchive导出成 ipa 所需要的配置参数。后面会说到。

xcodebuild还能进行各种操作,挑选一些常用的来讲:

  • build:构建,默认操作。如果没有指定,则默认为 build ,将会在SYMROOT中生成文件夹[configuration]-iphones的文件夹( configuration 见上述参数介绍)。其中有项目用到的第三方库的 framework 、.app文件、.dSYM文件。
  • archive:对应 Xcode 中的 Archive ,在 build 的基础上还将生成.xcarchive文件。
  • clean:对应 Xcode 中的 clean ,删除上次构建过程产生的中间文件。

xcrun

xcrun -sdk iphoneos PackageApplication "./Release-iphoneos/${APP_NAME}.app" -o ~/"${IPANAME}"

xcrun 更多地用来将app文件打包到 ipa 中,得以安装在用户的设备上。其中使用到 PackageApplication 这个工具,命令也相对简单,就不多说了。

需要注意的是:从 Xcode8.3 开始,PackageApplication 从开发工具中被移除,因此我们用来将.app打包成ipa最常用的xcrun指令也变得不好用了。当然可以从旧版本的 Xcode 中将 PackageApplication 拷贝一份放在新的 Xcode 中,让 PackageApplication 继续发光发热。请见stackoverflow

不过使用xcrun还可能存在一些问题:当解压 ipa 后,你会发现,在 app 文件中,Payload文件夹存在,但是BCSymbolMapsSwiftSupportSymbols却找不到了,这有可能会在提交到 App Store 的时候出现问题。

但是从 Xcode 中 Export xcarchive 则不会出现这样的问题,说明单纯使用 xcrun 的打包旧命令已经不被建议使用了。

Demonstration🌰

笔者在使用脚本打包的时候舍弃了 xcrun ,直接用 xcodebuild 来完成工作,提供一个示例:

# 工程名
APP_NAME="KeyX_iOS"
# info.plist路径
PROJECT_INFOPLIST_PATH="./${APP_NAME}/Supporting Files/Info.plist"
# exportOptions.plist路径
EXPORT_PLIST="./KeyXExportOptions.plist"
echo "==============some message=============="
echo "info.plist路径 = ${PROJECT_INFOPLIST_PATH}"
# 取版本号
BUNDLE_SHORT_VERSION=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${PROJECT_INFOPLIST_PATH}")
echo "版本号 = ${BUNDLE_SHORT_VERSION}"
# 取build值
BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${PROJECT_INFOPLIST_PATH}")
echo "开发版本 = ${BUNDLE_VERSION}"
DATE="$(date +%Y%m%d)"
IPA_NAME="${APP_NAME}_V${BUNDLE_SHORT_VERSION}.${BUNDLE_VERSION}_${DATE}.ipa"
# ipa文件路径,保存起来以便后面使用
IPA_PATH="$PWD/${IPA_NAME}"
echo "要上传的ipa文件路径 = ${IPA_PATH}"
echo "${IPA_PATH}">> ipa_path
# 下面3行是集成有Cocopods的用法
echo "=============pod install==============="
pod install

echo "================clean=================="
xcodebuild \
    -workspace "${APP_NAME}.xcworkspace" \
    -scheme "${APP_NAME}"  \
    -configuration 'Release' \
    clean

echo "===============archive================="
xcodebuild \
    -workspace "${APP_NAME}.xcworkspace" \
    -scheme "${APP_NAME}" \
    -sdk iphoneos \
    -configuration 'Release' \
    -archivePath "${APP_NAME}.xcarchive" \
    PROVISIONING_PROFILE_SPECIFIER="PROVISIONING_PROFILE_NAME_OR_ID" \
    DEVELOPMENT_TEAM="TEAMID" \
    -allowProvisioningUpdates \
    SYMROOT='$PWD' \
    archive

# 将app打包成ipa
echo "================export================="
xcodebuild \
    -exportArchive \
    -archivePath "${APP_NAME}.xcarchive" \
    -exportOptionsPlist "${EXPORT_PLIST}" \
    -exportPath "$PWD"

# 将ipa的名称修改成我们自定义的名称 IPA_NAME
mv "${APP_NAME}.ipa" "${IPA_NAME}"

需要注意几点:

  1. archive 前 clean 是一个比较好的习惯,清除上次构建形成的中间文件。
  2. archive 时配置了PROVISIONING_PROFILE_SPECIFIERDEVELOPMENT_TEAM,用于指定 archive 的签名使用的证书,需要使用 development 用的 Provisioning Profile ,不可以用 app store 或 ad-hoc 的。
  3. 第 2 点中那两个配置,如果去掉,则默认读取 xcodeproj 中的配置。此时可以在 Xcode 的Target->Signing->Automaticall manage signing打勾由 Xcode 设置开发证书,或自己设置开发用的 Provisioning Profiles 并确保配置无误( manual signing )。
  4. 还是第 2 点的配置,在命令中可以配置的命令请见这里
  5. allowProvisioningUpdates是 Xcode9 才引入的,允许xcodebuild自动更新 Provisioning profile 等。

-exportOptionsPlist 参数

ExportOptions.plist 存在很久了,只是一直没有被拿来使用。在 xcodebuild 打包的时候,这个参数指定 ExportOptions.plist 的位置,用来告诉xcodebuild怎么将.xcarchive文件打包。

下面是笔者所使用的 plist 文件:

provisioningProfiles 中,key 是 bundleid , value 是对应的 provisioningProfile 的名称或 ID 。

有哪些键可以通过xcodebuild -help,然后拉到最下面查看。

如果还是觉得创建麻烦,可以先在 Xcode 中手动打包一遍,在 Export 后的文件夹中找到ExportOptions.plist,这就是适用于项目的 plist 文件了。

Troubleshotting

编写此章节,是为了记录打包时出现的错误,长期更新:

1.ipatool-json-filepath-XXXXXX “No value”.

笔者遇到此问题的时候,是因为 archive 失败,但是路径中还是出现了.xcarchive文件,继续往下 export 的时候就报错了,此时不妨尝试一下,只是 archive 是否有问题?具体原因笔者尚未发现。

Reference

1.Building from the Command Line with Xcode FAQ

2.Using -exportArchive instead of Package Application to export an IPA

3.XCArchive to ipa

4.Xcode Help