前言
Pkl(全称为 Pickle)是苹果推出的一种全新的专用于配置的编程语言。它允许开发人员通过类型和内置验证安全、直观地设计数据模型。
作为苹果语言,Pkl 有一个可用于从 .pkl
配置文件生成 Swift 接口的套件工具,这是它与其他语言的开发者有所不同的地方。
在本文中,你将学习如何安装和使用 pkl-gen-swift
命令行工具,并将其集成到你的 Swift Package Manager(SPM)项目中,方法是使用 SPM 插件。
注意:需要注意的一点是,目前 Pkl 仅适用于 macOS。
示例展示 Pkl 配置
让我们首先创建一个名为 Config 的简单 Pkl 模块,其中包含一组属性,用于定义一个小型 macOS Swift Package 库的配置,Config.pkl 文件配置如下:
swift
module Config
baseUrl: String
retryCount: Int(isBetween(0, 3))
timeout: Duration
如上面的片段所示,我们使用类型和范围来约束可以分配给属性的值,并减少错误的可能性。
Pkl CLI 工具将使用这些类型来验证配置文件并帮助生成 Swift 接口。
现在让我们编写一个单独的 .pkl
文件,修改我们之前创建的模块文件,并为本地开发提供配置值,local.pkl 配置如下:
swift
amends "Config.pkl"
baseUrl = "https://localhost:8080"
retryCount = 0
timeout = 30.s
就像这样,我们编写了一个小型配置,并指定了一些类型和约束,我们可以强制执行它们。
现在让我们安装pkl命令行工具,并评估定义实际值的模块,终端执行命令如下:
ruby
# 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
上述命令的输出将打印正确的值,这意味着配置可以正确验证:
ruby
baseUrl = "https://localhost:8080"
retryCount = 0
timeout = 30.s
生成 Swift 绑定
正如我在文章开头提到的,使用Pkl定义配置的最强大功能之一是,你可以为你的应用程序生成 Swift 接口。
要从 .pkl
文件生成 Swift 接口,你需要安装 pkl
和 pkl-gen-swift
命令行工具。
手动安装和使用 pkl-gen-swift
首先,让我们安装 pkl-gen-swift
命令行工具:
ruby
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 接口:
ruby
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
下文件源代码如下:
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
// 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")])
]
)
让我们一步一步来解释上面的内容:
- 我们声明该包仅适用于 macOS 13 及更高版本,以满足
pkl-swift
的要求。 - 我们声明了一个新产品,类型为插件,将用于公开
swift-pkl
命令。 - 我们将 Apple 的 pkl-swift 声明为包的唯一依赖项。pkl-swift 提供了 Pkl 语言的 Swift 绑定和用于生成 Swift 接口的可执行文件。
- 我们为
swift-pkl
命令插件声明了一个新目标。我们还声明了插件的依赖项,其中包括pkl-gen-swift
可执行文件和Pkl
命令行工具的构件束。幸运的是,我们可以依赖于pkl-swift
包中的可执行文件产品来将 Swift 生成器作为依赖项,但我们需要手动创建一个pkl
命令行工具的构件束。 - 我们为
pkl
命令行工具的构件束声明了一个新的二进制目标。 - 我们为用于测试的库声明了一个新目标。这是包含
.pkl
配置文件的目标。
要创建一个封装 pkl
命令行工具的构件束,你只需要创建一个与包清单中声明的相同名称的目录,后面跟上 .artifactbundle
扩展名。在此目录中,创建以下文件夹结构:
swift
Pkl.artifactbundle
├── info.json
├── pkl-0.25.2-macos
│ └── bin
│ └── pkl
info.json
文件应包含以下内容:
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
下的源代码如下:
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
swift package --disable-sandbox swift-pkl --allow-writing-to-package-directory
请注意,你需要使用 --disable-sandbox
标志,否则插件将无限期挂起。命令的输出结果与以前相同。
加载 Pkl 配置
现在我们已经生成了 Swift 接口,可以使用以下代码将其加载到我们的应用程序中,路径 Sources/ClientExample/main.swift
下源代码如下:
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 命令插件来自动生成代码。