diff --git a/internal/crosscompile/compile/compile.go b/internal/crosscompile/compile/compile.go index 0fc7d79a..01136d8e 100644 --- a/internal/crosscompile/compile/compile.go +++ b/internal/crosscompile/compile/compile.go @@ -95,9 +95,17 @@ func (g CompileGroup) Compile( // CompileConfig represents compilation configuration type CompileConfig struct { + Groups []CompileGroup + ExportCFlags []string +} + +type LibConfig struct { Url string Name string // compile name (e.g., "picolibc", "musl", "glibc") - Groups []CompileGroup + Version string ArchiveSrcDir string - LibcCFlags []string +} + +func (cfg LibConfig) String() string { + return fmt.Sprintf("%s-%s", cfg.Name, cfg.Version) } diff --git a/internal/crosscompile/compile/compile_test.go b/internal/crosscompile/compile/compile_test.go index d2125e5a..f5f5ba7d 100644 --- a/internal/crosscompile/compile/compile_test.go +++ b/internal/crosscompile/compile/compile_test.go @@ -207,3 +207,60 @@ func TestCompile(t *testing.T) { } }) } + +func TestLibConfig_String(t *testing.T) { + tests := []struct { + name string + config LibConfig + expected string + }{ + { + name: "Normal name and version", + config: LibConfig{ + Name: "picolibc", + Version: "1.0", + }, + expected: "picolibc-1.0", + }, + { + name: "Empty name", + config: LibConfig{ + Name: "", + Version: "2.5", + }, + expected: "-2.5", + }, + { + name: "Empty version", + config: LibConfig{ + Name: "musl", + Version: "", + }, + expected: "musl-", + }, + { + name: "Both empty", + config: LibConfig{ + Name: "", + Version: "", + }, + expected: "-", + }, + { + name: "Special characters", + config: LibConfig{ + Name: "glibc++", + Version: "v3.2.1", + }, + expected: "glibc++-v3.2.1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.config.String(); got != tt.expected { + t.Errorf("String() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/internal/crosscompile/compile/libc/libc_test.go b/internal/crosscompile/compile/libc/libc_test.go index 20022229..3689d9fa 100644 --- a/internal/crosscompile/compile/libc/libc_test.go +++ b/internal/crosscompile/compile/libc/libc_test.go @@ -9,27 +9,20 @@ 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) - } + config := GetPicolibcCompileConfig(baseDir, target) // Test LibcCFlags - if len(config.LibcCFlags) != 2 { - t.Errorf("Expected 2 LibcCFlags, got %d", len(config.LibcCFlags)) + if len(config.ExportCFlags) != 2 { + t.Errorf("Expected 2 LibcCFlags, got %d", len(config.ExportCFlags)) } else { expected := "-I" + baseDir - if config.LibcCFlags[0] != expected { - t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0]) + if config.ExportCFlags[0] != expected { + t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.ExportCFlags[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]) + if config.ExportCFlags[1] != expected { + t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.ExportCFlags[1]) } } @@ -113,22 +106,22 @@ func TestGetPicolibcConfig(t *testing.T) { func TestGetPicolibcConfig_EdgeCases(t *testing.T) { t.Run("EmptyBaseDir", func(t *testing.T) { - config := GetPicolibcConfig("", "test-target") + config := GetPicolibcCompileConfig("", "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]) + if config.ExportCFlags[0] != expected { + t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.ExportCFlags[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]) + if config.ExportCFlags[1] != expected { + t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.ExportCFlags[1]) } }) t.Run("EmptyTarget", func(t *testing.T) { - config := GetPicolibcConfig("/test/base", "") + config := GetPicolibcCompileConfig("/test/base", "") // Check output file name formatting expectedOutput := "libc-.a" @@ -144,17 +137,6 @@ func TestGetNewlibESP32ConfigRISCV(t *testing.T) { 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{ @@ -162,12 +144,12 @@ func TestGetNewlibESP32ConfigRISCV(t *testing.T) { "-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)) + if len(config.ExportCFlags) != len(expectedCFlags) { + t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.ExportCFlags)) } 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]) + if config.ExportCFlags[i] != expected { + t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.ExportCFlags[i]) } } } @@ -288,17 +270,6 @@ func TestGetNewlibESP32ConfigXtensa(t *testing.T) { 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{ @@ -306,12 +277,12 @@ func TestGetNewlibESP32ConfigXtensa(t *testing.T) { "-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)) + if len(config.ExportCFlags) != len(expectedCFlags) { + t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.ExportCFlags)) } 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]) + if config.ExportCFlags[i] != expected { + t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.ExportCFlags[i]) } } } @@ -407,8 +378,8 @@ func TestEdgeCases(t *testing.T) { // 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]) + if config.ExportCFlags[0] != expected { + t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.ExportCFlags[0]) } }) @@ -428,8 +399,8 @@ func TestEdgeCases(t *testing.T) { // 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]) + if config.ExportCFlags[0] != expected { + t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.ExportCFlags[0]) } }) diff --git a/internal/crosscompile/compile/libc/newlibesp.go b/internal/crosscompile/compile/libc/newlibesp.go index 23d7930d..dd6c57fb 100644 --- a/internal/crosscompile/compile/libc/newlibesp.go +++ b/internal/crosscompile/compile/libc/newlibesp.go @@ -21,16 +21,20 @@ var _libcCCFlags = []string{ "-ffreestanding", } -const ( - _newlibUrl = "https://github.com/goplus/newlib/archive/refs/tags/v0.2.0.tar.gz" - _archiveInternalSrcDir = "newlib-0.2.0" -) - func withDefaultCCFlags(ccflags []string) []string { return append(ccflags, _libcCCFlags...) } -func getNewlibESP32ConfigRISCV(baseDir, target string) *compile.CompileConfig { +func GetNewlibESP32Config() compile.LibConfig { + return compile.LibConfig{ + Url: "https://github.com/goplus/newlib/archive/refs/tags/v0.3.0.tar.gz", + Name: "newlib-esp32", + Version: "v0.3.0", + ArchiveSrcDir: "newlib-0.3.0", + } +} + +func getNewlibESP32ConfigRISCV(baseDir, target string) compile.CompileConfig { libcDir := filepath.Join(baseDir, "newlib", "libc") libcIncludeDir := []string{ @@ -39,11 +43,8 @@ func getNewlibESP32ConfigRISCV(baseDir, target string) *compile.CompileConfig { "-I" + libcDir, } - return &compile.CompileConfig{ - Url: _newlibUrl, - Name: "newlib-esp32", - LibcCFlags: libcIncludeDir, - ArchiveSrcDir: _archiveInternalSrcDir, + return compile.CompileConfig{ + ExportCFlags: libcIncludeDir, Groups: []compile.CompileGroup{ { OutputFileName: fmt.Sprintf("libcrt0-%s.a", target), @@ -1092,7 +1093,7 @@ func getNewlibESP32ConfigRISCV(baseDir, target string) *compile.CompileConfig { } } -func getNewlibESP32ConfigXtensa(baseDir, target string) *compile.CompileConfig { +func getNewlibESP32ConfigXtensa(baseDir, target string) compile.CompileConfig { libcDir := filepath.Join(baseDir, "newlib", "libc") libcIncludeDir := []string{ @@ -1101,11 +1102,8 @@ func getNewlibESP32ConfigXtensa(baseDir, target string) *compile.CompileConfig { "-I" + libcDir, } - return &compile.CompileConfig{ - Url: _newlibUrl, - Name: "newlib-esp32", - ArchiveSrcDir: _archiveInternalSrcDir, - LibcCFlags: libcIncludeDir, + return compile.CompileConfig{ + ExportCFlags: libcIncludeDir, Groups: []compile.CompileGroup{ { OutputFileName: fmt.Sprintf("libcrt0-%s.a", target), @@ -2075,7 +2073,7 @@ func getNewlibESP32ConfigXtensa(baseDir, target string) *compile.CompileConfig { } // getNewlibESP32Config returns configuration for newlib esp32 -func GetNewlibESP32Config(baseDir, target, mcpu string) *compile.CompileConfig { +func GetNewlibESP32CompileConfig(baseDir, target, mcpu string) compile.CompileConfig { if strings.Contains(target, "riscv32") { return getNewlibESP32ConfigRISCV(baseDir, target) } diff --git a/internal/crosscompile/compile/libc/picolibc.go b/internal/crosscompile/compile/libc/picolibc.go index 7736ccf7..7a6d4b3f 100644 --- a/internal/crosscompile/compile/libc/picolibc.go +++ b/internal/crosscompile/compile/libc/picolibc.go @@ -7,16 +7,22 @@ import ( "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{ +func GetPicolibcConfig() compile.LibConfig { + return compile.LibConfig{ + Name: "picolibc", + Version: "v0.1.0", + Url: "https://github.com/goplus/picolibc/archive/refs/heads/main.zip", + ArchiveSrcDir: "picolibc-main", + } +} + +// GetPicolibcCompileConfig returns configuration for picolibc +func GetPicolibcCompileConfig(baseDir, target string) compile.CompileConfig { + return compile.CompileConfig{ + ExportCFlags: []string{ "-I" + baseDir, "-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"), }, - Groups: []compile.CompileGroup{ { OutputFileName: fmt.Sprintf("libc-%s.a", target), @@ -158,6 +164,5 @@ func GetPicolibcConfig(baseDir, target string) *compile.CompileConfig { CCFlags: _libcCCFlags, }, }, - ArchiveSrcDir: "picolibc-main", } } diff --git a/internal/crosscompile/compile/rtlib/compiler_rt.go b/internal/crosscompile/compile/rtlib/compiler_rt.go index d307132c..1a7f4590 100644 --- a/internal/crosscompile/compile/rtlib/compiler_rt.go +++ b/internal/crosscompile/compile/rtlib/compiler_rt.go @@ -100,10 +100,17 @@ func withPlatformSpecifiedFiles(baseDir, target string, files []string) []string return append(files, platformSpecifiedFiles(builtinsDir, target)...) } -func GetCompilerRTConfig(baseDir, target string) *compile.CompileConfig { - return &compile.CompileConfig{ +func GetCompilerRTConfig() compile.LibConfig { + return compile.LibConfig{ + Name: "compiler-rt", Url: "https://github.com/goplus/compiler-rt/archive/refs/tags/v0.1.0.tar.gz", + Version: "v0.1.0", ArchiveSrcDir: "compiler-rt-0.1.0", + } +} + +func GetCompilerRTCompileConfig(baseDir, target string) compile.CompileConfig { + return compile.CompileConfig{ Groups: []compile.CompileGroup{ { OutputFileName: fmt.Sprintf("libclang_builtins-%s.a", target), @@ -277,7 +284,8 @@ func GetCompilerRTConfig(baseDir, target string) *compile.CompileConfig { "-Werror=return-stack-address", "-Werror=sizeof-array-decay", "-Werror=format-insufficient-args", - "-Wformat -std=c11", + "-Wformat", + "-std=c11", "-fno-builtin", "-fvisibility=hidden", "-fomit-frame-pointer", diff --git a/internal/crosscompile/compile/rtlib/rt_test.go b/internal/crosscompile/compile/rtlib/rt_test.go index 58aeb05e..ca18e1b5 100644 --- a/internal/crosscompile/compile/rtlib/rt_test.go +++ b/internal/crosscompile/compile/rtlib/rt_test.go @@ -60,7 +60,7 @@ func TestGetCompilerRTConfig(t *testing.T) { baseDir := "/test/base" target := "riscv32-unknown-elf" - config := GetCompilerRTConfig(baseDir, target) + config := GetCompilerRTCompileConfig(baseDir, target) // Test groups configuration if len(config.Groups) != 1 { @@ -101,15 +101,8 @@ func TestGetCompilerRTConfig_DifferentTargets(t *testing.T) { baseDir := "/test/base" for _, target := range targets { t.Run(target, func(t *testing.T) { - config := GetCompilerRTConfig(baseDir, target) + config := GetCompilerRTCompileConfig(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") } diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index fbd8962a..7c2d1dd4 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -171,16 +171,10 @@ func ldFlagsFromFileName(fileName string) string { return strings.TrimPrefix(strings.TrimSuffix(fileName, ".a"), "lib") } -func getOrCompileWithConfig( - compileConfig *compile.CompileConfig, +func compileWithConfig( + 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 { @@ -586,16 +580,16 @@ func UseTarget(targetName string) (export Export, err error) { var libcIncludeDir []string if config.Libc != "" { + var outputDir string var libcLDFlags []string - var compileConfig *compile.CompileConfig + 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) + outputDir, compileConfig, err = getLibcCompileConfigByName(baseDir, config.Libc, config.LLVMTarget, config.CPU) if err != nil { return } - libcLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{ + libcLDFlags, err = compileWithConfig(compileConfig, outputDir, compile.CompileOptions{ CC: export.CC, Linker: export.Linker, CCFLAGS: ccflags, @@ -604,24 +598,24 @@ func UseTarget(targetName string) (export Export, err error) { if err != nil { return } - cflags = append(cflags, compileConfig.LibcCFlags...) + cflags = append(cflags, compileConfig.ExportCFlags...) ldflags = append(ldflags, libcLDFlags...) - libcIncludeDir = compileConfig.LibcCFlags + libcIncludeDir = compileConfig.ExportCFlags export.Libc = config.Libc } if config.RTLib != "" { + var outputDir string var rtLibLDFlags []string - var compileConfig *compile.CompileConfig + var compileConfig compile.CompileConfig baseDir := filepath.Join(cacheRoot(), "crosscompile") - outputDir := filepath.Join(baseDir, config.RTLib) - compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget) + outputDir, compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget) if err != nil { return } - rtLibLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{ + rtLibLDFlags, err = compileWithConfig(compileConfig, outputDir, compile.CompileOptions{ CC: export.CC, Linker: export.Linker, CCFLAGS: ccflags, diff --git a/internal/crosscompile/libc.go b/internal/crosscompile/libc.go index fdbb7307..fb255419 100644 --- a/internal/crosscompile/libc.go +++ b/internal/crosscompile/libc.go @@ -9,34 +9,68 @@ import ( "github.com/goplus/llgo/internal/crosscompile/compile/rtlib" ) +// for testing, in testing env, we use fake path, it will cause downloading failure +var needSkipDownload = false + // GetCompileConfigByName retrieves libc compilation configuration by name // Returns compilation file lists and corresponding cflags -func getLibcCompileConfigByName(baseDir, libcName, target, mcpu string) (*compile.CompileConfig, error) { +func getLibcCompileConfigByName(baseDir, libcName, target, mcpu string) (outputDir string, cfg compile.CompileConfig, err error) { if libcName == "" { - return nil, fmt.Errorf("libc name cannot be empty") + err = fmt.Errorf("libc name cannot be empty") + return } - libcDir := filepath.Join(baseDir, libcName) + var libcDir string + var config compile.LibConfig + var compileConfig compile.CompileConfig switch libcName { case "picolibc": - return libc.GetPicolibcConfig(libcDir, target), nil + config = libc.GetPicolibcConfig() + libcDir = filepath.Join(baseDir, config.String()) + compileConfig = libc.GetPicolibcCompileConfig(libcDir, target) case "newlib-esp32": - return libc.GetNewlibESP32Config(libcDir, target, mcpu), nil + config = libc.GetNewlibESP32Config() + libcDir = filepath.Join(baseDir, config.String()) + compileConfig = libc.GetNewlibESP32CompileConfig(libcDir, target, mcpu) default: - return nil, fmt.Errorf("unsupported libc: %s", libcName) + err = fmt.Errorf("unsupported libc: %s", libcName) + return } + if needSkipDownload { + return libcDir, compileConfig, err + } + + if err = checkDownloadAndExtractLib(config.Url, libcDir, config.ArchiveSrcDir); err != nil { + return + } + + return libcDir, compileConfig, nil } -func getRTCompileConfigByName(baseDir, rtName, target string) (*compile.CompileConfig, error) { +func getRTCompileConfigByName(baseDir, rtName, target string) (outputDir string, cfg compile.CompileConfig, err error) { if rtName == "" { - return nil, fmt.Errorf("rt name cannot be empty") + err = fmt.Errorf("rt name cannot be empty") + return } - rtDir := filepath.Join(baseDir, rtName) + var rtDir string + var config compile.LibConfig + var compileConfig compile.CompileConfig switch rtName { case "compiler-rt": - return rtlib.GetCompilerRTConfig(rtDir, target), nil + config = rtlib.GetCompilerRTConfig() + rtDir = filepath.Join(baseDir, config.String()) + compileConfig = rtlib.GetCompilerRTCompileConfig(rtDir, target) default: - return nil, fmt.Errorf("unsupported rt: %s", rtName) + err = fmt.Errorf("unsupported rt: %s", rtName) } + if needSkipDownload { + return rtDir, compileConfig, err + } + + if err = checkDownloadAndExtractLib(config.Url, rtDir, config.ArchiveSrcDir); err != nil { + return + } + + return rtDir, compileConfig, nil } diff --git a/internal/crosscompile/libc_test.go b/internal/crosscompile/libc_test.go index 5953d56c..f03a4646 100644 --- a/internal/crosscompile/libc_test.go +++ b/internal/crosscompile/libc_test.go @@ -3,9 +3,14 @@ package crosscompile import ( + "fmt" "path/filepath" "slices" + "strings" "testing" + + "github.com/goplus/llgo/internal/crosscompile/compile/libc" + "github.com/goplus/llgo/internal/crosscompile/compile/rtlib" ) func TestGetLibcCompileConfigByName(t *testing.T) { @@ -13,22 +18,23 @@ func TestGetLibcCompileConfigByName(t *testing.T) { target := "armv7" mcpu := "cortex-m4" + needSkipDownload = true t.Run("EmptyName", func(t *testing.T) { - _, err := getLibcCompileConfigByName(baseDir, "", target, mcpu) + _, _, 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) + _, _, 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) + _, cfg, err := getLibcCompileConfigByName(baseDir, "picolibc", target, mcpu) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -38,19 +44,19 @@ func TestGetLibcCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] - expectedFile := filepath.Join(baseDir, "picolibc", "newlib", "libc", "string", "memmem.c") + expectedFile := filepath.Join(baseDir, libc.GetPicolibcConfig().String(), "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") + expectedFlag := "-I" + filepath.Join("/test", "base", libc.GetPicolibcConfig().String()) 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) + _, cfg, err := getLibcCompileConfigByName(baseDir, "newlib-esp32", target, mcpu) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -60,12 +66,12 @@ func TestGetLibcCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] - expectedFile := filepath.Join(baseDir, "newlib-esp32", "libgloss", "xtensa", "crt1-boards.S") + expectedFile := filepath.Join(baseDir, libc.GetNewlibESP32Config().String(), "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") + expectedFlags := "-I" + filepath.Join(baseDir, libc.GetNewlibESP32Config().String(), "libgloss") if !slices.Contains(group.CFlags, expectedFlags) { t.Errorf("Expected flags %v, got: %v", expectedFlags, group.CFlags) } @@ -75,23 +81,24 @@ func TestGetLibcCompileConfigByName(t *testing.T) { func TestGetRTCompileConfigByName(t *testing.T) { baseDir := "/test/base" target := "wasm32" + needSkipDownload = true t.Run("EmptyName", func(t *testing.T) { - _, err := getRTCompileConfigByName(baseDir, "", target) + _, _, 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) + _, _, 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) + _, cfg, err := getRTCompileConfigByName(baseDir, "compiler-rt", target) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -101,9 +108,302 @@ func TestGetRTCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] - expectedFile := filepath.Join(baseDir, "compiler-rt", "lib", "builtins", "absvdi2.c") + expectedFile := filepath.Join(baseDir, rtlib.GetCompilerRTConfig().String(), "lib", "builtins", "absvdi2.c") if !slices.Contains(group.Files, expectedFile) { t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files) } }) } + +// TestCompilerRTCompileConfigPaths tests that file paths in the CompileConfig +// are correctly based on the provided baseDir for various target platforms. +func TestCompilerRTCompileConfigPaths(t *testing.T) { + // Define test cases for different target platforms + tests := []struct { + name string // Test case name + baseDir string // Input base directory + target string // Target platform + expected string // Expected platform-specific file + }{ + { + name: "RISC-V 32", + baseDir: "/test/base/dir", + target: "riscv32-unknown-elf", + expected: "riscv/mulsi3.S", // Expected platform file for RISC-V 32 + }, + { + name: "RISC-V 64", + baseDir: "/another/dir", + target: "riscv64-unknown-elf", + expected: "addtf3.c", // Expected platform file for RISC-V 64 + }, + { + name: "ARM", + baseDir: "/arm/dir", + target: "armv7-unknown-linux-gnueabihf", + expected: "arm/aeabi_cdcmp.S", // Expected platform file for ARM + }, + { + name: "AVR", + baseDir: "/avr/dir", + target: "avr", + expected: "avr/divmodhi4.S", // Expected platform file for AVR + }, + { + name: "XTENSA", + baseDir: "/xtensa/dir", + target: "xtensa", + expected: "xtensa/ieee754_sqrtf.S", // Expected platform file for XTENSA + }, + } + needSkipDownload = true + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Get the compile configuration for this target + cfg := rtlib.GetCompilerRTCompileConfig(tt.baseDir, tt.target) + + // Verify there is at least one compile group + if len(cfg.Groups) == 0 { + t.Fatal("CompileConfig has no groups") + } + + group := cfg.Groups[0] + found := false + + // Check all files in the group + for _, file := range group.Files { + // Verify file path starts with baseDir + if !strings.HasPrefix(file, tt.baseDir) { + t.Errorf("File path %q should start with baseDir %q", file, tt.baseDir) + } + + // Verify file path contains the expected platform-specific file + if strings.Contains(file, tt.expected) { + found = true + + // Construct the expected full path + expectedPath := filepath.Join(tt.baseDir, "lib", "builtins", tt.expected) + + // Verify the actual path matches the expected path + if file != expectedPath { + t.Errorf("Expected file path %q, got %q", expectedPath, file) + } + } + } + + // Verify the platform-specific file was found + if !found { + t.Errorf("Expected platform-specific file %q not found in file list", tt.expected) + } + + // Verify the output file name format + expectedOutput := fmt.Sprintf("libclang_builtins-%s.a", tt.target) + if !strings.HasSuffix(group.OutputFileName, expectedOutput) { + t.Errorf("OutputFileName should end with %q, got %q", expectedOutput, group.OutputFileName) + } + }) + } +} + +// TestCompilerRTCompileConfigPathRelations tests the general path relationships +// in the CompileConfig for a specific target. +func TestCompilerRTCompileConfigPathRelations(t *testing.T) { + baseDir := "/test/base/dir" + target := "riscv64-unknown-elf" + + // Get the compile configuration + cfg := rtlib.GetCompilerRTCompileConfig(baseDir, target) + + // Verify there is at least one compile group + if len(cfg.Groups) == 0 { + t.Fatal("CompileConfig has no groups") + } + needSkipDownload = true + + group := cfg.Groups[0] + + // Check all files in the group + for _, file := range group.Files { + // Verify file path starts with baseDir + if !strings.HasPrefix(file, baseDir) { + t.Errorf("File path %q should start with baseDir %q", file, baseDir) + } + + // Verify file path contains the expected subdirectory structure + expectedSubdir := filepath.Join(baseDir, "lib", "builtins") + if !strings.Contains(file, expectedSubdir) { + t.Errorf("File path %q should contain %q", file, expectedSubdir) + } + } + + // Verify the output file name format + expectedOutput := fmt.Sprintf("libclang_builtins-%s.a", target) + if !strings.HasSuffix(group.OutputFileName, expectedOutput) { + t.Errorf("OutputFileName should end with %q, got %q", expectedOutput, group.OutputFileName) + } +} + +// TestGetPicolibcCompileConfigPaths tests that all paths in the CompileConfig +// are correctly based on the provided baseDir. +func TestGetPicolibcCompileConfigPaths(t *testing.T) { + // Define test cases with different base directories + tests := []struct { + name string + baseDir string + target string + }{ + { + name: "Unix-like path", + baseDir: "/test/base/dir", + target: "riscv64-unknown-elf", + }, + { + name: "Windows-like path", + baseDir: "C:\\test\\base\\dir", + target: "x86_64-pc-windows-msvc", + }, + { + name: "Relative path", + baseDir: "test/base/dir", + target: "armv7-unknown-linux-gnueabihf", + }, + } + needSkipDownload = true + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Get the compile configuration + cfg := libc.GetPicolibcCompileConfig(tt.baseDir, tt.target) + + // Verify ExportCFlags paths + for _, flag := range cfg.ExportCFlags { + if strings.HasPrefix(flag, "-I") || strings.HasPrefix(flag, "-isystem") { + path := strings.TrimPrefix(flag, "-I") + path = strings.TrimPrefix(path, "-isystem") + path = strings.TrimSpace(path) + + if !strings.HasPrefix(path, tt.baseDir) { + t.Errorf("ExportCFlags path %q should start with baseDir %q", path, tt.baseDir) + } + } + } + + // Verify there is at least one compile group + if len(cfg.Groups) == 0 { + t.Fatal("CompileConfig has no groups") + } + + group := cfg.Groups[0] + + // Verify output file name format + expectedOutput := fmt.Sprintf("libc-%s.a", tt.target) + if group.OutputFileName != expectedOutput { + t.Errorf("Expected OutputFileName %q, got %q", expectedOutput, group.OutputFileName) + } + + // Verify all file paths start with baseDir + for _, file := range group.Files { + if !strings.HasPrefix(file, tt.baseDir) { + t.Errorf("File path %q should start with baseDir %q", file, tt.baseDir) + } + + // Verify file path contains expected subdirectories + if !strings.Contains(file, filepath.Join(tt.baseDir, "newlib")) { + t.Errorf("File path %q should contain 'newlib' subdirectory", file) + } + } + + // Verify CFlags paths + for _, flag := range group.CFlags { + if strings.HasPrefix(flag, "-I") { + path := strings.TrimPrefix(flag, "-I") + path = strings.TrimSpace(path) + + if !strings.HasPrefix(path, tt.baseDir) { + t.Errorf("CFlags path %q should start with baseDir %q", path, tt.baseDir) + } + } + } + }) + } +} + +// TestGetPicolibcCompileConfigSpecificPaths tests specific path constructions +// in the CompileConfig for a given baseDir and target. +func TestGetPicolibcCompileConfigSpecificPaths(t *testing.T) { + baseDir := "/test/base/dir" + target := "riscv64-unknown-elf" + needSkipDownload = true + + // Get the compile configuration + cfg := libc.GetPicolibcCompileConfig(baseDir, target) + + // Verify ExportCFlags + expectedInclude := filepath.Join(baseDir, "newlib", "libc", "include") + foundInclude := false + for _, flag := range cfg.ExportCFlags { + if flag == "-I"+baseDir || flag == "-isystem"+expectedInclude { + foundInclude = true + } + } + if !foundInclude { + t.Errorf("Expected ExportCFlags to contain -I%s and -isystem%s", baseDir, expectedInclude) + } + + // Verify there is at least one compile group + if len(cfg.Groups) == 0 { + t.Fatal("CompileConfig has no groups") + } + + group := cfg.Groups[0] + + // Verify output file name + expectedOutput := fmt.Sprintf("libc-%s.a", target) + if group.OutputFileName != expectedOutput { + t.Errorf("Expected OutputFileName %q, got %q", expectedOutput, group.OutputFileName) + } + + // Verify specific file paths + expectedFiles := []string{ + 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 _, expected := range expectedFiles { + found := false + for _, file := range group.Files { + if file == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected file %q not found in file list", expected) + } + } + + // Verify CFlags paths + expectedCFlags := []string{ + "-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"), + } + + for _, expected := range expectedCFlags { + found := false + for _, flag := range group.CFlags { + if flag == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected CFlag %q not found", expected) + } + } +}