Skip to content

Commit

Permalink
feat: add go plugin APIs and examples (#233)
Browse files Browse the repository at this point in the history
Signed-off-by: peefy <[email protected]>
  • Loading branch information
Peefy authored Feb 22, 2024
1 parent d03d3a9 commit 45b5847
Show file tree
Hide file tree
Showing 35 changed files with 628 additions and 353 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ import (
)

func main() {
yaml := kcl.MustRun("kubernetes.k", kcl.WithCode(k_code)).GetRawYamlResult()
yaml := kcl.MustRun("kubernetes.k", kcl.WithCode(code)).GetRawYamlResult()
fmt.Println(yaml)
}

const k_code = `
const code = `
apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
Expand Down Expand Up @@ -96,6 +96,33 @@ spec:
- containerPort: 80
```
## Run KCL Code with Go Plugin
```go
package main

import (
"fmt"

"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kcl-go/pkg/native" // Import the native API
_ "kcl-lang.io/kcl-go/pkg/plugin/hello_plugin" // Import the hello plugin
)

func main() {
// Note we use `native.MustRun` here instead of `kcl.MustRun`, because it needs the cgo feature.
yaml := native.MustRun("main.k", kcl.WithCode(code)).GetRawYamlResult()
fmt.Println(yaml)
}

const code = `
import kcl_plugin.hello

name = "kcl"
three = hello.add(1,2) # hello.add is written by Go
`
```

## Documents

See the [KCL website](https://kcl-lang.io)
Expand Down
5 changes: 2 additions & 3 deletions examples/kubernetes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import (
"fmt"

kcl "kcl-lang.io/kcl-go"
_ "kcl-lang.io/kcl-go/pkg/kcl_plugin/hello_plugin"
)

func main() {
yaml := kcl.MustRun("kubernetes.k", kcl.WithCode(k_code)).GetRawYamlResult()
yaml := kcl.MustRun("kubernetes.k", kcl.WithCode(code)).GetRawYamlResult()
fmt.Println(yaml)
}

const k_code = `
const code = `
apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
Expand Down
22 changes: 22 additions & 0 deletions examples/plugin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"fmt"

"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kcl-go/pkg/native" // Import the native API
_ "kcl-lang.io/kcl-go/pkg/plugin/hello_plugin" // Import the hello plugin
)

func main() {
// Note we use `native.MustRun` here instead of `kcl.MustRun`, because it needs the cgo feature.
yaml := native.MustRun("main.k", kcl.WithCode(code)).GetRawYamlResult()
fmt.Println(yaml)
}

const code = `
import kcl_plugin.hello
name = "kcl"
three = hello.add(1,2)
`
6 changes: 3 additions & 3 deletions kclvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"io"

"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kcl-go/pkg/kclvm_runtime"
"kcl-lang.io/kcl-go/pkg/runtime"
"kcl-lang.io/kcl-go/pkg/tools/format"
"kcl-lang.io/kcl-go/pkg/tools/lint"
"kcl-lang.io/kcl-go/pkg/tools/list"
Expand All @@ -59,12 +59,12 @@ type (

// InitKclvmPath init kclvm path.
func InitKclvmPath(kclvmRoot string) {
kclvm_runtime.InitKclvmRoot(kclvmRoot)
runtime.InitKclvmRoot(kclvmRoot)
}

// InitKclvmRuntime init kclvm process.
func InitKclvmRuntime(n int) {
kclvm_runtime.InitRuntime(n)
runtime.InitRuntime(n)
}

// MustRun is like Run but panics if return any error.
Expand Down
85 changes: 85 additions & 0 deletions pkg/3rdparty/dlopen/dlopen_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2016 CoreOS, Inc.
//
// 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
//
// http://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 linux || darwin
// +build linux darwin

// Package dlopen provides some convenience functions to dlopen a library and
// get its symbols.
package dlopen

// #cgo LDFLAGS: -ldl
// #include <stdlib.h>
// #include <dlfcn.h>
import "C"
import (
"errors"
"fmt"
"unsafe"
)

var ErrSoNotFound = errors.New("unable to open a handle to the library")

// LibHandle represents an open handle to a library (.so)
type LibHandle struct {
Handle unsafe.Pointer
Libname string
}

// GetHandle tries to get a handle to a library (.so), attempting to access it
// by the names specified in libs and returning the first that is successfully
// opened. Callers are responsible for closing the handler. If no library can
// be successfully opened, an error is returned.
func GetHandle(libs []string) (*LibHandle, error) {
for _, name := range libs {
libname := C.CString(name)
defer C.free(unsafe.Pointer(libname))
handle := C.dlopen(libname, C.RTLD_LAZY)
if handle != nil {
h := &LibHandle{
Handle: handle,
Libname: name,
}
return h, nil
}
}
return nil, ErrSoNotFound
}

// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
sym := C.CString(symbol)
defer C.free(unsafe.Pointer(sym))

C.dlerror()
p := C.dlsym(l.Handle, sym)
e := C.dlerror()
if e != nil {
return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e)))
}

return p, nil
}

// Close closes a LibHandle.
func (l *LibHandle) Close() error {
C.dlerror()
C.dlclose(l.Handle)
e := C.dlerror()
if e != nil {
return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e)))
}

return nil
}
46 changes: 46 additions & 0 deletions pkg/3rdparty/dlopen/dlopen_windws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//go:build windows
// +build windows

// Package dlopen provides some convenience functions to dlopen a library and
// get its symbols.
package dlopen

import (
"errors"
"unsafe"

"syscall"
)

var ErrSoNotFound = errors.New("unable to open a handle to the library")

// LibHandle represents an open handle to a library (.so)
type LibHandle struct {
Handle *syscall.DLL
Libname string
}

func GetHandle(libs []string) (*LibHandle, error) {
if len(libs) == 0 {
return nil, ErrSoNotFound
}
name := libs[0]
dll, err := syscall.LoadDLL(name)
if err != nil {
return nil, err
}
return &LibHandle{
Handle: dll,
Libname: name,
}, nil
}

// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
panic("unsupported feature on windows")
}

// Close closes a LibHandle.
func (l *LibHandle) Close() error {
panic("unsupported feature on windows")
}
41 changes: 21 additions & 20 deletions pkg/kcl/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strings"

Expand Down Expand Up @@ -324,28 +325,10 @@ func GetSchemaTypeMapping(file, code, schemaName string) (map[string]*gpyrpc.Kcl
return resp.SchemaTypeMapping, nil
}

func run(pathList []string, opts ...Option) (*KCLResultList, error) {
return runWithHooks(pathList, []Hook{
&typeAttributeHook{},
}, opts...)
}

func runWithHooks(pathList []string, hooks Hooks, opts ...Option) (*KCLResultList, error) {
args, err := ParseArgs(pathList, opts...)
if err != nil {
return nil, err
}

client := service.NewKclvmServiceClient()
resp, err := client.ExecProgram(args.ExecProgram_Args)
if err != nil {
return nil, err
}
func ExecResultToKCLResult(o *Option, resp *gpyrpc.ExecProgram_Result, logger io.Writer, hooks Hooks) (*KCLResultList, error) {
for _, hook := range hooks {
hook.Do(&args, resp)
hook.Do(o, resp)
}
// Output log message
logger := args.GetLogger()
if logger != nil && resp.LogMessage != "" {
_, err := logger.Write([]byte(resp.LogMessage))
if err != nil {
Expand Down Expand Up @@ -391,3 +374,21 @@ func runWithHooks(pathList []string, hooks Hooks, opts ...Option) (*KCLResultLis
result.raw_yaml_result = resp.YamlResult
return &result, nil
}

func runWithHooks(pathList []string, hooks Hooks, opts ...Option) (*KCLResultList, error) {
args, err := ParseArgs(pathList, opts...)
if err != nil {
return nil, err
}

client := service.NewKclvmServiceClient()
resp, err := client.ExecProgram(args.ExecProgram_Args)
if err != nil {
return nil, err
}
return ExecResultToKCLResult(&args, resp, args.GetLogger(), hooks)
}

func run(pathList []string, opts ...Option) (*KCLResultList, error) {
return runWithHooks(pathList, DefaultHooks, opts...)
}
6 changes: 6 additions & 0 deletions pkg/kcl/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import (
"kcl-lang.io/kcl-go/pkg/spec/gpyrpc"
)

var (
DefaultHooks Hooks = []Hook{
&typeAttributeHook{},
}
)

type Hook interface {
Do(o *Option, r *gpyrpc.ExecProgram_Result) error
}
Expand Down
Loading

0 comments on commit 45b5847

Please sign in to comment.