Introduction
KCL provides plugin support through a plugin agent and auxiliary command line tools, and the KCL plugin framework supports different general-purpose languages to develop plugins. The KCL plugin framework currently supports the development of plugins in Python and Go languages.
KCL plugin Git repository: https://github.com/kcl-lang/kcl-plugin
Use Go to Write Plugins
0. Prerequisites
Using the KCL Go plugin requires the presence of Go 1.21+
in your PATH
and add the dependency of KCL Go SDK
1. Hello Plugin
Write the following Go code and add the dependency of the hello plugin
package main
import (
"fmt"
"kcl-lang.io/kcl-go/pkg/kcl" // Import the native API
_ "kcl-lang.io/kcl-go/pkg/plugin/hello_plugin" // Import the hello plugin
)
func main() {
yaml := kcl.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
`
In KCL code, the hello
plugin can be imported via import kcl_plugin.hello
. The output result is
name: kcl
three: 3
2. Plugin Directory Structure
The KCL Go plugin is essentially a simple Go project, mainly containing the Go file api.go
for the plugin code, which defines the registration and implementation functions of the plugin.
package hello_plugin
import (
"kcl-lang.io/kcl-go/pkg/plugin"
)
func init() {
plugin.RegisterPlugin(plugin.Plugin{
Name: "hello",
MethodMap: map[string]plugin.MethodSpec{
"add": {
Body: func(args *plugin.MethodArgs) (*plugin.MethodResult, error) {
v := args.IntArg(0) + args.IntArg(1)
return &plugin.MethodResult{V: v}, nil
},
},
},
})
}
3. Test Plugin
Write a file called api_test.go
to perform unit testing on plugin functions.
package hello_plugin
import (
"testing"
"kcl-lang.io/kcl-go/pkg/plugin"
)
func TestPluginAdd(t *testing.T) {
result_json := plugin.Invoke("kcl_plugin.hello.add", []interface{}{111, 22}, nil)
if result_json != "133" {
t.Fatal(result_json)
}
}
Use Python to Write Plugins
0. Prerequisites
Using the KCL Python plugin requires the presence of Python 3.7+
in your PATH
and install the KCL python SDK.
python3 -m pip kcl_lib
1. Hello Plugin
Write the following Python code and add the the plugin named my_plugin
.
import kcl_lib.plugin as plugin
import kcl_lib.api as api
plugin.register_plugin("my_plugin", {"add": lambda x, y: x + y})
def main():
result = api.API().exec_program(
api.ExecProgram_Args(k_filename_list=["test.k"])
)
assert result.yaml_result == "result: 2"
main()
The content of test.k
are:
import kcl_plugin.my_plugin
result = my_plugin.add(1, 1)
Use Java to Write Plugins
0. Prerequisites
Using the KCL Java plugin requires the presence of Java 8+
in your PATH
and install the KCL Java SDK.
1. Hello Plugin
Write the following Java code and add the the plugin named my_plugin
.
package com.kcl;
import com.kcl.api.API;
import com.kcl.api.Spec.ExecProgram_Args;
import com.kcl.api.Spec.ExecProgram_Result;
import java.util.Collections;
public class PluginTest {
public static void main(String[] mainArgs) throws Exception {
API.registerPlugin("my_plugin", Collections.singletonMap("add", (args, kwArgs) -> {
return (int) args[0] + (int) args[1];
}));
API api = new API();
ExecProgram_Result result = api
.execProgram(ExecProgram_Args.newBuilder().addKFilenameList("test.k").build());
System.out.println(result.getYamlResult());
}
}
The content of test.k
are:
import kcl_plugin.my_plugin
result = my_plugin.add(1, 1)
Use Rust to Write Plugins
0. Prerequisites
Using the KCL Rust plugin requires the presence of Rust 1.79+
in your PATH
and install the KCL Rust SDK.
cargo add anyhow
cargo add kclvm-parser --git https://github.com/kcl-lang/kcl
cargo add kclvm-loader --git https://github.com/kcl-lang/kcl
cargo add kclvm-evaluator --git https://github.com/kcl-lang/kcl
cargo add kclvm-runtime --git https://github.com/kcl-lang/kcl
1. Hello Plugin
Write the following Rust code and add the the plugin named my_plugin
.
use anyhow::{anyhow, Result};
use kclvm_evaluator::Evaluator;
use kclvm_loader::{load_packages, LoadPackageOptions};
use kclvm_parser::LoadProgramOptions;
use kclvm_runtime::{Context, IndexMap, PluginFunction, ValueRef};
use std::{cell::RefCell, rc::Rc, sync::Arc};
fn my_plugin_sum(_: &Context, args: &ValueRef, _: &ValueRef) -> Result<ValueRef> {
let a = args
.arg_i_int(0, Some(0))
.ok_or(anyhow!("expect int value for the first param"))?;
let b = args
.arg_i_int(1, Some(0))
.ok_or(anyhow!("expect int value for the second param"))?;
Ok((a + b).into())
}
fn context_with_plugin() -> Rc<RefCell<Context>> {
let mut plugin_functions: IndexMap<String, PluginFunction> = Default::default();
let func = Arc::new(my_plugin_sum);
plugin_functions.insert("my_plugin.add".to_string(), func);
let mut ctx = Context::new();
ctx.plugin_functions = plugin_functions;
Rc::new(RefCell::new(ctx))
}
fn main() -> Result<()> {
let src = r#"
import kcl_plugin.my_plugin
sum = my_plugin.add(1, 1)
"#;
let p = load_packages(&LoadPackageOptions {
paths: vec!["test.k".to_string()],
load_opts: Some(LoadProgramOptions {
load_plugins: true,
k_code_list: vec![src.to_string()],
..Default::default()
}),
load_builtin: false,
..Default::default()
})?;
let evaluator = Evaluator::new_with_runtime_ctx(&p.program, context_with_plugin());
let result = evaluator.run()?;
println!("yaml result {}", result.1);
Ok(())
}
The content of test.k
are:
import kcl_plugin.my_plugin
result = my_plugin.add(1, 1)