mirror of https://github.com/apple/pkl-swift
Add Pkl 0.26 features (#23)
* Add Pkl 0.26 features * Add multi-version support * Bump Pkl version in CI * Update CI * Split tests across different Pkl versions Co-authored-by: Daniel Chao <daniel.h.chao@gmail.com>
This commit is contained in:
parent
b23fd2369f
commit
48bdb4f503
|
@ -15,6 +15,8 @@
|
||||||
// ===----------------------------------------------------------------------===//
|
// ===----------------------------------------------------------------------===//
|
||||||
amends "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.circleci@1.0.1#/PklCI.pkl"
|
amends "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.circleci@1.0.1#/PklCI.pkl"
|
||||||
|
|
||||||
|
import "pkl:semver"
|
||||||
|
|
||||||
local swiftTest = new RunStep {
|
local swiftTest = new RunStep {
|
||||||
name = "swift test"
|
name = "swift test"
|
||||||
command = """
|
command = """
|
||||||
|
@ -23,59 +25,78 @@ local swiftTest = new RunStep {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
local pklVersion = "0.25.1"
|
local class PklDistribution {
|
||||||
local pklBinary = "https://repo1.maven.org/maven2/org/pkl-lang/pkl-cli-linux-amd64/\(pklVersion)/pkl-cli-linux-amd64-\(pklVersion).bin"
|
/// The version of this distribution
|
||||||
|
version: String(semver.isValid(this))
|
||||||
|
|
||||||
local downloadPkl = new RunStep {
|
/// Normalized version for use in task names
|
||||||
name = "Downloading pkl"
|
fixed normalizedVersion: String = version.replaceAll(".", "-")
|
||||||
|
|
||||||
|
/// The URL to download this distribution
|
||||||
|
fixed downloadUrl: String = "https://github.com/apple/pkl/releases/download/\(version)/pkl-linux-amd64"
|
||||||
|
|
||||||
|
fixed downloadRunStep: RunStep = new {
|
||||||
|
name = "Downloading pkl-\(version)"
|
||||||
command = """
|
command = """
|
||||||
mkdir /tmp/pkl
|
PKL=$(mktemp /tmp/pkl-\(version)-XXXXXX)
|
||||||
curl -L "\(pklBinary)" > /tmp/pkl/pkl
|
curl -L "\(downloadUrl)" > $PKL
|
||||||
chmod +x /tmp/pkl/pkl
|
chmod +x $PKL
|
||||||
echo 'export PKL_EXEC=/tmp/pkl/pkl' >> $BASH_ENV
|
echo "export PKL_EXEC=$PKL" >> $BASH_ENV
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local pklCurrent: PklDistribution = new {
|
||||||
|
version = "0.26.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
local pklDistributions: Listing<PklDistribution> = new {
|
||||||
|
new { version = "0.25.3" }
|
||||||
|
pklCurrent
|
||||||
|
}
|
||||||
|
|
||||||
|
local testJobs = jobs.keys.filter((it) -> it.startsWith("test"))
|
||||||
|
|
||||||
main {
|
main {
|
||||||
jobs {
|
jobs {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prb {
|
prb {
|
||||||
jobs {
|
jobs {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
jobs {
|
jobs {
|
||||||
"test"
|
...testJobs
|
||||||
new {
|
new {
|
||||||
["pkl-package"] {
|
["pkl-package"] {
|
||||||
requires {
|
requires {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
["pkl-gen-swift-macos"] {
|
["pkl-gen-swift-macos"] {
|
||||||
requires {
|
requires {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
["pkl-gen-swift-linux-amd64"] {
|
["pkl-gen-swift-linux-amd64"] {
|
||||||
requires {
|
requires {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
["pkl-gen-swift-linux-aarch64"] {
|
["pkl-gen-swift-linux-aarch64"] {
|
||||||
requires {
|
requires {
|
||||||
"test"
|
...testJobs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +121,8 @@ triggerDocsBuild = "release"
|
||||||
triggerPackageDocsBuild = "release"
|
triggerPackageDocsBuild = "release"
|
||||||
|
|
||||||
jobs {
|
jobs {
|
||||||
["test"] {
|
for (distribution in pklDistributions) {
|
||||||
|
["test-pkl-\(distribution.normalizedVersion)"] {
|
||||||
docker {
|
docker {
|
||||||
new {
|
new {
|
||||||
image = "swift:5.9-rhel-ubi9"
|
image = "swift:5.9-rhel-ubi9"
|
||||||
|
@ -109,7 +131,7 @@ jobs {
|
||||||
resource_class = "xlarge"
|
resource_class = "xlarge"
|
||||||
steps {
|
steps {
|
||||||
"checkout"
|
"checkout"
|
||||||
downloadPkl
|
distribution.downloadRunStep
|
||||||
swiftTest
|
swiftTest
|
||||||
new RunStep { command = "make test-snippets" }
|
new RunStep { command = "make test-snippets" }
|
||||||
new RunStep { command = "make test-pkl" }
|
new RunStep { command = "make test-pkl" }
|
||||||
|
@ -119,6 +141,7 @@ jobs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
["pkl-gen-swift-macos"] {
|
["pkl-gen-swift-macos"] {
|
||||||
macos {
|
macos {
|
||||||
|
@ -168,7 +191,7 @@ jobs {
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
"checkout"
|
"checkout"
|
||||||
downloadPkl
|
pklCurrent.downloadRunStep
|
||||||
new RunStep {
|
new RunStep {
|
||||||
// TODO remove skip-publish-check after initial release
|
// TODO remove skip-publish-check after initial release
|
||||||
command = #"$PKL_EXEC project package --skip-publish-check --output-path out/pkl-package/ codegen/src/"#
|
command = #"$PKL_EXEC project package --skip-publish-check --output-path out/pkl-package/ codegen/src/"#
|
||||||
|
|
|
@ -3,16 +3,42 @@ version: '2.1'
|
||||||
orbs:
|
orbs:
|
||||||
pr-approval: apple/pr-approval@0.1.0
|
pr-approval: apple/pr-approval@0.1.0
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test-pkl-0-25-3:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
command: |-
|
command: |-
|
||||||
mkdir /tmp/pkl
|
PKL=$(mktemp /tmp/pkl-0.25.3-XXXXXX)
|
||||||
curl -L "https://repo1.maven.org/maven2/org/pkl-lang/pkl-cli-linux-amd64/0.25.1/pkl-cli-linux-amd64-0.25.1.bin" > /tmp/pkl/pkl
|
curl -L "https://github.com/apple/pkl/releases/download/0.25.3/pkl-linux-amd64" > $PKL
|
||||||
chmod +x /tmp/pkl/pkl
|
chmod +x $PKL
|
||||||
echo 'export PKL_EXEC=/tmp/pkl/pkl' >> $BASH_ENV
|
echo "export PKL_EXEC=$PKL" >> $BASH_ENV
|
||||||
name: Downloading pkl
|
name: Downloading pkl-0.25.3
|
||||||
|
- run:
|
||||||
|
command: |-
|
||||||
|
mkdir -p .out/test-results/
|
||||||
|
swift test -vv --parallel --num-workers 1 --xunit-output .out/test-results/xunit.xml -Xswiftc -warnings-as-errors
|
||||||
|
name: swift test
|
||||||
|
- run:
|
||||||
|
command: make test-snippets
|
||||||
|
- run:
|
||||||
|
command: make test-pkl
|
||||||
|
- run:
|
||||||
|
command: make generate-fixtures
|
||||||
|
- store_test_results:
|
||||||
|
path: .out/test-results/
|
||||||
|
resource_class: xlarge
|
||||||
|
docker:
|
||||||
|
- image: swift:5.9-rhel-ubi9
|
||||||
|
test-pkl-0-26-0:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
command: |-
|
||||||
|
PKL=$(mktemp /tmp/pkl-0.26.0-XXXXXX)
|
||||||
|
curl -L "https://github.com/apple/pkl/releases/download/0.26.0/pkl-linux-amd64" > $PKL
|
||||||
|
chmod +x $PKL
|
||||||
|
echo "export PKL_EXEC=$PKL" >> $BASH_ENV
|
||||||
|
name: Downloading pkl-0.26.0
|
||||||
- run:
|
- run:
|
||||||
command: |-
|
command: |-
|
||||||
mkdir -p .out/test-results/
|
mkdir -p .out/test-results/
|
||||||
|
@ -82,11 +108,11 @@ jobs:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
command: |-
|
command: |-
|
||||||
mkdir /tmp/pkl
|
PKL=$(mktemp /tmp/pkl-0.26.0-XXXXXX)
|
||||||
curl -L "https://repo1.maven.org/maven2/org/pkl-lang/pkl-cli-linux-amd64/0.25.1/pkl-cli-linux-amd64-0.25.1.bin" > /tmp/pkl/pkl
|
curl -L "https://github.com/apple/pkl/releases/download/0.26.0/pkl-linux-amd64" > $PKL
|
||||||
chmod +x /tmp/pkl/pkl
|
chmod +x $PKL
|
||||||
echo 'export PKL_EXEC=/tmp/pkl/pkl' >> $BASH_ENV
|
echo "export PKL_EXEC=$PKL" >> $BASH_ENV
|
||||||
name: Downloading pkl
|
name: Downloading pkl-0.26.0
|
||||||
- run:
|
- run:
|
||||||
command: $PKL_EXEC project package --skip-publish-check --output-path out/pkl-package/ codegen/src/
|
command: $PKL_EXEC project package --skip-publish-check --output-path out/pkl-package/ codegen/src/
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
|
@ -164,7 +190,11 @@ workflows:
|
||||||
type: approval
|
type: approval
|
||||||
- pr-approval/authenticate:
|
- pr-approval/authenticate:
|
||||||
context: pkl-pr-approval
|
context: pkl-pr-approval
|
||||||
- test:
|
- test-pkl-0-25-3:
|
||||||
|
requires:
|
||||||
|
- hold
|
||||||
|
- pr-approval/authenticate
|
||||||
|
- test-pkl-0-26-0:
|
||||||
requires:
|
requires:
|
||||||
- hold
|
- hold
|
||||||
- pr-approval/authenticate
|
- pr-approval/authenticate
|
||||||
|
@ -174,14 +204,21 @@ workflows:
|
||||||
pattern: ^pull/\d+(/head)?$
|
pattern: ^pull/\d+(/head)?$
|
||||||
main:
|
main:
|
||||||
jobs:
|
jobs:
|
||||||
- test
|
- test-pkl-0-25-3
|
||||||
|
- test-pkl-0-26-0
|
||||||
when:
|
when:
|
||||||
equal:
|
equal:
|
||||||
- main
|
- main
|
||||||
- << pipeline.git.branch >>
|
- << pipeline.git.branch >>
|
||||||
release:
|
release:
|
||||||
jobs:
|
jobs:
|
||||||
- test:
|
- test-pkl-0-25-3:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
tags:
|
||||||
|
only: /^v?\d+\.\d+\.\d+$/
|
||||||
|
- test-pkl-0-26-0:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
@ -189,7 +226,8 @@ workflows:
|
||||||
only: /^v?\d+\.\d+\.\d+$/
|
only: /^v?\d+\.\d+\.\d+$/
|
||||||
- pkl-package:
|
- pkl-package:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test-pkl-0-25-3
|
||||||
|
- test-pkl-0-26-0
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
@ -197,7 +235,8 @@ workflows:
|
||||||
only: /^v?\d+\.\d+\.\d+$/
|
only: /^v?\d+\.\d+\.\d+$/
|
||||||
- pkl-gen-swift-macos:
|
- pkl-gen-swift-macos:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test-pkl-0-25-3
|
||||||
|
- test-pkl-0-26-0
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
@ -205,7 +244,8 @@ workflows:
|
||||||
only: /^v?\d+\.\d+\.\d+$/
|
only: /^v?\d+\.\d+\.\d+$/
|
||||||
- pkl-gen-swift-linux-amd64:
|
- pkl-gen-swift-linux-amd64:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test-pkl-0-25-3
|
||||||
|
- test-pkl-0-26-0
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
@ -213,7 +253,8 @@ workflows:
|
||||||
only: /^v?\d+\.\d+\.\d+$/
|
only: /^v?\d+\.\d+\.\d+$/
|
||||||
- pkl-gen-swift-linux-aarch64:
|
- pkl-gen-swift-linux-aarch64:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test-pkl-0-25-3
|
||||||
|
- test-pkl-0-26-0
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
{
|
{
|
||||||
"pins" : [
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "semanticversion",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ea8eea9d89842a29af1b8e6c7677f1c86e72fa42",
|
||||||
|
"version" : "0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-argument-parser",
|
"identity" : "swift-argument-parser",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
|
@ -5,7 +5,7 @@ let package = Package(
|
||||||
name: "pkl-swift",
|
name: "pkl-swift",
|
||||||
platforms: [
|
platforms: [
|
||||||
// required because of `Duration` API
|
// required because of `Duration` API
|
||||||
.macOS(.v13)
|
.macOS(.v13),
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
|
@ -24,11 +24,12 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/apple/swift-system", from: "1.2.1"),
|
.package(url: "https://github.com/apple/swift-system", from: "1.2.1"),
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.3"),
|
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.3"),
|
||||||
|
.package(url: "https://github.com/SwiftPackageIndex/SemanticVersion", from: "0.4.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "PklSwift",
|
name: "PklSwift",
|
||||||
dependencies: ["MessagePack", "PklSwiftInternals"]
|
dependencies: ["MessagePack", "PklSwiftInternals", "SemanticVersion"]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "PklSwiftInternals"
|
name: "PklSwiftInternals"
|
||||||
|
|
|
@ -185,7 +185,6 @@ extension MessagePackValue {
|
||||||
case .ext:
|
case .ext:
|
||||||
// TODO: implement this?
|
// TODO: implement this?
|
||||||
fatalError("Cannot convert \(self) to \(T.self)")
|
fatalError("Cannot convert \(self) to \(T.self)")
|
||||||
|
|
||||||
case .timestamp:
|
case .timestamp:
|
||||||
// TODO: implement this?
|
// TODO: implement this?
|
||||||
fatalError("Cannot convert \(self) to \(T.self)")
|
fatalError("Cannot convert \(self) to \(T.self)")
|
||||||
|
|
|
@ -36,17 +36,14 @@ public func withEvaluator<T>(_ action: (Evaluator) async throws -> T) async thro
|
||||||
try await withEvaluator(options: .preconfigured, action)
|
try await withEvaluator(options: .preconfigured, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like ``withProjectEvaluator(projectDir:options:_:)``, but configured with preconfigured otions.
|
/// Like ``withProjectEvaluator(projectBaseURI:options:_:)``, but configured with preconfigured options.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - projectDir: The directory containing the PklProject file.
|
/// - projectBaseURI: The base path containing the PklProject file.
|
||||||
/// - action: The action to perform.
|
/// - action: The action to perform.
|
||||||
/// - Returns: The result of the action.
|
/// - Returns: The result of the action.
|
||||||
public func withProjectEvaluator<T>(
|
public func withProjectEvaluator<T>(projectBaseURI: URL, _ action: (Evaluator) async throws -> T) async throws -> T {
|
||||||
projectDir: String,
|
try await withProjectEvaluator(projectBaseURI: projectBaseURI, options: .preconfigured, action)
|
||||||
_ action: (Evaluator) async throws -> T
|
|
||||||
) async throws -> T {
|
|
||||||
try await withProjectEvaluator(projectDir: projectDir, options: .preconfigured, action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method for initializing an evaluator from the project.
|
/// Convenience method for initializing an evaluator from the project.
|
||||||
|
@ -56,17 +53,17 @@ public func withProjectEvaluator<T>(
|
||||||
///
|
///
|
||||||
/// After `action` completes, the evaluator is closed.
|
/// After `action` completes, the evaluator is closed.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - projectDir: The directory containing the PklProject file.
|
/// - projectBaseURI: The base path containing the PklProject file.
|
||||||
/// - options: The base options used to configure the evaluator.
|
/// - options: The base options used to configure the evaluator.
|
||||||
/// - action: The action to perform.
|
/// - action: The action to perform.
|
||||||
/// - Returns: The result of the action.
|
/// - Returns: The result of the action.
|
||||||
public func withProjectEvaluator<T>(
|
public func withProjectEvaluator<T>(
|
||||||
projectDir: String,
|
projectBaseURI: URL,
|
||||||
options: EvaluatorOptions,
|
options: EvaluatorOptions,
|
||||||
_ action: (Evaluator) async throws -> T
|
_ action: (Evaluator) async throws -> T
|
||||||
) async throws -> T {
|
) async throws -> T {
|
||||||
try await withEvaluatorManager { manager in
|
try await withEvaluatorManager { manager in
|
||||||
let evaluator = try await manager.newProjectEvaluator(projectDir: projectDir, options: options)
|
let evaluator = try await manager.newProjectEvaluator(projectBaseURI: projectBaseURI, options: options)
|
||||||
return try await action(evaluator)
|
return try await action(evaluator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MessagePack
|
import MessagePack
|
||||||
|
import SemanticVersion
|
||||||
|
|
||||||
/// Perfoms `action`, returns its result and then closes the manager.
|
/// Perfoms `action`, returns its result and then closes the manager.
|
||||||
///
|
///
|
||||||
|
@ -37,6 +38,36 @@ public func withEvaluatorManager<T>(_ action: (EvaluatorManager) async throws ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve the (CLI) command to invoke Pkl.
|
||||||
|
///
|
||||||
|
/// First, checks the `PKL_EXEC` environment variable. If that is not set, searches the `PATH` for a directory
|
||||||
|
/// containing `pkl`.
|
||||||
|
func getPklCommand() throws -> [String] {
|
||||||
|
if let exec = ProcessInfo.processInfo.environment["PKL_EXEC"] {
|
||||||
|
return exec.components(separatedBy: " ")
|
||||||
|
}
|
||||||
|
guard let path = ProcessInfo.processInfo.environment["PATH"] else {
|
||||||
|
throw PklError("Unable to read PATH environment variable.")
|
||||||
|
}
|
||||||
|
for dir in path.components(separatedBy: ":") {
|
||||||
|
do {
|
||||||
|
let contents = try FileManager.default.contentsOfDirectory(atPath: dir)
|
||||||
|
if let pkl = contents.first(where: { $0 == "pkl" }) {
|
||||||
|
let file = NSString.path(withComponents: [dir, pkl])
|
||||||
|
if FileManager.default.isExecutableFile(atPath: file) {
|
||||||
|
return [file]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if error._domain == NSCocoaErrorDomain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw PklError("Unable to find `pkl` command on PATH.")
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides handlers for managing the lifecycles of Pkl evaluators. If binding to Pkl as a child process, an evaluator
|
/// Provides handlers for managing the lifecycles of Pkl evaluators. If binding to Pkl as a child process, an evaluator
|
||||||
/// manager represents a single child process.
|
/// manager represents a single child process.
|
||||||
///
|
///
|
||||||
|
@ -55,6 +86,8 @@ public actor EvaluatorManager {
|
||||||
|
|
||||||
var isClosed: Bool = false
|
var isClosed: Bool = false
|
||||||
|
|
||||||
|
var pklVersion: String?
|
||||||
|
|
||||||
// note; when our C bindings are released, change `init()` based on compiler flags.
|
// note; when our C bindings are released, change `init()` based on compiler flags.
|
||||||
public init() {
|
public init() {
|
||||||
self.init(transport: ChildProcessMessageTransport())
|
self.init(transport: ChildProcessMessageTransport())
|
||||||
|
@ -72,6 +105,31 @@ public actor EvaluatorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the semantic version as a String of the Pkl interpreter being used.
|
||||||
|
func getVersion() throws -> String {
|
||||||
|
if let pklVersion {
|
||||||
|
return pklVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
let pklCommand = try getPklCommand()
|
||||||
|
let process = Process()
|
||||||
|
process.executableURL = URL(fileURLWithPath: pklCommand[0])
|
||||||
|
process.arguments = Array(pklCommand.dropFirst()) + ["--version"]
|
||||||
|
let pipe = Pipe()
|
||||||
|
process.standardOutput = pipe
|
||||||
|
debug("Spawning command \(pklCommand[0]) with arguments \(process.arguments!)")
|
||||||
|
try process.run()
|
||||||
|
guard let outputData = try pipe.fileHandleForReading.readToEnd(),
|
||||||
|
let output = String(data: outputData, encoding: .utf8)?.split(separator: " "),
|
||||||
|
output.count > 2,
|
||||||
|
output[0] == "Pkl" else {
|
||||||
|
throw PklError("Could not get version from Pkl binary")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pklVersion = String(output[1])
|
||||||
|
return self.pklVersion!
|
||||||
|
}
|
||||||
|
|
||||||
private func listenForIncomingMessages() async throws {
|
private func listenForIncomingMessages() async throws {
|
||||||
for try await message in try self.transport.getMessages() {
|
for try await message in try self.transport.getMessages() {
|
||||||
debug("EvaluatorManager got message \(message)")
|
debug("EvaluatorManager got message \(message)")
|
||||||
|
@ -127,24 +185,24 @@ public actor EvaluatorManager {
|
||||||
/// - options: The options used to configure the evaluator.
|
/// - options: The options used to configure the evaluator.
|
||||||
/// - action: The action to run with the evaluator.
|
/// - action: The action to run with the evaluator.
|
||||||
public func withEvaluator<T>(options: EvaluatorOptions, _ action: (Evaluator) async throws -> T) async throws -> T {
|
public func withEvaluator<T>(options: EvaluatorOptions, _ action: (Evaluator) async throws -> T) async throws -> T {
|
||||||
let evalautor = try await newEvaluator(options: options)
|
let evaluator = try await newEvaluator(options: options)
|
||||||
var closed = false
|
var closed = false
|
||||||
do {
|
do {
|
||||||
let result = try await action(evalautor)
|
let result = try await action(evaluator)
|
||||||
try await evalautor.close()
|
try await evaluator.close()
|
||||||
closed = true
|
closed = true
|
||||||
return result
|
return result
|
||||||
} catch {
|
} catch {
|
||||||
if !closed {
|
if !closed {
|
||||||
try await evalautor.close()
|
try await evaluator.close()
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method for constructing a project evaluator with preconfigured base options.
|
/// Convenience method for constructing a project evaluator with preconfigured base options.
|
||||||
public func withProjectEvaluator<T>(projectDir: String, _ action: (Evaluator) async throws -> T) async throws -> T {
|
public func withProjectEvaluator<T>(projectBaseURI: URL, _ action: (Evaluator) async throws -> T) async throws -> T {
|
||||||
try await self.withProjectEvaluator(projectDir: projectDir, options: .preconfigured, action)
|
try await self.withProjectEvaluator(projectBaseURI: projectBaseURI, options: .preconfigured, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs an evaluator that is configured by the project within the project dir.
|
/// Constructs an evaluator that is configured by the project within the project dir.
|
||||||
|
@ -155,20 +213,20 @@ public actor EvaluatorManager {
|
||||||
/// After the action completes or throws, the evaluator is closed.
|
/// After the action completes or throws, the evaluator is closed.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - projectDir: The project directory that contains the PklProject file.
|
/// - projectBaseURI: The project base path that contains the PklProject file.
|
||||||
/// - options: The options used to configure the evaluator.
|
/// - options: The options used to configure the evaluator.
|
||||||
/// - action: The action to run with the evaluator.
|
/// - action: The action to run with the evaluator.
|
||||||
public func withProjectEvaluator<T>(projectDir: String, options: EvaluatorOptions, _ action: (Evaluator) async throws -> T) async throws -> T {
|
public func withProjectEvaluator<T>(projectBaseURI: URL, options: EvaluatorOptions, _ action: (Evaluator) async throws -> T) async throws -> T {
|
||||||
let evalautor = try await newProjectEvaluator(projectDir: projectDir, options: options)
|
let evaluator = try await newProjectEvaluator(projectBaseURI: projectBaseURI, options: options)
|
||||||
var closed = false
|
var closed = false
|
||||||
do {
|
do {
|
||||||
let result = try await action(evalautor)
|
let result = try await action(evaluator)
|
||||||
try await evalautor.close()
|
try await evaluator.close()
|
||||||
closed = true
|
closed = true
|
||||||
return result
|
return result
|
||||||
} catch {
|
} catch {
|
||||||
if !closed {
|
if !closed {
|
||||||
try await evalautor.close()
|
try await evaluator.close()
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -176,13 +234,18 @@ public actor EvaluatorManager {
|
||||||
|
|
||||||
/// Creates a new evaluator with the provided options.
|
/// Creates a new evaluator with the provided options.
|
||||||
///
|
///
|
||||||
/// To create an evaluator that understands project dependencies, use ``newProjectEvaluator(projectDir:options:)``.
|
/// To create an evaluator that understands project dependencies, use
|
||||||
|
/// ``newProjectEvaluator(projectBaseURI:options:)``.
|
||||||
///
|
///
|
||||||
/// - Parameter options: The options used to configure the evaluator.
|
/// - Parameter options: The options used to configure the evaluator.
|
||||||
public func newEvaluator(options: EvaluatorOptions) async throws -> Evaluator {
|
public func newEvaluator(options: EvaluatorOptions) async throws -> Evaluator {
|
||||||
if self.isClosed {
|
if self.isClosed {
|
||||||
throw PklError("The evaluator manager is closed")
|
throw PklError("The evaluator manager is closed")
|
||||||
}
|
}
|
||||||
|
let version = try SemanticVersion(getVersion())!
|
||||||
|
guard options.http == nil || version >= pklVersion0_26 else {
|
||||||
|
throw PklError("http options are not supported on Pkl versions lower than 0.26")
|
||||||
|
}
|
||||||
let req = options.toMessage()
|
let req = options.toMessage()
|
||||||
guard let response = try await ask(req) as? CreateEvaluatorResponse else {
|
guard let response = try await ask(req) as? CreateEvaluatorResponse else {
|
||||||
throw PklBugError.invalidMessageCode(
|
throw PklBugError.invalidMessageCode(
|
||||||
|
@ -209,15 +272,15 @@ public actor EvaluatorManager {
|
||||||
// Any `evaluatorSettings` set within the PklProject file overwrites any fields set on `options`.
|
// Any `evaluatorSettings` set within the PklProject file overwrites any fields set on `options`.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - projectDir: The project directory containing the `PklProject` file.
|
/// - projectBaseURI: The project base path containing the `PklProject` file.
|
||||||
/// - options: The base options used to configure the evaluator.
|
/// - options: The base options used to configure the evaluator.
|
||||||
public func newProjectEvaluator(projectDir: String, options: EvaluatorOptions) async throws -> Evaluator {
|
public func newProjectEvaluator(projectBaseURI: URL, options: EvaluatorOptions) async throws -> Evaluator {
|
||||||
if self.isClosed {
|
if self.isClosed {
|
||||||
throw PklError("The evaluator manager is closed")
|
throw PklError("The evaluator manager is closed")
|
||||||
}
|
}
|
||||||
return try await self.withEvaluator(options: .preconfigured) { projectEvaluator in
|
return try await self.withEvaluator(options: .preconfigured) { projectEvaluator in
|
||||||
let project = try await projectEvaluator.evaluateOutputValue(
|
let project = try await projectEvaluator.evaluateOutputValue(
|
||||||
source: .path("\(projectDir)/PklProject"),
|
source: .path("\(projectBaseURI)/PklProject"),
|
||||||
asType: Project.self
|
asType: Project.self
|
||||||
)
|
)
|
||||||
return try await self.newEvaluator(options: options.withProject(project))
|
return try await self.newEvaluator(options: options.withProject(project))
|
||||||
|
@ -287,3 +350,11 @@ enum PklBugError: Error {
|
||||||
case invalidEvaluatorId(String)
|
case invalidEvaluatorId(String)
|
||||||
case unknownMessage(String)
|
case unknownMessage(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pklVersion0_25 = SemanticVersion("0.25.0")!
|
||||||
|
let pklVersion0_26 = SemanticVersion("0.26.0")!
|
||||||
|
|
||||||
|
let supportedPklVersions = [
|
||||||
|
pklVersion0_25,
|
||||||
|
pklVersion0_26,
|
||||||
|
]
|
||||||
|
|
|
@ -30,7 +30,8 @@ public struct EvaluatorOptions {
|
||||||
cacheDir: String? = nil,
|
cacheDir: String? = nil,
|
||||||
outputFormat: String? = nil,
|
outputFormat: String? = nil,
|
||||||
logger: Logger = Loggers.noop,
|
logger: Logger = Loggers.noop,
|
||||||
projectDir: String? = nil,
|
projectBaseURI: URL? = nil,
|
||||||
|
http: Http? = nil,
|
||||||
declaredProjectDependencies: [String: ProjectDependency]? = nil
|
declaredProjectDependencies: [String: ProjectDependency]? = nil
|
||||||
) {
|
) {
|
||||||
self.allowedModules = allowedModules
|
self.allowedModules = allowedModules
|
||||||
|
@ -45,7 +46,8 @@ public struct EvaluatorOptions {
|
||||||
self.cacheDir = cacheDir
|
self.cacheDir = cacheDir
|
||||||
self.outputFormat = outputFormat
|
self.outputFormat = outputFormat
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.projectDir = projectDir
|
self.projectBaseURI = projectBaseURI
|
||||||
|
self.http = http
|
||||||
self.declaredProjectDependencies = declaredProjectDependencies
|
self.declaredProjectDependencies = declaredProjectDependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,13 +108,19 @@ public struct EvaluatorOptions {
|
||||||
/// It is meant to be set by lower level logic in Swift code that first evaluates the PklProject,
|
/// It is meant to be set by lower level logic in Swift code that first evaluates the PklProject,
|
||||||
/// which then configures ``EvaluatorOptions`` accordingly.
|
/// which then configures ``EvaluatorOptions`` accordingly.
|
||||||
///
|
///
|
||||||
/// To emulate the CLI's `--project-dir` flag, create an evaluator with ``withProjectEvaluator(projectDir:_:)``,
|
/// To emulate the CLI's `--project-dir` flag, create an evaluator with ``withProjectEvaluator(projectBaseURI:_:)``,
|
||||||
/// or ``EvaluatorManager/newProjectEvaluator()``.
|
/// or ``EvaluatorManager/newProjectEvaluator()``.
|
||||||
public var projectDir: String?
|
public var projectBaseURI: URL?
|
||||||
|
|
||||||
/// The set of dependencies available to modules within ``projectDir``.
|
/// Settings that control how Pkl talks to HTTP(S) servers.
|
||||||
///
|
///
|
||||||
/// When importing dependencies, a `PklProject.deps.json` file must exist within ``projectDir``
|
/// Added in Pkl 0.26.
|
||||||
|
/// These fields are ignored if targeting Pkl 0.25.
|
||||||
|
public var http: Http?
|
||||||
|
|
||||||
|
/// The set of dependencies available to modules within ``projectBaseURI``.
|
||||||
|
///
|
||||||
|
/// When importing dependencies, a `PklProject.deps.json` file must exist within ``projectBaseURI``
|
||||||
/// that contains the project's resolved dependencies.
|
/// that contains the project's resolved dependencies.
|
||||||
public var declaredProjectDependencies: [String: ProjectDependency]?
|
public var declaredProjectDependencies: [String: ProjectDependency]?
|
||||||
}
|
}
|
||||||
|
@ -153,18 +161,19 @@ extension EvaluatorOptions {
|
||||||
rootDir: self.rootDir,
|
rootDir: self.rootDir,
|
||||||
cacheDir: self.cacheDir,
|
cacheDir: self.cacheDir,
|
||||||
outputFormat: self.outputFormat,
|
outputFormat: self.outputFormat,
|
||||||
project: self.project()
|
project: self.project(),
|
||||||
|
http: self.http
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func project() -> ProjectOrDependency? {
|
func project() -> ProjectOrDependency? {
|
||||||
guard let projectDir else {
|
guard let projectBaseURI else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(
|
return .init(
|
||||||
packageUri: nil,
|
packageUri: nil,
|
||||||
type: "project",
|
type: "project",
|
||||||
projectFileUri: "file://\(projectDir)/PklProject",
|
projectFileUri: "file://\(projectBaseURI)/PklProject",
|
||||||
checksums: nil,
|
checksums: nil,
|
||||||
dependencies: self.declaredProjectDependenciesToMessage(self.declaredProjectDependencies)
|
dependencies: self.declaredProjectDependenciesToMessage(self.declaredProjectDependencies)
|
||||||
)
|
)
|
||||||
|
@ -227,28 +236,15 @@ extension EvaluatorOptions {
|
||||||
/// Builds options that configures the evaluator with settings set on the project.
|
/// Builds options that configures the evaluator with settings set on the project.
|
||||||
///
|
///
|
||||||
/// Skips any settings that are nil.
|
/// Skips any settings that are nil.
|
||||||
public func withProjectEvaluatorSettings(_ evaluatorSettings: Project.EvaluatorSettings) -> EvaluatorOptions {
|
public func withProjectEvaluatorSettings(_ evaluatorSettings: PklEvaluatorSettings) -> EvaluatorOptions {
|
||||||
var options = self
|
var options = self
|
||||||
if evaluatorSettings.externalProperties != nil {
|
options.properties = evaluatorSettings.externalProperties ?? self.properties
|
||||||
options.properties = evaluatorSettings.externalProperties
|
options.env = evaluatorSettings.env ?? self.env
|
||||||
}
|
options.allowedModules = evaluatorSettings.allowedModules ?? self.allowedModules
|
||||||
if evaluatorSettings.env != nil {
|
options.allowedResources = evaluatorSettings.allowedResources ?? self.allowedResources
|
||||||
options.env = evaluatorSettings.env
|
options.cacheDir = evaluatorSettings.noCache != nil ? nil : (evaluatorSettings.moduleCacheDir ?? self.cacheDir)
|
||||||
}
|
options.rootDir = evaluatorSettings.rootDir ?? self.rootDir
|
||||||
if evaluatorSettings.allowedModules != nil {
|
options.http = evaluatorSettings.http ?? self.http
|
||||||
options.allowedModules = evaluatorSettings.allowedModules
|
|
||||||
}
|
|
||||||
if evaluatorSettings.allowedResources != nil {
|
|
||||||
options.allowedResources = evaluatorSettings.allowedResources
|
|
||||||
}
|
|
||||||
if evaluatorSettings.noCache == true {
|
|
||||||
options.cacheDir = nil
|
|
||||||
} else if evaluatorSettings.moduleCacheDir != nil {
|
|
||||||
options.cacheDir = evaluatorSettings.moduleCacheDir
|
|
||||||
}
|
|
||||||
if evaluatorSettings.rootDir != nil {
|
|
||||||
options.rootDir = evaluatorSettings.rootDir
|
|
||||||
}
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +273,8 @@ extension EvaluatorOptions {
|
||||||
/// Builds options with dependencies from the input project.
|
/// Builds options with dependencies from the input project.
|
||||||
public func withProjectDependencies(_ project: Project) -> EvaluatorOptions {
|
public func withProjectDependencies(_ project: Project) -> EvaluatorOptions {
|
||||||
var options = self
|
var options = self
|
||||||
options.projectDir = String(URL(string: project.projectFileUri)!.path.dropLast("/PklProject".count))
|
options.projectBaseURI = URL(string: project.projectFileUri)!
|
||||||
|
options.projectBaseURI?.deleteLastPathComponent()
|
||||||
options.declaredProjectDependencies = self.projectDependencies(project)
|
options.declaredProjectDependencies = self.projectDependencies(project)
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ enum MessageType: Int, Codable {
|
||||||
case CREATE_EVALUATOR_RESPONSE = 0x21
|
case CREATE_EVALUATOR_RESPONSE = 0x21
|
||||||
case CLOSE_EVALUATOR = 0x22
|
case CLOSE_EVALUATOR = 0x22
|
||||||
case EVALUATOR_REQUEST = 0x23
|
case EVALUATOR_REQUEST = 0x23
|
||||||
case EVALUATOR_RESPOSNE = 0x24
|
case EVALUATOR_RESPONSE = 0x24
|
||||||
case LOG_MESSAGE = 0x25
|
case LOG_MESSAGE = 0x25
|
||||||
case READ_RESOURCE_REQUEST = 0x26
|
case READ_RESOURCE_REQUEST = 0x26
|
||||||
case READ_RESOURCE_RESPONSE = 0x27
|
case READ_RESOURCE_RESPONSE = 0x27
|
||||||
|
@ -87,7 +87,7 @@ extension MessageType {
|
||||||
case is EvaluateRequest:
|
case is EvaluateRequest:
|
||||||
return MessageType.EVALUATOR_REQUEST
|
return MessageType.EVALUATOR_REQUEST
|
||||||
case is EvaluateResponse:
|
case is EvaluateResponse:
|
||||||
return MessageType.EVALUATOR_RESPOSNE
|
return MessageType.EVALUATOR_RESPONSE
|
||||||
case is LogMessage:
|
case is LogMessage:
|
||||||
return MessageType.LOG_MESSAGE
|
return MessageType.LOG_MESSAGE
|
||||||
case is ListResourcesRequest:
|
case is ListResourcesRequest:
|
||||||
|
@ -122,6 +122,7 @@ struct CreateEvaluatorRequest: ClientRequestMessage {
|
||||||
var cacheDir: String?
|
var cacheDir: String?
|
||||||
var outputFormat: String?
|
var outputFormat: String?
|
||||||
var project: ProjectOrDependency?
|
var project: ProjectOrDependency?
|
||||||
|
var http: Http?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProjectOrDependency: Codable {
|
struct ProjectOrDependency: Codable {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MessagePack
|
import MessagePack
|
||||||
|
import SemanticVersion
|
||||||
|
|
||||||
protocol MessageTransport {
|
protocol MessageTransport {
|
||||||
/// Send a message to the Pkl server.
|
/// Send a message to the Pkl server.
|
||||||
|
@ -66,32 +67,6 @@ public class ChildProcessMessageTransport: MessageTransport {
|
||||||
self.pklCommand = pklCommand
|
self.pklCommand = pklCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPklCommand() throws -> [String] {
|
|
||||||
if let exec = ProcessInfo.processInfo.environment["PKL_EXEC"] {
|
|
||||||
return exec.components(separatedBy: " ")
|
|
||||||
}
|
|
||||||
guard let path = ProcessInfo.processInfo.environment["PATH"] else {
|
|
||||||
throw PklError("Unable to find `pkl` command on PATH.")
|
|
||||||
}
|
|
||||||
for dir in path.components(separatedBy: ":") {
|
|
||||||
do {
|
|
||||||
let contents = try FileManager.default.contentsOfDirectory(atPath: dir)
|
|
||||||
if let pkl = contents.first(where: { $0 == "pkl" }) {
|
|
||||||
let file = NSString.path(withComponents: [dir, pkl])
|
|
||||||
if FileManager.default.isExecutableFile(atPath: file) {
|
|
||||||
return [file]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
if error._domain == NSCocoaErrorDomain {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw PklError("Unable to find `pkl` command on PATH.")
|
|
||||||
}
|
|
||||||
|
|
||||||
private func ensureProcessStarted() throws {
|
private func ensureProcessStarted() throws {
|
||||||
if self.process?.isRunning == true { return }
|
if self.process?.isRunning == true { return }
|
||||||
let pklCommand = try getPklCommand()
|
let pklCommand = try getPklCommand()
|
||||||
|
@ -140,7 +115,7 @@ public class ChildProcessMessageTransport: MessageTransport {
|
||||||
switch messageType {
|
switch messageType {
|
||||||
case MessageType.CREATE_EVALUATOR_RESPONSE:
|
case MessageType.CREATE_EVALUATOR_RESPONSE:
|
||||||
return try self.decoder.decode(as: CreateEvaluatorResponse.self)
|
return try self.decoder.decode(as: CreateEvaluatorResponse.self)
|
||||||
case MessageType.EVALUATOR_RESPOSNE:
|
case MessageType.EVALUATOR_RESPONSE:
|
||||||
return try self.decoder.decode(as: EvaluateResponse.self)
|
return try self.decoder.decode(as: EvaluateResponse.self)
|
||||||
case MessageType.READ_MODULE_REQUEST:
|
case MessageType.READ_MODULE_REQUEST:
|
||||||
return try self.decoder.decode(as: ReadModuleRequest.self)
|
return try self.decoder.decode(as: ReadModuleRequest.self)
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// ===----------------------------------------------------------------------===//
|
||||||
|
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// ===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
/// The Swift representation of standard library module `pkl.EvaluatorSettings`.
|
||||||
|
public struct PklEvaluatorSettings: Decodable, Hashable {
|
||||||
|
let externalProperties: [String: String]?
|
||||||
|
let env: [String: String]?
|
||||||
|
let allowedModules: [String]?
|
||||||
|
let allowedResources: [String]?
|
||||||
|
let noCache: Bool?
|
||||||
|
let modulePath: [String]?
|
||||||
|
let timeout: Duration?
|
||||||
|
let moduleCacheDir: String?
|
||||||
|
let rootDir: String?
|
||||||
|
let http: Http?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Settings that control how Pkl talks to HTTP(S) servers.
|
||||||
|
public struct Http: Codable, Hashable {
|
||||||
|
/// PEM format certificates to trust when making HTTP requests.
|
||||||
|
///
|
||||||
|
/// If empty, Pkl will trust its own built-in certificates.
|
||||||
|
var caCertificates: [UInt8]?
|
||||||
|
|
||||||
|
/// Configuration of the HTTP proxy to use.
|
||||||
|
///
|
||||||
|
/// If `nil`, uses the operating system's proxy configuration.
|
||||||
|
/// Configuration of the HTTP proxy to use.
|
||||||
|
var proxy: Proxy?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Settings that control how Pkl talks to HTTP proxies.
|
||||||
|
public struct Proxy: Codable, Hashable {
|
||||||
|
/// The proxy to use for HTTP(S) connections.
|
||||||
|
///
|
||||||
|
/// Only HTTP proxies are supported.
|
||||||
|
/// The address must start with `"http://"`, and cannot contain anything other than a host and an optional port.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// "http://my.proxy.example.com:5080"
|
||||||
|
/// ```
|
||||||
|
var address: String?
|
||||||
|
|
||||||
|
/// Hosts to which all connections should bypass a proxy.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Values can be either hostnames, or IP addresses.
|
||||||
|
/// IP addresses can optionally be provided using
|
||||||
|
/// [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation).
|
||||||
|
///
|
||||||
|
/// The value `"*"` is a wildcard that disables proxying for all hosts.
|
||||||
|
///
|
||||||
|
/// A hostname matches all subdomains.
|
||||||
|
/// For example, `example.com` matches `foo.example.com`, but not `fooexample.com`.
|
||||||
|
/// A hostname that is prefixed with a dot matches the hostname itself,
|
||||||
|
/// so `.example.com` matches `example.com`.
|
||||||
|
///
|
||||||
|
/// Hostnames do not match their resolved IP addresses.
|
||||||
|
/// For example, the hostname `localhost` will not match `127.0.0.1`.
|
||||||
|
///
|
||||||
|
/// Optionally, a port can be specified.
|
||||||
|
/// If a port is omitted, all ports are matched.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// [ "127.0.0.1",
|
||||||
|
/// "169.254.0.0/16",
|
||||||
|
/// "example.com",
|
||||||
|
/// "localhost:5050" ]
|
||||||
|
/// ```
|
||||||
|
var noProxy: [String]?
|
||||||
|
}
|
|
@ -14,15 +14,13 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// ===----------------------------------------------------------------------===//
|
// ===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// The Swift representation of `pkl.Project`
|
/// The Swift representation of `pkl.Project`
|
||||||
public struct Project: PklRegisteredType, Hashable, DependencyDeclaredInProjectFile {
|
public struct Project: PklRegisteredType, Hashable, DependencyDeclaredInProjectFile {
|
||||||
public static let registeredIdentifier: String = "pkl.Project"
|
public static let registeredIdentifier: String = "pkl.Project"
|
||||||
|
|
||||||
let package: Package?
|
let package: Package?
|
||||||
|
|
||||||
let evaluatorSettings: Project.EvaluatorSettings
|
let evaluatorSettings: PklEvaluatorSettings
|
||||||
|
|
||||||
let projectFileUri: String
|
let projectFileUri: String
|
||||||
|
|
||||||
|
@ -52,7 +50,7 @@ extension Project: Decodable {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let dec = try decoder.container(keyedBy: PklCodingKey.self)
|
let dec = try decoder.container(keyedBy: PklCodingKey.self)
|
||||||
let package = try dec.decode(Package?.self, forKey: PklCodingKey(stringValue: "package")!)
|
let package = try dec.decode(Package?.self, forKey: PklCodingKey(stringValue: "package")!)
|
||||||
let evaluatorSettings = try dec.decode(EvaluatorSettings.self, forKey: PklCodingKey(stringValue: "evaluatorSettings")!)
|
let evaluatorSettings = try dec.decode(PklEvaluatorSettings.self, forKey: PklCodingKey(stringValue: "evaluatorSettings")!)
|
||||||
let projectFileUri = try dec.decode(String.self, forKey: PklCodingKey(stringValue: "projectFileUri")!)
|
let projectFileUri = try dec.decode(String.self, forKey: PklCodingKey(stringValue: "projectFileUri")!)
|
||||||
let tests = try dec.decode([String].self, forKey: PklCodingKey(stringValue: "tests")!)
|
let tests = try dec.decode([String].self, forKey: PklCodingKey(stringValue: "tests")!)
|
||||||
let dependencies = try dec.decode([String: PklAny].self, forKey: PklCodingKey(stringValue: "dependencies")!)
|
let dependencies = try dec.decode([String: PklAny].self, forKey: PklCodingKey(stringValue: "dependencies")!)
|
||||||
|
@ -98,16 +96,11 @@ extension Project {
|
||||||
let uri: String
|
let uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Swift representation of `pkl.Project#EvaluatorSettings
|
@available(
|
||||||
public struct EvaluatorSettings: Decodable, Hashable {
|
*,
|
||||||
let externalProperties: [String: String]?
|
deprecated,
|
||||||
let env: [String: String]?
|
message: "Replaced by PklEvaluatorSettings, independent of Project",
|
||||||
let allowedModules: [String]?
|
renamed: "PklEvaluatorSettings"
|
||||||
let allowedResources: [String]?
|
)
|
||||||
let noCache: Bool?
|
public typealias EvaluatorSettings = PklEvaluatorSettings
|
||||||
let modulePath: [String]?
|
|
||||||
let timeout: Duration?
|
|
||||||
let moduleCacheDir: String?
|
|
||||||
let rootDir: String?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// ===----------------------------------------------------------------------===//
|
// ===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import Foundation
|
import SemanticVersion
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
@testable import PklSwift
|
@testable import PklSwift
|
||||||
|
@ -55,10 +55,10 @@ class EvaluatorManagerTest: XCTestCase {
|
||||||
let manager1 = EvaluatorManager()
|
let manager1 = EvaluatorManager()
|
||||||
let manager2 = EvaluatorManager()
|
let manager2 = EvaluatorManager()
|
||||||
let manager3 = EvaluatorManager()
|
let manager3 = EvaluatorManager()
|
||||||
async let evalautor1 = try manager1.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 1\""))
|
async let evaluator1 = try manager1.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 1\""))
|
||||||
async let evalautor2 = try manager2.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 2\""))
|
async let evaluator2 = try manager2.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 2\""))
|
||||||
async let evalautor3 = try manager3.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 3\""))
|
async let evaluator3 = try manager3.newEvaluator(options: .preconfigured).evaluateOutputText(source: .text("res = \"evaluator 3\""))
|
||||||
let (result1, result2, result3) = try await (evalautor1, evalautor2, evalautor3)
|
let (result1, result2, result3) = try await (evaluator1, evaluator2, evaluator3)
|
||||||
XCTAssertEqual(result1, "res = \"evaluator 1\"\n")
|
XCTAssertEqual(result1, "res = \"evaluator 1\"\n")
|
||||||
XCTAssertEqual(result2, "res = \"evaluator 2\"\n")
|
XCTAssertEqual(result2, "res = \"evaluator 2\"\n")
|
||||||
XCTAssertEqual(result3, "res = \"evaluator 3\"\n")
|
XCTAssertEqual(result3, "res = \"evaluator 3\"\n")
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// ===----------------------------------------------------------------------===//
|
// ===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
@testable import MessagePack
|
@testable import MessagePack
|
||||||
@testable import PklSwift
|
@testable import PklSwift
|
||||||
|
import SemanticVersion
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class TestLogger: Logger {
|
class TestLogger: Logger {
|
||||||
|
@ -91,6 +91,33 @@ final class PklSwiftTests: XCTestCase {
|
||||||
XCTAssertEqual(output, "foo = 1\n")
|
XCTAssertEqual(output, "foo = 1\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testVersionCoverage() async throws {
|
||||||
|
let output = try await SemanticVersion(EvaluatorManager().getVersion())!
|
||||||
|
XCTAssert(supportedPklVersions.contains { $0.major == output.major && $0.minor == output.minor })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCustomProxyOptions() async throws {
|
||||||
|
let version = try await SemanticVersion(EvaluatorManager().getVersion())!
|
||||||
|
let expected = version < pklVersion0_26
|
||||||
|
? "http options are not supported on Pkl versions lower than 0.26"
|
||||||
|
: "ConnectException: Error connecting to host `example.com`"
|
||||||
|
var options = EvaluatorOptions.preconfigured
|
||||||
|
options.http = .init(
|
||||||
|
caCertificates: nil,
|
||||||
|
proxy: .init(
|
||||||
|
address: "http://localhost:1",
|
||||||
|
noProxy: ["myhost.com:1337", "myotherhost.org:42"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
do {
|
||||||
|
let evaluator = try await manager.newEvaluator(options: options)
|
||||||
|
let _ = try await evaluator.evaluateOutputText(source: .uri("https://example.com")!)
|
||||||
|
XCTFail("Should have thrown an error")
|
||||||
|
} catch {
|
||||||
|
XCTAssert("\(error)".contains(expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testCustomModuleReader() async throws {
|
func testCustomModuleReader() async throws {
|
||||||
let reader = VirtualModuleReader(
|
let reader = VirtualModuleReader(
|
||||||
read: { _ in
|
read: { _ in
|
||||||
|
|
|
@ -14,13 +14,14 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// ===----------------------------------------------------------------------===//
|
// ===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import Foundation
|
import SemanticVersion
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
@testable import PklSwift
|
@testable import PklSwift
|
||||||
|
|
||||||
class ProjectTest: XCTestCase {
|
class ProjectTest: XCTestCase {
|
||||||
func testLoadProject() async throws {
|
func testLoadProject() async throws {
|
||||||
|
let version = try await SemanticVersion(EvaluatorManager().getVersion())!
|
||||||
let tempDir = NSTemporaryDirectory()
|
let tempDir = NSTemporaryDirectory()
|
||||||
try FileManager.default.createDirectory(atPath: tempDir + "/subdir", withIntermediateDirectories: true)
|
try FileManager.default.createDirectory(atPath: tempDir + "/subdir", withIntermediateDirectories: true)
|
||||||
let otherProjectFile = URL(fileURLWithPath: tempDir, isDirectory: true)
|
let otherProjectFile = URL(fileURLWithPath: tempDir, isDirectory: true)
|
||||||
|
@ -39,6 +40,21 @@ class ProjectTest: XCTestCase {
|
||||||
|
|
||||||
let file = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
let file = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
.appendingPathComponent("PklProject")
|
.appendingPathComponent("PklProject")
|
||||||
|
let httpSetting = version < pklVersion0_26 ? "" : """
|
||||||
|
http {
|
||||||
|
proxy {
|
||||||
|
address = "http://localhost:1"
|
||||||
|
noProxy {
|
||||||
|
"example.com"
|
||||||
|
"foo.bar.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let httpExpectation = version < pklVersion0_26 ? nil : Http(
|
||||||
|
caCertificates: nil,
|
||||||
|
proxy: .init(address: "http://localhost:1", noProxy: ["example.com", "foo.bar.org"])
|
||||||
|
)
|
||||||
try #"""
|
try #"""
|
||||||
amends "pkl:Project"
|
amends "pkl:Project"
|
||||||
|
|
||||||
|
@ -83,6 +99,7 @@ class ProjectTest: XCTestCase {
|
||||||
timeout = 5.min
|
timeout = 5.min
|
||||||
moduleCacheDir = "/bar/buzz"
|
moduleCacheDir = "/bar/buzz"
|
||||||
rootDir = "/buzzy"
|
rootDir = "/buzzy"
|
||||||
|
\#(httpSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -97,7 +114,7 @@ class ProjectTest: XCTestCase {
|
||||||
source: .url(file),
|
source: .url(file),
|
||||||
asType: Project.self
|
asType: Project.self
|
||||||
)
|
)
|
||||||
let expectedSettings = PklSwift.Project.EvaluatorSettings(
|
let expectedSettings = PklSwift.PklEvaluatorSettings(
|
||||||
externalProperties: ["myprop": "1"],
|
externalProperties: ["myprop": "1"],
|
||||||
env: ["myenv": "2"],
|
env: ["myenv": "2"],
|
||||||
allowedModules: ["foo:"],
|
allowedModules: ["foo:"],
|
||||||
|
@ -106,7 +123,8 @@ class ProjectTest: XCTestCase {
|
||||||
modulePath: ["/bar/baz"],
|
modulePath: ["/bar/baz"],
|
||||||
timeout: .minutes(5),
|
timeout: .minutes(5),
|
||||||
moduleCacheDir: "/bar/buzz",
|
moduleCacheDir: "/bar/buzz",
|
||||||
rootDir: "/buzzy"
|
rootDir: "/buzzy",
|
||||||
|
http: httpExpectation
|
||||||
)
|
)
|
||||||
let expectedPackage = PklSwift.Project.Package(
|
let expectedPackage = PklSwift.Project.Package(
|
||||||
name: "hawk",
|
name: "hawk",
|
||||||
|
@ -160,7 +178,8 @@ class ProjectTest: XCTestCase {
|
||||||
modulePath: nil,
|
modulePath: nil,
|
||||||
timeout: nil,
|
timeout: nil,
|
||||||
moduleCacheDir: nil,
|
moduleCacheDir: nil,
|
||||||
rootDir: nil
|
rootDir: nil,
|
||||||
|
http: nil
|
||||||
),
|
),
|
||||||
projectFileUri: "\(otherProjectFile)",
|
projectFileUri: "\(otherProjectFile)",
|
||||||
tests: [],
|
tests: [],
|
||||||
|
|
Loading…
Reference in New Issue