Native Plugins
Native plugins are compiled executables that run directly on the host system. They have full access to system resources and are ideal for feature-rich tools.
Advantages
Section titled “Advantages”- Full System Access: Read/write files, make network requests, spawn processes
- No Memory Limits: Use as much memory as the system provides
- Maximum Performance: Native code runs at full speed
- Rich Ecosystem: Use any Go library
When to Use Native Plugins
Section titled “When to Use Native Plugins”Native plugins are the best choice when you need:
- Filesystem operations (reading/writing files)
- Network requests (API calls, downloads)
- External process execution
- High memory usage
- Maximum performance
Creating a Native Plugin
Section titled “Creating a Native Plugin”-
Initialize the project
Terminal window sky plugin init my-toolcd my-tool -
Implement your logic
Edit
main.goto add your functionality. -
Build the plugin
Terminal window go build -o plugin -
Install and test
Terminal window sky plugin install --path ./plugin my-toolsky my-tool --help
Project Structure
Section titled “Project Structure”A typical native plugin project:
my-tool/├── main.go # Entry point├── go.mod # Go module├── go.sum # Dependencies├── internal/ # Internal packages│ └── analyzer/│ └── analyzer.go├── BUILD.bazel # Optional: Bazel build└── README.mdComplete Example
Section titled “Complete Example”Here’s a complete native plugin that analyzes Starlark files:
package main
import ( "encoding/json" "flag" "fmt" "os" "path/filepath")
const ( pluginName = "star-analyzer" pluginVersion = "1.0.0" pluginSummary = "Analyzes Starlark files")
func main() { if os.Getenv("SKY_PLUGIN") != "1" { fmt.Fprintf(os.Stderr, "Run via: sky %s\n", pluginName) os.Exit(1) }
if os.Getenv("SKY_PLUGIN_MODE") == "metadata" { outputMetadata() return }
os.Exit(run(os.Args[1:]))}
func outputMetadata() { json.NewEncoder(os.Stdout).Encode(map[string]any{ "api_version": 1, "name": pluginName, "version": pluginVersion, "summary": pluginSummary, })}
func run(args []string) int { fs := flag.NewFlagSet(pluginName, flag.ContinueOnError) recursive := fs.Bool("r", false, "recursive scan") jsonOut := fs.Bool("json", false, "JSON output")
if err := fs.Parse(args); err != nil { return 2 }
paths := fs.Args() if len(paths) == 0 { paths = []string{"."} }
var files []string for _, path := range paths { found, _ := findStarlarkFiles(path, *recursive) files = append(files, found...) }
if *jsonOut { json.NewEncoder(os.Stdout).Encode(map[string]any{ "files": files, "count": len(files), }) } else { for _, f := range files { fmt.Println(f) } fmt.Printf("\nFound %d Starlark files\n", len(files)) }
return 0}
func findStarlarkFiles(root string, recursive bool) ([]string, error) { var files []string
walkFn := func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() && !recursive && path != root { return filepath.SkipDir } if isStarlarkFile(path) { files = append(files, path) } return nil }
filepath.Walk(root, walkFn) return files, nil}
func isStarlarkFile(path string) bool { ext := filepath.Ext(path) base := filepath.Base(path) return ext == ".star" || ext == ".bzl" || base == "BUILD" || base == "BUILD.bazel"}Using External Libraries
Section titled “Using External Libraries”Native plugins can use any Go library. For Starlark analysis, the buildtools library is popular:
go get github.com/bazelbuild/buildtools@latestimport "github.com/bazelbuild/buildtools/build"
func analyzeFile(path string) error { content, err := os.ReadFile(path) if err != nil { return err }
file, err := build.ParseDefault(path, content) if err != nil { return err }
// Walk the AST build.Walk(file, func(expr build.Expr, stack []build.Expr) { // Analyze nodes })
return nil}Respecting Environment Variables
Section titled “Respecting Environment Variables”Good plugins respect the environment variables set by Sky:
func outputResult(result any, textFn func() string) { if os.Getenv("SKY_OUTPUT_FORMAT") == "json" { json.NewEncoder(os.Stdout).Encode(result) } else { fmt.Println(textFn()) }}
func verbose(level int, msg string) { v, _ := strconv.Atoi(os.Getenv("SKY_VERBOSE")) if v >= level { fmt.Fprintln(os.Stderr, msg) }}Cross-Compilation
Section titled “Cross-Compilation”Build for multiple platforms:
# Linux AMD64GOOS=linux GOARCH=amd64 go build -o plugin-linux-amd64
# Linux ARM64GOOS=linux GOARCH=arm64 go build -o plugin-linux-arm64
# macOS ARM64GOOS=darwin GOARCH=arm64 go build -o plugin-darwin-arm64
# WindowsGOOS=windows GOARCH=amd64 go build -o plugin-windows-amd64.exeBest Practices
Section titled “Best Practices”Testing
Section titled “Testing”Test your plugin logic separately from the plugin harness:
func TestRun(t *testing.T) { // Set up test environment os.Setenv("SKY_PLUGIN", "1") defer os.Unsetenv("SKY_PLUGIN")
code := run([]string{"-r", "testdata"}) if code != 0 { t.Errorf("expected exit code 0, got %d", code) }}See Testing Plugins for more testing strategies.