对于构建原生项目,Gradle 采用约定优于配置的方法。如果您来自其他原生构建系统,这些概念最初可能不太熟悉,但它们的目的是简化构建脚本的编写。

在本章中,我们将详细介绍 Swift 项目,但大多数主题也适用于其他受支持的原生语言。

引言

最简单的 Swift 项目构建脚本应用 Swift 应用插件或 Swift 库插件,并可选择设置项目版本

build.gradle.kts
plugins {
    `swift-application` // or `swift-library`
}

version = "1.2.1"
build.gradle
plugins {
    id 'swift-application' // or 'swift-library'
}

version = '1.2.1'

通过应用任一 Swift 插件,您可以获得一系列功能

  • compileDebugSwiftcompileReleaseSwift 任务,分别用于为熟知的 debug 和 release 构建类型编译 src/main/swift 目录下的 Swift 源文件。

  • linkDebuglinkRelease 任务,用于将编译后的 Swift 对象文件链接成应用的的可执行文件,或将共享链接的库链接成共享库,分别用于 debug 和 release 构建类型。

  • createDebugcreateRelease 任务,用于将编译后的 Swift 对象文件组合成静态链接的库的静态库,分别用于 debug 和 release 构建类型。

对于任何非简单的 Swift 项目,您可能都会有一些文件依赖项以及针对的项目所需的额外配置。

Swift 插件还将上述任务集成到标准的生命周期任务中。生成开发二进制文件的任务会关联到 assemble。默认情况下,开发二进制文件是 debug variant。

本章的其余部分将解释在构建库和应用时根据您的需求自定义构建的不同方法。

引入构建 Variant

原生项目通常可以生成多种不同的二进制文件,例如 debug 或 release 版本,或者针对特定平台和处理器架构的版本。Gradle 通过维度(dimensions)Variant(variants)的概念来管理这一点。

维度(dimension)只是一个类别,其中每个类别都与其他类别正交。例如,“构建类型”维度是包含 debug 和 release 的类别。“架构”维度涵盖 x86-64 和 x86 等处理器架构。

Variant(variant)是这些维度的值组合,由每个维度的恰好一个值组成。您可能有一个“debug x86-64”或“release x86” Variant。

Gradle 内置支持多种维度以及每个维度内的多个值。您可以在原生插件参考章节中找到它们的列表。

声明源文件

Gradle 的 Swift 支持直接使用 applicationlibrary 脚本块中的 ConfigurableFileCollection 来配置要编译的源文件集。

库会区分私有(实现细节)和公共(导出给消费者)头文件。

对于源文件仅在特定目标机器上编译的情况,您还可以为每个二进制构建配置源文件。

swift sourcesets compilation
图 1. 源文件和 Swift 编译

管理依赖项

绝大多数项目都依赖于其他项目,因此管理项目的依赖项是构建任何项目的重要组成部分。依赖管理是一个很大的主题,所以我们在这里只关注 Swift 项目的基础知识。如果您想深入了解细节,请查看依赖管理入门

Gradle 支持使用 Gradle 发布的 Maven 仓库中的预构建二进制文件[1]

我们将介绍如何在多构建项目中的项目之间添加依赖项。

为您的 Swift 项目指定依赖项需要两个信息

  • 依赖项的标识信息(项目路径、Maven GAV)

  • 需要它做什么,例如编译、链接、运行时或以上所有。

这些信息在 Swift applicationlibrary 脚本块的 dependencies {} 块中指定。例如,要告诉 Gradle 您的项目需要库 common 来编译和链接您的生产代码,您可以使用以下片段

示例 2. 声明依赖项
build.gradle.kts
application {
    dependencies {
        implementation(project(":common"))
    }
}
build.gradle
application {
    dependencies {
        implementation project(':common')
    }
}

这三个元素的 Gradle 术语如下

  • 配置(Configuration)(例如:implementation)- 依赖项的命名集合,为了特定目标(如编译或链接模块)而分组在一起

  • 项目引用(Project reference)(例如:project(':common'))- 由指定路径引用的项目

您可以在此处找到更全面的依赖管理术语表。

至于配置,主要关注的有

  • implementation - 用于编译、链接和运行时

  • swiftCompileVariant - 用于编译生产代码必需的依赖项,但不应包含在链接或运行时过程中

  • nativeLinkVariant - 用于链接代码必需的依赖项,但不应包含在编译或运行时过程中

  • nativeRuntimeVariant - 用于运行组件必需的依赖项,但不应包含在编译或链接过程中

您可以在原生插件参考章节中了解更多关于这些配置以及它们之间的关系。

请注意,Swift 库插件会创建一个额外的配置 — api — 用于编译和链接模块本身以及依赖于它的任何模块所需的依赖项。

我们在这里只触及了皮毛,因此一旦您熟悉了使用 Gradle 构建 Swift 项目的基础知识,我们建议您阅读专门的依赖管理章节

需要进一步阅读的常见场景包括

您会发现 Gradle 有一个丰富的 API 用于处理依赖项 — 掌握它需要时间,但在常见场景下使用起来很简单。

如果您遵循约定,编译您的代码会非常容易

  1. 将您的源代码放在 src/main/swift 目录下

  2. implementation 配置中声明您的编译依赖项(参见上一节)

  3. 运行 assemble 任务

我们建议您尽可能遵循这些约定,但并非强制要求。

有几种自定义选项,您将在接下来看到。

所有 SwiftCompile 任务都是增量的且可缓存。

支持的 Toolchain

Gradle 支持macOS 和 Linux 官方 Swift Toolchain。当您构建原生二进制文件时,Gradle 会尝试在您的机器上查找能够构建该二进制文件的 Toolchain。Gradle 会选择第一个能够构建目标操作系统、架构和支持 Swift 语言的 Toolchain。

对于 Linux 用户,Gradle 将使用系统 PATH 发现 Toolchain。

自定义文件和目录位置

想象一下,您正在迁移一个遵循 Swift Package Manager 布局(例如,生产代码位于 Sources/ModuleName_ 目录)的库项目。传统的目录结构将无法工作,因此您需要告诉 Gradle 在哪里查找源文件。您可以通过 applicationlibrary 脚本块来完成此操作。

每个组件脚本块以及每个二进制文件都定义了其源代码所在的位置。您可以使用以下语法覆盖约定值

build.gradle.kts
extensions.configure<SwiftLibrary> {
    source.from(file("Sources/Common"))
}
build.gradle
library {
    source.from file('src')
}

现在 Gradle 将仅直接在 Sources/Common 中搜索源文件。

大多数编译器和链接器选项都可以通过相应的任务访问,例如 compileVariantSwiftlinkVariantcreateVariant。这些任务分别属于 SwiftCompileLinkSharedLibraryCreateStaticLibrary 类型。请阅读任务参考以获取最新的、完整的选项列表。

例如,如果您想更改编译器为所有 Variant 生成的警告级别,可以使用此配置

build.gradle.kts
tasks.withType(SwiftCompile::class.java).configureEach {
    // Define a preprocessor macro for every binary
    macros.add("NDEBUG")

    // Define a compiler options
    compilerArgs.add("-O")
}
build.gradle
tasks.withType(SwiftCompile).configureEach {
    // Define a preprocessor macro for every binary
    macros.add("NDEBUG")

    // Define a compiler options
    compilerArgs.add '-O'
}

也可以通过 applicationlibrary 脚本块上的 BinaryCollection 查找特定 Variant 的实例

build.gradle.kts
application {
    binaries.configureEach(SwiftStaticLibrary::class.java) {
        // Define a preprocessor macro for every binary
        compileTask.get().macros.add("NDEBUG")

        // Define a compiler options
        compileTask.get().compilerArgs.add("-O")
    }
}
build.gradle
application {
    binaries.configureEach(SwiftStaticLibrary) {
        // Define a preprocessor macro for every binary
        compileTask.get().macros.add("NDEBUG")

        // Define a compiler options
        compileTask.get().compilerArgs.add '-O'
    }
}

选择目标机器

默认情况下,Gradle 会尝试为宿主操作系统和架构创建 Swift 二进制 Variant。通过在 applicationlibrary 脚本块上指定 TargetMachine 集合,可以覆盖此设置

build.gradle.kts
application {
    targetMachines = listOf(machines.linux.x86_64, machines.macOS.x86_64)
}
build.gradle
application {
    targetMachines = [
        machines.linux.x86_64,
        machines.macOS.x86_64
    ]
}

打包和发布

在原生世界中,如何打包和潜在地发布 Swift 项目差异很大。Gradle 提供了一些默认设置,但自定义打包也可以毫无问题地实现。

  • 可执行文件直接发布到 Maven 仓库。

  • 共享库和静态库文件以及公共头文件的 zip 包会直接发布到 Maven 仓库。

  • 对于应用,Gradle 还支持将可执行文件及其所有共享库依赖项安装并运行到已知位置。

清理构建

Swift 应用和库插件通过使用基础插件为您的项目添加了一个 clean 任务。此任务会简单地删除 layout.buildDirectory 目录中的所有内容,这就是为什么您应该始终将构建生成的文件放在那里。该任务是 Delete 的一个实例,您可以通过设置其 dir 属性来更改它删除的目录。

构建 Swift 库

库项目的独特之处在于它们被其他 Swift 项目使用(或“消费”)。这意味着随二进制文件和头文件发布的依赖元数据 — 以 Gradle Module Metadata 的形式 — 至关重要。特别是,您的库的消费者应该能够区分两种不同类型的依赖项:那些仅用于编译您的库所需的,以及那些也用于编译消费者所需的。

Gradle 通过Swift 库插件来管理这种区别,该插件在本章中涵盖的 implementation 配置之外,还引入了 api 配置。如果依赖项中的类型作为静态库的未解析符号或公共头文件内部出现,那么该依赖项会通过您的库的公共 API 暴露出来,因此应该添加到 api 配置中。否则,该依赖项是内部实现细节,应该添加到 implementation 中。

如果您不确定 API 依赖和 implementation 依赖之间的区别,Swift 库插件章节有详细解释。此外,您可以在相应的示例中看到构建 Swift 库的基本实践示例。

构建 Swift 应用

有关更多详细信息,请参见Swift 应用插件章节,但这里快速总结一下您将获得的功能

  • install 创建一个包含运行所需所有内容的目录

  • 用于启动应用的 Shell 和 Windows Batch 脚本

您可以在相应的示例中看到构建 Swift 应用的基本示例。


1. 遗憾的是,Cocoapods 仓库尚未作为核心功能支持