feat(crosscompile): extend Export struct and add target-based configuration
- Add LLVMTarget, CPU, Features, BuildTags fields to Export struct - Implement UseTarget() function for target name-based configuration loading - Add UseWithTarget() function combining target and goos/goarch fallback - Include comprehensive unit tests for target integration - Support 206+ embedded platform configurations with inheritance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,15 @@ package crosscompile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/goplus/llgo/internal/env"
|
"github.com/goplus/llgo/internal/env"
|
||||||
|
"github.com/goplus/llgo/internal/targets"
|
||||||
"github.com/goplus/llgo/internal/xtool/llvm"
|
"github.com/goplus/llgo/internal/xtool/llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,6 +20,15 @@ type Export struct {
|
|||||||
CFLAGS []string
|
CFLAGS []string
|
||||||
LDFLAGS []string
|
LDFLAGS []string
|
||||||
EXTRAFLAGS []string
|
EXTRAFLAGS []string
|
||||||
|
|
||||||
|
// Additional fields from target configuration
|
||||||
|
LLVMTarget string
|
||||||
|
CPU string
|
||||||
|
Features string
|
||||||
|
BuildTags []string
|
||||||
|
GOOS string
|
||||||
|
GOARCH string
|
||||||
|
Linker string // Linker to use (e.g., "ld.lld", "avr-ld")
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasiSdkUrl = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz"
|
const wasiSdkUrl = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz"
|
||||||
@@ -109,8 +121,8 @@ func Use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
|||||||
"-I" + includeDir,
|
"-I" + includeDir,
|
||||||
}
|
}
|
||||||
// Add WebAssembly linker flags
|
// Add WebAssembly linker flags
|
||||||
export.LDFLAGS = []string{
|
export.LDFLAGS = append(export.LDFLAGS, export.CCFLAGS...)
|
||||||
"-target", targetTriple,
|
export.LDFLAGS = append(export.LDFLAGS, []string{
|
||||||
"-Wno-override-module",
|
"-Wno-override-module",
|
||||||
"-Wl,--error-limit=0",
|
"-Wl,--error-limit=0",
|
||||||
"-L" + libDir,
|
"-L" + libDir,
|
||||||
@@ -134,18 +146,18 @@ func Use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
|||||||
"-lwasi-emulated-signal",
|
"-lwasi-emulated-signal",
|
||||||
"-fwasm-exceptions",
|
"-fwasm-exceptions",
|
||||||
"-mllvm", "-wasm-enable-sjlj",
|
"-mllvm", "-wasm-enable-sjlj",
|
||||||
}
|
}...)
|
||||||
// Add thread support if enabled
|
// Add thread support if enabled
|
||||||
if wasiThreads {
|
if wasiThreads {
|
||||||
export.CCFLAGS = append(
|
export.CCFLAGS = append(
|
||||||
export.CCFLAGS,
|
export.CCFLAGS,
|
||||||
"-pthread",
|
"-pthread",
|
||||||
)
|
)
|
||||||
|
export.LDFLAGS = append(export.LDFLAGS, export.CCFLAGS...)
|
||||||
export.LDFLAGS = append(
|
export.LDFLAGS = append(
|
||||||
export.LDFLAGS,
|
export.LDFLAGS,
|
||||||
"-lwasi-emulated-pthread",
|
"-lwasi-emulated-pthread",
|
||||||
"-lpthread",
|
"-lpthread",
|
||||||
"-pthread", // global is immutable if -pthread is not specified
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,3 +206,114 @@ func Use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// useTarget loads configuration from a target name (e.g., "rp2040", "wasi")
|
||||||
|
func useTarget(targetName string) (export Export, err error) {
|
||||||
|
resolver := targets.NewDefaultResolver()
|
||||||
|
|
||||||
|
config, err := resolver.Resolve(targetName)
|
||||||
|
if err != nil {
|
||||||
|
return export, fmt.Errorf("failed to resolve target %s: %w", targetName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert target config to Export - only export necessary fields
|
||||||
|
export.BuildTags = config.BuildTags
|
||||||
|
export.GOOS = config.GOOS
|
||||||
|
export.GOARCH = config.GOARCH
|
||||||
|
|
||||||
|
// Convert LLVMTarget, CPU, Features to CCFLAGS/LDFLAGS
|
||||||
|
var ccflags []string
|
||||||
|
var ldflags []string
|
||||||
|
|
||||||
|
target := config.LLVMTarget
|
||||||
|
if target == "" {
|
||||||
|
target = llvm.GetTargetTriple(config.GOOS, config.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
ccflags = append(ccflags, "-Wno-override-module", "--target="+config.LLVMTarget)
|
||||||
|
|
||||||
|
// Inspired by tinygo
|
||||||
|
cpu := config.CPU
|
||||||
|
if cpu != "" {
|
||||||
|
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
|
||||||
|
ccflags = append(ccflags, "-march="+cpu)
|
||||||
|
} else if strings.HasPrefix(target, "avr") {
|
||||||
|
ccflags = append(ccflags, "-mmcu="+cpu)
|
||||||
|
} else {
|
||||||
|
ccflags = append(ccflags, "-mcpu="+cpu)
|
||||||
|
}
|
||||||
|
// Only add -mllvm flags for non-WebAssembly linkers
|
||||||
|
if config.Linker == "ld.lld" {
|
||||||
|
ldflags = append(ldflags, "-mllvm", "-mcpu="+cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Features
|
||||||
|
if config.Features != "" {
|
||||||
|
// Only add -mllvm flags for non-WebAssembly linkers
|
||||||
|
if config.Linker == "ld.lld" {
|
||||||
|
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Linker - keep it for external usage
|
||||||
|
export.Linker = config.Linker
|
||||||
|
|
||||||
|
// Combine with config flags
|
||||||
|
export.CFLAGS = config.CFlags
|
||||||
|
export.CCFLAGS = ccflags
|
||||||
|
export.LDFLAGS = append(ldflags, filterCompatibleLDFlags(config.LDFlags)...)
|
||||||
|
export.EXTRAFLAGS = []string{}
|
||||||
|
|
||||||
|
return export, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseWithTarget extends the original Use function to support target-based configuration
|
||||||
|
// If targetName is provided, it takes precedence over goos/goarch
|
||||||
|
func UseWithTarget(goos, goarch string, wasiThreads bool, targetName string) (export Export, err error) {
|
||||||
|
if targetName != "" {
|
||||||
|
return useTarget(targetName)
|
||||||
|
}
|
||||||
|
return Use(goos, goarch, wasiThreads)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterCompatibleLDFlags filters out linker flags that are incompatible with clang/lld
|
||||||
|
func filterCompatibleLDFlags(ldflags []string) []string {
|
||||||
|
if len(ldflags) == 0 {
|
||||||
|
return ldflags
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered []string
|
||||||
|
|
||||||
|
incompatiblePrefixes := []string{
|
||||||
|
"--defsym=", // Use -Wl,--defsym= instead
|
||||||
|
"-T", // Linker script, needs special handling
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for i < len(ldflags) {
|
||||||
|
flag := ldflags[i]
|
||||||
|
|
||||||
|
// Check incompatible prefixes
|
||||||
|
skip := false
|
||||||
|
for _, prefix := range incompatiblePrefixes {
|
||||||
|
if strings.HasPrefix(flag, prefix) {
|
||||||
|
skip = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
// Skip -T and its argument if separate
|
||||||
|
if flag == "-T" && i+1 < len(ldflags) {
|
||||||
|
i += 2 // Skip both -T and the script path
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, flag)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ package crosscompile
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,3 +155,181 @@ func TestUseCrossCompileSDK(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUseTarget(t *testing.T) {
|
||||||
|
// Test cases for target-based configuration
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
targetName string
|
||||||
|
expectError bool
|
||||||
|
expectLLVM string
|
||||||
|
expectCPU string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "WASI Target",
|
||||||
|
targetName: "wasi",
|
||||||
|
expectError: false,
|
||||||
|
expectLLVM: "",
|
||||||
|
expectCPU: "generic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RP2040 Target",
|
||||||
|
targetName: "rp2040",
|
||||||
|
expectError: false,
|
||||||
|
expectLLVM: "thumbv6m-unknown-unknown-eabi",
|
||||||
|
expectCPU: "cortex-m0plus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cortex-M Target",
|
||||||
|
targetName: "cortex-m",
|
||||||
|
expectError: false,
|
||||||
|
expectLLVM: "",
|
||||||
|
expectCPU: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Arduino Target (with filtered flags)",
|
||||||
|
targetName: "arduino",
|
||||||
|
expectError: false,
|
||||||
|
expectLLVM: "avr",
|
||||||
|
expectCPU: "atmega328p",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nonexistent Target",
|
||||||
|
targetName: "nonexistent-target",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
export, err := useTarget(tc.targetName)
|
||||||
|
|
||||||
|
if tc.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error for target %s, but got none", tc.targetName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error for target %s: %v", tc.targetName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if LLVM target is in CCFLAGS
|
||||||
|
if tc.expectLLVM != "" {
|
||||||
|
found := false
|
||||||
|
expectedFlag := "--target=" + tc.expectLLVM
|
||||||
|
for _, flag := range export.CCFLAGS {
|
||||||
|
if flag == expectedFlag {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected LLVM target %s in CCFLAGS, got %v", expectedFlag, export.CCFLAGS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if CPU is in CCFLAGS
|
||||||
|
if tc.expectCPU != "" {
|
||||||
|
found := false
|
||||||
|
expectedFlags := []string{"-mmcu=" + tc.expectCPU, "-mcpu=" + tc.expectCPU}
|
||||||
|
for _, flag := range export.CCFLAGS {
|
||||||
|
for _, expectedFlag := range expectedFlags {
|
||||||
|
if flag == expectedFlag {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected CPU %s in CCFLAGS, got %v", tc.expectCPU, export.CCFLAGS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Target %s: BuildTags=%v, CFlags=%v, CCFlags=%v, LDFlags=%v",
|
||||||
|
tc.targetName, export.BuildTags, export.CFLAGS, export.CCFLAGS, export.LDFLAGS)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseWithTarget(t *testing.T) {
|
||||||
|
// Test target-based configuration takes precedence
|
||||||
|
export, err := UseWithTarget("linux", "amd64", false, "wasi")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if LLVM target is in CCFLAGS
|
||||||
|
found := slices.Contains(export.CCFLAGS, "-mcpu=generic")
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected CPU generic in CCFLAGS, got %v", export.CCFLAGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test fallback to goos/goarch when no target specified
|
||||||
|
export, err = UseWithTarget(runtime.GOOS, runtime.GOARCH, false, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should use native configuration (only check for macOS since that's where tests run)
|
||||||
|
if runtime.GOOS == "darwin" && len(export.LDFLAGS) == 0 {
|
||||||
|
t.Error("Expected LDFLAGS to be set for native build")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterCompatibleLDFlags(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty flags",
|
||||||
|
input: []string{},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compatible flags only",
|
||||||
|
input: []string{"-lm", "-lpthread"},
|
||||||
|
expected: []string{"-lm", "-lpthread"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Incompatible flags filtered",
|
||||||
|
input: []string{"--gc-sections", "-lm", "--emit-relocs", "-lpthread"},
|
||||||
|
expected: []string{"--gc-sections", "-lm", "--emit-relocs", "-lpthread"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Defsym flags filtered",
|
||||||
|
input: []string{"--defsym=_stack_size=512", "-lm", "--defsym=_bootloader_size=512"},
|
||||||
|
expected: []string{"-lm"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Linker script flags filtered",
|
||||||
|
input: []string{"-T", "script.ld", "-lm"},
|
||||||
|
expected: []string{"-lm"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mixed compatible and incompatible",
|
||||||
|
input: []string{"-lm", "--gc-sections", "--defsym=test=1", "-lpthread", "--no-demangle"},
|
||||||
|
expected: []string{"-lm", "--gc-sections", "-lpthread", "--no-demangle"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := filterCompatibleLDFlags(tc.input)
|
||||||
|
|
||||||
|
if len(result) != len(tc.expected) {
|
||||||
|
t.Errorf("Expected %d flags, got %d: %v", len(tc.expected), len(result), result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, expected := range tc.expected {
|
||||||
|
if result[i] != expected {
|
||||||
|
t.Errorf("Expected flag[%d] = %s, got %s", i, expected, result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user