Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*.pb.go
.gradle/
*.jar
libpkl/
out/
docker/files/bin/
.vscode/
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
2 changes: 2 additions & 0 deletions pkl/evaluator_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//go:build !libpkl

package pkl

import (
Expand Down
2 changes: 2 additions & 0 deletions pkl/evaluator_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package pkl
import (
"context"
"errors"
"fmt"
"log"
"path"
"sync"
Expand Down Expand Up @@ -300,6 +301,7 @@ func (m *evaluatorManager) closeErr(e error) error {
ev := v.(*evaluator)
// if an error occurs, still try to keep closing.
if cerr := ev.Close(); cerr != nil {
fmt.Printf("closeErr=%#v\n", cerr)
err = cerr
}
return true
Expand Down
2 changes: 2 additions & 0 deletions pkl/evaluator_manager_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//go:build !libpkl

package pkl

import (
Expand Down
4 changes: 2 additions & 2 deletions pkl/evaluator_manager_exec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//go:build unix
// +build unix
//go:build unix && !libpkl
// +build unix,!libpkl

package pkl

Expand Down
3 changes: 3 additions & 0 deletions pkl/evaluator_manager_exec_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//go:build !libpkl
// +build !libpkl

package pkl

import (
Expand Down
152 changes: 152 additions & 0 deletions pkl/evaluator_manager_native.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 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.
//===----------------------------------------------------------------------===//

//go:build libpkl

package pkl

import (
"bytes"
"fmt"
"github.com/apple/pkl-go/pkl/internal"
"github.com/vmihailenco/msgpack/v5"
"io"
"sync"
"unsafe"

"github.com/apple/pkl-go/pkl/internal/msgapi"
"github.com/apple/pkl-go/pkl/libpkl"
)

var _ evaluatorManagerImpl = (*nativeEvaluator)(nil)

// NewEvaluatorManager creates a new EvaluatorManager using the `libpkl` native bindings.
func NewEvaluatorManager() EvaluatorManager {
m := &evaluatorManager{
impl: &nativeEvaluator{
in: make(chan msgapi.IncomingMessage),
out: make(chan msgapi.OutgoingMessage),
received: make(chan []byte),
closed: make(chan error),
},
interrupts: &sync.Map{},
evaluators: &sync.Map{},
pendingEvaluators: &sync.Map{},
}

go m.listen()
go m.listenForImplClose()
return m
}

type nativeEvaluator struct {
client *libpkl.PklClient
in chan msgapi.IncomingMessage
out chan msgapi.OutgoingMessage
received chan []byte
closed chan error

// exited is a flag that indicates evaluator was closed explicitly
exited atomicBool
version *semver
}

func (n *nativeEvaluator) init() error {
c, err := libpkl.New(n.responseHandler)
if err != nil {
panic(fmt.Sprintf("Couldn't initialise libpkl C bindings: %e", err))
}

n.client = c

go n.handleSendMessages()

return nil
}

func (n *nativeEvaluator) deinit() error {
n.exited.set(true)

close(n.closed)
close(n.in)
close(n.out)
close(n.received)

if n.client == nil {
return nil
}

return n.client.Close()
}

func (n *nativeEvaluator) inChan() chan msgapi.IncomingMessage { return n.in }

func (n *nativeEvaluator) outChan() chan msgapi.OutgoingMessage { return n.out }

func (n *nativeEvaluator) closedChan() chan error { return n.closed }

func (n *nativeEvaluator) getVersion() (*semver, error) {
if n.exited.get() {
return nil, fmt.Errorf("evaluator is closed")
}

version := libpkl.Version()
parsed, err := parseSemver(version)
if err != nil {
return nil, err
}
n.version = parsed
return n.version, nil
}

func (n *nativeEvaluator) handleSendMessages() {
for msg := range n.out {
if n.exited.get() {
return
}

internal.Debug("Sending message: %#v", msg)
b, err := msg.ToMsgPack()
if err != nil {
n.closed <- &InternalError{err: err}
return
}

if err = n.client.SendMessage(b); err != nil {
if !n.exited.get() {
n.closed <- &InternalError{err: err}
}
return
}
}
}

func (n *nativeEvaluator) responseHandler(message []byte, userData unsafe.Pointer) {
r := bytes.NewBuffer(message)
dec := msgpack.NewDecoder(r)

msg, err := msgapi.Decode(dec)
if n.exited.get() || err == io.EOF {
return
}

if err != nil {
n.closed <- &InternalError{err: err}
return
}
internal.Debug("Received message: %#v userData=%#v", msg, userData)
n.in <- msg
}
69 changes: 69 additions & 0 deletions pkl/evaluator_native.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 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.
//===----------------------------------------------------------------------===//

//go:build libpkl

package pkl

import (
"context"
"path/filepath"
)

// NewEvaluator returns an evaluator backed by a single EvaluatorManager.
// Its manager gets closed when the evaluator is closed.
//
// If creating multiple evaluators, prefer using EvaluatorManager.NewEvaluator instead,
// because it lessens the overhead of each successive evaluator.
func NewEvaluator(ctx context.Context, opts ...func(options *EvaluatorOptions)) (Evaluator, error) {
manager := NewEvaluatorManager()
ev, err := manager.NewEvaluator(ctx, opts...)
if err != nil {
return nil, err
}
return &simpleEvaluator{Evaluator: ev, manager: manager}, nil
}

// NewProjectEvaluator is an easy way to create an evaluator that is configured by the specified
// projectDir.
//
// It is similar to running the `pkl eval` or `pkl test` CLI command with a set `--project-dir`.
//
// When using project dependencies, they must first be resolved using the `pkl project resolve`
// CLI command.
func NewProjectEvaluator(ctx context.Context, projectDir string, opts ...func(options *EvaluatorOptions)) (Evaluator, error) {
manager := NewEvaluatorManager()
projectEvaluator, err := manager.NewEvaluator(ctx, opts...)
if err != nil {
return nil, err
}
defer projectEvaluator.Close()

projectPath := filepath.Join(projectDir, "PklProject")
project, err := LoadProjectFromEvaluator(ctx, projectEvaluator, projectPath)
if err != nil {
return nil, err
}
newOpts := []func(options *EvaluatorOptions){
WithProject(project),
}
newOpts = append(newOpts, opts...)
ev, err := manager.NewEvaluator(ctx, newOpts...)
if err != nil {
return nil, err
}
return &simpleEvaluator{Evaluator: ev, manager: manager}, nil
}
5 changes: 2 additions & 3 deletions pkl/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ func getOpenPort() int {

func TestEvaluator(t *testing.T) {
manager := NewEvaluatorManager()

projectDir := setupProject(t)

t.Run("EvaluateOutputText", func(t *testing.T) {
Expand Down Expand Up @@ -502,7 +501,7 @@ age = 43
t.Fatal(err)
}
if pklVersion0_26.isGreaterThan(version) {
t.SkipNow()
t.Skip("evaluator is older than 0.26")
}
ev, err := manager.NewEvaluator(context.Background(), PreconfiguredOptions, func(options *EvaluatorOptions) {
options.Http = &Http{
Expand All @@ -524,7 +523,7 @@ age = 43
t.Fatal(err)
}
if version.isGreaterThan(pklVersion0_25) {
t.SkipNow()
t.Skip("evaluator is greater than 0.25")
}
_, err = manager.NewEvaluator(context.Background(), PreconfiguredOptions, func(options *EvaluatorOptions) {
options.Http = &Http{
Expand Down
11 changes: 7 additions & 4 deletions pkl/external_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const externalReaderTest1 = `
Expand All @@ -40,12 +41,14 @@ fibErrC = test.catch(() -> read("fib:-10"))
func TestExternalReaderE2E(t *testing.T) {
manager := NewEvaluatorManager()
defer manager.Close()

_, err := manager.NewEvaluator(context.Background(), PreconfiguredOptions)
require.Nil(t, err)

version, err := manager.(*evaluatorManager).getVersion()
if err != nil {
t.Fatal(err)
}
require.Nil(t, err)
if pklVersion0_27.isGreaterThan(version) {
t.SkipNow()
t.Skip("evaluator is less than 0.27")
}

tempDir := t.TempDir()
Expand Down
Loading