Vision Pro で盛り上がる最中、Apple からコンフィグレーションファイルを生成するための静的型付言語「Pkl」がオープンソースで公開されたので軽く触ってみました。 github.com
発音は "Pickle" (「ピックル?ピクルゥ?」 )だそうで、 Pickle と聞くと Python の "Pickle化" が頭をよぎりますが、今回はそこには触れずに進めていきましょう!
Installation
公式ドキュメントの Installation からインストールします。今回は M1 MacBook Air から試してみるので、 Native macOS executable for amd64 (tested on macOS 10.15)
を使います。
$ curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.1/pkl-macos-amd64 $ chmod +x pkl $ ./pkl --version Pkl 0.25.1 (macOS, native)
単に実行ファイルが降ってくるだけなので、実用を考えるなら ./.pkl/bin
みたいなディレクトリを作っておいた上でダウンロードして、 .zshrc
などに export PATH=$PATH:$HOME/.pkl/bin
と追加しておくと任意の場所で実行できるようにできます。(Go風味)
触ってみる
コンフィグレーションファイルを生成するための言語として CUE などが比較にあがりますが、 Pkl がユニークなのはコンフィグレーションファイルの生成だけではなくアプリケーションへ埋め込み可能な言語でもあることでしょう。現状 Language Binding はすでに
- Java
- Kotlin
- Swift
- Go
がサポートされており、今後も増えていくよと語られています。Go が最初からサポートされているのははっきりとターゲットを意識しているな〜と感じました。 github.com
Swift
せっかくなので Swift で触ってみます。 Quickstart に沿ってやっていきましょう。追加でバイナリが必要なので落としてきます。
$ curl -L https://github.com/apple/pkl-swift/releases/download/0.2.1/pkl-gen-swift-macos.bin -o pkl-gen-swift $ chmod +x pkl-gen-swift
バージョンが表示されていればOKです。
$ pkl-gen-swift --version 0.2.1
Vapor を使って、簡単なWEBアプリケーションサーバーのプロジェクト用意しておき、そこに pkl-swift
を依存として追加します。
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "hello", platforms: [ .macOS(.v13) ], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0"), .package(url: "https://github.com/apple/pkl-swift", from: "0.2.1"), ], targets: [ .target( name: "Generated", dependencies: [ .product(name: "PklSwift", package: "pkl-swift") ] ), .executableTarget( name: "App", dependencies: [ "Generated", .product(name: "Vapor", package: "vapor"), ] ), .testTarget( name: "AppTests", dependencies: [ "Generated", .target(name: "App"), .product(name: "XCTVapor", package: "vapor"), // Workaround for https://github.com/apple/swift-package-manager/issues/6940 .product(name: "Vapor", package: "vapor"), ]), ] )
今回は Example で紹介されているサーバーのホストとポートを環境ごとに用意するユースケースで確認してみましょう。まず AppConfig.pkl
というファイルを作り、以下のように書きます。
// AppConfig.pkl module AppConfig host: String // UInt16 は Int(isBetween(0, 65_535)) の typealias として定義されています port: UInt16
AppConfig
モジュールからローカル環境向けのコンフィグを作ります。
// pkl/Local/config.pkl amends "../AppConfig.pkl" host = "localhost" port = 8080
pkl
コマンドで正しく設定できているか確認できます。
$ pkl eval ./pkl/Local/config.pkl host = "localhost" port = 8080
今回はそのままアプリケーションから読み込んで使いますが、 JSON や YAML にも当然変換できますね。
$ pkl eval -f json ./pkl/Local/config.pkl { "host": "localhost", "port": 8080 } $ pkl eval -f yaml ./pkl/Local/config.pkl host: localhost port: 8080
実際に書き出すなら、-o
オプション*1をつければいい。
$ pkl eval -f json -o config.json ./pkl/Local/config.pkl
実際にSwift アプリケーションから使えるようにするには pkl-gen-swift
で Swift ファイルを生成します。
$ pkl-gen-swift pkl/AppConfig.pkl -o Sources/App/Generated/
すると、以下のようなファイルが生成されます。
// Code generated from Pkl module `AppConfig`. DO NOT EDIT. import PklSwift public enum AppConfig {} extension AppConfig { public struct Module: PklRegisteredType, Decodable, Hashable { public static var registeredIdentifier: String = "AppConfig" public var host: String public var port: Int public init(host: String, port: Int) { self.host = host self.port = port } } /// Load the Pkl module at the given source and evaluate it into `AppConfig.Module`. /// /// - Parameter source: The source of the Pkl module. public static func loadFrom(source: ModuleSource) async throws -> AppConfig.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 /// `AppConfig.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 -> AppConfig.Module { try await evaluator.evaluateModule(source: source, as: Module.self) } }
AppConfig.Module
構造体とpklモジュールから AppConfig.Module
に評価・変換する関数が含まれていますね。
生成された関数 loadFrom
を使って、先ほど作ったローカル環境向けの設定ファイルを読み込めば、意図した環境の AppConfig.Module
が得ることができます。あとはそれらをアプリケーションに適用するだけです。
import Generated import PklSwift import Vapor public func configure(_ app: Application) async throws { let config = try await AppConfig.loadFrom(source: ModuleSource.path("pkl/Local/config.pkl")) app.http.server.configuration.hostname = config.host app.http.server.configuration.port = config.port try routes(app) }
swift run
で動かせば、 Local/config.pkl
で設定した値でサーバーが起動することが確認できるはずです。
[ NOTICE ] Server starting on http://localhost:8080
ざっくり触ってみるところまでやってみましたが、Pkl という言語の表現力にはあまり触れることができませんでした。それはまた別の機会にやってみたいと思います。
言語仕様については紹介しきれない箇所が沢山(throwできたり、Null Value があったりなどなど)あるので、気になる方は公式ドキュメントをご確認ください。
最後に
CUE や toml でいいという声も聞こえてきそうですが、哲学も異なりますし、Framework Integrerations のサポートは Spring (Boot) のみという状況から見ても、Apple の内部のバックエンドサーバー開発のために作ったものを公開したという感じなんですかね(?)。ちなみに公式サイトでは "Incredible IDE Integration" と謳っていますが、 Xcode への統合サポートは現状ありません。Apple 製というのもありしっかり Plist 生成のサポートもあります。今後のアップデートに期待ですね!