Skip to content

convert compilers to use functional options #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 8, 2025
Merged
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
13 changes: 3 additions & 10 deletions engine/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ type Config struct {
dataProvider data.Provider
// Loader for the script content
loader loader.Loader
// Machine-specific options
compilerOptions any
}

// Option is a function that modifies Config
Expand Down Expand Up @@ -121,12 +119,7 @@ func (c *Config) GetLoader() loader.Loader {
return c.loader
}

// GetCompilerOptions returns the machine-specific compiler options
func (c *Config) GetCompilerOptions() any {
return c.compilerOptions
}

// SetCompilerOptions sets the machine-specific compiler options
func (c *Config) SetCompilerOptions(options any) {
c.compilerOptions = options
// SetLoader sets the loader
func (c *Config) SetLoader(l loader.Loader) {
c.loader = l
}
11 changes: 4 additions & 7 deletions engine/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,16 @@ func TestConfigGetters(t *testing.T) {
testHandler := slog.NewTextHandler(os.Stdout, nil)
testDataProvider := data.NewStaticProvider(map[string]any{"test": "value"})
testLoader := NewMockLoader()
testCompilerOpts := "test-options"

cfg := &Config{
handler: testHandler,
machineType: types.Starlark,
dataProvider: testDataProvider,
loader: testLoader,
compilerOptions: testCompilerOpts,
handler: testHandler,
machineType: types.Starlark,
dataProvider: testDataProvider,
loader: testLoader,
}

require.Equal(t, testHandler, cfg.GetHandler())
require.Equal(t, types.Starlark, cfg.GetMachineType())
require.Equal(t, testDataProvider, cfg.GetDataProvider())
require.Equal(t, testLoader, cfg.GetLoader())
require.Equal(t, testCompilerOpts, cfg.GetCompilerOptions())
}
55 changes: 40 additions & 15 deletions machines/extism/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,55 @@ type Compiler struct {
logger *slog.Logger
}

type CompilerOptions interface {
GetEntryPointName() string
}
// NewCompiler creates a new Extism WASM Compiler instance with the provided options.
func NewCompiler(opts ...CompilerOption) (*Compiler, error) {
// Initialize config with defaults
cfg := &compilerOptions{}
applyDefaults(cfg)

// Apply all options
for _, opt := range opts {
if err := opt(cfg); err != nil {
return nil, fmt.Errorf("error applying compiler option: %w", err)
}
}

// NewCompiler creates a new Extism WASM Compiler instance. External config of the compiler not
// currently supported.
func NewCompiler(handler slog.Handler, compilerOptions CompilerOptions) *Compiler {
handler, logger := helpers.SetupLogger(handler, "extism", "Compiler")
// Validate the configuration
if err := validate(cfg); err != nil {
return nil, fmt.Errorf("invalid compiler configuration: %w", err)
}

entryPointName := compilerOptions.GetEntryPointName()
if entryPointName == "" {
entryPointName = defaultEntryPoint
var handler slog.Handler
var logger *slog.Logger

// Set up logging based on provided options
if cfg.Logger != nil {
// User provided a custom logger
logger = cfg.Logger
handler = logger.Handler()
} else {
// User provided a handler or we're using the default
handler, logger = helpers.SetupLogger(cfg.LogHandler, "extism", "Compiler")
}

var funcName atomic.Value
funcName.Store(entryPointName)
// Set up entry point name in atomic.Value
var entryPointAtomicValue atomic.Value
entryPointAtomicValue.Store(cfg.EntryPoint)

// Create compile options from config
compileOpts := &compileOptions{
EnableWASI: cfg.EnableWASI,
RuntimeConfig: cfg.RuntimeConfig,
HostFunctions: cfg.HostFunctions,
}

return &Compiler{
entryPointName: funcName,
entryPointName: entryPointAtomicValue,
ctx: context.Background(),
options: withDefaultCompileOptions(),
options: compileOpts,
logHandler: handler,
logger: logger,
}
}, nil
}

func (c *Compiler) String() string {
Expand Down
137 changes: 137 additions & 0 deletions machines/extism/compilerOptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package extism

import (
"fmt"
"log/slog"
"os"

extismSDK "github.com/extism/go-sdk"
"github.com/tetratelabs/wazero"
)

// compilerOptions holds the configuration for the Extism compiler
type compilerOptions struct {
EntryPoint string
LogHandler slog.Handler
Logger *slog.Logger
EnableWASI bool
RuntimeConfig wazero.RuntimeConfig
HostFunctions []extismSDK.HostFunction
}

// CompilerOption is a function that configures a compilerOptions instance
type CompilerOption func(*compilerOptions) error

// WithEntryPoint creates an option to set the entry point for Extism WASM modules
func WithEntryPoint(entryPoint string) CompilerOption {
return func(cfg *compilerOptions) error {
if entryPoint == "" {
return fmt.Errorf("entry point cannot be empty")
}
cfg.EntryPoint = entryPoint
return nil
}
}

// WithLogHandler creates an option to set the log handler for Extism compiler.
// This is the preferred option for logging configuration as it provides
// more flexibility through the slog.Handler interface.
func WithLogHandler(handler slog.Handler) CompilerOption {
return func(cfg *compilerOptions) error {
if handler == nil {
return fmt.Errorf("log handler cannot be nil")
}
cfg.LogHandler = handler
// Clear logger if handler is explicitly set
cfg.Logger = nil
return nil
}
}

// WithLogger creates an option to set a specific logger for Extism compiler.
// This is less flexible than WithLogHandler but allows users to customize
// their logging group configuration.
func WithLogger(logger *slog.Logger) CompilerOption {
return func(cfg *compilerOptions) error {
if logger == nil {
return fmt.Errorf("logger cannot be nil")
}
cfg.Logger = logger
// Clear handler if logger is explicitly set
cfg.LogHandler = nil
return nil
}
}

// WithWASIEnabled creates an option to enable or disable WASI support
func WithWASIEnabled(enabled bool) CompilerOption {
return func(cfg *compilerOptions) error {
cfg.EnableWASI = enabled
return nil
}
}

// WithRuntimeConfig creates an option to set a custom wazero runtime configuration
func WithRuntimeConfig(config wazero.RuntimeConfig) CompilerOption {
return func(cfg *compilerOptions) error {
if config == nil {
return fmt.Errorf("runtime config cannot be nil")
}
cfg.RuntimeConfig = config
return nil
}
}

// WithHostFunctions creates an option to set additional host functions
func WithHostFunctions(funcs []extismSDK.HostFunction) CompilerOption {
return func(cfg *compilerOptions) error {
cfg.HostFunctions = funcs
return nil
}
}

// applyDefaults sets the default values for a compilerConfig
func applyDefaults(cfg *compilerOptions) {
// Default to stderr for logging if neither handler nor logger specified
if cfg.LogHandler == nil && cfg.Logger == nil {
cfg.LogHandler = slog.NewTextHandler(os.Stderr, nil)
}

// Default entry point
if cfg.EntryPoint == "" {
cfg.EntryPoint = defaultEntryPoint
}

// Default WASI setting
cfg.EnableWASI = true

// Default runtime config
if cfg.RuntimeConfig == nil {
cfg.RuntimeConfig = wazero.NewRuntimeConfig()
}

// Default to empty host functions
if cfg.HostFunctions == nil {
cfg.HostFunctions = []extismSDK.HostFunction{}
}
}

// validate checks if the configuration is valid
func validate(cfg *compilerOptions) error {
// Ensure we have either a logger or a handler
if cfg.LogHandler == nil && cfg.Logger == nil {
return fmt.Errorf("either log handler or logger must be specified")
}

// Entry point must be non-empty
if cfg.EntryPoint == "" {
return fmt.Errorf("entry point must be specified")
}

// Runtime config cannot be nil
if cfg.RuntimeConfig == nil {
return fmt.Errorf("runtime config cannot be nil")
}

return nil
}
Loading