什么是 Swift Package Manager? – wiki基地


Swift Package Manager:Swift 生态系统的基石与代码协作的未来

在现代软件开发中,代码的复用和依赖管理是至关重要的环节。无论是构建小型应用还是大型系统,开发者都需要引入外部库、框架或模块来避免重复造轮子,并利用社区的力量。对于 Swift 语言而言,长久以来,开发者们依赖于一些第三方工具来管理这些外部依赖,比如 CocoaPods 和 Carthage。然而,随着 Swift 语言的成熟和跨平台能力的增强,社区迫切需要一个官方的、内置的、跨平台的包管理解决方案。

正是在这样的背景下,Swift Package Manager (SPM) 应运而生。自 Swift 3 发布以来,SPM 逐渐成为 Swift 生态系统中不可或缺的一部分,并随着 Swift 版本的迭代而不断演进和完善。它不仅仅是一个简单的依赖下载工具,更是一个用于分发源代码、构建库和可执行文件、运行测试以及管理项目依赖关系的综合性工具。

那么,究竟什么是 Swift Package Manager?它如何工作?以及它在 Swift 开发中扮演着怎样的角色?本文将深入探讨 SPM 的各个方面,从基础概念到高级用法,带你全面了解这个强大的工具。

一、 理解软件包管理:为什么我们需要 SPM?

在深入 SPM 的细节之前,先回顾一下软件包管理的普遍需求。想象一下,你的项目需要使用一个处理 JSON 的库、一个网络请求库和一个图像加载库。如果没有软件包管理器,你可能需要:

  1. 手动下载这些库的源代码。
  2. 将它们添加到你的项目中。
  3. 解决它们之间的潜在依赖冲突(例如,库 A 需要库 C 的 1.x 版本,而库 B 需要库 C 的 2.x 版本)。
  4. 手动处理库的更新。
  5. 确保每个开发者使用相同版本的库,以保证构建的一致性。

这个过程繁琐、易错且难以维护,尤其是在大型项目或团队协作中。软件包管理器就是为了解决这些问题而出现的。它们提供了一个标准化的方式来:

  • 声明项目所依赖的外部代码(依赖关系声明)。
  • 自动下载和集成这些依赖(依赖获取)。
  • 处理依赖之间的版本冲突(依赖解析)。
  • 方便地更新依赖(依赖更新)。
  • 确保项目的可重复构建性(锁定依赖版本)。

对于 Swift 而言,SPM 作为 Swift 语言的官方工具,被设计成能够与 Swift 编译器、构建系统以及未来的开发工具紧密集成,从而提供比第三方工具更原生、更流畅的体验。它支持跨平台(macOS, Linux, iOS, tvOS, watchOS, Windows 等),不仅仅服务于 Apple 平台开发,也广泛应用于 Swift 的服务器端和命令行工具开发。

二、 SPM 的核心概念

理解 SPM 需要掌握几个核心概念:

  1. Package(软件包):
    软件包是 SPM 管理的基本单元。它是一个包含源代码、资源、测试以及一个 Package.swift 清单文件的目录。Package.swift 文件定义了软件包的名称、目标平台、依赖关系、产品以及包含哪些目标。软件包是可分发的单位,通常托管在 Git 仓库中。

  2. Target(目标):
    目标是软件包中的一个构建单元。每个软件包至少包含一个目标。目标可以是:

    • Library(库): 生成一个可以被其他代码导入和使用的模块(framework 或 static/dynamic library)。
    • Executable(可执行文件): 生成一个独立的、可运行的程序。
    • Test Target(测试目标): 包含用于测试库或可执行文件目标的源代码。

    目标通常对应软件包目录结构中的一个子目录(例如,Sources/MyLibrarySources/MyApp)。每个目标都有自己的源文件、依赖关系(声明在该目标内部)以及潜在的资源文件。

  3. Product(产品):
    产品是软件包构建后可供外部使用的成果。软件包可以定义一个或多个产品。产品通常是:

    • Library(库): 一个或多个目标的组合,形成一个可导入的模块。这是最常见的产品类型,用于代码共享。
    • Executable(可执行文件): 由一个可执行文件目标构建的程序。

    产品是软件包的使用者会直接依赖和链接到的东西。一个软件包可能包含多个目标,但只暴露其中部分目标作为产品。

  4. Dependency(依赖):
    依赖是指一个软件包需要使用的其他软件包。在 Package.swift 文件中声明。依赖关系定义了所需的软件包来源(通常是一个 Git 仓库 URL)以及版本要求(例如,某个特定的版本、一个版本范围或某个分支/提交)。

  5. Package Manifest (Package.swift):
    这是软件包的配置文件,使用 Swift 语言编写。它位于软件包的根目录,定义了软件包的所有元数据:名称、平台兼容性、依赖项、目标以及产品。它是 SPM 理解和处理软件包的关键文件。

  6. Dependency Resolution(依赖解析):
    这是 SPM 的核心功能之一。当 SPM 处理一个项目的依赖关系时,它会读取项目的 Package.swift 文件以及其所有依赖的 Package.swift 文件,然后计算出一个满足所有版本约束的最小兼容版本集合。

  7. Package.resolved 文件:
    在成功解析依赖后,SPM 会生成一个 Package.resolved 文件。这个文件精确地记录了每个直接和间接依赖所使用的 具体 版本、提交哈希或分支。这个文件的目的是确保项目在不同时间、不同机器上构建时,使用的依赖版本是完全一致的,从而保证构建的可重复性。这个文件应该被提交到版本控制系统中。

三、 Package.swift 清单文件详解

Package.swift 是用 Swift 编写的声明性文件,它利用 Swift 的 DSL(领域特定语言)来定义软件包结构。一个典型的 Package.swift 文件结构如下:

“`swift
// swift-tools-version:5.9 // 指定使用的 Swift 工具版本

import PackageDescription

let package = Package(
name: “MyAwesomeLibrary”, // 软件包名称
platforms: [ // 指定兼容的平台和最低版本
.iOS(.v13),
.macOS(.v10_15),
.tvOS(.v13),
.watchOS(.v6)
],
products: [ // 定义软件包暴露的产品
.library( // 这是一个库产品
name: “MyAwesomeLibrary”, // 产品名称
targets: [“MyAwesomeLibrary”] // 包含哪些目标
),
.executable( // 这是一个可执行产品
name: “my-cli-tool”,
targets: [“MyCLITool”]
)
],
dependencies: [ // 声明软件包依赖的其他软件包
// 依赖于 Alamofire 库,版本要求大于等于 5.4.0 且小于 6.0.0
.package(url: “https://github.com/Alamofire/Alamofire.git”, from: “5.4.0”),

    // 依赖于第三方库,版本要求大于等于 1.0.0 且小于 2.0.0
    .package(url: "https://github.com/example/ThirdPartyLib.git", .upToNextMajor(from: "1.0.0")),

    // 依赖于另一个内部软件包,版本要求大于等于 2.1.0 且小于 2.2.0
    .package(url: "https://github.com/myorg/AnotherPackage.git", .upToNextMinor(from: "2.1.0")),

    // 依赖于指定提交哈希的版本 (不推荐用于发布版本)
    // .package(url: "https://github.com/example/SomeLib.git", .revision("abcdef1234567890")),

    // 依赖于指定分支的最新提交 (不推荐用于发布版本)
    // .package(url: "https://github.com/example/DevelopLib.git", .branch("develop")),
],
targets: [ // 定义软件包内部的目标
    .target( // 这是一个库目标
        name: "MyAwesomeLibrary", // 目标名称
        dependencies: [ // 这个目标依赖的其他目标或产品
            .product(name: "Alamofire", package: "Alamofire"), // 依赖于上面声明的 Alamofire 产品的名称和软件包名称
            "ThirdPartyLib" // 如果依赖的软件包只有一个产品且名称与软件包相同,可以省略 .product
        ],
        path: "Sources/MyAwesomeLibrary", // 源文件路径,相对于 Package.swift
        resources: [ // 包含的资源文件
            .process("Resources") // 处理 Resources 目录下的所有资源
        ]
    ),
    .executableTarget( // 这是一个可执行文件目标
        name: "MyCLITool",
        dependencies: ["MyAwesomeLibrary"] // 依赖于本软件包内的 MyAwesomeLibrary 目标
    ),
    .testTarget( // 这是一个测试目标
        name: "MyAwesomeLibraryTests",
        dependencies: ["XCTest", "MyAwesomeLibrary"] // 测试目标通常依赖 XCTest 和被测试的目标
    )
]

)
“`

  • swift-tools-version: 必须在文件顶部声明,指定解析此清单文件所需的 Swift 工具版本。这确保了 SPM 工具本身能够正确理解清单文件的语法和结构。
  • name: 软件包的名称。
  • platforms: 可选,指定软件包兼容的平台和最低版本。如果未指定,SPM 会根据使用它的环境来确定默认平台。
  • products: 定义构建后可以供其他软件包或应用使用的成果。
  • dependencies: 声明软件包需要依赖的外部 Swift 软件包。
    • .package(url: ..., from: ...): 声明对指定 Git URL 仓库的依赖,并指定一个最低版本 (from)。SPM 会解析到满足此最低要求的最新 主要 版本。例如,from: "5.4.0" 会匹配 5.4.0, 5.4.1, 5.5.0, …, 5.9.9 等 5.x.y 版本,但不匹配 6.0.0 及以上版本。这遵循 Semantic Versioning (SemVer) 的原则。
    • .package(url: ..., .upToNextMajor(from: ...)): 明确指定依赖到下一个主要版本之前。例如,.upToNextMajor(from: "1.0.0") 匹配 1.x.y 版本,但不匹配 2.0.0 及以上。
    • .package(url: ..., .upToNextMinor(from: ...)): 明确指定依赖到下一个次要版本之前。例如,.upToNextMinor(from: "1.2.0") 匹配 1.2.x 版本,但不匹配 1.3.0 及以上。
    • .package(url: ..., .exact(...)): 依赖于 精确 的某个版本。
    • .package(url: ..., .branch(...)): 依赖于某个分支的最新提交。不推荐用于生产环境,因为分支是易变的。
    • .package(url: ..., .revision(...)): 依赖于某个特定的 Git 提交哈希。通常用于测试或特殊情况,不推荐用于正常依赖管理。
  • targets: 定义软件包内部的构建单元。
    • .target(...): 定义一个库目标。
    • .executableTarget(...): 定义一个可执行文件目标。
    • .testTarget(...): 定义一个测试目标。
    • dependencies (在 target 内部): 声明 这个目标 依赖的其他 目标(可以是本软件包内的其他目标,也可以是其依赖的外部软件包暴露的产品)。
    • path: 可选,指定目标源代码所在的目录。如果遵循 SPM 的标准目录结构(Sources/TargetName),可以省略。
    • resources: 可选,指定需要包含在目标构建产物中的资源文件(如图片、音频、配置文件等)。.process("...") 会智能处理不同类型的资源。

四、 如何使用 Swift Package Manager

使用 SPM 主要有两种方式:通过命令行工具和通过 Xcode 集成。

1. 通过命令行工具 (CLI)

SPM 提供了一系列 swift package 命令来管理软件包:

  • swift package init: 初始化一个新的 Swift 软件包。可以指定类型(--type library--type executable)。
    bash
    mkdir MyNewPackage
    cd MyNewPackage
    swift package init --type library

    这会创建一个 Package.swift 文件、一个 Sources 目录(包含一个与软件包同名的子目录及一个 .swift 文件)和一个 Tests 目录(包含一个测试子目录及测试文件)。

  • swift package edit <package-name> --branch <branch-name>: 克隆指定的依赖软件包到本地,并在指定分支上进行编辑。SPM 会暂时使用本地副本而不是远程仓库。这对于调试依赖问题或对依赖进行临时修改非常有用。完成后使用 swift package unedit <package-name> 恢复。

  • swift package update: 检查依赖关系是否有更新的版本,并更新 Package.resolved 文件。
    bash
    swift package update

    SPM 会尝试找到满足所有约束的最新版本组合。

  • swift package resolve: 根据 Package.swift 中的声明和当前的 Package.resolved 文件(如果存在),解析依赖关系并更新 Package.resolved 文件。如果 Package.resolved 不存在,它会进行首次解析并生成该文件。

  • swift build: 构建当前软件包及其依赖。构建产物会放在 .build 目录下。
    bash
    swift build

    可以使用 --configuration release 构建发布版本(默认是 debug)。

  • swift run <executable-target-name>: 构建并运行软件包中的可执行文件目标。
    bash
    swift run my-cli-tool

  • swift test: 构建并运行软件包中的测试目标。
    bash
    swift test

  • swift package clean: 清理构建产物和缓存。

  • swift package generate-xcodeproj: (在 Xcode 11 之前常用) 生成一个 Xcode 项目文件,以便在 Xcode 中开发和调试软件包。注意: 在 Xcode 11 及更高版本中,Xcode 原生支持打开和编辑 Swift 软件包目录,通常不再需要手动生成 .xcodeproj 文件。

这些命令行工具使得 SPM 成为构建命令行工具、服务器应用或跨平台库的理想选择,因为它不依赖于 Xcode GUI。

2. 通过 Xcode 集成

自 Xcode 11 起,SPM 被深度集成到 Xcode 中,为 iOS、macOS、tvOS、watchOS 等应用开发带来了极大的便利。

添加软件包依赖:

  1. 打开你的 Xcode 项目 (.xcodeproj.xcworkspace)。
  2. 选择 “File” > “Add Packages…”.
  3. 在弹出的搜索框中,输入你想要添加的软件包的 Git 仓库 URL(例如 https://github.com/Alamofire/Alamofire.git)。
  4. Xcode 会自动识别并加载软件包信息。
  5. 选择依赖规则(版本、分支或提交)。通常推荐使用 “Up to Next Major Version” 并指定一个起始版本。
  6. 点击 “Add Package”.
  7. Xcode 会开始下载和解析依赖。完成后,你会看到需要将该软件包的哪些产品添加到你项目中的哪个目标(Target)。选择相应的目标并点击 “Add Package”。

管理软件包依赖:

  • 在 Project Navigator 中,项目的根节点下方会有一个 “Package Dependencies” 部分。展开它可以看到所有直接依赖的软件包及其版本。
  • 选中项目文件(顶部的蓝色图标),然后在主编辑区域选择你的项目或工作区,再选择 “Package Dependencies” 标签页。这里列出了所有依赖的软件包,你可以修改版本规则、更新依赖、移除依赖等。
  • Xcode 会自动管理 Package.resolved 文件,并将其保存在 .xcodeproj.xcworkspace 文件所在的目录中(通常是 你的项目.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved)。这个文件应该被提交到版本控制中

使用软件包中的代码:

一旦软件包被添加到项目的目标中,你就可以像使用项目内部模块一样,在你的源代码文件中使用 import ModuleName 来引入软件包暴露的产品模块。例如,import Alamofire

Xcode 的集成使得在 App 项目中使用 SPM 变得非常直观和便捷,极大地简化了第三方库的集成过程。

五、 创建你自己的 Swift 软件包

除了使用现有的软件包,SPM 的另一个重要用途是让你能够轻松地创建自己的可复用代码库或工具,并将其分享给他人或在自己的其他项目中使用。

创建一个 Swift 软件包的基本步骤:

  1. 初始化软件包:
    使用命令行 swift package init 命令。选择 --type library 创建一个库(用于代码共享),或 --type executable 创建一个可运行的工具。

    bash
    mkdir MyUtilities
    cd MyUtilities
    swift package init --type library

    这将生成:
    * Package.swift
    * Sources/MyUtilities/MyUtilities.swift (一个简单的示例源文件)
    * Tests/MyUtilitiesTests/MyUtilitiesTests.swift (一个简单的示例测试文件)

  2. 编写代码:
    Sources/你的软件包名称 目录下编写你的源代码。每个目标通常对应一个目录。SPM 会自动将该目录下的 .swift 文件编译成该目标的模块。

  3. 定义产品和目标:
    编辑 Package.swift 文件,定义你的软件包提供的产品(通常是库或可执行文件)以及内部的目标结构。确保 products 部分暴露了你希望使用者能够导入和链接到的模块。

  4. 添加依赖(如果需要):
    如果你的软件包需要依赖其他的 Swift 软件包,在 Package.swiftdependencies 部分声明它们。然后在需要使用这些依赖的目标的 dependencies 数组中引用它们。

  5. 编写测试:
    Tests/你的软件包名称Tests 目录下编写测试代码。SPM 使用 XCTest 框架。使用 swift test 命令运行测试。

  6. 添加资源(如果需要):
    如果你的库需要包含资源文件(如图片、配置文件等),将它们放在目标目录下的某个子目录中(例如 Sources/MyLibrary/Resources),然后在该目标的定义中,使用 resources: [.process("Resources")].copy("Resources") 来包含这些资源。.process 是推荐的方式,它会根据平台和资源类型进行优化。

  7. 构建和测试:
    使用 swift buildswift test 命令在本地验证你的软件包。

  8. 版本控制和发布:
    将你的软件包代码托管到 Git 仓库(如 GitHub, GitLab, Bitbucket 等)。为了让其他开发者能够稳定地依赖你的软件包,强烈建议使用 Semantic Versioning (SemVer) 进行版本标记(例如 1.0.0, 1.1.0, 2.0.0)。每次发布新版本时,创建一个带有版本号标签的 Git 提交。例如:
    bash
    git add .
    git commit -m "Release 1.0.0"
    git tag 1.0.0
    git push origin main --tags # 提交代码和标签

    其他开发者就可以通过你的 Git 仓库 URL 和版本标签来依赖你的软件包了。

六、 高级特性和注意事项

  • 二进制依赖: SPM 支持引用预构建的二进制库 (.xcframework 文件)。这对于分发闭源库或大型、复杂难以从源代码构建的库非常有用。需要在 Package.swift 中声明 .binaryTarget(...)
  • 本地依赖: 在开发过程中,你可以直接将本地文件系统中的另一个软件包添加为依赖,而无需通过 Git 仓库。这对于在开发一个库的同时开发使用它的应用非常方便。
  • 平台条件: 可以在 Package.swift 中使用 .when(platforms: [...]) 条件性地包含依赖或目标,以处理不同平台的需求差异。
  • 插件 API: SPM 正在引入插件 API,允许开发者定义自定义的构建工具插件(例如代码生成、格式化)和命令行插件。这使得 SPM 成为一个更强大的构建系统。
  • 资源处理: SPM 对资源处理提供了内置支持,不同于 CocoaPods 或 Carthage 可能需要额外的脚本。.process 会根据平台自动处理,例如在 Apple 平台上将资源打包到 Bundle 中。
  • 跨语言兼容性: 虽然 SPM 是为 Swift 设计的,但它可以集成包含 C, C++, Objective-C, Objective-C++ 代码的模块。这些模块需要遵循特定的目录结构。
  • 依赖冲突解决: SPM 的依赖解析器非常智能,会尝试找到一个兼容所有依赖版本约束的解决方案。如果存在不可调和的冲突,它会报错并指出问题所在。Package.resolved 文件是确保构建一致性的关键。
  • Package Collections: SPM 支持 Package Collections,这是一个 JSON 文件,列出了策展过的、推荐的软件包列表,通常由组织或社区维护,方便开发者发现和添加高质量的软件包。Xcode 也支持添加 Package Collections URL。

七、 SPM、CocoaPods 和 Carthage 的简要对比

尽管本文重点是 SPM,但简要对比一下 Swift 社区中常见的包管理器有助于更好地理解 SPM 的位置:

  • CocoaPods: 最老牌、生态最丰富的 iOS/macOS 依赖管理器。基于 Ruby。需要安装 CocoaPods gem。通过创建 .xcworkspace 文件来工作。主要服务于 Apple 平台。生态成熟,但也可能带来一些复杂性和构建时间开销。
  • Carthage: 另一种流行的 iOS/macOS 依赖管理器。基于 Swift。不修改你的 Xcode 项目文件,而是构建依赖为 Frameworks,你需要手动将它们拖入你的项目。更去中心化,构建过程相对透明。同样主要服务于 Apple 平台。
  • Swift Package Manager (SPM): Swift 语言的官方、内置工具。跨平台支持好(Apple 平台、Linux、Windows 等)。无需额外安装(随 Swift 工具链和 Xcode 提供)。深度集成到 Xcode 中。生态正在快速发展壮大。作为官方工具,它与 Swift 编译器和构建系统的集成更为紧密。它既支持源码依赖,也支持二进制依赖。

SPM 的最大优势在于其原生性、跨平台能力以及与 Swift 工具链的深度集成。对于新的 Swift 项目,特别是跨平台项目、服务器端项目或命令行工具,SPM 是首选。对于现有的 Apple 平台项目,迁移到 SPM 越来越可行,社区的许多常用库也已支持 SPM。

八、 未来展望

Swift Package Manager 的发展仍在继续。未来的版本可能会带来更多高级特性,例如更强大的插件系统、更好的资源处理、更灵活的构建配置以及对更多分发渠道的支持。作为 Swift 生态系统的核心组成部分,SPM 的成熟将进一步推动 Swift 在各个领域的应用,并促进代码的共享和协作。

九、 结论

Swift Package Manager 是 Swift 语言不可或缺的依赖管理和构建工具。它提供了一种标准化、高效且跨平台的方式来声明、解析、获取和构建项目依赖,以及创建和分发你自己的可复用代码。通过理解其核心概念、掌握命令行工具和 Xcode 集成的使用方法,以及学会如何创建自己的软件包,开发者可以极大地提高开发效率,简化项目维护,并更好地参与到 Swift 生态系统的协作中。

无论是开发复杂的 iOS 应用、构建高性能的服务器端服务,还是创建实用的命令行工具,Swift Package Manager 都将是你的得力助手,为你的 Swift 项目奠定坚实的基础。随着 SPM 的不断发展,它在 Swift 开发中的重要性只会越来越高,成为每一个 Swift 开发者都应该熟练掌握的必备技能。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部