feat(crosscompile): add ESP Clang multi-platform support
- Add ESP Clang download and extraction for cross-compilation - Support multiple platforms: darwin/amd64, darwin/arm64, linux/amd64, linux/arm64, windows/amd64 - Integrate ESP Clang with target-based configuration system - Add ClangRoot and ClangBinPath fields to Export struct - Support .tar.xz extraction for ESP Clang packages - Prioritize LLGoROOT clang installation over cached downloads - Update build system to use custom clang for embedded platforms 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -151,7 +151,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
|||||||
conf.Goarch = runtime.GOARCH
|
conf.Goarch = runtime.GOARCH
|
||||||
}
|
}
|
||||||
// Handle crosscompile configuration first to set correct GOOS/GOARCH
|
// Handle crosscompile configuration first to set correct GOOS/GOARCH
|
||||||
export, err := crosscompile.UseWithTarget(conf.Goos, conf.Goarch, IsWasiThreadsEnabled(), conf.Target)
|
export, err := crosscompile.Use(conf.Goos, conf.Goarch, IsWasiThreadsEnabled(), conf.Target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to setup crosscompile: %w", err)
|
return nil, fmt.Errorf("failed to setup crosscompile: %w", err)
|
||||||
}
|
}
|
||||||
@@ -385,7 +385,7 @@ func (c *context) compiler() *clang.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) linker() *clang.Cmd {
|
func (c *context) linker() *clang.Cmd {
|
||||||
cmd := c.env.Clang()
|
cmd := c.compiler()
|
||||||
if c.crossCompile.Linker != "" {
|
if c.crossCompile.Linker != "" {
|
||||||
cmd = clang.New(c.crossCompile.Linker)
|
cmd = clang.New(c.crossCompile.Linker)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,59 @@ type Export struct {
|
|||||||
EXTRAFLAGS []string
|
EXTRAFLAGS []string
|
||||||
|
|
||||||
// Additional fields from target configuration
|
// Additional fields from target configuration
|
||||||
LLVMTarget string
|
LLVMTarget string
|
||||||
CPU string
|
CPU string
|
||||||
Features string
|
Features string
|
||||||
BuildTags []string
|
BuildTags []string
|
||||||
GOOS string
|
GOOS string
|
||||||
GOARCH string
|
GOARCH string
|
||||||
Linker string // Linker to use (e.g., "ld.lld", "avr-ld")
|
Linker string // Linker to use (e.g., "ld.lld", "avr-ld")
|
||||||
|
ClangRoot string // Root directory of custom clang installation
|
||||||
|
ClangBinPath string // Path to clang binary directory
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
|
|
||||||
|
const (
|
||||||
|
espClangBaseUrl = "https://github.com/espressif/llvm-project/releases/download/esp-19.1.2_20250312"
|
||||||
|
espClangVersion = "esp-19.1.2_20250312"
|
||||||
|
)
|
||||||
|
|
||||||
func cacheDir() string {
|
func cacheDir() string {
|
||||||
return filepath.Join(env.LLGoCacheDir(), "crosscompile")
|
return filepath.Join(env.LLGoCacheDir(), "crosscompile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
// getESPClangPlatform returns the platform suffix for ESP Clang downloads
|
||||||
|
func getESPClangPlatform(goos, goarch string) string {
|
||||||
|
switch goos {
|
||||||
|
case "darwin":
|
||||||
|
switch goarch {
|
||||||
|
case "amd64":
|
||||||
|
return "x86_64-apple-darwin"
|
||||||
|
case "arm64":
|
||||||
|
return "aarch64-apple-darwin"
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
switch goarch {
|
||||||
|
case "amd64":
|
||||||
|
return "x86_64-linux-gnu"
|
||||||
|
case "arm64":
|
||||||
|
return "aarch64-linux-gnu"
|
||||||
|
case "arm":
|
||||||
|
return "arm-linux-gnueabihf"
|
||||||
|
}
|
||||||
|
case "windows":
|
||||||
|
switch goarch {
|
||||||
|
case "amd64":
|
||||||
|
return "x86_64-w64-mingw32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
||||||
targetTriple := llvm.GetTargetTriple(goos, goarch)
|
targetTriple := llvm.GetTargetTriple(goos, goarch)
|
||||||
|
llgoRoot := env.LLGoROOT()
|
||||||
|
|
||||||
if runtime.GOOS == goos && runtime.GOARCH == goarch {
|
if runtime.GOOS == goos && runtime.GOARCH == goarch {
|
||||||
// not cross compile
|
// not cross compile
|
||||||
@@ -86,7 +122,6 @@ func Use(goos, goarch string, wasiThreads bool) (export Export, err error) {
|
|||||||
switch goos {
|
switch goos {
|
||||||
case "wasip1":
|
case "wasip1":
|
||||||
// Set wasiSdkRoot path
|
// Set wasiSdkRoot path
|
||||||
llgoRoot := env.LLGoROOT()
|
|
||||||
wasiSdkRoot := filepath.Join(llgoRoot, "crosscompile", "wasi-libc")
|
wasiSdkRoot := filepath.Join(llgoRoot, "crosscompile", "wasi-libc")
|
||||||
|
|
||||||
// If not exists in LLGoROOT, download and use cached wasiSdkRoot
|
// If not exists in LLGoROOT, download and use cached wasiSdkRoot
|
||||||
@@ -222,6 +257,38 @@ func useTarget(targetName string) (export Export, err error) {
|
|||||||
return export, fmt.Errorf("failed to resolve target %s: %w", targetName, err)
|
return export, fmt.Errorf("failed to resolve target %s: %w", targetName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for ESP Clang support for target-based builds
|
||||||
|
llgoRoot := env.LLGoROOT()
|
||||||
|
var clangRoot string
|
||||||
|
|
||||||
|
// First check if clang exists in LLGoROOT
|
||||||
|
espClangRoot := filepath.Join(llgoRoot, "crosscompile", "clang")
|
||||||
|
if _, err = os.Stat(espClangRoot); err == nil {
|
||||||
|
clangRoot = espClangRoot
|
||||||
|
} else {
|
||||||
|
// Try to download ESP Clang if platform is supported
|
||||||
|
platformSuffix := getESPClangPlatform(runtime.GOOS, runtime.GOARCH)
|
||||||
|
if platformSuffix != "" {
|
||||||
|
cacheClangDir := filepath.Join(env.LLGoCacheDir(), "crosscompile", "clang")
|
||||||
|
if _, err = os.Stat(cacheClangDir); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = downloadAndExtractESPClang(platformSuffix, cacheClangDir); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clangRoot = cacheClangDir
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("ESP Clang not found in LLGoROOT and platform %s/%s is not supported for download", runtime.GOOS, runtime.GOARCH)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ClangRoot and CC if clang is available
|
||||||
|
export.ClangRoot = clangRoot
|
||||||
|
export.CC = filepath.Join(clangRoot, "bin", "clang++")
|
||||||
|
|
||||||
// Convert target config to Export - only export necessary fields
|
// Convert target config to Export - only export necessary fields
|
||||||
export.BuildTags = config.BuildTags
|
export.BuildTags = config.BuildTags
|
||||||
export.GOOS = config.GOOS
|
export.GOOS = config.GOOS
|
||||||
@@ -274,13 +341,13 @@ func useTarget(targetName string) (export Export, err error) {
|
|||||||
return export, nil
|
return export, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseWithTarget extends the original Use function to support target-based configuration
|
// Use extends the original Use function to support target-based configuration
|
||||||
// If targetName is provided, it takes precedence over goos/goarch
|
// If targetName is provided, it takes precedence over goos/goarch
|
||||||
func UseWithTarget(goos, goarch string, wasiThreads bool, targetName string) (export Export, err error) {
|
func Use(goos, goarch string, wasiThreads bool, targetName string) (export Export, err error) {
|
||||||
if targetName != "" {
|
if targetName != "" {
|
||||||
return useTarget(targetName)
|
return useTarget(targetName)
|
||||||
}
|
}
|
||||||
return Use(goos, goarch, wasiThreads)
|
return use(goos, goarch, wasiThreads)
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterCompatibleLDFlags filters out linker flags that are incompatible with clang/lld
|
// filterCompatibleLDFlags filters out linker flags that are incompatible with clang/lld
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func TestUseCrossCompileSDK(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
export, err := Use(tc.goos, tc.goarch, false)
|
export, err := use(tc.goos, tc.goarch, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
@@ -255,7 +255,7 @@ func TestUseTarget(t *testing.T) {
|
|||||||
|
|
||||||
func TestUseWithTarget(t *testing.T) {
|
func TestUseWithTarget(t *testing.T) {
|
||||||
// Test target-based configuration takes precedence
|
// Test target-based configuration takes precedence
|
||||||
export, err := UseWithTarget("linux", "amd64", false, "wasi")
|
export, err := Use("linux", "amd64", false, "wasi")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -267,7 +267,7 @@ func TestUseWithTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test fallback to goos/goarch when no target specified
|
// Test fallback to goos/goarch when no target specified
|
||||||
export, err = UseWithTarget(runtime.GOOS, runtime.GOARCH, false, "")
|
export, err = Use(runtime.GOOS, runtime.GOARCH, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,8 @@ func downloadAndExtract(url, dir string) (wasiSdkRoot string, err error) {
|
|||||||
|
|
||||||
if strings.HasSuffix(filename, ".tar.gz") || strings.HasSuffix(filename, ".tgz") {
|
if strings.HasSuffix(filename, ".tar.gz") || strings.HasSuffix(filename, ".tgz") {
|
||||||
err = extractTarGz(localFile, tempDir)
|
err = extractTarGz(localFile, tempDir)
|
||||||
|
} else if strings.HasSuffix(filename, ".tar.xz") {
|
||||||
|
err = extractTarXz(localFile, tempDir)
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf("unsupported archive format: %s", filename)
|
return "", fmt.Errorf("unsupported archive format: %s", filename)
|
||||||
}
|
}
|
||||||
@@ -108,3 +111,61 @@ func extractTarGz(tarGzFile, dest string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractTarXz(tarXzFile, dest string) error {
|
||||||
|
// Use external tar command to extract .tar.xz files
|
||||||
|
cmd := exec.Command("tar", "-xf", tarXzFile, "-C", dest)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadAndExtractESPClang downloads and extracts ESP Clang binaries and libraries
|
||||||
|
func downloadAndExtractESPClang(platformSuffix, dir string) error {
|
||||||
|
if _, err := os.Stat(dir); err == nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create download temp directory
|
||||||
|
downloadDir := dir + "-download"
|
||||||
|
os.RemoveAll(downloadDir)
|
||||||
|
if err := os.MkdirAll(downloadDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create download directory: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(downloadDir)
|
||||||
|
|
||||||
|
// Download clang binary package
|
||||||
|
clangUrl := fmt.Sprintf("%s/clang-%s-%s.tar.xz", espClangBaseUrl, espClangVersion, platformSuffix)
|
||||||
|
clangFile := filepath.Join(downloadDir, fmt.Sprintf("clang-%s-%s.tar.xz", espClangVersion, platformSuffix))
|
||||||
|
if err := downloadFile(clangUrl, clangFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to download clang: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download libs package
|
||||||
|
libsUrl := fmt.Sprintf("%s/libs-clang-%s-%s.tar.xz", espClangBaseUrl, espClangVersion, platformSuffix)
|
||||||
|
libsFile := filepath.Join(downloadDir, fmt.Sprintf("libs-clang-%s-%s.tar.xz", espClangVersion, platformSuffix))
|
||||||
|
if err := downloadFile(libsUrl, libsFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to download libs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create extract temp directory
|
||||||
|
extractDir := dir + "-extract"
|
||||||
|
os.RemoveAll(extractDir)
|
||||||
|
if err := os.MkdirAll(extractDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create extract directory: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(extractDir)
|
||||||
|
|
||||||
|
// Extract both packages to extract directory
|
||||||
|
if err := extractTarXz(clangFile, extractDir); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract clang: %w", err)
|
||||||
|
}
|
||||||
|
if err := extractTarXz(libsFile, extractDir); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract libs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename esp-clang directory to final destination
|
||||||
|
espClangDir := filepath.Join(extractDir, "esp-clang")
|
||||||
|
if err := os.Rename(espClangDir, dir); err != nil {
|
||||||
|
return fmt.Errorf("failed to rename esp-clang directory: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user