Xcode使用Xcode命令构建的常见问题

Xcode使用Xcode命令构建的常见问题

Posted by WTJ on September 14, 2022

目录

内容概览

命令行工具包是什么?

  • macOS 10.9 及以后的版本上的 Xcode 不再提供命令行工具下载入口,应该如何安装?
  • 如何卸载命令行工具?
  • 多个 Xcode 并存的情况下,如何查看命令行工具使用的 Xcode 版本?
  • 如何修改命令行工具使用的 Xcode 版本?
  • 如何使用命令行构建 Xcode 项目?
  • 如果有多个 Configurations ,如何给 xcodebuild 设置一个默认的 Configuration ?
  • 如何利用命令行实施单元测试?
  • 如何利用命令行实现 Xcode 中的 Build For Testing 和 Test Without Building 功能?
  • exportOptionsPlist 文件可以包含哪些配置?
  • 如何归档并导出应用?

命令行工具包是什么?

命令行工具包是一个小型的自包含工具包,可以独立下载,可以在 macOS 上进行命令行开发。它由 macOS SDK 和命令行工具(例如 Clang,位于 /Library/Developer/CommandLineTools)组成。 macOS 10.9 及以后的版本上的 Xcode 不再提供命令行工具下载入口,应该如何安装?

在 macOS 10.9 及以后的版本上,Xcode 的偏好设置中的下载面板不再支持命令行工具直接下载,可以使用下面任意一种方式来安装:

安装 Xcode

Xcode 已经包含命令行工具,因此只要安装了 Xcode,就无需额外安装命令行工具。

从 Apple 开发者网站下载

下载地址:developer.apple.com/download/al… ,需要使用 Membership Account 登录,搜索并下载和系统版本匹配的命令行工具版本。

e005f233741d40f684cb255863d7b667~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0 image

在 macOS 10.9 及以后,当有新的命令行版本可用时,将会收到软件更新的通知。

通过 Terminal 安装

在 Terminal 中执行如下命令进行安装:

xcode-select --install

macOS 自带 xcode-select 命令,在 /usr/bin 目录下。

如何卸载命令行工具?

  • 删除 Xcode 应用
  • 删除 /Library/Developer/CommandLineTools 目录

多个 Xcode 并存的情况下,如何查看命令行工具使用的 Xcode 版本?

在 Terminal 中执行如下命令:

xcode-select --print-path

示例:

$ xcode-select --print-path
/Applications/Xcode.app/Contents/Developer

表示当前命令行工具使用的Xcode位于 /Applications/Xcode.app

如何修改命令行工具使用的 Xcode 版本?

在 Terminal 中执行如下命令:

sudo xcode-select -switch <path/to/>Xcode.app

示例:

$ sudo xcode-select -switch /Applications/Xcode.app

表示当前命令行工具使用的Xcode位于 /Applications/Xcode.app

如何修改命令行工具使用的 Xcode 版本?

在 Terminal 中执行如下命令:

sudo xcode-select -switch <path/to/>Xcode.app

实例:

$ sudo xcode-select -switch /Applications/Xcode.app

如何使用命令行构建 Xcode 项目?

使用 xcodebuild 命令,它可以对 Xcode 项目和工作空间进行编译、查询、分析、测试和归档。如果操作的是 Xcode 项目,需要指定至少一个 target 或一个 scheme,而如果是工作空间,则只需指定一个 scheme。在 Terminal 中执行 man xcodebuild 可以查看 xcodebuild 使用手册。xcodebuild 执行的结果默认存储位置定义在:Xcode > Preferences > Locations。

注意:在执行命令前,要先切换到包含项目或工作空间的目录下,或指定完整的项目或工作空间的路径。

列出工作空间包含的所有 scheme

xcodebuild -list -workspace <your_workspace_name>.xcworkspace

示例:

$ cd /Users/username/Desktop/MyApplication
$ xcodebuild -list -workspace MyApplication.xcworkspace
Information about workspace "MyApplication":
    Schemes:
        iOSApp
        tvOSApp
        macOSApp
        iOSWithWatchApp
        iOSWithWatchApp WatchKit App

列出项目包含的所有 target 、configuration 和 scheme

xcodebuild -list -project MyProject.xcodeproj

示例:

$ cd /Users/username/Desktop/MyProject
$ xcodebuild -list -project MyProject.xcodeproj
Information about project "MyProject":
    Targets:
        iOS
        iOSTests
        iOSUITests
        watchOS App
        watchOS App Extension
        tvOS
        tvOSTests
        tvOSUITests
        macOS
        macOSTests
        macOSUITests

    Build Configurations:
        Debug
        Release

 If no build configuration is specified and -scheme is not passed then "Debug" is used.

    Schemes:
        iOS
        watchOS App
        tvOS
        macOS

编译项目中的一个 scheme

xcodebuild -scheme <your_scheme_name> build

示例:

$ xcodebuild -scheme tvOS build
=== BUILD TARGET tvOS OF PROJECT MyProject WITH CONFIGURATION Debug ===
...

注意:xcodebuild 支持编译、分析、测试和归档等操作,当不指定任何操作时,默认执行编译操作

构建时指定 configuration 文件

xcodebuild -target <your_target_name> -xcconfig <your_configuration_file>.xcconfig

示例:

$ xcodebuild -target iOS -xcconfig configuration.xcconfig
Build settings from configuration file 'configuration.xcconfig':
   IPHONEOS_DEPLOYMENT_TARGET = 10
   SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
=== BUILD TARGET watchOS Extension OF PROJECT MyProject WITH THE DEFAULT CONFIGURATION (Debug) ===
...
=== BUILD TARGET watchOS App OF PROJECT MyProject WITH THE DEFAULT CONFIGURATION (Debug) ===
...
=== BUILD TARGET iOS OF PROJECT MyProject WITH THE DEFAULT CONFIGURATION (Debug) ===
...

修改 xcodebuild 的输出位置

可以在命令行中指定 SYMROOT(构建路径,存放 debug 包和 .dSYM 文件)和 DSTROOT(安装位置,存放 release 包和 .dSYM 文件)的值来达到修改目的。

修改 debug 版本的输出位置:

$ xcodebuild -scheme iOS SYMROOT="/Users/username/Desktop/DebugLocation"
Build settings from command line:
    SYMROOT = "/Users/username/Desktop/DebugLocation"
=== BUILD TARGET watchOS Extension OF PROJECT MyProject WITH CONFIGURATION Debug ===
...
=== BUILD TARGET watchOS App OF PROJECT MyProject WITH CONFIGURATION Debug ===
...
=== BUILD TARGET iOS OF PROJECT MyProject WITH CONFIGURATION Debug ===
...

修改 release 版本的输出位置:

$ xcodebuild -scheme iOS  DSTROOT="/Users/username/Desktop/ReleaseLocation" archive
Build settings from command line:
    DSTROOT = /Users/username/Desktop/ReleaseLocation
=== BUILD TARGET watchOS Extension OF PROJECT MyProject WITH CONFIGURATION Release ===
...
=== BUILD TARGET watchOS App OF PROJECT MyProject WITH CONFIGURATION Release ===
...
=== BUILD TARGET iOS OF PROJECT MyProject WITH CONFIGURATION Release ===
...

如果有多个 Configurations ,如何给 xcodebuild 设置一个默认的 Configuration ?

导航到 Project > Info > Configurations ,选择一个 configuration 即可:

c2aa066e1aea41eabaedfd145055bf44~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0 image

如何利用命令行实施单元测试?

xcodebuild 也支持单元测试,使用如下命令实现:

xcodebuild test [-workspace <your_workspace_name>]
                [-project <your_project_name>]
                -scheme <your_scheme_name>
                -destination <destination-specifier>
                [-only-testing:<test-identifier>]
                [-skip-testing:<test-identifier>]

test 操作需要同时指定 scheme 和 destination。 -workspace 指向 .xcworkspace 文件路径。 -project 指向 .xcodeproj 文件路径。 -destination 确定了单元测试的运行环境, 是对用于测试的设备、模拟器或 Mac 的描述,用 key=value 来确定,多个键值对以逗号分隔。 -only-testing 是可选的,指定了只测试哪些用例,-skip-testing 也是可选的,指定了跳过测试哪些用例, 是对测试用例的描述,格式为:

TestTarget[/TestClass[/TestMethod]]

TestTarget 是测试 Target 的名称,必须要指定。TestClass 和 TestMethod 都是可选的,表示要进行测试的类和方法。

对于 macOS 应用,destinationspecifier 用 platform 和 arch 两个值表示

Key Description Value
platform 测试运行的平台 macOS
arch 测试使用的架构 i386 或 x86_64

示例,在 macOS 平台的 x86_64 架构上进行测试:

xcodebuild test -scheme macOS -destination 'platform=macOS,arch=x86_64'```
对于 iOS 和 tvOS 应用,destinationspecifier 用 platform、name 和 id 表示,name 和 id 只能指定其中一个
Key Description Value
platform 测试运行的平台 iOS(针对 iOS 应用),tvOS(针对 tvOS 应用)
name 测试使用的设备全称 Xcode 设备管理器中显示的设备名称
id 测试使用的设备ID Xcode 设备管理器中显示的设备ID

示例,在 ID 为 965058a1c30d845d0dcec81cd6b908650a0d701c 的 iOS 设备上进行测试:

xcodebuild test -workspace MyApplication.xcworkspace -scheme iOSApp -destination 'platform=iOS,id=965058a1c30d845d0dcec81cd6b908650a0d701c'

示例,在名为 iPhone 的 iOS 设备上进行测试:


$ xcodebuild test -workspace MyApplication.xcworkspace -scheme iOSApp -destination 'platform=iOS,name=iPhone'
...
=== BUILD TARGET iOSApp OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
=== BUILD TARGET iOSAppTests OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
=== BUILD TARGET iOSAppUITests OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
Test Suite 'All tests' started at ...
Test Suite 'iOSAppTests.xctest' started at ...
Test Suite 'SecondTestClass' started at ...
Test Case '-[iOSAppTests.SecondTestClass testExampleA]' started.
Test Case '-[iOSAppTests.SecondTestClass testExampleB]' started.
Test Suite 'SecondTestClass' passed at ...
...
Test Suite 'iOSAppTests' started at ...
Test Case '-[iOSAppTests.iOSAppTests testExample]' started.
Test Case '-[iOSAppTests.iOSAppTests testPerformanceExample]' started.
Test Suite 'iOSAppTests' passed at ...
...
Test Suite 'iOSAppUITests.xctest' started at ...
Test Suite 'iOSAppUITests' started at ...
Test Case '-[iOSAppUITests.iOSAppUITests testLabels]' started.
Test Case '-[iOSAppUITests.iOSAppUITests testToolbar]' started.
...
** TEST SUCCEEDED **

示例,在名为 iPhone 的 iOS 设备上进行测试,但跳过对 iOSAppUITests 的测试:

xcodebuild test -workspace MyApplication.xcworkspace -scheme iOSApp -destination 'platform=iOS,name=iPhone' -skip-testing:iOSAppUITests

示例,在名为 iPhone 的 iOS 设备上进行测试,但只测试 iOSAppTests 中的 SecondTestClass 类的 testExampleB 方法:

$ xcodebuild test -workspace MyApplication.xcworkspace -scheme iOSApp -destination 'platform=iOS,name=iPhone' -only-testing:iOSAppTests/SecondTestClass/testExampleB
...
=== BUILD TARGET iOSApp OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
=== BUILD TARGET iOSAppTests OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
=== BUILD TARGET iOSAppUITests OF PROJECT iOSApp WITH CONFIGURATION Debug ===
...
Test Suite 'Selected tests' started at ...
Test Suite 'iOSAppTests.xctest' started at ...
Test Suite 'SecondTestClass' started at ...
Test Case '-[iOSAppTests.SecondTestClass testExampleB]' started.
Test Case '-[iOSAppTests.SecondTestClass testExampleB]' passed (0.007 seconds).
Test Suite 'SecondTestClass' passed at ...
    ...
** TEST SUCCEEDED **

对于 iOS 模拟器和 tvOS 模拟器应用,destinationspecifier 用 platform、name、id 和 OS 表示,name 和 id 只能指定其中一个,OS 是可选的


Key | Description | Value
-- | -- | --
platform | 测试运行的平台 | iOS Simulator(针对 iOS 应用),tvOS Simulator(针对 tvOS 应用)
name | 测试使用的模拟器全称 | Xcode 设备管理器中显示的模拟器名称
id | 测试使用的模拟器ID | Xcode 设备管理器中显示的模拟器ID
OS | iOS 或 tvOS 的版本号,或 latest 字符串 | 具体的 iOS 版本、 tvOS 版本,或者是 latest

示例,在名为 iPad Pro (12.9 inch)、版本号为 10.2 的 iOS 模拟器上进行测试:

xcodebuild test -scheme iOS -destination 'platform=iOS Simulator,name=iPad Pro (12.9-inch),OS=10.2'

示例,在 ID 为 D6FA2C2A-E297-406A-AA22-624B4834ACB2 的 tvOS 模拟器上进行测试:

xcodebuild test -scheme tvOS -destination 'platform=tvOS Simulator,id=D6FA2C2A-E297-406A-AA22-624B4834ACB2'

一个测试用例允许指定多个 -destination 选项,xcodebuild 将会按顺序在每个 -destination 指定的环境上测试。 示例,在 iOS 模拟器和 iPod touch 上依次进行测试:

xcodebuild test -scheme iOS -destination 'platform=iOS Simulator,name=iPhone 6s,OS=10.3' -destination 'platform=iOS,name=iPod touch'

如何利用命令行实现 Xcode 中的 Build For Testing 和 Test Without Building 功能?

xcodebuild 的 build-for-testing 操作实现了 Xcode菜单中的 Product > Build For > Testing 功能,需要指定一个 scheme

具体命令:

xcodebuild build-for-testing [-workspace <your_workspace_name>]
                             [-project <your_project_name>]
                             -scheme <your_scheme_name>
                             -destination <destination-specifier>

示例,在 ID 为 D6FA2C2A-E297-406A-AA22-624B4834ACB2 的 tvOS 模拟器上构建一个测试:

xcodebuild build-for-testing -scheme tvOS -destination 'platform=tvOS Simulator,id=D6FA2C2A-E297-406A-AA22-624B4834ACB2'

build-for-testing 操作会生成一个 xctestrun 文件,保存在 DerivedData 目录下。

xcodebuild 的 test-without-building 操作实现了 Xcode 菜单中的 Product > Perform Action > Test Without Building 功能,需要指定一个 scheme 或者一个 xctestrun 文件

指定 scheme 的用法:

xcodebuild test-without-building [-workspace <your_workspace_name>]
                                 [-project <your_project_name>]
                                 -scheme <your_scheme_name>
                                 -destination <destination-specifier>
                                 [-only-testing:<test-identifier>]
                                 [-skip-testing:<test-identifier>]

test-without-building 在指定 scheme 进行测试时,它会在构建根目录(SYMROOT)寻找可以测试的包,因此请确保项目已经编译过或构建根目录已经存在可以测试的包。 示例,在名为 iPhone SE、版本为 10.1 的 iOS 模拟器上执行 test-without-building 测试:

xcodebuild test-without-building -workspace MyApplication.xcworkspace -scheme iOSApp -destination 'platform=iOS Simulator,name=iPhone SE,OS=10.1'

指定 xctestrun 文件的用法:

xcodebuild test-without-building -xctestrun <your_xctestrun_name>.xctestrun
                                 -destination <destination-specifier>
                                 [-only-testing:<test-identifier>]
                                 [-skip-testing:<test-identifier>]

test-without-building 在指定 xctestrun 进行测试时,它会寻找 xctestrun 文件中指定的包,因此请确保这些包存在。 示例,在 ID 为 6DC4A7BA-EA7F-40D6-A327-A0A9DF82F7F6 的 iOS 模拟器上测试iOSApp_iphonesimulator.xctestrun 文件指定的包:

$ cat iOSApp_iphonesimulator.xctestrun
<?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>iOSAppTests</key>
    <dict>
        <key>BundleIdentifiersForCrashReportEmphasis</key>
        <array>
            <string>com.myapps.iOSApp</string>
            <string>com.myapps.iOSAppTests</string>
            <string>com.myapps.iOSAppUITests</string>
        </array>
        …
        <key>TestBundlePath</key>
        <string>__TESTHOST__/PlugIns/iOSAppTests.xctest</string>
        <key>TestHostBundleIdentifier</key>
        <string>com.myapps.iOSApp</string>
        <key>TestHostPath</key>
        <string>__TESTROOT__/Debug-iphonesimulator/iOSApp.app</string>
        …
    </dict>
    <key>iOSAppUITests</key>
    …
</dict>
</plist>

$ xcodebuild test-without-building -xctestrun iOSApp_iphonesimulator.xctestrun -destination 'platform=iOS Simulator,id=6DC4A7BA-EA7F-40D6-A327-A0A9DF82F7F6'

示例,在 ID 为 3D95DF14-E8B7-4A05-B65B-78F381B74B22 的 iOS 模拟器上测试iOSApp_iphonesimulator.xctestrun 文件指定的所有包,但跳过对 iOSAppUITests 的测试:

xcodebuild test-without-building -xctestrun iOSApp_iphonesimulator.xctestrun -destination 'platform=iOS Simulator,id=3D95DF14-E8B7-4A05-B65B-78F381B74B22' -skip-testing:iOSAppUITests

注意:build-for-testing 和 test-without-building 也支持持续集成系统。

exportOptionsPlist 文件可以包含哪些配置?

在 Terminal 中执行如下命令进行查看:

xcodebuild -help

示例:

$ xcodebuild -help
Usage: xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]
     ...
Available keys for -exportOptionsPlist:
    compileBitcode : Bool
         ...
    embedOnDemandResourcesAssetPacksInBundle : Bool
         ...
    iCloudContainerEnvironment
         ...
    manifest : Dictionary
         ...
    method : String
         ...
    onDemandResourcesAssetPacksBaseURL : String
         ...
    teamID : String
         ...
    thinning : String
         ...
    uploadBitcode : Bool
         ...
    uploadSymbols : Bool

fd04416a1d4a4733a81a6521ec30d4e2~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0 image

如何归档并导出应用?

xcodebuild -project <xcodeprojpath> -scheme <scheme_name> -archivePath <xcarchivepath> archive
指向工程的 .xcodeproj 文件路径。如果操作的是工作空间,将 -project 替换为 -workspace 即可。
是工程中定义的 scheme 名称,它确定了具体要操作的 target 和 configuration 等信息。 是导出的归档文件路径,包含 .app 和 dSYM 等文件,它也是下一步操作的参数。 示例: ``` $ xcodebuild -project iOSApp.xcodeproj -scheme iOSApp -archivePath iOSApp.xcarchive archive Command line invocation:    /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -project iOSApp.xcodeproj -scheme iOSApp -archivePath iOSApp.xcarchive archive User defaults from command line:    IDEArchivePathOverride = /Users/username/iOSApp/iOSApp.xcarchive    IDEPackageSupportUseBuiltinSCM = YES note: Using new build system note: Building targets in parallel note: Planning build note: Analyzing workspace note: Constructing build description note: Build preparation complete ... ** ARCHIVE SUCCEEDED ** ``` ## 导出 ``` xcodebuild -exportArchive -archivePath  -exportPath  -exportOptionsPlist  ``` > 指向第一步中归档后的文件。 指向即将导出的 .ipa 文件路径。 指向 ExportOptions.plist 文件路径。ExportOptions.plist 文件可以在 Xcode 中手动 Archive 并 Export 一次获得。 示例: ``` $ xcodebuild -exportArchive -archivePath iOSApp.xcarchive -exportPath Release/MyApp -exportOptionsPlist ExportOptions.plist ... Exported iOSApp to: /Users/virusbee/Desktop/iOSApp/Release/MyApp ** EXPORT SUCCEEDED ** ``` 参考: https://developer.apple.com/library/archive/technotes/tn2339/_index.html