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:
@@ -66,6 +66,24 @@ const (
|
||||
ModeGen
|
||||
)
|
||||
|
||||
type BuildMode string
|
||||
|
||||
const (
|
||||
BuildModeExe BuildMode = "exe"
|
||||
BuildModeCArchive BuildMode = "c-archive"
|
||||
BuildModeCShared BuildMode = "c-shared"
|
||||
)
|
||||
|
||||
// ValidateBuildMode checks if the build mode is valid
|
||||
func ValidateBuildMode(mode string) error {
|
||||
switch BuildMode(mode) {
|
||||
case BuildModeExe, BuildModeCArchive, BuildModeCShared:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid build mode %q, must be one of: exe, c-archive, c-shared", mode)
|
||||
}
|
||||
}
|
||||
|
||||
type AbiMode = cabi.Mode
|
||||
|
||||
const (
|
||||
@@ -104,6 +122,7 @@ type Config struct {
|
||||
BaudRate int // baudrate for serial communication
|
||||
RunArgs []string
|
||||
Mode Mode
|
||||
BuildMode BuildMode // Build mode: exe, c-archive, c-shared
|
||||
AbiMode AbiMode
|
||||
GenExpect bool // only valid for ModeCmpTest
|
||||
Verbose bool
|
||||
@@ -136,11 +155,12 @@ func NewDefaultConf(mode Mode) *Config {
|
||||
goarch = runtime.GOARCH
|
||||
}
|
||||
conf := &Config{
|
||||
Goos: goos,
|
||||
Goarch: goarch,
|
||||
BinPath: bin,
|
||||
Mode: mode,
|
||||
AbiMode: cabi.ModeAllFunc,
|
||||
Goos: goos,
|
||||
Goarch: goarch,
|
||||
BinPath: bin,
|
||||
Mode: mode,
|
||||
BuildMode: BuildModeExe,
|
||||
AbiMode: cabi.ModeAllFunc,
|
||||
}
|
||||
return conf
|
||||
}
|
||||
@@ -156,23 +176,6 @@ func envGOPATH() (string, error) {
|
||||
return filepath.Join(home, "go"), nil
|
||||
}
|
||||
|
||||
func defaultAppExt(conf *Config) string {
|
||||
if conf.Target != "" {
|
||||
if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") {
|
||||
return ".wasm"
|
||||
}
|
||||
return ".elf"
|
||||
}
|
||||
|
||||
switch conf.Goos {
|
||||
case "windows":
|
||||
return ".exe"
|
||||
case "wasi", "wasip1", "js":
|
||||
return ".wasm"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
@@ -361,12 +364,21 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Link main package using the base output path
|
||||
// Link main package using the output path from buildOutFmts
|
||||
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate C headers for c-archive and c-shared modes before linking
|
||||
if ctx.buildConf.BuildMode == BuildModeCArchive || ctx.buildConf.BuildMode == BuildModeCShared {
|
||||
headerErr := generateCHeader(ctx, pkg, outFmts.Out, verbose)
|
||||
if headerErr != nil {
|
||||
return nil, headerErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
envMap := outFmts.ToEnvMap()
|
||||
|
||||
// Only convert formats when Target is specified
|
||||
@@ -794,13 +806,69 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
}
|
||||
}
|
||||
|
||||
return linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose)
|
||||
err = linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(lijie): export C header from function list of the pkg
|
||||
func generateCHeader(ctx *context, pkg *packages.Package, outputPath string, verbose bool) error {
|
||||
// Determine header file path
|
||||
headerPath := strings.TrimSuffix(outputPath, filepath.Ext(outputPath)) + ".h"
|
||||
|
||||
// Generate header content
|
||||
headerContent := fmt.Sprintf(`/* Code generated by llgo; DO NOT EDIT. */
|
||||
|
||||
#ifndef __%s_H_
|
||||
#define __%s_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __%s_H_ */
|
||||
`,
|
||||
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")),
|
||||
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")),
|
||||
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")))
|
||||
|
||||
// Write header file
|
||||
err := os.WriteFile(headerPath, []byte(headerContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write header file %s: %w", headerPath, err)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "Generated C header: %s\n", headerPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
|
||||
// Handle c-archive mode differently - use ar tool instead of linker
|
||||
if ctx.buildConf.BuildMode == BuildModeCArchive {
|
||||
return createStaticArchive(ctx, app, objFiles, verbose)
|
||||
}
|
||||
|
||||
buildArgs := []string{"-o", app}
|
||||
buildArgs = append(buildArgs, linkArgs...)
|
||||
|
||||
// Add build mode specific linker arguments
|
||||
switch ctx.buildConf.BuildMode {
|
||||
case BuildModeCShared:
|
||||
buildArgs = append(buildArgs, "-shared", "-fPIC")
|
||||
case BuildModeExe:
|
||||
// Default executable mode, no additional flags needed
|
||||
}
|
||||
|
||||
// Add common linker arguments based on target OS and architecture
|
||||
if IsDbgSymsEnabled() {
|
||||
buildArgs = append(buildArgs, "-gdwarf-4")
|
||||
@@ -813,6 +881,27 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
|
||||
return cmd.Link(buildArgs...)
|
||||
}
|
||||
|
||||
func createStaticArchive(ctx *context, archivePath string, objFiles []string, verbose bool) error {
|
||||
// Use ar tool to create static archive
|
||||
args := []string{"rcs", archivePath}
|
||||
args = append(args, objFiles...)
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "ar %s\n", strings.Join(args, " "))
|
||||
}
|
||||
|
||||
cmd := exec.Command("ar", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if verbose && len(output) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "ar output: %s\n", output)
|
||||
}
|
||||
return fmt.Errorf("ar command failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isWasmTarget(goos string) bool {
|
||||
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user