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:
@@ -75,7 +75,7 @@ func PassBuildFlags(cmd *Command) *PassArgs {
|
|||||||
p.Bool("a")
|
p.Bool("a")
|
||||||
p.Bool("linkshared", "race", "msan", "asan",
|
p.Bool("linkshared", "race", "msan", "asan",
|
||||||
"trimpath", "work")
|
"trimpath", "work")
|
||||||
p.Var("p", "asmflags", "compiler", "buildmode",
|
p.Var("p", "asmflags", "compiler",
|
||||||
"gcflags", "gccgoflags", "installsuffix",
|
"gcflags", "gccgoflags", "installsuffix",
|
||||||
"ldflags", "pkgdir", "toolexec", "buildvcs")
|
"ldflags", "pkgdir", "toolexec", "buildvcs")
|
||||||
return p
|
return p
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func init() {
|
|||||||
|
|
||||||
flags.AddCommonFlags(&Cmd.Flag)
|
flags.AddCommonFlags(&Cmd.Flag)
|
||||||
flags.AddBuildFlags(&Cmd.Flag)
|
flags.AddBuildFlags(&Cmd.Flag)
|
||||||
|
flags.AddBuildModeFlags(&Cmd.Flag)
|
||||||
flags.AddEmulatorFlags(&Cmd.Flag)
|
flags.AddEmulatorFlags(&Cmd.Flag)
|
||||||
flags.AddEmbeddedFlags(&Cmd.Flag)
|
flags.AddEmbeddedFlags(&Cmd.Flag)
|
||||||
flags.AddOutputFlags(&Cmd.Flag)
|
flags.AddOutputFlags(&Cmd.Flag)
|
||||||
@@ -51,7 +52,10 @@ func runCmd(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := build.NewDefaultConf(build.ModeBuild)
|
conf := build.NewDefaultConf(build.ModeBuild)
|
||||||
flags.UpdateConfig(conf)
|
if err := flags.UpdateBuildConfig(conf); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
mockable.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
args = cmd.Flag.Args()
|
args = cmd.Flag.Args()
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func AddOutputFlags(fs *flag.FlagSet) {
|
|||||||
|
|
||||||
var Verbose bool
|
var Verbose bool
|
||||||
var BuildEnv string
|
var BuildEnv string
|
||||||
|
var BuildMode string
|
||||||
var Tags string
|
var Tags string
|
||||||
var Target string
|
var Target string
|
||||||
var Emulator bool
|
var Emulator bool
|
||||||
@@ -52,6 +53,10 @@ func AddBuildFlags(fs *flag.FlagSet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddBuildModeFlags(fs *flag.FlagSet) {
|
||||||
|
fs.StringVar(&BuildMode, "buildmode", "exe", "Build mode (exe, c-archive, c-shared)")
|
||||||
|
}
|
||||||
|
|
||||||
var Gen bool
|
var Gen bool
|
||||||
|
|
||||||
func AddEmulatorFlags(fs *flag.FlagSet) {
|
func AddEmulatorFlags(fs *flag.FlagSet) {
|
||||||
@@ -68,12 +73,13 @@ func AddCmpTestFlags(fs *flag.FlagSet) {
|
|||||||
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
|
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateConfig(conf *build.Config) {
|
func UpdateConfig(conf *build.Config) error {
|
||||||
conf.Tags = Tags
|
conf.Tags = Tags
|
||||||
conf.Verbose = Verbose
|
conf.Verbose = Verbose
|
||||||
conf.Target = Target
|
conf.Target = Target
|
||||||
conf.Port = Port
|
conf.Port = Port
|
||||||
conf.BaudRate = BaudRate
|
conf.BaudRate = BaudRate
|
||||||
|
|
||||||
switch conf.Mode {
|
switch conf.Mode {
|
||||||
case build.ModeBuild:
|
case build.ModeBuild:
|
||||||
conf.OutFile = OutputFile
|
conf.OutFile = OutputFile
|
||||||
@@ -99,4 +105,20 @@ func UpdateConfig(conf *build.Config) {
|
|||||||
conf.GenLL = GenLLFiles
|
conf.GenLL = GenLLFiles
|
||||||
conf.ForceEspClang = ForceEspClang
|
conf.ForceEspClang = ForceEspClang
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateBuildConfig(conf *build.Config) error {
|
||||||
|
// First apply common config
|
||||||
|
if err := UpdateConfig(conf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and set build mode
|
||||||
|
if err := build.ValidateBuildMode(BuildMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conf.BuildMode = build.BuildMode(BuildMode)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ func runCmd(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := build.NewDefaultConf(build.ModeInstall)
|
conf := build.NewDefaultConf(build.ModeInstall)
|
||||||
flags.UpdateConfig(conf)
|
if err := flags.UpdateConfig(conf); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
mockable.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
args = cmd.Flag.Args()
|
args = cmd.Flag.Args()
|
||||||
_, err := build.Do(args, conf)
|
_, err := build.Do(args, conf)
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ func runCmdEx(cmd *base.Command, args []string, mode build.Mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := build.NewDefaultConf(mode)
|
conf := build.NewDefaultConf(mode)
|
||||||
flags.UpdateConfig(conf)
|
if err := flags.UpdateConfig(conf); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
mockable.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
args = cmd.Flag.Args()
|
args = cmd.Flag.Args()
|
||||||
args, runArgs, err := parseRunArgs(args)
|
args, runArgs, err := parseRunArgs(args)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/goplus/llgo/cmd/internal/base"
|
"github.com/goplus/llgo/cmd/internal/base"
|
||||||
"github.com/goplus/llgo/cmd/internal/flags"
|
"github.com/goplus/llgo/cmd/internal/flags"
|
||||||
"github.com/goplus/llgo/internal/build"
|
"github.com/goplus/llgo/internal/build"
|
||||||
|
"github.com/goplus/llgo/internal/mockable"
|
||||||
)
|
)
|
||||||
|
|
||||||
// llgo test
|
// llgo test
|
||||||
@@ -30,12 +31,15 @@ func runCmd(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := build.NewDefaultConf(build.ModeTest)
|
conf := build.NewDefaultConf(build.ModeTest)
|
||||||
flags.UpdateConfig(conf)
|
if err := flags.UpdateConfig(conf); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
mockable.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
args = cmd.Flag.Args()
|
args = cmd.Flag.Args()
|
||||||
_, err := build.Do(args, conf)
|
_, err := build.Do(args, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
mockable.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,24 @@ const (
|
|||||||
ModeGen
|
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
|
type AbiMode = cabi.Mode
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -104,6 +122,7 @@ type Config struct {
|
|||||||
BaudRate int // baudrate for serial communication
|
BaudRate int // baudrate for serial communication
|
||||||
RunArgs []string
|
RunArgs []string
|
||||||
Mode Mode
|
Mode Mode
|
||||||
|
BuildMode BuildMode // Build mode: exe, c-archive, c-shared
|
||||||
AbiMode AbiMode
|
AbiMode AbiMode
|
||||||
GenExpect bool // only valid for ModeCmpTest
|
GenExpect bool // only valid for ModeCmpTest
|
||||||
Verbose bool
|
Verbose bool
|
||||||
@@ -136,11 +155,12 @@ func NewDefaultConf(mode Mode) *Config {
|
|||||||
goarch = runtime.GOARCH
|
goarch = runtime.GOARCH
|
||||||
}
|
}
|
||||||
conf := &Config{
|
conf := &Config{
|
||||||
Goos: goos,
|
Goos: goos,
|
||||||
Goarch: goarch,
|
Goarch: goarch,
|
||||||
BinPath: bin,
|
BinPath: bin,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
AbiMode: cabi.ModeAllFunc,
|
BuildMode: BuildModeExe,
|
||||||
|
AbiMode: cabi.ModeAllFunc,
|
||||||
}
|
}
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
@@ -156,23 +176,6 @@ func envGOPATH() (string, error) {
|
|||||||
return filepath.Join(home, "go"), nil
|
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 (
|
const (
|
||||||
@@ -361,12 +364,21 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
|||||||
return nil, err
|
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)
|
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
envMap := outFmts.ToEnvMap()
|
||||||
|
|
||||||
// Only convert formats when Target is specified
|
// 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 {
|
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 := []string{"-o", app}
|
||||||
buildArgs = append(buildArgs, linkArgs...)
|
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
|
// Add common linker arguments based on target OS and architecture
|
||||||
if IsDbgSymsEnabled() {
|
if IsDbgSymsEnabled() {
|
||||||
buildArgs = append(buildArgs, "-gdwarf-4")
|
buildArgs = append(buildArgs, "-gdwarf-4")
|
||||||
@@ -813,6 +881,27 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
|
|||||||
return cmd.Link(buildArgs...)
|
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 {
|
func isWasmTarget(goos string) bool {
|
||||||
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
|
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,46 +47,94 @@ func setOutFmt(conf *Config, formatName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildOutFmts creates OutFmtDetails based on package, configuration and multi-package status
|
// buildOutFmts creates OutFmtDetails based on package, configuration and multi-package status
|
||||||
|
// determineBaseNameAndDir extracts the base name and directory from configuration
|
||||||
|
func determineBaseNameAndDir(pkgName string, conf *Config, multiPkg bool) (baseName, dir string) {
|
||||||
|
switch conf.Mode {
|
||||||
|
case ModeInstall:
|
||||||
|
return pkgName, conf.BinPath
|
||||||
|
case ModeBuild:
|
||||||
|
if !multiPkg && conf.OutFile != "" {
|
||||||
|
dir = filepath.Dir(conf.OutFile)
|
||||||
|
baseName = strings.TrimSuffix(filepath.Base(conf.OutFile), conf.AppExt)
|
||||||
|
if dir == "." {
|
||||||
|
dir = ""
|
||||||
|
}
|
||||||
|
return baseName, dir
|
||||||
|
}
|
||||||
|
return pkgName, ""
|
||||||
|
}
|
||||||
|
// Other modes (run, test, etc.)
|
||||||
|
return pkgName, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyPrefix applies build mode specific naming conventions
|
||||||
|
func applyPrefix(baseName string, buildMode BuildMode, target string, goos string) string {
|
||||||
|
// Determine the effective OS for naming conventions
|
||||||
|
effectiveGoos := goos
|
||||||
|
if target != "" {
|
||||||
|
// Embedded targets follow Linux conventions
|
||||||
|
effectiveGoos = "linux"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch buildMode {
|
||||||
|
case BuildModeCArchive:
|
||||||
|
// Static libraries: libname.a (add lib prefix if missing)
|
||||||
|
if !strings.HasPrefix(baseName, "lib") {
|
||||||
|
return "lib" + baseName
|
||||||
|
}
|
||||||
|
return baseName
|
||||||
|
|
||||||
|
case BuildModeCShared:
|
||||||
|
// Shared libraries: libname.so/libname.dylib (add lib prefix if missing, except on Windows)
|
||||||
|
if effectiveGoos != "windows" && !strings.HasPrefix(baseName, "lib") {
|
||||||
|
return "lib" + baseName
|
||||||
|
}
|
||||||
|
return baseName
|
||||||
|
|
||||||
|
case BuildModeExe:
|
||||||
|
// Executables: name or name.exe (no lib prefix)
|
||||||
|
if strings.HasPrefix(baseName, "lib") {
|
||||||
|
return strings.TrimPrefix(baseName, "lib")
|
||||||
|
}
|
||||||
|
return baseName
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseName
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOutputPath creates the final output path from baseName, dir and other parameters
|
||||||
|
func buildOutputPath(baseName, dir string, conf *Config, multiPkg bool, appExt string) (string, error) {
|
||||||
|
baseName = applyPrefix(baseName, conf.BuildMode, conf.Target, conf.Goos)
|
||||||
|
|
||||||
|
if dir != "" {
|
||||||
|
return filepath.Join(dir, baseName+appExt), nil
|
||||||
|
} else if (conf.Mode == ModeBuild && multiPkg) || (conf.Mode != ModeBuild && conf.Mode != ModeInstall) {
|
||||||
|
return genTempOutputFile(baseName, appExt)
|
||||||
|
} else {
|
||||||
|
return baseName + appExt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) {
|
func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) {
|
||||||
details := &OutFmtDetails{}
|
details := &OutFmtDetails{}
|
||||||
var err error
|
|
||||||
|
// Determine base name and directory
|
||||||
|
baseName, dir := determineBaseNameAndDir(pkgName, conf, multiPkg)
|
||||||
|
|
||||||
|
// Build output path
|
||||||
|
outputPath, err := buildOutputPath(baseName, dir, conf, multiPkg, conf.AppExt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
details.Out = outputPath
|
||||||
|
|
||||||
if conf.Target == "" {
|
if conf.Target == "" {
|
||||||
// Native target
|
// Native target - we're done
|
||||||
if conf.Mode == ModeInstall {
|
|
||||||
details.Out = filepath.Join(conf.BinPath, pkgName+conf.AppExt)
|
|
||||||
} else if conf.Mode == ModeBuild && !multiPkg && conf.OutFile != "" {
|
|
||||||
base := strings.TrimSuffix(conf.OutFile, conf.AppExt)
|
|
||||||
details.Out = base + conf.AppExt
|
|
||||||
} else if conf.Mode == ModeBuild && !multiPkg {
|
|
||||||
details.Out = pkgName + conf.AppExt
|
|
||||||
} else {
|
|
||||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return details, nil
|
return details, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
needRun := slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode)
|
needRun := slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode)
|
||||||
|
|
||||||
if multiPkg {
|
|
||||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if conf.OutFile != "" {
|
|
||||||
base := strings.TrimSuffix(conf.OutFile, conf.AppExt)
|
|
||||||
details.Out = base + conf.AppExt
|
|
||||||
} else if conf.Mode == ModeBuild {
|
|
||||||
details.Out = pkgName + conf.AppExt
|
|
||||||
} else {
|
|
||||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check emulator format if emulator mode is enabled
|
// Check emulator format if emulator mode is enabled
|
||||||
outFmt := ""
|
outFmt := ""
|
||||||
if needRun {
|
if needRun {
|
||||||
@@ -163,3 +211,39 @@ func (details *OutFmtDetails) ToEnvMap() map[string]string {
|
|||||||
|
|
||||||
return envMap
|
return envMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultAppExt(conf *Config) string {
|
||||||
|
// Handle build mode specific extensions first
|
||||||
|
switch conf.BuildMode {
|
||||||
|
case BuildModeCArchive:
|
||||||
|
return ".a"
|
||||||
|
case BuildModeCShared:
|
||||||
|
switch conf.Goos {
|
||||||
|
case "windows":
|
||||||
|
return ".dll"
|
||||||
|
case "darwin":
|
||||||
|
return ".dylib"
|
||||||
|
default:
|
||||||
|
return ".so"
|
||||||
|
}
|
||||||
|
case BuildModeExe:
|
||||||
|
// For executable mode, handle target-specific logic
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should not be reached, but kept for safety
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user