探究 Xcode 命令行用法一:Xcode 构建必备认知

本篇是 adat 项目的延伸文章,也是后续 Xcode 构建实操文章的铺垫,目的是让本系列的读者都有一个共同的认知,当读到某个章节时,不至于对某些概念产生疑惑。这些基础共识,如果你感觉有点模糊,请一定认真读完,如果已经具备,不妨再扫一眼,也许有惊喜哦。


内容概览

  • 命令行用法文档
  • 命令行使用手册
  • 如何利用文档写一条命令
  • Target、Configuration 和 Scheme 到底是什么东西?
  • Project、Workspace 又是什么东西?
  • xcodebuild 基础命令
  • 精彩预告

命令行用法文档

这里的 “用法” 取自英文的 “Usage”,是用法的简要形式。获取命令的 “用法文档” 取决于命令自身,因此没有固定的写法,工作经验告诉我们有下面几种常见写法(注意 <命令> 后有空格):

  • <命令> -h
  • <命令> help
  • <命令> --help
  • <命令> -usage

或者直接使用空命令尝试,如果命令出错,输出错误信息的同时一般会输出正确的用法。如果命令没有出错,表示该命令无需参数或使用了默认参数,可以通过阅读使用手册来获取。如果还是找不到,最后寻求官方网站的帮助。

示例,查看git命令的用法文档:

$ git --help

命令执行后,会将简要用法直接输出在当前界面:

$ git --help
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]
...
See 'git help git' for an overview of the system.

用法文档适用于快速查看命令用法。比如工作中突然忘记了一些选项,或某些选项是组合单词太长,不确定是否输入正确,就可以快速看一眼,然后继续工作。


命令行使用手册

这里的 “使用手册” 取自英文的 “Manual”,是一种更为详细的文档形式。macOS 使用BSD General Commands Manual,一般包含NAMESYNOPSISDESCRIPTIONEXAMPLESSEE ALSO 等常用的部分。

含义描述
NAME名字一句话描述命令的名称
SYNOPSIS概要遵循命令行语法的格式,列举常用功能对应的命令行写法
DESCRIPTION描述命令的详细说明,一一列举每个参数的名称、简写方式、意义、组合用法、注意点等,参数包括:选项 option、标记 flag、值 value
EXAMPLES示例实现某个功能的具体写法示例
SEE ALSO参见相关联的其他命令

使用方式:

man <命令>

示例,查看echo命令的使用手册:

$ man echo

命令执行后,自动切换到vim命令模式,并输出文档详情:

ECHO(1)    BSD General Commands Manual    ECHO(1)

NAME
     echo -- write arguments to the standard output

SYNOPSIS
     echo [-n] [string ...]

DESCRIPTION
     The echo utility writes any specified operands, separated by single blank (` ') characters and followed by a newline (`\n') character, to the standard output.

     The following option is available:

     -n    Do not print the trailing newline character.  This may also be achieved by appending `\c' to the end of the string, as is done by iBCS2 compatible systems.
...

SEE ALSO
     builtin(1), csh(1), printf(1), sh(1)
...

BSD    April 12, 2003    BSD
(END)

在命令模式下输入字母q退出命令模式,同时退出了使用手册界面。注意字母 q 输入成功后会立即退出。

尝试用 man git 获取使用手册,再用 git --help 获取用法文档,对比两者有何不同。

使用手册适用于学习或研究某个命令的阶段。因为非常详尽,往往有好几页,例如man gitman xcodebuild,不方便快速查看,但是对于深究是很有帮助的,特别是DESCRIPTIONEXAMPLES部分。


如何利用文档写一条命令

首先要读懂文档的内容,但是文档中的诸多符号成为阅读障碍,需要先解决它。


命令行符号

命令也是一个程序,它必须能够识别参数并根据参数调整功能。命令行文档利用约定的符号和顺序来识别参数,描述和限定命令行的用法。下面是一些常见符号:

符号含义描述
[ ]可选某个选项、标记或参数可以不用指定,多个可选组合使用,可以实现完全不同的功能
|互斥使用 | 连接的多个选项,只能选择其中一个使用
重复选项、标记、参数或它们的组合可以多次出现在命令行中
< >必选必须指定,使用时将 < > 及内部的文本整体替换为具体值
无符号不可修改文档中怎么写的,使用时原样写入

命令行符号使用示例

在 Terminal 中执行:

$ xcodebuild -help

提取输出中的一行:

xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]

按照这个说明,结合符号的含义,我们知道:

  • xcodebuild是命令,-list是选项,两者没有符号修饰,因此必须要出现在命令行中。
  • -project <projectname>-workspace <workspacename>|连接,是互斥关系,如果要选择只能二选一。两者分别被[ ]包裹,代表可选,因此都可以不选。两者之外又被一个大的[ ]包裹,表示这一个整体可以完全忽略。<projectname><workspacename>分别被< >包裹,表示必须指定。整个意思是:要么指定-project,其后跟 project 路径,要么指定-workspace,其后跟 workspace 路径,要么整个忽略。
  • -json 是一个可选的功能开关,命令行中指定-json以 json 格式输出,不指定则原样输出。

上面三条的写法数分别是:1,3,2,组合起来共计有 6 种写法(顺序调整不计入写法数)。列举几种可能的写法:

  • xcodebuild -list
  • xcodebuild -list -project /Users/username/Networking.xcodeproj
  • xcodebuild -list -workspace /Users/username/A.xcworkspace -json

至此,我们对命令行有了基本的认识。熟记符号,多加练习,以后看到陌生的命令,应该不会再恐惧了。还有一些命令行符号没有列举,感兴趣的话可以去探索一番,例如{ }( )

如果你喜欢在网上搜索某个功能的命令行,然后拷贝、粘贴、运行,成功了,就再也不管。现在是时候思考一下:为什么要那样写,会不会有隐患,是否有改进空间。



Target、Configuration 和 Scheme 到底是什么东西?

TargetConfigurationScheme决定了最终产物,下面分别介绍。


Target

以 Xcode 13.1 为例,创建一个 App 项目就自动创建了一个 Target,如果勾选 Include Tests,还会自动创建两个 Target:单元测试 Target 和 UI 测试 Target。另外 Xcode -> File -> New -> Target 可以手动创建 Target。实际上,Target 描述了最终产物的形态,是 App、Framework、Bundle 或者是 Extension,因此 Target 的最终产物可能是 xxx.app、xxx.framework、xxx.bundle、xxx.appex。

在 Xcode -> Targets 列表中切换 Target,会发现每个 Target 都有自己的 Build Settings、Build Phases 和 Build Rules,修改一个 Target 的这些配置项,不会影响其他 Target(受关联影响的配置项除外)。例如,我们非常熟悉的 Build Settings 下的Other Linker Flags,Build Phases 下的Run Script。实际上,Target 确实定义了 Build Settings、Build Phases 和 Build Rules,就像自身的属性一样,修改它们就会影响 Target 的最终产物。例如,修改一个 Framework Target 的 Build Settings 下的Mach-O Type,可以决定最终生成静态库还是动态库。
在这里插入图片描述

一个大型项目往往有多个 Target,从 Github 克隆 facebook-ios-sdk 项目,检出 tag v12.0.2 ,可以看到 FBSDKCoreKit 这个 Project 下,有 7 个 Target:
在这里插入图片描述

查看产物,我们可以自己编译整个项目,也可以在 facebook-ios-sdk 项目的 Release 部分,下载官方帮我们编译好的产物:
在这里插入图片描述
可以看到,一个文件内,确实包含了多个平台和架构的产物。

此处先忽略 XCFramework 的包装,它只是一种分发形式,实际内部的每个产物都由各 Target 贡献。在 Facebook SDK v12.0.0 以前,FBSDKCoreKit 还是区分平台的 FAT Framework。

后续会有文章介绍 FAT Framework 以及如何生成 XCFramework。


Configuration

使用 Android Studio 进行 Android 开发的读者,应该很熟悉构建变体这个概念,Configuration就是 Xcode 中 Target 的 Build Settings 的变体,每个变体都有自己的配置,这些配置影响最终产物。新建一个 App 项目,默认创建了两个变体:DebugRelease

变体可以独立修改。例如在 Release 变体中生成调试符号文件,用于分析生产环境报告的崩溃信息,而在 Debug 变体中禁用这项配置,以减少开发阶段的构建时间:
在这里插入图片描述

在 Xcode -> Project 中管理变体:修改变体名称,新增变体,设置命令行构建默认变体,设置变体配置文件:
在这里插入图片描述

变体配置文件可以覆盖 Xcode 中 Build Settings 的配置,便于统一环境配置和版本管理,为大型项目的团队协作提供了很好的支持。变体配置文件后缀为.xcconfig,文件内容格式为:Key=Value,Key 来自 Build Settings,Value 可以继承默认值,也可以重写。在文件内还能引用其他变体配置文件。

示例,下面是 FBSDKCoreKit-Dynamic.xcconfig 的内容:

...
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Target/DynamicFramework.xcconfig"
#include "Shared/Version.xcconfig"

PRODUCT_NAME = FBSDKCoreKit
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.sdk.FBSDKCoreKit

CURRENT_PROJECT_VERSION = $(FBSDK_PROJECT_VERSION)

INFOPLIST_FILE = $(SRCROOT)/FBSDKCoreKit/Info.plist
MODULEMAP_FILE = $(SRCROOT)/FBSDKCoreKit/FBSDKCoreKit.modulemap

Scheme

Target 和 Configuration 只是定义了产物,并不生成产物,Scheme 的作用就是驱动产物的生成。Scheme 给 Target 绑定了一系列操作,并为每个操作绑定了 Configuration:
在这里插入图片描述

在 Xcode -> Product 执行BuildRunTestProfileAnalyzeArchive的操作时,实际执行了当前 Scheme 上对应的操作。当执行不同的操作时,同一个 Target 和 Configuration 的产物也会不同,例如Run的结果是运行到设备上,Archive输出归档文件。

Scheme 是一个操作列表,每个操作对应一条构建路径:

SchemeActionTargetConfigurationProduct
AppBuildAppDebugApp.app
RunAppDebug运行到设备
TestAppTests、AppUITestsDebug运行测试用例
ProfileAppRelease动态分析
AnalyzeAppDebug静态分析
ArchiveAppReleaseApp.xcarchive


Project、Workspace 又是什么东西?

ProjectWorkspace是 Xcode 项目的两种组织方式,下面分别介绍。


Project

Project 是直接容器,直接管理项目中所有的代码文件、资源、脚本、依赖库,以及 Target、Configuration 和 Scheme。以 Project 方式组织的项目的入口文件后缀是.xcodeproj

一个 Project 可以引用其他 Project 作为自己的依赖项,和 Workspace 的操作方式一样。


Workspace

Workspace 是间接容器,它引用 Project 后才有意义。 当然在 Workspace 内也能够创建代码文件、添加资源等,如果这些文件不被任何 Project 使用,那么它们就只是一堆普通文件而已,不会起任何作用。以 Workspace 方式组织的项目的入口文件后缀是.xcworkspace

示例,Workspace A 引用 App(一个 App Project) 和 Networking(一个 Framework Project),找到项目的.xcworkspace文件,右键显示包内容,查看contents.xcworkspacedata文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:Networking/Networking.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:App/App.xcodeproj">
   </FileRef>
</Workspace>

可以看到, Workspace 把多个 Project 组织在一起了,而且没有做什么特殊的工作。

通过 Workspace 来组织 Project,能够非常便捷地在 Project 之间建立依赖。
示例,App Project 要依赖 Framework Project 的产物 Networking.framework,只需在 App Project 中找到 Framework Project,并添加其产物 Networking.framework,整个过程像添加系统库一样:
在这里插入图片描述

注意,workspace 并非只是一个空壳,它也有自己的设置,导航到 Xcode -> File -> Workspace Settings:
在这里插入图片描述

在构建系统中,workspace 必须搭配 scheme 使用。

TargetConfigurationSchemeProjectWorkspace这些概念在模块化中非常重要,后续会有文章介绍 iOS 模块化的实现方案。
点关注,第一时间获取:virusbee - 本文作者



xcodebuild 基础命令

执行 xcodebuild 命令前,要切换到包含.xcodeproj.xcworkspace文件所在的目录,或在命令中通过-project-workspace指定。如果目录下有多个.xcodeproj文件,默认使用第一个。


查看 SDK 信息
xcodebuild -version -sdk

示例:

$ xcodebuild -version -sdk
iPhoneOS15.0.sdk - iOS 15.0 (iphoneos15.0)
SDKVersion: 15.0
Path: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.0.sdk
PlatformVersion: 15.0
PlatformPath: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
BuildID: 84856584-0587-11EC-B99C-6807972BB3D4
ProductBuildVersion: 19A339
ProductCopyright: 1983-2021 Apple Inc.
ProductName: iPhone OS
ProductVersion: 15.0

...

Xcode 13.0
Build version 13A233

输出了 Xcode 版本和所有 SDK 信息:iOS、iOS Simulator、DriverKit、macOS、tvOS、tv Simulator、watchOS、watch Simulator。在命令后附加-json,将以 json 格式输出信息。

这个命令没有指定 Project 或 Workspace,是因为它获取的是构建系统的信息,和具体项目无关。


查看 Project 信息
xcodebuild -list -project App/App.xcodeproj

示例,以 json 格式输出 App Project 信息:

$ xcodebuild -list -project App/App.xcodeproj -json
{
  "project" : {
    "configurations" : [
      "Debug",
      "Release"
    ],
    "name" : "App",
    "schemes" : [
      "App"
    ],
    "targets" : [
      "App"
    ]
  }
}

输出了 App Project 的 Target、Configuration 和 Scheme,这些信息非常重要,在上文已经分析过。


查看 Build Settings 信息
xcodebuild -project App/App.xcodeproj -showBuildSettings -destination "generic/platform=iOS"

示例:

$ xcodebuild -project App/App.xcodeproj -showBuildSettings -destination "generic/platform=iOS"
Command line invocation:
    /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -project App/App.xcodeproj -showBuildSettings -destination generic/platform=iOS

User defaults from command line:
    IDEPackageSupportUseBuiltinSCM = YES

Build settings for action build and target App:
    ACTION = build
    ...
    ARCHS = arm64
    ...
    BUILD_DIR = /Users/username/Library/Developer/Xcode/DerivedData/App-ejgzkwsxgbtdqdefiyltxpffjdbg/Build/Products
    ...
    CODE_SIGN_STYLE = Automatic
    ...

输出了从命令行对 Project 执行 Build 操作所使用的 Build Settings 信息。


对 App Project 执行 Clean 操作
xcodebuild -project App.xcodeproj -scheme App -destination "generic/platform=iOS" clean

关于 destination 的详细说明,参见 adat 项目的第一篇文章: TN2339: 使用 Xcode 命令行构建的常见问题

输出:

$ xcodebuild -project App/App.xcodeproj -scheme App -destination "generic/platform=iOS" clean
Command line invocation:
    /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -project App/App.xcodeproj -scheme App -destination generic/platform=iOS clean

User defaults from command line:
    IDEPackageSupportUseBuiltinSCM = YES

note: Using new build system
note: Build preparation complete

** CLEAN SUCCEEDED **

命令执行成功,输出没有警告,Build 目录也已删除,非常完美。

Clean 操作指定的 -destination 可以是 iOS 平台,也可以是 iOS Simulator 平台,任选一种,都可以将 Build 目录删除。选择后者的写法为:-destination “generic/platform=iOS Simulator”



精彩预告

本篇概念比较多,如果以前没有深入理解的,可能需要一点时间消化。不过不要紧,后续文章也是紧密衔接的,相信在不断阅读和实践中,你一定能理解透彻。

下面是后续文章的计划(标题和顺序可能变动,以实际发布为准):

  • 探究 Xcode 命令行用法二:单元测试与 UI 测试
  • 探究 Xcode 命令行用法三:xcodebuild 打包
  • 探究 Xcode 命令行用法四:codesign 签名
  • 探究 Xcode 命令行用法五:上传与分发
  • 探究 Xcode 命令行用法六:Jenkins 持续构建

关注我,精彩不错过:virusbee - 本文作者