expand template vars in targets config

This commit is contained in:
Li Jie
2025-08-21 18:51:54 +08:00
parent 5cfd996659
commit 06568da140
2 changed files with 209 additions and 3 deletions

View File

@@ -44,6 +44,83 @@ func cacheDir() string {
return filepath.Join(env.LLGoCacheDir(), "crosscompile") return filepath.Join(env.LLGoCacheDir(), "crosscompile")
} }
// expandEnv expands template variables in a string
// Supports variables like {port}, {hex}, {bin}, {root}, {tmpDir}, etc.
// Special case: {} expands to the first available file variable (hex, bin, img, zip)
func expandEnv(template string, envs map[string]string) string {
return expandEnvWithDefault(template, envs)
}
// expandEnvWithDefault expands template variables with optional default for {}
func expandEnvWithDefault(template string, envs map[string]string, defaultValue ...string) string {
if template == "" {
return ""
}
result := template
// Handle special case of {} - use provided default or first available file variable
if strings.Contains(result, "{}") {
defaultVal := ""
if len(defaultValue) > 0 && defaultValue[0] != "" {
defaultVal = defaultValue[0]
} else {
// Priority order: hex, bin, img, zip
for _, key := range []string{"hex", "bin", "img", "zip"} {
if value, exists := envs[key]; exists && value != "" {
defaultVal = value
break
}
}
}
result = strings.ReplaceAll(result, "{}", defaultVal)
}
// Replace named variables
for key, value := range envs {
if key != "" { // Skip empty key used for {} default
result = strings.ReplaceAll(result, "{"+key+"}", value)
}
}
return result
}
// expandEnvSlice expands template variables in a slice of strings
func expandEnvSlice(templates []string, envs map[string]string) []string {
return expandEnvSliceWithDefault(templates, envs)
}
// expandEnvSliceWithDefault expands template variables in a slice with optional default for {}
func expandEnvSliceWithDefault(templates []string, envs map[string]string, defaultValue ...string) []string {
if len(templates) == 0 {
return templates
}
result := make([]string, len(templates))
for i, template := range templates {
result[i] = expandEnvWithDefault(template, envs, defaultValue...)
}
return result
}
// buildEnvMap creates a map of template variables for the current context
func buildEnvMap(llgoRoot string) map[string]string {
envs := make(map[string]string)
// Basic paths
envs["root"] = llgoRoot
envs["tmpDir"] = os.TempDir()
// These will typically be set by calling code when actual values are known
// envs["port"] = "" // Serial port (e.g., "/dev/ttyUSB0", "COM3")
// envs["hex"] = "" // Path to hex file
// envs["bin"] = "" // Path to binary file
// envs["img"] = "" // Path to image file
// envs["zip"] = "" // Path to zip file
return envs
}
// getMacOSSysroot returns the macOS SDK path using xcrun // getMacOSSysroot returns the macOS SDK path using xcrun
func getMacOSSysroot() (string, error) { func getMacOSSysroot() (string, error) {
cmd := exec.Command("xcrun", "--sdk", "macosx", "--show-sdk-path") cmd := exec.Command("xcrun", "--sdk", "macosx", "--show-sdk-path")
@@ -347,6 +424,9 @@ func useTarget(targetName string) (export Export, err error) {
export.GOOS = config.GOOS export.GOOS = config.GOOS
export.GOARCH = config.GOARCH export.GOARCH = config.GOARCH
// Build environment map for template variable expansion
envs := buildEnvMap(env.LLGoROOT())
// Convert LLVMTarget, CPU, Features to CCFLAGS/LDFLAGS // Convert LLVMTarget, CPU, Features to CCFLAGS/LDFLAGS
var ccflags []string var ccflags []string
var ldflags []string var ldflags []string
@@ -361,7 +441,9 @@ func useTarget(targetName string) (export Export, err error) {
cflags = append(cflags, "--target="+config.LLVMTarget) cflags = append(cflags, "--target="+config.LLVMTarget)
ccflags = append(ccflags, "--target="+config.LLVMTarget) ccflags = append(ccflags, "--target="+config.LLVMTarget)
} }
cflags = append(cflags, config.CFlags...) // Expand template variables in cflags
expandedCFlags := expandEnvSlice(config.CFlags, envs)
cflags = append(cflags, expandedCFlags...)
// Inspired by tinygo // Inspired by tinygo
cpu := config.CPU cpu := config.CPU
@@ -394,10 +476,11 @@ func useTarget(targetName string) (export Export, err error) {
} }
ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld
// Combine with config flags // Combine with config flags and expand template variables
export.CFLAGS = cflags export.CFLAGS = cflags
export.CCFLAGS = ccflags export.CCFLAGS = ccflags
export.LDFLAGS = append(ldflags, config.LDFlags...) expandedLDFlags := expandEnvSlice(config.LDFlags, envs)
export.LDFLAGS = append(ldflags, expandedLDFlags...)
return export, nil return export, nil
} }

View File

@@ -301,3 +301,126 @@ func TestUseWithTarget(t *testing.T) {
t.Error("Expected LDFLAGS to be set for native build") t.Error("Expected LDFLAGS to be set for native build")
} }
} }
func TestExpandEnv(t *testing.T) {
envs := map[string]string{
"port": "/dev/ttyUSB0",
"hex": "firmware.hex",
"bin": "firmware.bin",
"root": "/usr/local/llgo",
}
tests := []struct {
template string
expected string
}{
{
"avrdude -c arduino -p atmega328p -P {port} -U flash:w:{hex}:i",
"avrdude -c arduino -p atmega328p -P /dev/ttyUSB0 -U flash:w:firmware.hex:i",
},
{
"simavr -m atmega328p -f 16000000 {}",
"simavr -m atmega328p -f 16000000 firmware.hex", // {} expands to hex (first priority)
},
{
"-I{root}/lib/CMSIS/CMSIS/Include",
"-I/usr/local/llgo/lib/CMSIS/CMSIS/Include",
},
{
"no variables here",
"no variables here",
},
{
"",
"",
},
}
for _, test := range tests {
result := expandEnv(test.template, envs)
if result != test.expected {
t.Errorf("expandEnv(%q) = %q, want %q", test.template, result, test.expected)
}
}
}
func TestExpandEnvSlice(t *testing.T) {
envs := map[string]string{
"root": "/usr/local/llgo",
"port": "/dev/ttyUSB0",
}
input := []string{
"-I{root}/include",
"-DPORT={port}",
"static-flag",
}
expected := []string{
"-I/usr/local/llgo/include",
"-DPORT=/dev/ttyUSB0",
"static-flag",
}
result := expandEnvSlice(input, envs)
if len(result) != len(expected) {
t.Fatalf("expandEnvSlice length mismatch: got %d, want %d", len(result), len(expected))
}
for i, exp := range expected {
if result[i] != exp {
t.Errorf("expandEnvSlice[%d] = %q, want %q", i, result[i], exp)
}
}
}
func TestExpandEnvWithDefault(t *testing.T) {
envs := map[string]string{
"port": "/dev/ttyUSB0",
"hex": "firmware.hex",
"bin": "firmware.bin",
"img": "image.img",
}
tests := []struct {
template string
defaultValue string
expected string
}{
{
"simavr {}",
"", // No default - should use hex (priority)
"simavr firmware.hex",
},
{
"simavr {}",
"custom.elf", // Explicit default
"simavr custom.elf",
},
{
"qemu -kernel {}",
"vmlinux", // Custom kernel
"qemu -kernel vmlinux",
},
{
"no braces here",
"ignored",
"no braces here",
},
}
for i, test := range tests {
var result string
if test.defaultValue == "" {
result = expandEnvWithDefault(test.template, envs)
} else {
result = expandEnvWithDefault(test.template, envs, test.defaultValue)
}
if result != test.expected {
t.Errorf("Test %d: expandEnvWithDefault(%q, envs, %q) = %q, want %q",
i, test.template, test.defaultValue, result, test.expected)
}
}
}