Merge commit '6588f36123eababf6e24564b49e5af374285d2b5' into optional-esp-clang

# Conflicts:
#	internal/crosscompile/crosscompile.go
#	internal/crosscompile/crosscompile_test.go
This commit is contained in:
Li Jie
2025-09-03 09:32:38 +08:00
59 changed files with 5523 additions and 2542 deletions

View File

@@ -0,0 +1,103 @@
package compile
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"github.com/goplus/llgo/internal/clang"
)
type CompileOptions struct {
CC string // Compiler to use
Linker string
CCFLAGS []string
CFLAGS []string
LDFLAGS []string
}
type CompileGroup struct {
OutputFileName string
Files []string // List of source files to compile
CFlags []string // C compiler flags
CCFlags []string
LDFlags []string // Linker flags
}
func (g CompileGroup) IsCompiled(outputDir string) bool {
archive := filepath.Join(outputDir, filepath.Base(g.OutputFileName))
_, err := os.Stat(archive)
return err == nil
}
func (g CompileGroup) Compile(
outputDir string, options CompileOptions,
) (err error) {
if g.IsCompiled(outputDir) {
return
}
tmpCompileDir, err := os.MkdirTemp("", "compile-group*")
if err != nil {
return
}
defer os.RemoveAll(tmpCompileDir)
compileLDFlags := append(slices.Clone(options.LDFLAGS), g.LDFlags...)
compileCCFlags := append(slices.Clone(options.CCFLAGS), g.CCFlags...)
compileCFFlags := append(slices.Clone(options.CFLAGS), g.CFlags...)
cfg := clang.NewConfig(options.CC, compileCCFlags, compileCFFlags, compileLDFlags, options.Linker)
var objFiles []string
compiler := clang.NewCompiler(cfg)
compiler.Verbose = true
archive := filepath.Join(outputDir, filepath.Base(g.OutputFileName))
fmt.Fprintf(os.Stderr, "Start to compile group %s to %s...\n", g.OutputFileName, archive)
for _, file := range g.Files {
var tempObjFile *os.File
tempObjFile, err = os.CreateTemp(tmpCompileDir, fmt.Sprintf("%s*.o", strings.ReplaceAll(file, string(os.PathSeparator), "-")))
if err != nil {
return
}
lang := "c"
if filepath.Ext(file) == ".S" {
lang = "assembler-with-cpp"
}
err = compiler.Compile("-o", tempObjFile.Name(), "-x", lang, "-c", file)
if err != nil {
return
}
objFiles = append(objFiles, tempObjFile.Name())
}
args := []string{"rcs", archive}
args = append(args, objFiles...)
ccDir := filepath.Dir(options.CC)
llvmAr := filepath.Join(ccDir, "llvm-ar")
cmd := exec.Command(llvmAr, args...)
// TODO(MeteorsLiu): support verbose
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err = cmd.Run()
return
}
// CompileConfig represents compilation configuration
type CompileConfig struct {
Url string
Name string // compile name (e.g., "picolibc", "musl", "glibc")
Groups []CompileGroup
ArchiveSrcDir string
LibcCFlags []string
}

View File

@@ -0,0 +1,209 @@
//go:build !llgo
package compile
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/goplus/llgo/xtool/nm"
)
func TestIsCompile(t *testing.T) {
t.Run("IsCompile Not Exists", func(t *testing.T) {
cfg := CompileConfig{
Groups: []CompileGroup{
{
OutputFileName: "fakefile1.a",
},
},
}
if cfg.Groups[0].IsCompiled(".") {
t.Errorf("unexpected result: should false")
}
})
t.Run("IsCompile Exists", func(t *testing.T) {
tmpFile, err := os.CreateTemp("", "test*.a")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmpFile.Name())
cfg := CompileConfig{
Groups: []CompileGroup{
{
OutputFileName: tmpFile.Name(),
},
},
}
if !cfg.Groups[0].IsCompiled(filepath.Dir(tmpFile.Name())) {
t.Errorf("unexpected result: should true")
}
})
}
func TestCompile(t *testing.T) {
t.Run("Skip compile", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.a")
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: tmpFile.Name(),
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err != nil {
t.Errorf("unexpected result: should nil: %v", err)
}
})
t.Run("TmpDir Fail", func(t *testing.T) {
tmpDir := filepath.Join(t.TempDir(), "test-compile")
os.RemoveAll(tmpDir)
err := os.Mkdir(tmpDir, 0)
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
os.Setenv("TMPDIR", tmpDir)
defer os.Unsetenv("TMPDIR")
group := CompileGroup{
OutputFileName: "nop.a",
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err == nil {
t.Errorf("unexpected result: should not nil")
}
})
t.Run("Compile", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.c")
if err != nil {
t.Error(err)
return
}
_, err = tmpFile.Write([]byte(`#include <math.h>
void Foo() {
double x = 2.0;
double y = sqrt(x);
(void) y ;
}
`))
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: "nop.a",
Files: []string{tmpFile.Name()},
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
CCFLAGS: []string{"-nostdinc"},
})
if err == nil {
t.Errorf("unexpected result: should not nil")
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err != nil {
t.Errorf("unexpected result: should not nil")
}
if _, err := os.Stat(filepath.Join(tmpDir, "nop.a")); os.IsNotExist(err) {
t.Error("unexpected result: compiled nop.a not found")
return
}
items, err := nm.New("").List(filepath.Join(tmpDir, "nop.a"))
if err != nil {
t.Error(err)
return
}
want := "Foo"
found := false
loop:
for _, item := range items {
for _, sym := range item.Symbols {
if strings.Contains(sym.Name, want) {
found = true
break loop
}
}
}
if !found {
t.Errorf("cannot find symbol Foo")
}
})
t.Run("Compile Asm", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.S")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write([]byte(`
.text
.globl _test
`))
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: "nop.a",
Files: []string{tmpFile.Name()},
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
CCFLAGS: []string{"--target=x86_64-linux-gnu"},
})
if err != nil {
t.Errorf("unexpected result: should nil %v", err)
}
})
}

View File

@@ -0,0 +1,630 @@
package libc
import (
"path/filepath"
"testing"
)
func TestGetPicolibcConfig(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
config := GetPicolibcConfig(baseDir, target)
if config.Name != "picolibc" {
t.Errorf("Expected Name 'picolibc', got '%s'", config.Name)
}
if config.ArchiveSrcDir != "picolibc-main" {
t.Errorf("Expected ArchiveSrcDir 'picolibc-main', got '%s'", config.ArchiveSrcDir)
}
// Test LibcCFlags
if len(config.LibcCFlags) != 2 {
t.Errorf("Expected 2 LibcCFlags, got %d", len(config.LibcCFlags))
} else {
expected := "-I" + baseDir
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
expected = "-isystem" + filepath.Join(baseDir, "newlib", "libc", "include")
if config.LibcCFlags[1] != expected {
t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.LibcCFlags[1])
}
}
// Test Groups configuration
if len(config.Groups) != 1 {
t.Errorf("Expected 1 group, got %d", len(config.Groups))
} else {
group := config.Groups[0]
// Test output file name
expectedOutput := "libc-" + target + ".a"
if group.OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, group.OutputFileName)
}
// Test files list
if len(group.Files) == 0 {
t.Error("Expected non-empty files list")
} else {
// Check a few sample files
sampleFiles := []string{
filepath.Join(baseDir, "newlib", "libc", "string", "bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlen.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-malloc.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "printf.c"),
}
for _, sample := range sampleFiles {
found := false
for _, file := range group.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group files", sample)
}
}
}
// Test CFlags
expectedCFlags := []string{
"-D_COMPILING_NEWLIB",
"-D_HAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER",
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1",
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-nostdlib",
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
"-I" + filepath.Join(baseDir, "newlib", "libm", "common"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "locale"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "tinystdio"),
}
if len(group.CFlags) != len(expectedCFlags) {
t.Errorf("Expected %d CFlags, got %d", len(expectedCFlags), len(group.CFlags))
} else {
for i, expected := range expectedCFlags {
if group.CFlags[i] != expected {
t.Errorf("CFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, group.CFlags[i])
}
}
}
// Test LDFlags and CCFlags
if len(group.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags")
}
if len(group.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags")
}
}
}
func TestGetPicolibcConfig_EdgeCases(t *testing.T) {
t.Run("EmptyBaseDir", func(t *testing.T) {
config := GetPicolibcConfig("", "test-target")
// Check that paths are constructed correctly even with empty baseDir
expected := "-I"
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
expected = "-isystem" + filepath.Join("", "newlib", "libc", "include")
if config.LibcCFlags[1] != expected {
t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.LibcCFlags[1])
}
})
t.Run("EmptyTarget", func(t *testing.T) {
config := GetPicolibcConfig("/test/base", "")
// Check output file name formatting
expectedOutput := "libc-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
}
func TestGetNewlibESP32ConfigRISCV(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
config := getNewlibESP32ConfigRISCV(baseDir, target)
// Test basic configuration
if config.Url != _newlibUrl {
t.Errorf("Expected URL '%s', got '%s'", _newlibUrl, config.Url)
}
if config.Name != "newlib-esp32" {
t.Errorf("Expected Name 'newlib-esp32', got '%s'", config.Name)
}
if config.ArchiveSrcDir != _archiveInternalSrcDir {
t.Errorf("Expected ArchiveSrcDir '%s', got '%s'", _archiveInternalSrcDir, config.ArchiveSrcDir)
}
// Test LibcCFlags
libcDir := filepath.Join(baseDir, "newlib", "libc")
expectedCFlags := []string{
"-isystem" + filepath.Join(libcDir, "include"),
"-I" + filepath.Join(baseDir, "newlib"),
"-I" + libcDir,
}
if len(config.LibcCFlags) != len(expectedCFlags) {
t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.LibcCFlags))
} else {
for i, expected := range expectedCFlags {
if config.LibcCFlags[i] != expected {
t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.LibcCFlags[i])
}
}
}
// Test Groups configuration
if len(config.Groups) != 3 {
t.Errorf("Expected 3 groups, got %d", len(config.Groups))
} else {
// Group 0: libcrt0
group0 := config.Groups[0]
expectedOutput0 := "libcrt0-" + target + ".a"
if group0.OutputFileName != expectedOutput0 {
t.Errorf("Group0 OutputFileName expected '%s', got '%s'", expectedOutput0, group0.OutputFileName)
}
// Check sample files in group0
sampleFiles0 := []string{
filepath.Join(baseDir, "libgloss", "riscv", "esp", "esp_board.c"),
filepath.Join(baseDir, "libgloss", "riscv", "esp", "crt1-board.S"),
}
for _, sample := range sampleFiles0 {
found := false
for _, file := range group0.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group0 files", sample)
}
}
// Group 1: libgloss
group1 := config.Groups[1]
expectedOutput1 := "libgloss-" + target + ".a"
if group1.OutputFileName != expectedOutput1 {
t.Errorf("Group1 OutputFileName expected '%s', got '%s'", expectedOutput1, group1.OutputFileName)
}
// Check sample files in group1
sampleFiles1 := []string{
filepath.Join(baseDir, "libgloss", "libnosys", "close.c"),
filepath.Join(baseDir, "libgloss", "libnosys", "sbrk.c"),
}
for _, sample := range sampleFiles1 {
found := false
for _, file := range group1.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group1 files", sample)
}
}
// Group 2: libc
group2 := config.Groups[2]
expectedOutput2 := "libc-" + target + ".a"
if group2.OutputFileName != expectedOutput2 {
t.Errorf("Group2 OutputFileName expected '%s', got '%s'", expectedOutput2, group2.OutputFileName)
}
// Check sample files in group2
sampleFiles2 := []string{
filepath.Join(libcDir, "string", "memcpy.c"),
filepath.Join(libcDir, "stdlib", "malloc.c"),
}
for _, sample := range sampleFiles2 {
found := false
for _, file := range group2.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group2 files", sample)
}
}
// Test CFlags for group2
expectedCFlagsGroup2 := []string{
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-DHAVE_NANOSLEEP",
"-D__NO_SYSCALLS__",
// ... (other expected flags)
}
for _, expectedFlag := range expectedCFlagsGroup2 {
found := false
for _, flag := range group2.CFlags {
if flag == expectedFlag {
found = true
break
}
}
if !found {
t.Errorf("Expected flag '%s' not found in group2 CFlags", expectedFlag)
}
}
// Test LDFlags and CCFlags
if len(group0.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags in group0")
}
if len(group0.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags in group0")
}
}
}
func TestGetNewlibESP32ConfigXtensa(t *testing.T) {
baseDir := "/test/base"
target := "xtensa-esp32-elf"
config := getNewlibESP32ConfigXtensa(baseDir, target)
// Test basic configuration
if config.Url != _newlibUrl {
t.Errorf("Expected URL '%s', got '%s'", _newlibUrl, config.Url)
}
if config.Name != "newlib-esp32" {
t.Errorf("Expected Name 'newlib-esp32', got '%s'", config.Name)
}
if config.ArchiveSrcDir != _archiveInternalSrcDir {
t.Errorf("Expected ArchiveSrcDir '%s', got '%s'", _archiveInternalSrcDir, config.ArchiveSrcDir)
}
// Test LibcCFlags
libcDir := filepath.Join(baseDir, "newlib", "libc")
expectedCFlags := []string{
"-I" + filepath.Join(libcDir, "include"),
"-I" + filepath.Join(baseDir, "newlib"),
"-I" + libcDir,
}
if len(config.LibcCFlags) != len(expectedCFlags) {
t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.LibcCFlags))
} else {
for i, expected := range expectedCFlags {
if config.LibcCFlags[i] != expected {
t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.LibcCFlags[i])
}
}
}
// Test Groups configuration
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups, got %d", len(config.Groups))
} else {
// Group 0: libcrt0
group0 := config.Groups[0]
expectedOutput0 := "libcrt0-" + target + ".a"
if group0.OutputFileName != expectedOutput0 {
t.Errorf("Group0 OutputFileName expected '%s', got '%s'", expectedOutput0, group0.OutputFileName)
}
// Check sample files in group0
sampleFiles0 := []string{
filepath.Join(baseDir, "libgloss", "xtensa", "clibrary_init.c"),
filepath.Join(baseDir, "libgloss", "xtensa", "crt1-boards.S"),
}
for _, sample := range sampleFiles0 {
found := false
for _, file := range group0.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group0 files", sample)
}
}
// Group 1: libgloss + libc
group1 := config.Groups[1]
expectedOutput1 := "libgloss-" + target + ".a"
if group1.OutputFileName != expectedOutput1 {
t.Errorf("Group1 OutputFileName expected '%s', got '%s'", expectedOutput1, group1.OutputFileName)
}
// Check sample files in group1
sampleFiles1 := []string{
filepath.Join(baseDir, "libgloss", "libnosys", "close.c"),
}
for _, sample := range sampleFiles1 {
found := false
for _, file := range group1.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group1 files", sample)
}
}
// Test CFlags for group1
expectedCFlagsGroup1 := []string{
"-D__NO_SYSCALLS__",
"-D_NO_GETUT",
"-DHAVE_CONFIG_H",
"-D_LIBC",
// ... (other expected flags)
}
for _, expectedFlag := range expectedCFlagsGroup1 {
found := false
for _, flag := range group1.CFlags {
if flag == expectedFlag {
found = true
break
}
}
if !found {
t.Errorf("Expected flag '%s' not found in group1 CFlags", expectedFlag)
}
}
// Test LDFlags and CCFlags
if len(group0.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags in group0")
}
if len(group0.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags in group0")
}
}
}
func TestEdgeCases(t *testing.T) {
t.Run("EmptyBaseDir_RISCV", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV("", "test-target")
libcDir := filepath.Join("", "newlib", "libc")
// Check that paths are constructed correctly
expected := "-isystem" + filepath.Join(libcDir, "include")
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
})
t.Run("EmptyTarget_RISCV", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV("/test/base", "")
// Check output file name formatting
expectedOutput := "libcrt0-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
t.Run("EmptyBaseDir_Xtensa", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa("", "test-target")
libcDir := filepath.Join("", "newlib", "libc")
// Check that paths are constructed correctly
expected := "-I" + filepath.Join(libcDir, "include")
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
})
t.Run("EmptyTarget_Xtensa", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa("/test/base", "")
// Check output file name formatting
expectedOutput := "libcrt0-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
}
func TestGroupConfiguration(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
t.Run("RISCV_GroupCount", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
if len(config.Groups) != 3 {
t.Errorf("Expected 3 groups for RISCV, got %d", len(config.Groups))
}
})
t.Run("Xtensa_GroupCount", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups for Xtensa, got %d", len(config.Groups))
}
})
t.Run("RISCV_GroupNames", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
expectedNames := []string{
"libcrt0-" + target + ".a",
"libgloss-" + target + ".a",
"libc-" + target + ".a",
}
for i, group := range config.Groups {
if group.OutputFileName != expectedNames[i] {
t.Errorf("Group %d expected name '%s', got '%s'", i, expectedNames[i], group.OutputFileName)
}
}
})
t.Run("Xtensa_GroupNames", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
expectedNames := []string{
"libcrt0-" + target + ".a",
"libgloss-" + target + ".a",
}
for i, group := range config.Groups {
if i >= len(expectedNames) {
return
}
if group.OutputFileName != expectedNames[i] {
t.Errorf("Group %d expected name '%s', got '%s'", i, expectedNames[i], group.OutputFileName)
}
}
})
}
func TestCompilerFlags(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
t.Run("RISCV_CFlags", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
group := config.Groups[2] // libc group
requiredFlags := []string{
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-D__NO_SYSCALLS__",
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
}
for _, flag := range requiredFlags {
found := false
for _, cflag := range group.CFlags {
if cflag == flag {
found = true
break
}
}
if !found {
t.Errorf("Required flag '%s' not found in RISCV CFlags", flag)
}
}
})
t.Run("Xtensa_CFlags", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
group := config.Groups[1] // libgloss+libc group
requiredFlags := []string{
"-D__NO_SYSCALLS__",
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
}
for _, flag := range requiredFlags {
found := false
for _, cflag := range group.CFlags {
if cflag == flag {
found = true
break
}
}
if !found {
t.Errorf("Required flag '%s' not found in Xtensa CFlags", flag)
}
}
})
t.Run("CommonFlags", func(t *testing.T) {
configRISCV := getNewlibESP32ConfigRISCV(baseDir, target)
configXtensa := getNewlibESP32ConfigXtensa(baseDir, target)
// Test LDFlags
expectedLDFlags := []string{
"-nostdlib",
"-ffunction-sections",
"-fdata-sections",
}
for _, group := range configRISCV.Groups {
for _, expected := range expectedLDFlags {
found := false
for _, flag := range group.LDFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required LDFlag '%s' not found in RISCV group", expected)
}
}
}
for _, group := range configXtensa.Groups {
for _, expected := range expectedLDFlags {
found := false
for _, flag := range group.LDFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required LDFlag '%s' not found in Xtensa group", expected)
}
}
}
// Test CCFlags
expectedCCFlags := []string{
"-Oz",
"-fno-builtin",
"-ffreestanding",
}
for _, group := range configRISCV.Groups {
for _, expected := range expectedCCFlags {
found := false
for _, flag := range group.CCFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required CCFlag '%s' not found in RISCV group", expected)
}
}
}
for _, group := range configXtensa.Groups {
for _, expected := range expectedCCFlags {
found := false
for _, flag := range group.CCFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required CCFlag '%s' not found in Xtensa group", expected)
}
}
}
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
package libc
import (
"fmt"
"path/filepath"
"github.com/goplus/llgo/internal/crosscompile/compile"
)
// getPicolibcConfig returns configuration for picolibc
func GetPicolibcConfig(baseDir, target string) *compile.CompileConfig {
return &compile.CompileConfig{
Url: "https://github.com/goplus/picolibc/archive/refs/heads/main.zip",
Name: "picolibc",
LibcCFlags: []string{
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
},
Groups: []compile.CompileGroup{
{
OutputFileName: fmt.Sprintf("libc-%s.a", target),
Files: []string{
filepath.Join(baseDir, "newlib", "libc", "string", "bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "bcopy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "bzero.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "explicit_bzero.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "ffsl.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "ffsll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "fls.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "flsl.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "flsll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "gnu_basename.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "index.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memccpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memmem.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memmove.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "mempcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memset.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "rawmemchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "rindex.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "stpcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "stpncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasestr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strchrnul.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcoll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcoll_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strerror_r.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlwr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strndup.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strnlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strnstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strpbrk.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strsep.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strsignal.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strtok.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strtok_r.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strupr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strverscmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strxfrm.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strxfrm_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "swab.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "timingsafe_bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "timingsafe_memcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strerror.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcpcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcpncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcschr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscoll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscoll_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsdup.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsnlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcspbrk.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcstok.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcswidth.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsxfrm.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsxfrm_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcwidth.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemmove.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmempcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemset.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "xpg_strerror_r.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-calloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-malloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-pvalloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-realloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-valloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "rand.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "srand.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-free.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "printf.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "putchar.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "puts.c"),
},
CFlags: []string{
"-D_COMPILING_NEWLIB",
"-D_HAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER",
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1",
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-nostdlib",
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
"-I" + filepath.Join(baseDir, "newlib", "libm", "common"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "locale"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "tinystdio"),
},
LDFlags: _libcLDFlags,
CCFlags: _libcCCFlags,
},
},
ArchiveSrcDir: "picolibc-main",
}
}

View File

@@ -0,0 +1,288 @@
package rtlib
import (
"fmt"
"path/filepath"
"strings"
"github.com/goplus/llgo/internal/crosscompile/compile"
)
func platformSpecifiedFiles(builtinsDir, target string) []string {
switch {
case strings.Contains(target, "riscv32"):
return []string{
filepath.Join(builtinsDir, "riscv", "mulsi3.S"),
filepath.Join(builtinsDir, "riscv", "fp_mode.c"),
filepath.Join(builtinsDir, "riscv", "save.S"),
filepath.Join(builtinsDir, "riscv", "restore.S"),
filepath.Join(builtinsDir, "atomic.c"),
}
case strings.Contains(target, "riscv64"):
return []string{
filepath.Join(builtinsDir, "addtf3.c"),
filepath.Join(builtinsDir, "comparetf2.c"),
filepath.Join(builtinsDir, "divtc3.c"),
filepath.Join(builtinsDir, "divtf3.c"),
filepath.Join(builtinsDir, "extenddftf2.c"),
filepath.Join(builtinsDir, "extendhftf2.c"),
filepath.Join(builtinsDir, "extendsftf2.c"),
filepath.Join(builtinsDir, "fixtfdi.c"),
filepath.Join(builtinsDir, "fixtfsi.c"),
filepath.Join(builtinsDir, "fixtfti.c"),
filepath.Join(builtinsDir, "fixunstfdi.c"),
filepath.Join(builtinsDir, "fixunstfsi.c"),
filepath.Join(builtinsDir, "fixunstfti.c"),
filepath.Join(builtinsDir, "floatditf.c"),
filepath.Join(builtinsDir, "floatsitf.c"),
filepath.Join(builtinsDir, "floattitf.c"),
filepath.Join(builtinsDir, "floatunditf.c"),
filepath.Join(builtinsDir, "floatunsitf.c"),
filepath.Join(builtinsDir, "floatuntitf.c"),
filepath.Join(builtinsDir, "multc3.c"),
filepath.Join(builtinsDir, "multf3.c"),
filepath.Join(builtinsDir, "powitf2.c"),
filepath.Join(builtinsDir, "subtf3.c"),
filepath.Join(builtinsDir, "trunctfdf2.c"),
filepath.Join(builtinsDir, "trunctfhf2.c"),
filepath.Join(builtinsDir, "trunctfsf2.c"),
filepath.Join(builtinsDir, "atomic.c"),
}
case strings.Contains(target, "arm"):
return []string{
filepath.Join(builtinsDir, "arm", "aeabi_cdcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_cdcmpeq_check_nan.c"),
filepath.Join(builtinsDir, "arm", "aeabi_cfcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_cfcmpeq_check_nan.c"),
filepath.Join(builtinsDir, "arm", "aeabi_dcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_div0.c"),
filepath.Join(builtinsDir, "arm", "aeabi_drsub.c"),
filepath.Join(builtinsDir, "arm", "aeabi_fcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_frsub.c"),
filepath.Join(builtinsDir, "arm", "aeabi_idivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_ldivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memcpy.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memmove.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memset.S"),
filepath.Join(builtinsDir, "arm", "aeabi_uidivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_uldivmod.S"),
// These two are not technically EABI builtins but are used by them and only
// seem to be used on ARM. LLVM seems to use __divsi3 and __modsi3 on most
// other architectures.
// Most importantly, they have a different calling convention on AVR so
// should not be used on AVR.
filepath.Join(builtinsDir, "divmodsi4.c"),
filepath.Join(builtinsDir, "udivmodsi4.c"),
}
case strings.Contains(target, "avr"):
return []string{
filepath.Join(builtinsDir, "avr", "divmodhi4.S"),
filepath.Join(builtinsDir, "avr", "divmodqi4.S"),
filepath.Join(builtinsDir, "avr", "mulhi3.S"),
filepath.Join(builtinsDir, "avr", "mulqi3.S"),
filepath.Join(builtinsDir, "avr", "udivmodhi4.S"),
filepath.Join(builtinsDir, "avr", "udivmodqi4.S"),
}
case target == "xtensa":
return []string{
filepath.Join(builtinsDir, "xtensa", "ieee754_sqrtf.S"),
filepath.Join(builtinsDir, "atomic.c"),
}
}
return nil
}
func withPlatformSpecifiedFiles(baseDir, target string, files []string) []string {
builtinsDir := filepath.Join(baseDir, "lib", "builtins")
return append(files, platformSpecifiedFiles(builtinsDir, target)...)
}
func GetCompilerRTConfig(baseDir, target string) *compile.CompileConfig {
return &compile.CompileConfig{
Url: "https://github.com/goplus/compiler-rt/archive/refs/tags/v0.1.0.tar.gz",
ArchiveSrcDir: "compiler-rt-0.1.0",
Groups: []compile.CompileGroup{
{
OutputFileName: fmt.Sprintf("libclang_builtins-%s.a", target),
Files: withPlatformSpecifiedFiles(baseDir, target, []string{
filepath.Join(baseDir, "lib", "builtins", "absvdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "absvsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "absvti2.c"),
filepath.Join(baseDir, "lib", "builtins", "adddf3.c"),
filepath.Join(baseDir, "lib", "builtins", "addsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "apple_versioning.c"),
filepath.Join(baseDir, "lib", "builtins", "ashldi3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashlti3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashrdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashrti3.c"),
filepath.Join(baseDir, "lib", "builtins", "bswapdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "bswapsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzti2.c"),
filepath.Join(baseDir, "lib", "builtins", "cmpdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "cmpti2.c"),
filepath.Join(baseDir, "lib", "builtins", "comparedf2.c"),
filepath.Join(baseDir, "lib", "builtins", "comparesf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzti2.c"),
filepath.Join(baseDir, "lib", "builtins", "divdc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divdf3.c"),
filepath.Join(baseDir, "lib", "builtins", "divdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "divmoddi4.c"),
filepath.Join(baseDir, "lib", "builtins", "divmodsi4.c"),
filepath.Join(baseDir, "lib", "builtins", "divmodti4.c"),
filepath.Join(baseDir, "lib", "builtins", "divsc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "divsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "divti3.c"),
filepath.Join(baseDir, "lib", "builtins", "extendsfdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendhfsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffsdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffssi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffsti2.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfti.c"),
filepath.Join(baseDir, "lib", "builtins", "floatdidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatdisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatundidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatundisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntisf.c"),
filepath.Join(baseDir, "lib", "builtins", "fp_mode.c"),
filepath.Join(baseDir, "lib", "builtins", "int_util.c"),
filepath.Join(baseDir, "lib", "builtins", "lshrdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "lshrti3.c"),
filepath.Join(baseDir, "lib", "builtins", "moddi3.c"),
filepath.Join(baseDir, "lib", "builtins", "modsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "modti3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldc3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldf3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulodi4.c"),
filepath.Join(baseDir, "lib", "builtins", "mulosi4.c"),
filepath.Join(baseDir, "lib", "builtins", "muloti4.c"),
filepath.Join(baseDir, "lib", "builtins", "mulsc3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "multi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "negdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "negdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "negti2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvti2.c"),
filepath.Join(baseDir, "lib", "builtins", "os_version_check.c"),
filepath.Join(baseDir, "lib", "builtins", "paritydi2.c"),
filepath.Join(baseDir, "lib", "builtins", "paritysi2.c"),
filepath.Join(baseDir, "lib", "builtins", "parityti2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountti2.c"),
filepath.Join(baseDir, "lib", "builtins", "powidf2.c"),
filepath.Join(baseDir, "lib", "builtins", "powisf2.c"),
filepath.Join(baseDir, "lib", "builtins", "subdf3.c"),
filepath.Join(baseDir, "lib", "builtins", "subsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "trampoline_setup.c"),
filepath.Join(baseDir, "lib", "builtins", "truncdfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "truncdfsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "truncsfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ucmpdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ucmpti2.c"),
filepath.Join(baseDir, "lib", "builtins", "udivdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmoddi4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmodsi4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmodti4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "udivti3.c"),
filepath.Join(baseDir, "lib", "builtins", "umoddi3.c"),
filepath.Join(baseDir, "lib", "builtins", "umodsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "umodti3.c"),
filepath.Join(baseDir, "lib", "builtins", "gcc_personality_v0.c"),
filepath.Join(baseDir, "lib", "builtins", "clear_cache.c"),
filepath.Join(baseDir, "lib", "builtins", "addtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "comparetf2.c"),
filepath.Join(baseDir, "lib", "builtins", "divtc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "extenddftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendhftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendsftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfti.c"),
filepath.Join(baseDir, "lib", "builtins", "floatditf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunditf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntitf.c"),
filepath.Join(baseDir, "lib", "builtins", "multc3.c"),
filepath.Join(baseDir, "lib", "builtins", "multf3.c"),
filepath.Join(baseDir, "lib", "builtins", "powitf2.c"),
filepath.Join(baseDir, "lib", "builtins", "subtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfsf2.c"),
}),
CFlags: []string{
"-DNDEBUG",
"-DVISIBILITY_HIDDEN",
},
CCFlags: []string{
"-Oz",
"-fno-ident",
"-Wno-unused-parameter",
"-fno-lto",
"-Werror=array-bounds",
"-Werror=uninitialized",
"-Werror=shadow",
"-Werror=empty-body",
"-Werror=sizeof-pointer-memaccess",
"-Werror=sizeof-array-argument",
"-Werror=suspicious-memaccess",
"-Werror=builtin-memcpy-chk-size",
"-Werror=array-bounds-pointer-arithmetic",
"-Werror=return-stack-address",
"-Werror=sizeof-array-decay",
"-Werror=format-insufficient-args",
"-Wformat -std=c11",
"-fno-builtin",
"-fvisibility=hidden",
"-fomit-frame-pointer",
},
},
},
}
}

View File

@@ -0,0 +1,124 @@
package rtlib
import (
"strings"
"testing"
)
func TestPlatformSpecifiedFiles(t *testing.T) {
tests := []struct {
target string
expected int // Number of expected files
}{
{"riscv32-unknown-elf", 5},
{"riscv64-unknown-elf", 27},
{"arm-none-eabi", 19},
{"avr-unknown-elf", 6},
{"xtensa", 2},
{"x86_64-pc-windows", 0},
}
builtinsDir := "/test/builtins"
for _, tt := range tests {
t.Run(tt.target, func(t *testing.T) {
result := platformSpecifiedFiles(builtinsDir, tt.target)
if len(result) != tt.expected {
t.Errorf("For target %s, expected %d files, got %d", tt.target, tt.expected, len(result))
}
})
}
}
func TestWithPlatformSpecifiedFiles(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
inputFiles := []string{"file1.c", "file2.c"}
result := withPlatformSpecifiedFiles(baseDir, target, inputFiles)
// Should have input files + platform specific files
if len(result) <= len(inputFiles) {
t.Errorf("Expected more files than input, got %d", len(result))
}
// Check that input files are preserved
for _, inputFile := range inputFiles {
found := false
for _, resultFile := range result {
if resultFile == inputFile {
found = true
break
}
}
if !found {
t.Errorf("Input file %s not found in result", inputFile)
}
}
}
func TestGetCompilerRTConfig(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
config := GetCompilerRTConfig(baseDir, target)
// Test groups configuration
if len(config.Groups) != 1 {
t.Errorf("Expected 1 group, got %d", len(config.Groups))
} else {
group := config.Groups[0]
expectedOutput := "libclang_builtins-" + target + ".a"
if group.OutputFileName != expectedOutput {
t.Errorf("Expected output file %s, got %s", expectedOutput, group.OutputFileName)
}
// Check that files list contains platform-specific files
if len(group.Files) == 0 {
t.Error("Expected non-empty files list")
}
// Check that CFlags are set
if len(group.CFlags) == 0 {
t.Error("Expected non-empty CFlags")
}
// Check that CCFlags are set
if len(group.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags")
}
}
}
func TestGetCompilerRTConfig_DifferentTargets(t *testing.T) {
targets := []string{
"riscv32-unknown-elf",
"riscv64-unknown-elf",
"arm-none-eabi",
"avr-unknown-elf",
"xtensa",
}
baseDir := "/test/base"
for _, target := range targets {
t.Run(target, func(t *testing.T) {
config := GetCompilerRTConfig(baseDir, target)
// Basic validation
if config.Url == "" {
t.Error("URL should not be empty")
}
if config.ArchiveSrcDir == "" {
t.Error("ArchiveSrcDir should not be empty")
}
if len(config.Groups) == 0 {
t.Error("Should have at least one group")
}
// Check output filename contains target
group := config.Groups[0]
if !strings.Contains(group.OutputFileName, target) {
t.Errorf("Output filename %s should contain target %s", group.OutputFileName, target)
}
})
}
}

View File

@@ -10,6 +10,7 @@ import (
"runtime"
"strings"
"github.com/goplus/llgo/internal/crosscompile/compile"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/targets"
"github.com/goplus/llgo/internal/xtool/llvm"
@@ -25,6 +26,7 @@ type Export struct {
BuildTags []string
GOOS string
GOARCH string
Libc string
Linker string // Linker to use (e.g., "ld.lld", "avr-ld")
ExtraFiles []string // Extra files to compile and link (e.g., .s, .c files)
ClangRoot string // Root directory of custom clang installation
@@ -219,6 +221,35 @@ func getESPClangPlatform(goos, goarch string) string {
return ""
}
func ldFlagsFromFileName(fileName string) string {
return strings.TrimPrefix(strings.TrimSuffix(fileName, ".a"), "lib")
}
func getOrCompileWithConfig(
compileConfig *compile.CompileConfig,
outputDir string, options compile.CompileOptions,
) (ldflags []string, err error) {
if err = checkDownloadAndExtractLib(
compileConfig.Url, outputDir,
compileConfig.ArchiveSrcDir,
); err != nil {
return
}
ldflags = append(ldflags, "-nostdlib", "-L"+outputDir)
for _, group := range compileConfig.Groups {
err = group.Compile(outputDir, options)
if err != nil {
break
}
if filepath.Ext(group.OutputFileName) == ".o" {
continue
}
ldflags = append(ldflags, "-l"+ldFlagsFromFileName(group.OutputFileName))
}
return
}
func use(goos, goarch string, wasiThreads, forceEspClang bool) (export Export, err error) {
targetTriple := llvm.GetTargetTriple(goos, goarch)
llgoRoot := env.LLGoROOT()
@@ -583,6 +614,57 @@ func useTarget(targetName string) (export Export, err error) {
}
ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld
var libcIncludeDir []string
if config.Libc != "" {
var libcLDFlags []string
var compileConfig *compile.CompileConfig
baseDir := filepath.Join(cacheRoot(), "crosscompile")
outputDir := filepath.Join(baseDir, config.Libc)
compileConfig, err = getLibcCompileConfigByName(baseDir, config.Libc, config.LLVMTarget, config.CPU)
if err != nil {
return
}
libcLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{
CC: export.CC,
Linker: export.Linker,
CCFLAGS: ccflags,
LDFLAGS: ldflags,
})
if err != nil {
return
}
cflags = append(cflags, compileConfig.LibcCFlags...)
ldflags = append(ldflags, libcLDFlags...)
libcIncludeDir = compileConfig.LibcCFlags
export.Libc = config.Libc
}
if config.RTLib != "" {
var rtLibLDFlags []string
var compileConfig *compile.CompileConfig
baseDir := filepath.Join(cacheRoot(), "crosscompile")
outputDir := filepath.Join(baseDir, config.RTLib)
compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget)
if err != nil {
return
}
rtLibLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{
CC: export.CC,
Linker: export.Linker,
CCFLAGS: ccflags,
LDFLAGS: ldflags,
CFLAGS: libcIncludeDir,
})
if err != nil {
return
}
ldflags = append(ldflags, rtLibLDFlags...)
}
// Combine with config flags and expand template variables
export.CFLAGS = cflags
export.CCFLAGS = ccflags
@@ -595,7 +677,7 @@ func useTarget(targetName string) (export Export, err error) {
// Use extends the original Use function to support target-based configuration
// If targetName is provided, it takes precedence over goos/goarch
func Use(goos, goarch, targetName string, wasiThreads, forceEspClang bool) (export Export, err error) {
if targetName != "" {
if targetName != "" && !strings.HasPrefix(targetName, "wasm") && !strings.HasPrefix(targetName, "wasi") {
return useTarget(targetName)
}
return use(goos, goarch, wasiThreads, forceEspClang)

View File

@@ -176,13 +176,14 @@ func TestUseTarget(t *testing.T) {
expectLLVM string
expectCPU string
}{
{
name: "WASI Target",
targetName: "wasi",
expectError: false,
expectLLVM: "",
expectCPU: "generic",
},
// FIXME(MeteorsLiu): wasi in useTarget
// {
// name: "WASI Target",
// targetName: "wasi",
// expectError: false,
// expectLLVM: "",
// expectCPU: "generic",
// },
{
name: "RP2040 Target",
targetName: "rp2040",
@@ -279,13 +280,13 @@ func TestUseTarget(t *testing.T) {
func TestUseWithTarget(t *testing.T) {
// Test target-based configuration takes precedence
export, err := Use("linux", "amd64", "wasi", false, true)
export, err := Use("linux", "amd64", "esp32", false, true)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check if LLVM target is in CCFLAGS
found := slices.Contains(export.CCFLAGS, "-mcpu=generic")
found := slices.Contains(export.CCFLAGS, "-mcpu=esp32")
if !found {
t.Errorf("Expected CPU generic in CCFLAGS, got %v", export.CCFLAGS)
}

View File

@@ -2,12 +2,14 @@ package crosscompile
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
@@ -78,6 +80,48 @@ func checkDownloadAndExtractESPClang(platformSuffix, dir string) error {
return nil
}
func checkDownloadAndExtractLib(url, dstDir, internalArchiveSrcDir string) error {
// Check if already exists
if _, err := os.Stat(dstDir); err == nil {
return nil
}
// Create lock file path for the final destination
lockPath := dstDir + ".lock"
lockFile, err := acquireLock(lockPath)
if err != nil {
return fmt.Errorf("failed to acquire lock: %w", err)
}
defer releaseLock(lockFile)
// Double-check after acquiring lock
if _, err := os.Stat(dstDir); err == nil {
return nil
}
fmt.Fprintf(os.Stderr, "%s not found in LLGO_ROOT or cache, will download and compile.\n", dstDir)
description := fmt.Sprintf("lib %s", path.Base(url))
// Use temporary extraction directory
tempExtractDir := dstDir + ".extract"
if err := downloadAndExtractArchive(url, tempExtractDir, description); err != nil {
return err
}
defer os.RemoveAll(tempExtractDir)
srcDir := tempExtractDir
if internalArchiveSrcDir != "" {
srcDir = filepath.Join(tempExtractDir, internalArchiveSrcDir)
}
if err := os.Rename(srcDir, dstDir); err != nil {
return fmt.Errorf("failed to rename lib directory: %w", err)
}
return nil
}
// acquireLock creates and locks a file to prevent concurrent operations
func acquireLock(lockPath string) (*os.File, error) {
// Ensure the parent directory exists
@@ -140,10 +184,14 @@ func downloadAndExtractArchive(url, destDir, description string) error {
if err != nil {
return fmt.Errorf("failed to extract %s archive: %w", description, err)
}
} else if strings.HasSuffix(filename, ".zip") {
err := extractZip(localFile, tempDir)
if err != nil {
return fmt.Errorf("failed to extract %s archive: %w", description, err)
}
} else {
return fmt.Errorf("unsupported archive format: %s", filename)
}
// Rename temp directory to target directory
if err := os.Rename(tempDir, destDir); err != nil {
return fmt.Errorf("failed to rename directory: %w", err)
@@ -223,3 +271,44 @@ func extractTarXz(tarXzFile, dest string) error {
cmd := exec.Command("tar", "-xf", tarXzFile, "-C", dest)
return cmd.Run()
}
func extractZip(zipFile, dest string) error {
r, err := zip.OpenReader(zipFile)
if err != nil {
return err
}
defer r.Close()
decompress := func(file *zip.File) error {
path := filepath.Join(dest, file.Name)
if file.FileInfo().IsDir() {
return os.MkdirAll(path, 0700)
}
fs, err := file.Open()
if err != nil {
return err
}
defer fs.Close()
w, err := os.Create(path)
if err != nil {
return err
}
if _, err := io.Copy(w, fs); err != nil {
w.Close()
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
for _, file := range r.File {
if err = decompress(file); err != nil {
break
}
}
return err
}

View File

@@ -5,10 +5,13 @@ package crosscompile
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -287,14 +290,14 @@ func TestDownloadAndExtractArchive(t *testing.T) {
func TestDownloadAndExtractArchiveUnsupportedFormat(t *testing.T) {
server := createTestServer(t, map[string]string{
"test.zip": "fake zip content",
"test.7z": "fake zip content",
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "extracted")
err := downloadAndExtractArchive(server.URL+"/test.zip", destDir, "Test Archive")
err := downloadAndExtractArchive(server.URL+"/test.7z", destDir, "Test Archive")
if err == nil {
t.Error("Expected error for unsupported format, got nil")
}
@@ -303,6 +306,162 @@ func TestDownloadAndExtractArchiveUnsupportedFormat(t *testing.T) {
}
}
func TestCheckDownloadAndExtractLib(t *testing.T) {
files := map[string]string{
"lib-src/file1.c": "int func1() { return 1; }",
"lib-src/file2.c": "int func2() { return 2; }",
"lib-src/include/lib.h": "#define LIB_VERSION 1",
}
archivePath := createTestTarGz(t, files)
defer os.Remove(archivePath)
archiveContent, err := os.ReadFile(archivePath)
if err != nil {
t.Fatalf("Failed to read test archive: %v", err)
}
server := createTestServer(t, map[string]string{
"test-lib.tar.gz": string(archiveContent),
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "test-lib")
t.Run("LibAlreadyExists", func(t *testing.T) {
if err := os.MkdirAll(destDir, 0755); err != nil {
t.Fatalf("Failed to create existing lib dir: %v", err)
}
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "")
if err != nil {
t.Errorf("Expected no error when lib exists, got: %v", err)
}
})
t.Run("DownloadAndExtractWithoutInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
cmd := exec.Command("ls", destDir)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Run()
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "lib-src/")
filePath := filepath.Join(destDir, relPath)
fmt.Println(filePath, destDir)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("DownloadAndExtractWithInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "lib-src/")
filePath := filepath.Join(destDir, relPath)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("DownloadFailure", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/nonexistent.tar.gz", destDir, "")
if err == nil {
t.Error("Expected error for non-existent archive, got nil")
}
})
t.Run("RenameFailure", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src222")
if err == nil {
t.Error("Expected error for rename failure, got nil")
}
})
}
func TestCheckDownloadAndExtractLibInternalDir(t *testing.T) {
files := map[string]string{
"project-1.0.0/src/file1.c": "int func1() { return 1; }",
"project-1.0.0/include/lib.h": "#define LIB_VERSION 1",
"project-1.0.0/README.md": "Project documentation",
}
archivePath := createTestTarGz(t, files)
defer os.Remove(archivePath)
archiveContent, err := os.ReadFile(archivePath)
if err != nil {
t.Fatalf("Failed to read test archive: %v", err)
}
server := createTestServer(t, map[string]string{
"project.tar.gz": string(archiveContent),
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "project-lib")
t.Run("CorrectInternalDir", func(t *testing.T) {
err := checkDownloadAndExtractLib(server.URL+"/project.tar.gz", destDir, "project-1.0.0")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "project-1.0.0/")
filePath := filepath.Join(destDir, relPath)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("IncorrectInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/project.tar.gz", destDir, "wrong-dir")
if err == nil {
t.Error("Expected error for missing internal dir, got nil")
}
})
}
// Mock test for WASI SDK (without actual download)
func TestWasiSDKExtractionLogic(t *testing.T) {
tempDir := t.TempDir()
@@ -490,3 +649,130 @@ func TestESPClangDownloadWhenNotExists(t *testing.T) {
}
}
}
func TestExtractZip(t *testing.T) {
// Create temporary test directory
tempDir := t.TempDir()
zipPath := filepath.Join(tempDir, "test.zip")
destDir := filepath.Join(tempDir, "extracted")
// 1. Test successful extraction
t.Run("SuccessfulExtraction", func(t *testing.T) {
// Create test ZIP file
if err := createTestZip(zipPath); err != nil {
t.Fatalf("Failed to create test zip: %v", err)
}
// Execute extraction
if err := extractZip(zipPath, destDir); err != nil {
t.Fatalf("extractZip failed: %v", err)
}
// Verify extraction results
verifyExtraction(t, destDir)
})
// 2. Test invalid ZIP file
t.Run("InvalidZipFile", func(t *testing.T) {
// Create invalid ZIP file (actually a text file)
if err := os.WriteFile(zipPath, []byte("not a zip file"), 0644); err != nil {
t.Fatal(err)
}
// Execute extraction and expect error
if err := extractZip(zipPath, destDir); err == nil {
t.Error("Expected error for invalid zip file, got nil")
}
})
// 3. Test non-writable destination
t.Run("UnwritableDestination", func(t *testing.T) {
// Create test ZIP file
if err := createTestZip(zipPath); err != nil {
t.Fatal(err)
}
// Create read-only destination directory
readOnlyDir := filepath.Join(tempDir, "readonly")
if err := os.MkdirAll(readOnlyDir, 0400); err != nil {
t.Fatal(err)
}
// Execute extraction and expect error
if err := extractZip(zipPath, readOnlyDir); err == nil {
t.Error("Expected error for unwritable destination, got nil")
}
})
}
// Create test ZIP file
func createTestZip(zipPath string) error {
// Create ZIP file
zipFile, err := os.Create(zipPath)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Add directory
dirHeader := &zip.FileHeader{
Name: "testdir/",
Method: zip.Deflate,
Modified: time.Now(),
}
dirHeader.SetMode(os.ModeDir | 0755)
if _, err := zipWriter.CreateHeader(dirHeader); err != nil {
return err
}
// Add file1
file1, err := zipWriter.Create("file1.txt")
if err != nil {
return err
}
if _, err := file1.Write([]byte("Hello from file1")); err != nil {
return err
}
// Add nested file
nestedFile, err := zipWriter.Create("testdir/nested.txt")
if err != nil {
return err
}
if _, err := nestedFile.Write([]byte("Nested content")); err != nil {
return err
}
return nil
}
// Verify extraction results
func verifyExtraction(t *testing.T, destDir string) {
// Verify directory exists
if _, err := os.Stat(filepath.Join(destDir, "testdir")); err != nil {
t.Errorf("Directory not extracted: %v", err)
}
// Verify file1 content
file1Path := filepath.Join(destDir, "file1.txt")
content, err := os.ReadFile(file1Path)
if err != nil {
t.Errorf("Failed to read file1: %v", err)
}
if string(content) != "Hello from file1" {
t.Errorf("File1 content mismatch. Got: %s", content)
}
// Verify nested file content
nestedPath := filepath.Join(destDir, "testdir", "nested.txt")
content, err = os.ReadFile(nestedPath)
if err != nil {
t.Errorf("Failed to read nested file: %v", err)
}
if string(content) != "Nested content" {
t.Errorf("Nested file content mismatch. Got: %s", content)
}
}

View File

@@ -0,0 +1,42 @@
package crosscompile
import (
"fmt"
"path/filepath"
"github.com/goplus/llgo/internal/crosscompile/compile"
"github.com/goplus/llgo/internal/crosscompile/compile/libc"
"github.com/goplus/llgo/internal/crosscompile/compile/rtlib"
)
// GetCompileConfigByName retrieves libc compilation configuration by name
// Returns compilation file lists and corresponding cflags
func getLibcCompileConfigByName(baseDir, libcName, target, mcpu string) (*compile.CompileConfig, error) {
if libcName == "" {
return nil, fmt.Errorf("libc name cannot be empty")
}
libcDir := filepath.Join(baseDir, libcName)
switch libcName {
case "picolibc":
return libc.GetPicolibcConfig(libcDir, target), nil
case "newlib-esp32":
return libc.GetNewlibESP32Config(libcDir, target, mcpu), nil
default:
return nil, fmt.Errorf("unsupported libc: %s", libcName)
}
}
func getRTCompileConfigByName(baseDir, rtName, target string) (*compile.CompileConfig, error) {
if rtName == "" {
return nil, fmt.Errorf("rt name cannot be empty")
}
rtDir := filepath.Join(baseDir, rtName)
switch rtName {
case "compiler-rt":
return rtlib.GetCompilerRTConfig(rtDir, target), nil
default:
return nil, fmt.Errorf("unsupported rt: %s", rtName)
}
}

View File

@@ -0,0 +1,109 @@
//go:build !llgo
package crosscompile
import (
"path/filepath"
"slices"
"testing"
)
func TestGetLibcCompileConfigByName(t *testing.T) {
baseDir := "/test/base"
target := "armv7"
mcpu := "cortex-m4"
t.Run("EmptyName", func(t *testing.T) {
_, err := getLibcCompileConfigByName(baseDir, "", target, mcpu)
if err == nil || err.Error() != "libc name cannot be empty" {
t.Errorf("Expected empty name error, got: %v", err)
}
})
t.Run("UnsupportedLibc", func(t *testing.T) {
_, err := getLibcCompileConfigByName(baseDir, "invalid", target, mcpu)
if err == nil || err.Error() != "unsupported libc: invalid" {
t.Errorf("Expected unsupported libc error, got: %v", err)
}
})
t.Run("Picolibc", func(t *testing.T) {
cfg, err := getLibcCompileConfigByName(baseDir, "picolibc", target, mcpu)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 1 {
t.Fatalf("Expected 1 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "picolibc", "newlib", "libc", "string", "memmem.c")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
expectedFlag := "-I" + filepath.Join("/test", "base", "picolibc")
if !slices.Contains(group.CFlags, expectedFlag) {
t.Errorf("Expected flags [%s], got: %v", expectedFlag, group.CFlags)
}
})
t.Run("NewlibESP32", func(t *testing.T) {
cfg, err := getLibcCompileConfigByName(baseDir, "newlib-esp32", target, mcpu)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 3 {
t.Fatalf("Expected 3 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "newlib-esp32", "libgloss", "xtensa", "crt1-boards.S")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
expectedFlags := "-I" + filepath.Join(baseDir, "newlib-esp32", "libgloss")
if !slices.Contains(group.CFlags, expectedFlags) {
t.Errorf("Expected flags %v, got: %v", expectedFlags, group.CFlags)
}
})
}
func TestGetRTCompileConfigByName(t *testing.T) {
baseDir := "/test/base"
target := "wasm32"
t.Run("EmptyName", func(t *testing.T) {
_, err := getRTCompileConfigByName(baseDir, "", target)
if err == nil || err.Error() != "rt name cannot be empty" {
t.Errorf("Expected empty name error, got: %v", err)
}
})
t.Run("UnsupportedRT", func(t *testing.T) {
_, err := getRTCompileConfigByName(baseDir, "invalid", target)
if err == nil || err.Error() != "unsupported rt: invalid" {
t.Errorf("Expected unsupported rt error, got: %v", err)
}
})
t.Run("CompilerRT", func(t *testing.T) {
cfg, err := getRTCompileConfigByName(baseDir, "compiler-rt", target)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 1 {
t.Fatalf("Expected 1 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "compiler-rt", "lib", "builtins", "absvdi2.c")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
})
}