前语

Pkl(全称为 Pickle)是苹果推出的一种全新的专用于装备的编程言语。它答应开发人员经过类型和内置验证安全、直观地设计数据模型。

作为苹果言语,Pkl 有一个可用于从 .pkl 装备文件生成 Swift 接口的套件东西,这是它与其他言语的开发者有所不同的地方。

在本文中,你将学习怎么装置和运用 pkl-gen-swift 指令行东西,并将其集成到你的 Swift Package Manager(SPM)项目中,方法是运用 SPM 插件。

注意:需求注意的一点是,目前 Pkl 仅适用于 macOS。

示例展现 Pkl 装备

让咱们首要创立一个名为 Config 的简单 Pkl 模块,其间包括一组属性,用于界说一个小型 macOS Swift Package 库的装备,Config.pkl 文件装备如下:

module Config
baseUrl: String
retryCount: Int(isBetween(0, 3))
timeout: Duration

如上面的片段所示,咱们运用类型和规模来束缚能够分配给属性的值,并削减错误的可能性。

Pkl CLI 东西将运用这些类型来验证装备文件并帮助生成 Swift 接口。

现在让咱们编写一个独自的 .pkl 文件,修正咱们之前创立的模块文件,并为本地开发供给装备值,local.pkl 装备如下:

amends "Config.pkl"
baseUrl = "https://localhost:8080"
retryCount = 0
timeout = 30.s

就像这样,咱们编写了一个小型装备,并指定了一些类型和束缚,咱们能够强制执行它们。

现在让咱们装置pkl指令行东西,并评价界说实践值的模块,终端执行指令如下:

# Install pkl
curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.2/pkl-macos-aarch64
chmod +x pkl
# Evaluate the local file
./pkl eval Sources/ClientExample/Resources/local.pkl

上述指令的输出将打印正确的值,这意味着装备能够正确验证:

baseUrl = "https://localhost:8080"
retryCount = 0
timeout = 30.s

生成 Swift 绑定

正如我在文章最初说到的,运用Pkl界说装备的最强大功能之一是,你能够为你的应用程序生成 Swift 接口。

要从 .pkl 文件生成 Swift 接口,你需求装置 pklpkl-gen-swift 指令行东西。

手动装置和运用 pkl-gen-swift

首要,让咱们装置 pkl-gen-swift 指令行东西:

curl -L https://github.com/apple/pkl-swift/releases/download/0.2.3/pkl-gen-swift-macos.bin -o pkl-gen-swift
chmod +x pkl-gen-swift

现在,让咱们经过在终端中运转以下指令来从 .pkl 文件生成 Swift 接口:

PKL_EXEC=./pkl
./pkl-gen-swift Sources/ClientExample/Resources/*.pkl -o Sources/ClientExample/Generated

请注意,pkl-gen-swift 依赖于 pkl 指令行东西,后者需求在你的 PATH 中可用,或许能够运用 PKL_EXEC 环境变量指定。

指令的输出将是一个包括生成接口的单个 Swift 文件:

途径 Sources/ClientExample/Generated/Config.pkl.swift 下文件源代码如下:

// Code generated from Pkl module `Config`. DO NOT EDIT.
import PklSwift
public enum Config {}
extension Config {
    public struct Module: PklRegisteredType, Decodable, Hashable {
        public static var registeredIdentifier: String = "Config"
        public var baseUrl: String
        public var retryCount: Int
        public var timeout: Duration
        public init(baseUrl: String, retryCount: Int, timeout: Duration) {
            self.baseUrl = baseUrl
            self.retryCount = retryCount
            self.timeout = timeout
        }
    }
    /// Load the Pkl module at the given source and evaluate it into `Config.Module`.
    ///
    /// - Parameter source: The source of the Pkl module.
    public static func loadFrom(source: ModuleSource) async throws -> Config.Module {
        try await PklSwift.withEvaluator { evaluator in
            try await loadFrom(evaluator: evaluator, source: source)
        }
    }
    /// Load the Pkl module at the given source and evaluate it with the given evaluator into
    /// `Config.Module`.
    ///
    /// - Parameter evaluator: The evaluator to use for evaluation.
    /// - Parameter source: The module to evaluate.
    public static func loadFrom(
        evaluator: PklSwift.Evaluator,
        source: PklSwift.ModuleSource
    ) async throws -> Config.Module {
        try await evaluator.evaluateModule(source: source, as: Module.self)
    }
}

创立 SPM 指令插件

假设你不希望一切积极参与你的 Swift Package 的人在修正装备时手动装置一切必需的东西以生成代码。

相反,你能够创立一个 Swift Package Manager 指令插件,该插件将封装两个指令行东西,并揭露一个客户友好的指令,该指令将查找一切装备文件并从中生成 Swift 接口。

让咱们考虑以下 Swift Package,代码如下:

// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
    name: "PklSwiftPlugin",
    platforms: [
        // 1
        .macOS(.v13),
    ],
    products: [
        // 2
        .plugin(name: "PklSwiftCommand", targets: ["PklSwiftCommand"])
    ],
    dependencies: [
        // 3
        .package(url: "https://github.com/apple/pkl-swift.git", exact: "0.2.3")
    ],
    targets: [
        // 4
        .plugin(name: "PklSwiftCommand",
                capability: .command(intent: .custom(verb: "swift-pkl", description: ""),
                                     permissions: [.writeToPackageDirectory(reason: "Write pkl to pkg")]),
                dependencies: [.product(name: "pkl-gen-swift", package: "pkl-swift"), "Pkl"]),
        // 5
        .binaryTarget(name: "Pkl",
                      path: "Pkl.artifactbundle"),
        // 6
        .target(name: "ClientExample",
                dependencies: [.product(name: "PklSwift", package: "pkl-swift")])
    ]
)

让咱们一步一步来解释上面的内容:

  1. 咱们声明该包仅适用于 macOS 13 及更高版本,以满足 pkl-swift 的要求。
  2. 咱们声明晰一个新产品,类型为插件,将用于揭露 swift-pkl 指令。
  3. 咱们将 Apple 的 pkl-swift 声明为包的仅有依赖项。pkl-swift 供给了 Pkl 言语的 Swift 绑定和用于生成 Swift 接口的可执行文件。
  4. 咱们为 swift-pkl 指令插件声明晰一个新方针。咱们还声明晰插件的依赖项,其间包括 pkl-gen-swift 可执行文件和 Pkl 指令行东西的构件束。走运的是,咱们能够依赖于 pkl-swift 包中的可执行文件产品来将 Swift 生成器作为依赖项,但咱们需求手动创立一个 pkl 指令行东西的构件束。
  5. 咱们为 pkl 指令行东西的构件束声明晰一个新的二进制方针。
  6. 咱们为用于测验的库声明晰一个新方针。这是包括 .pkl 装备文件的方针。

要创立一个封装 pkl 指令行东西的构件束,你只需求创立一个与包清单中声明的相同称号的目录,后面跟上 .artifactbundle 扩展名。在此目录中,创立以下文件夹结构:

Pkl.artifactbundle
├── info.json
├── pkl-0.25.2-macos
 └── bin
 └── pkl

info.json 文件应包括以下内容:

{
  "schemaVersion": "1.0",
  "artifacts": {
    "pkl": {
      "version": "0.2.3",
      "type": "executable",
      "variants": [
        {
          "path": "pkl-0.25.2-macos/bin/pkl",
          "supportedTriples": ["arm64-apple-macosx"]
        }
      ]
    }
  }
}

现在让咱们编写指令插件的代码,该代码将从上下文中检索指令行东西,迭代方针以查找一切 .pkl 文件,然后最终运转 pkl-gen-swift 可执行文件以生成 Swift 接口,途径 Sources/PklSwiftCommand/main.swift 下的源代码如下:

import PackagePlugin
import Foundation
@main
struct PklSwiftCommandPlugin: CommandPlugin {
    func performCommand(context: PluginContext, arguments: [String]) async throws {
        let pklGenSwift = try context.tool(named: "pkl-gen-swift")
        let pkl = try context.tool(named: "pkl")
        let pklGenSwiftURL = URL(filePath: pklGenSwift.path.string)
        for target in context.package.targets {
            let dirEnum = FileManager.default.enumerator(atPath: target.directory.string)
            var pklFiles = [Path]()
            while let file = dirEnum?.nextObject() as? String {
                if file.hasSuffix(".pkl") {
                    pklFiles.append(target.directory.appending(subpath: file))
                }
            }
            let process = Process()
            process.executableURL = pklGenSwiftURL
            process.arguments = pklFiles.map { $0.string } + ["-o", target.directory.appending(subpath: "Generated").string]
            process.environment = ["PKL_EXEC": pkl.path.string]
            try process.run()
            process.waitUntilExit()
            let gracefulExit = process.terminationReason == .exit && process.terminationStatus == 0
            if !gracefulExit {
                throw " The plugin execution failed with reason: (process.terminationReason.rawValue) and status: (process.terminationStatus) "
            }
        }
    }
}
extension String: Error {}

现在能够像这样运转指令插件:

swift package --disable-sandbox swift-pkl --allow-writing-to-package-directory

请注意,你需求运用 --disable-sandbox 标志,不然插件将无限期挂起。指令的输出结果与曾经相同。

加载 Pkl 装备

现在咱们现已生成了 Swift 接口,能够运用以下代码将其加载到咱们的应用程序中,途径 Sources/ClientExample/main.swift 下源代码如下:

import PklSwift
import Foundation
func load() async throws {
    let pklGenSwift = Bundle.module.bundleURL.deletingLastPathComponent().appending(path: "pkl-gen-swift").path
    let pklFile = Bundle.module.url(forResource: "local", withExtension: "pkl")!
    setenv("PKL_EXEC", pklGenSwift, 1)
    let config = try await SomeConfig.loadFrom(source: .path(pklFile.path))
    print(config.baseUrl)
    print(config.timeout)
    print(config.retryCount)
}

在测验执行与文档中相同的代码时,我遇到了一个问题,即 PklSwift 无法在途径中找到 pkl。因而,我有必要手动设置 PKL_EXEC 环境变量在示例可执行文件中。

总结

本文介绍了 Pkl,这是苹果推出的一种专用于装备的新编程言语。它答应开发人员经过类型和内置验证安全地设计数据模型。Pkl 具有一套东西,可用于从 .pkl 装备文件生成 Swift 接口,这是其与其他言语的差异之一。文章详细介绍了怎么装置和运用 pkl-gen-swift 指令行东西,并将其集成到 Swift Package Manager(SPM) 项目中。然后,经过示例展现了怎么创立和修正 Pkl 装备文件,以及怎么运用 pkl 指令行东西评价装备文件。接着,介绍了怎么生成 Swift 接口文件,以及怎么创立 SPM 指令插件来主动生成代码。