Ctrl K
ComunidadGuías…

bldr/util/gocompiler/tinygo-options.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gocompiler

import (
	"os"
	"slices"
	"strings"
	"time"

	"github.com/pkg/errors"
)

const (
	// TinyGoProfileEnv selects the TinyGo build profile.
	TinyGoProfileEnv = "BLDR_TINYGO_PROFILE"
	// TinyGoOptEnv overrides the TinyGo optimization level.
	TinyGoOptEnv = "BLDR_TINYGO_OPT"
	// TinyGoPanicStrategyEnv controls the TinyGo panic strategy.
	TinyGoPanicStrategyEnv = "BLDR_TINYGO_PANIC"
	// TinyGoGCEnv overrides the TinyGo garbage collector.
	TinyGoGCEnv = "BLDR_TINYGO_GC"
	// TinyGoSchedulerEnv overrides the TinyGo scheduler.
	TinyGoSchedulerEnv = "BLDR_TINYGO_SCHEDULER"
	// TinyGoStackSizeEnv overrides the TinyGo goroutine stack size.
	TinyGoStackSizeEnv = "BLDR_TINYGO_STACK_SIZE"
	// TinyGoLLVMFeaturesEnv overrides the TinyGo LLVM feature list.
	TinyGoLLVMFeaturesEnv = "BLDR_TINYGO_LLVM_FEATURES"
	// TinyGoInterpTimeoutEnv overrides TinyGo interp optimization timeout.
	TinyGoInterpTimeoutEnv = "BLDR_TINYGO_INTERP_TIMEOUT"
	// TinyGoDebugInfoEnv retains TinyGo debug information in browser wasm.
	TinyGoDebugInfoEnv = "BLDR_TINYGO_DEBUG"
	// TinyGoTraceEnv prints TinyGo's internal build commands.
	TinyGoTraceEnv = "BLDR_TINYGO_TRACE"
	// TinyGoWorkEnv keeps TinyGo's temporary work directory for diagnostics.
	TinyGoWorkEnv = "BLDR_TINYGO_WORK"

	// TinyGoProfileDefault uses the optimized TinyGo defaults.
	TinyGoProfileDefault = "default"
	// TinyGoProfileFast uses a local development TinyGo profile.
	TinyGoProfileFast = "fast"
	// TinyGoProfileOptimized uses the optimized TinyGo profile.
	TinyGoProfileOptimized = "optimized"

	// TinyGoDefaultStackSize is the minimum stack headroom for TinyGo wasm
	// plugins that run browser crypto, storage, and SRPC workloads.
	TinyGoDefaultStackSize = "256KB"
)

var tinyGoStartupCacheEnvKeys = []string{
	TinyGoProfileEnv,
	TinyGoOptEnv,
	TinyGoPanicStrategyEnv,
	TinyGoGCEnv,
	TinyGoSchedulerEnv,
	TinyGoStackSizeEnv,
	TinyGoLLVMFeaturesEnv,
	TinyGoInterpTimeoutEnv,
	TinyGoDebugInfoEnv,
	TinyGoTraceEnv,
	TinyGoWorkEnv,
}

// TinyGoOptions configures TinyGo compiler arguments.
type TinyGoOptions struct {
	// Profile selects the default argument set.
	Profile string
	// Opt overrides the optimization level.
	Opt string
	// PanicStrategy overrides the panic strategy.
	PanicStrategy string
	// GC overrides the garbage collector.
	GC string
	// Scheduler overrides the scheduler.
	Scheduler string
	// StackSize overrides the goroutine stack size.
	StackSize string
	// LLVMFeatures overrides the comma-separated LLVM feature list.
	LLVMFeatures string
	// InterpTimeout overrides the TinyGo interp optimization timeout.
	InterpTimeout string
	// Trace prints TinyGo's internal build commands.
	Trace string
	// Work keeps TinyGo's temporary work directory.
	Work string
}

// TinyGoStartupCacheEnvKeys returns env keys that affect TinyGo artifact identity.
func TinyGoStartupCacheEnvKeys() []string {
	return slices.Clone(tinyGoStartupCacheEnvKeys)
}

// TinyGoOptionsFromEnv resolves TinyGo options from the process environment.
func TinyGoOptionsFromEnv() TinyGoOptions {
	return TinyGoOptions{
		Profile:       os.Getenv(TinyGoProfileEnv),
		Opt:           os.Getenv(TinyGoOptEnv),
		PanicStrategy: os.Getenv(TinyGoPanicStrategyEnv),
		GC:            os.Getenv(TinyGoGCEnv),
		Scheduler:     os.Getenv(TinyGoSchedulerEnv),
		StackSize:     os.Getenv(TinyGoStackSizeEnv),
		LLVMFeatures:  os.Getenv(TinyGoLLVMFeaturesEnv),
		InterpTimeout: os.Getenv(TinyGoInterpTimeoutEnv),
		Trace:         os.Getenv(TinyGoTraceEnv),
		Work:          os.Getenv(TinyGoWorkEnv),
	}
}

// GetDefaultTinygoArgs returns the TinyGo args selected by environment.
func GetDefaultTinygoArgs() ([]string, error) {
	return TinyGoArgs(TinyGoOptionsFromEnv())
}

// TinyGoArgs converts TinyGoOptions into compiler args.
func TinyGoArgs(opts TinyGoOptions) ([]string, error) {
	// Resolve the TinyGo profile that supplies compiler defaults.
	profile, err := resolveTinyGoProfile(opts.Profile)
	if err != nil {
		return nil, err
	}

	// Resolve the TinyGo optimization level for the selected profile.
	opt, err := resolveTinyGoOpt(profile, opts.Opt)
	if err != nil {
		return nil, err
	}

	// Resolve the TinyGo panic strategy and LLVM feature set.
	panicStrategy, err := resolveTinyGoPanicStrategy(opts.PanicStrategy)
	if err != nil {
		return nil, err
	}
	features, err := resolveTinyGoLLVMFeatures(opts.LLVMFeatures)
	if err != nil {
		return nil, err
	}

	// Assemble the TinyGo panic, LLVM and optimization arguments.
	args := []string{
		"-panic=" + panicStrategy,
		"-llvm-features=" + strings.Join(features, ","),
	}
	if opt != "" {
		args = append([]string{"-opt=" + opt}, args...)
	}

	// Resolve the TinyGo garbage collector argument.
	gc, err := resolveTinyGoGC(profile, opts.GC)
	if err != nil {
		return nil, err
	}
	if gc != "" {
		args = append(args, "-gc="+gc)
	}

	// Validate and append the TinyGo scheduler override.
	if scheduler := strings.TrimSpace(opts.Scheduler); scheduler != "" {
		if err := validateTinyGoArgValue(TinyGoSchedulerEnv, scheduler); err != nil {
			return nil, err
		}
		args = append(args, "-scheduler="+scheduler)
	}

	// Resolve the TinyGo goroutine stack argument.
	stackSize, err := resolveTinyGoStackSize(opts.StackSize)
	if err != nil {
		return nil, err
	}
	if stackSize != "" {
		args = append(args, "-stack-size="+stackSize)
	}

	// Resolve the TinyGo interpreter timeout argument.
	interpTimeout, err := resolveTinyGoInterpTimeout(profile, opts.InterpTimeout)
	if err != nil {
		return nil, err
	}
	if interpTimeout != "" {
		args = append(args, "-interp-timeout="+interpTimeout)
	}

	// Resolve whether TinyGo should print build commands.
	trace, err := tinyGoBoolEnabled(TinyGoTraceEnv, opts.Trace)
	if err != nil {
		return nil, err
	}
	if trace {
		args = append(args, "-x")
	}

	// Resolve whether TinyGo should retain its build directory.
	work, err := tinyGoBoolEnabled(TinyGoWorkEnv, opts.Work)
	if err != nil {
		return nil, err
	}
	if work {
		args = append(args, "-work")
	}
	return args, nil
}

// ResolveTinyGoPanicStrategy resolves the configured TinyGo panic strategy.
func ResolveTinyGoPanicStrategy() (string, error) {
	return resolveTinyGoPanicStrategy(os.Getenv(TinyGoPanicStrategyEnv))
}

// TinyGoDebugInfoEnabled reports whether TinyGo should retain debug info.
func TinyGoDebugInfoEnabled() (bool, error) {
	return tinyGoBoolEnabled(TinyGoDebugInfoEnv, os.Getenv(TinyGoDebugInfoEnv))
}

func tinyGoBoolEnabled(envKey, raw string) (bool, error) {
	switch strings.TrimSpace(raw) {
	case "", "0", "false", "no", "off":
		return false, nil
	case "1", "true", "yes", "on":
		return true, nil
	default:
		return false, errors.Errorf("unsupported %s value %q, expected true or false", envKey, raw)
	}
}

func resolveTinyGoProfile(profile string) (string, error) {
	switch strings.TrimSpace(profile) {
	case "", TinyGoProfileDefault, TinyGoProfileOptimized:
		return TinyGoProfileOptimized, nil
	case TinyGoProfileFast:
		return TinyGoProfileFast, nil
	default:
		return "", errors.Errorf("unsupported %s value %q, expected default, fast, or optimized", TinyGoProfileEnv, profile)
	}
}

func resolveTinyGoOpt(profile, rawOpt string) (string, error) {
	// Resolve and validate the TinyGo optimization level.
	opt := strings.TrimSpace(rawOpt)
	if opt == "" && profile == TinyGoProfileFast {
		opt = "1"
	}
	if opt == "" && profile == TinyGoProfileOptimized {
		opt = "2"
	}
	if opt == "" {
		return "", nil
	}
	if err := validateTinyGoOpt(opt); err != nil {
		return "", err
	}
	return opt, nil
}

func resolveTinyGoPanicStrategy(panicStrategy string) (string, error) {
	switch strings.TrimSpace(panicStrategy) {
	case "", "print":
		return "print", nil
	case "trap":
		return "trap", nil
	default:
		return "", errors.Errorf("unsupported %s value %q, expected trap or print", TinyGoPanicStrategyEnv, panicStrategy)
	}
}

func resolveTinyGoStackSize(rawStackSize string) (string, error) {
	// Resolve and validate the TinyGo goroutine stack size.
	stackSize := strings.TrimSpace(rawStackSize)
	if stackSize == "" {
		return TinyGoDefaultStackSize, nil
	}
	if stackSize == "default" {
		return "", nil
	}
	if err := validateTinyGoArgValue(TinyGoStackSizeEnv, stackSize); err != nil {
		return "", err
	}
	return stackSize, nil
}

func validateTinyGoOpt(opt string) error {
	switch opt {
	case "0", "1", "2", "s", "z":
		return nil
	default:
		return errors.Errorf("unsupported %s value %q, expected 0, 1, 2, s, or z", TinyGoOptEnv, opt)
	}
}

func resolveTinyGoGC(profile, rawGC string) (string, error) {
	// Resolve and validate the TinyGo garbage collector.
	gc := strings.TrimSpace(rawGC)
	if gc == "" && profile == TinyGoProfileFast {
		gc = "leaking"
	}
	if gc == "" {
		return "", nil
	}
	if err := validateTinyGoArgValue(TinyGoGCEnv, gc); err != nil {
		return "", err
	}
	return gc, nil
}

func resolveTinyGoLLVMFeatures(raw string) ([]string, error) {
	// Resolve and validate the TinyGo LLVM feature list.
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return GetDefaultTinygoLlvmFeatures(), nil
	}
	features := strings.Split(raw, ",")
	for _, feature := range features {
		if feature == "" {
			return nil, errors.Errorf("unsupported %s value %q: empty feature", TinyGoLLVMFeaturesEnv, raw)
		}
		if err := validateTinyGoArgValue(TinyGoLLVMFeaturesEnv, feature); err != nil {
			return nil, err
		}
	}
	return features, nil
}

func validateTinyGoArgValue(envKey, value string) error {
	if strings.ContainsAny(value, " \t\r\n,") {
		return errors.Errorf("unsupported %s value %q: values cannot contain whitespace or commas", envKey, value)
	}
	return nil
}

func resolveTinyGoInterpTimeout(profile, rawTimeout string) (string, error) {
	// Resolve and validate the TinyGo interpreter timeout.
	timeout := strings.TrimSpace(rawTimeout)
	if timeout == "" && profile == TinyGoProfileFast {
		timeout = "10m"
	}
	if timeout == "" {
		return "", nil
	}
	if _, err := time.ParseDuration(timeout); err != nil {
		return "", errors.Wrapf(err, "unsupported %s value %q", TinyGoInterpTimeoutEnv, timeout)
	}
	return timeout, nil
}