Implement llgo build mode support (#1197)

- Add BuildMode type with three build modes: exe, c-archive, c-shared
- Restrict buildmode flag to llgo build command only (not run/install/test)
- Implement build mode specific linker arguments:
  - c-shared: use -shared -fPIC flags
  - c-archive: use ar tool to create static archive
  - exe: default executable mode
- Add normalizeOutputPath function for platform-specific file naming conventions
- Generate C header files for library modes
- Fix buildmode flag conflict by removing from PassArgs
- Add comprehensive test coverage for all build modes
- Resolve duplicate logic between defaultAppExt and normalizeOutputPath

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Li Jie
2025-09-09 17:29:13 +08:00
parent d5ad4d997d
commit e05c8b9f46
9 changed files with 510 additions and 63 deletions

View File

@@ -364,3 +364,241 @@ func TestBuildOutFmtsNativeTarget(t *testing.T) {
})
}
}
func TestBuildOutFmtsBuildModes(t *testing.T) {
tests := []struct {
name string
pkgName string
buildMode BuildMode
outFile string
mode Mode
target string
goos string
appExt string
expectedOut string
}{
// C-Archive tests
{
name: "c_archive_build_linux",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "libmylib.a",
},
{
name: "c_archive_build_with_outfile",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "custom.a",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "libcustom.a",
},
{
name: "c_archive_build_with_path",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "build/custom.a",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "build/libcustom.a",
},
// C-Shared tests
{
name: "c_shared_build_linux",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".so",
expectedOut: "libmylib.so",
},
{
name: "c_shared_build_windows",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "windows",
appExt: ".dll",
expectedOut: "mylib.dll",
},
{
name: "c_shared_build_darwin",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "darwin",
appExt: ".dylib",
expectedOut: "libmylib.dylib",
},
{
name: "c_shared_embedded_target",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "rp2040",
goos: "windows",
appExt: ".so", // embedded follows linux rules
expectedOut: "libmylib.so",
},
// Executable tests
{
name: "exe_build_linux",
pkgName: "myapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: "",
expectedOut: "myapp",
},
{
name: "exe_build_windows",
pkgName: "myapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "windows",
appExt: ".exe",
expectedOut: "myapp.exe",
},
{
name: "exe_remove_lib_prefix",
pkgName: "libmyapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: "",
expectedOut: "myapp",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := &Config{
Mode: tt.mode,
BuildMode: tt.buildMode,
Target: tt.target,
Goos: tt.goos,
OutFile: tt.outFile,
AppExt: tt.appExt,
}
crossCompile := &crosscompile.Export{}
result, err := buildOutFmts(tt.pkgName, conf, false, crossCompile)
if err != nil {
t.Fatalf("buildOutFmts failed: %v", err)
}
if result.Out != tt.expectedOut {
t.Errorf("buildOutFmts(%q, buildMode=%v, target=%q, goos=%q) = %q, want %q",
tt.pkgName, tt.buildMode, tt.target, tt.goos, result.Out, tt.expectedOut)
}
})
}
}
func TestApplyBuildModeNaming(t *testing.T) {
tests := []struct {
name string
baseName string
buildMode BuildMode
target string
goos string
expected string
}{
// Executable tests
{
name: "exe_linux",
baseName: "myapp",
buildMode: BuildModeExe,
target: "",
goos: "linux",
expected: "myapp",
},
{
name: "exe_remove_lib_prefix",
baseName: "libmyapp",
buildMode: BuildModeExe,
target: "",
goos: "linux",
expected: "myapp",
},
// C-Archive tests
{
name: "c_archive_linux",
baseName: "mylib",
buildMode: BuildModeCArchive,
target: "",
goos: "linux",
expected: "libmylib",
},
{
name: "c_archive_existing_prefix",
baseName: "libmylib",
buildMode: BuildModeCArchive,
target: "",
goos: "linux",
expected: "libmylib",
},
// C-Shared tests
{
name: "c_shared_linux",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "",
goos: "linux",
expected: "libmylib",
},
{
name: "c_shared_windows",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "",
goos: "windows",
expected: "mylib", // Windows doesn't use lib prefix
},
{
name: "c_shared_embedded_rp2040",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "rp2040",
goos: "darwin",
expected: "libmylib", // embedded follows linux rules
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := applyPrefix(tt.baseName, tt.buildMode, tt.target, tt.goos)
if result != tt.expected {
t.Errorf("applyBuildModeNaming(%q, %v, %q, %q) = %q, want %q",
tt.baseName, tt.buildMode, tt.target, tt.goos, result, tt.expected)
}
})
}
}