Build and run for embeded
This commit is contained in:
@@ -19,6 +19,7 @@ var Verbose bool
|
|||||||
var BuildEnv string
|
var BuildEnv string
|
||||||
var Tags string
|
var Tags string
|
||||||
var Target string
|
var Target string
|
||||||
|
var Emulator bool
|
||||||
var AbiMode int
|
var AbiMode int
|
||||||
var CheckLinkArgs bool
|
var CheckLinkArgs bool
|
||||||
var CheckLLFiles bool
|
var CheckLLFiles bool
|
||||||
@@ -39,6 +40,10 @@ func AddBuildFlags(fs *flag.FlagSet) {
|
|||||||
|
|
||||||
var Gen bool
|
var Gen bool
|
||||||
|
|
||||||
|
func AddRunFlags(fs *flag.FlagSet) {
|
||||||
|
fs.BoolVar(&Emulator, "emulator", false, "Run in emulator mode")
|
||||||
|
}
|
||||||
|
|
||||||
func AddCmpTestFlags(fs *flag.FlagSet) {
|
func AddCmpTestFlags(fs *flag.FlagSet) {
|
||||||
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
|
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
|
||||||
}
|
}
|
||||||
@@ -51,6 +56,8 @@ func UpdateConfig(conf *build.Config) {
|
|||||||
case build.ModeBuild:
|
case build.ModeBuild:
|
||||||
conf.OutFile = OutputFile
|
conf.OutFile = OutputFile
|
||||||
conf.FileFormat = FileFormat
|
conf.FileFormat = FileFormat
|
||||||
|
case build.ModeRun:
|
||||||
|
conf.Emulator = Emulator
|
||||||
case build.ModeCmpTest:
|
case build.ModeCmpTest:
|
||||||
conf.GenExpect = Gen
|
conf.GenExpect = Gen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func init() {
|
|||||||
base.PassBuildFlags(Cmd)
|
base.PassBuildFlags(Cmd)
|
||||||
flags.AddBuildFlags(&Cmd.Flag)
|
flags.AddBuildFlags(&Cmd.Flag)
|
||||||
flags.AddBuildFlags(&CmpTestCmd.Flag)
|
flags.AddBuildFlags(&CmpTestCmd.Flag)
|
||||||
|
flags.AddRunFlags(&Cmd.Flag)
|
||||||
flags.AddCmpTestFlags(&CmpTestCmd.Flag)
|
flags.AddCmpTestFlags(&CmpTestCmd.Flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
doc/Embedded_Cmd.md
Normal file
52
doc/Embedded_Cmd.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# LLGo Embedded Development Command Line Options
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
- `-o <file>` - Specify output file name
|
||||||
|
- `-target <platform>` - Specify target platform for cross-compilation
|
||||||
|
- `-file-format <format>` - Convert to specified format (**requires `-target`**)
|
||||||
|
- Supported: `elf` (default), `bin`, `hex`, `uf2`, `zip`, `img`
|
||||||
|
- `-emulator` - Run using emulator (auto-detects required format)
|
||||||
|
- `-d <device>` - Target device for flashing or testing
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### llgo build
|
||||||
|
Compile program to output file.
|
||||||
|
- No `-target`: Native executable
|
||||||
|
- With `-target`: ELF executable (or `-file-format` if specified)
|
||||||
|
|
||||||
|
### llgo run
|
||||||
|
Compile and run program.
|
||||||
|
- No `-target`: Run locally
|
||||||
|
- With `-target`: Run on device or emulator
|
||||||
|
|
||||||
|
### llgo test
|
||||||
|
Compile and run tests.
|
||||||
|
- No `-target`: Run tests locally
|
||||||
|
- With `-target`: Run tests on device or emulator
|
||||||
|
- Supports `-emulator` and `-d` flags
|
||||||
|
|
||||||
|
### llgo install
|
||||||
|
Install program or flash to device.
|
||||||
|
- No `-target`: Install to `$GOPATH/bin`
|
||||||
|
- With `-target`: Flash to device (use `-d` to specify device)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Native development
|
||||||
|
llgo build hello.go # -> hello
|
||||||
|
llgo build -o myapp hello.go # -> myapp
|
||||||
|
llgo run hello.go # run locally
|
||||||
|
llgo install hello.go # install to bin
|
||||||
|
|
||||||
|
# Cross-compilation
|
||||||
|
llgo build -target esp32 hello.go # -> hello (ELF)
|
||||||
|
llgo build -target esp32 -file-format bin hello.go # -> hello.bin
|
||||||
|
llgo run -target esp32 hello.go # run on ESP32
|
||||||
|
llgo run -target esp32 -emulator hello.go # run in emulator
|
||||||
|
llgo test -target esp32 -d /dev/ttyUSB0 # run tests on device
|
||||||
|
llgo test -target esp32 -emulator # run tests in emulator
|
||||||
|
llgo install -target esp32 -d /dev/ttyUSB0 hello.go # flash to specific device
|
||||||
|
```
|
||||||
@@ -79,6 +79,7 @@ type Config struct {
|
|||||||
AppExt string // ".exe" on Windows, empty on Unix
|
AppExt string // ".exe" on Windows, empty on Unix
|
||||||
OutFile string // only valid for ModeBuild when len(pkgs) == 1
|
OutFile string // only valid for ModeBuild when len(pkgs) == 1
|
||||||
FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default
|
FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default
|
||||||
|
Emulator bool // only valid for ModeRun - run in emulator mode
|
||||||
RunArgs []string // only valid for ModeRun
|
RunArgs []string // only valid for ModeRun
|
||||||
Mode Mode
|
Mode Mode
|
||||||
AbiMode AbiMode
|
AbiMode AbiMode
|
||||||
@@ -628,83 +629,18 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
|
|||||||
return objFiles, nil
|
return objFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateOutputFilenames generates the final output filename (app) and intermediate filename (orgApp)
|
|
||||||
// based on configuration and build context.
|
|
||||||
func generateOutputFilenames(outFile, binPath, appExt, binExt, pkgName string, mode Mode, isMultiplePkgs bool) (app, orgApp string, err error) {
|
|
||||||
if outFile == "" {
|
|
||||||
if mode == ModeBuild && isMultiplePkgs {
|
|
||||||
// For multiple packages in ModeBuild mode, use temporary file
|
|
||||||
name := pkgName
|
|
||||||
if binExt != "" {
|
|
||||||
name += "*" + binExt
|
|
||||||
} else {
|
|
||||||
name += "*" + appExt
|
|
||||||
}
|
|
||||||
tmpFile, err := os.CreateTemp("", name)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
app = tmpFile.Name()
|
|
||||||
tmpFile.Close()
|
|
||||||
} else {
|
|
||||||
app = filepath.Join(binPath, pkgName+appExt)
|
|
||||||
}
|
|
||||||
orgApp = app
|
|
||||||
} else {
|
|
||||||
// outFile is not empty, use it as base part
|
|
||||||
base := outFile
|
|
||||||
if binExt != "" {
|
|
||||||
// If binExt has value, use temporary file as orgApp for firmware conversion
|
|
||||||
tmpFile, err := os.CreateTemp("", "llgo-*"+appExt)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
orgApp = tmpFile.Name()
|
|
||||||
tmpFile.Close()
|
|
||||||
// Check if base already ends with binExt, if so, don't add it again
|
|
||||||
if strings.HasSuffix(base, binExt) {
|
|
||||||
app = base
|
|
||||||
} else {
|
|
||||||
app = base + binExt
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No binExt, use base + AppExt directly
|
|
||||||
if filepath.Ext(base) == "" {
|
|
||||||
app = base + appExt
|
|
||||||
} else {
|
|
||||||
app = base
|
|
||||||
}
|
|
||||||
orgApp = app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return app, orgApp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) {
|
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) {
|
||||||
pkgPath := pkg.PkgPath
|
pkgPath := pkg.PkgPath
|
||||||
name := path.Base(pkgPath)
|
name := path.Base(pkgPath)
|
||||||
binFmt := ctx.crossCompile.BinaryFormat
|
binFmt := ctx.crossCompile.BinaryFormat
|
||||||
binExt := firmware.BinaryExt(binFmt)
|
|
||||||
|
|
||||||
// Determine final output extension from user-specified file format
|
// Generate output configuration using the centralized function
|
||||||
outExt := binExt
|
outputCfg, err := GenOutputs(conf, name, len(ctx.initial) > 1, ctx.crossCompile.Emulator, binFmt)
|
||||||
if conf.FileFormat != "" {
|
|
||||||
outExt = firmware.GetFileExtFromFormat(conf.FileFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// app: converted firmware output file or executable file
|
|
||||||
// orgApp: before converted output file
|
|
||||||
app, orgApp, err := generateOutputFilenames(
|
|
||||||
conf.OutFile,
|
|
||||||
conf.BinPath,
|
|
||||||
conf.AppExt,
|
|
||||||
outExt,
|
|
||||||
name,
|
|
||||||
mode,
|
|
||||||
len(ctx.initial) > 1,
|
|
||||||
)
|
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
|
app := outputCfg.OutPath
|
||||||
|
orgApp := outputCfg.IntPath
|
||||||
|
|
||||||
needRuntime := false
|
needRuntime := false
|
||||||
needPyInit := false
|
needPyInit := false
|
||||||
pkgsMap := make(map[*packages.Package]*aPackage, len(pkgs))
|
pkgsMap := make(map[*packages.Package]*aPackage, len(pkgs))
|
||||||
@@ -769,28 +705,27 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
// Handle firmware conversion and file format conversion
|
// Handle firmware conversion and file format conversion
|
||||||
currentApp := orgApp
|
currentApp := orgApp
|
||||||
|
|
||||||
// Determine if firmware conversion is needed based on mode
|
useEmulator := false
|
||||||
needFirmwareConversion := false
|
if mode == ModeRun && conf.Emulator {
|
||||||
if mode == ModeBuild {
|
if ctx.crossCompile.Emulator == "" {
|
||||||
// For build command, do firmware conversion if file-format is specified
|
panic(fmt.Errorf("target %s does not have emulator configured", conf.Target))
|
||||||
needFirmwareConversion = conf.FileFormat != ""
|
}
|
||||||
} else {
|
useEmulator = true
|
||||||
// For run and install commands, do firmware conversion if binExt is set
|
|
||||||
needFirmwareConversion = binExt != ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Firmware conversion if needed
|
// Step 1: Firmware conversion if needed
|
||||||
if needFirmwareConversion {
|
if outputCfg.NeedFwGen {
|
||||||
if outExt == binExt {
|
if outputCfg.DirectGen {
|
||||||
// Direct conversion to final output
|
// Direct conversion to final output (including .img case)
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", ctx.crossCompile.BinaryFormat, currentApp, app)
|
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", outputCfg.BinFmt, currentApp, app)
|
||||||
}
|
}
|
||||||
err = firmware.MakeFirmwareImage(currentApp, app, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
err = firmware.MakeFirmwareImage(currentApp, app, outputCfg.BinFmt, ctx.crossCompile.FormatDetail)
|
||||||
check(err)
|
check(err)
|
||||||
currentApp = app
|
currentApp = app
|
||||||
} else {
|
} else {
|
||||||
// Convert to intermediate file first
|
// Convert to intermediate file first
|
||||||
|
binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat)
|
||||||
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
|
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
|
||||||
check(err)
|
check(err)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
@@ -813,12 +748,12 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
|
|
||||||
// Step 2: File format conversion if needed
|
// Step 2: File format conversion if needed
|
||||||
if currentApp != app {
|
if currentApp != app {
|
||||||
if conf.FileFormat != "" {
|
if outputCfg.FileFmt != "" {
|
||||||
// File format conversion
|
// File format conversion
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Fprintf(os.Stderr, "Converting to file format: %s (%s -> %s)\n", conf.FileFormat, currentApp, app)
|
fmt.Fprintf(os.Stderr, "Converting to file format: %s (%s -> %s)\n", outputCfg.FileFmt, currentApp, app)
|
||||||
}
|
}
|
||||||
err = firmware.ConvertOutput(currentApp, app, binFmt, conf.FileFormat)
|
err = firmware.ConvertOutput(currentApp, app, binFmt, outputCfg.FileFmt)
|
||||||
check(err)
|
check(err)
|
||||||
} else {
|
} else {
|
||||||
// Just move/copy the file
|
// Just move/copy the file
|
||||||
@@ -845,6 +780,12 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ModeRun:
|
case ModeRun:
|
||||||
|
if useEmulator {
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Using emulator: %s\n", ctx.crossCompile.Emulator)
|
||||||
|
}
|
||||||
|
runInEmulator(app, ctx.crossCompile.Emulator, conf.RunArgs, verbose)
|
||||||
|
} else {
|
||||||
args := make([]string, 0, len(conf.RunArgs)+1)
|
args := make([]string, 0, len(conf.RunArgs)+1)
|
||||||
copy(args, conf.RunArgs)
|
copy(args, conf.RunArgs)
|
||||||
if isWasmTarget(conf.Goos) {
|
if isWasmTarget(conf.Goos) {
|
||||||
@@ -879,6 +820,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
if s := cmd.ProcessState; s != nil {
|
if s := cmd.ProcessState; s != nil {
|
||||||
mockable.Exit(s.ExitCode())
|
mockable.Exit(s.ExitCode())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case ModeCmpTest:
|
case ModeCmpTest:
|
||||||
cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs)
|
cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs)
|
||||||
}
|
}
|
||||||
@@ -1371,6 +1313,57 @@ func findDylibDep(exe, lib string) string {
|
|||||||
|
|
||||||
type none struct{}
|
type none struct{}
|
||||||
|
|
||||||
|
// runInEmulator runs the application in emulator by formatting the emulator command template
|
||||||
|
func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose bool) {
|
||||||
|
// Build environment map for template variable expansion
|
||||||
|
envs := map[string]string{
|
||||||
|
"": appPath, // {} expands to app path
|
||||||
|
"bin": appPath,
|
||||||
|
"hex": appPath,
|
||||||
|
"zip": appPath,
|
||||||
|
"img": appPath,
|
||||||
|
"uf2": appPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the emulator command template
|
||||||
|
emulatorCmd := emulatorTemplate
|
||||||
|
for placeholder, path := range envs {
|
||||||
|
var target string
|
||||||
|
if placeholder == "" {
|
||||||
|
target = "{}"
|
||||||
|
} else {
|
||||||
|
target = "{" + placeholder + "}"
|
||||||
|
}
|
||||||
|
emulatorCmd = strings.ReplaceAll(emulatorCmd, target, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Running in emulator: %s\n", emulatorCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command and arguments
|
||||||
|
cmdParts := strings.Fields(emulatorCmd)
|
||||||
|
if len(cmdParts) == 0 {
|
||||||
|
panic(fmt.Errorf("empty emulator command"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add run arguments to the end
|
||||||
|
cmdParts = append(cmdParts, runArgs...)
|
||||||
|
|
||||||
|
// Execute the emulator command
|
||||||
|
cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if s := cmd.ProcessState; s != nil {
|
||||||
|
mockable.Exit(s.ExitCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func check(err error) {
|
func check(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goplus/llgo/internal/mockable"
|
"github.com/goplus/llgo/internal/mockable"
|
||||||
@@ -95,172 +94,4 @@ func TestCmpTest(t *testing.T) {
|
|||||||
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
|
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateOutputFilenames(t *testing.T) {
|
// TestGenerateOutputFilenames removed - functionality moved to filename_test.go
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
outFile string
|
|
||||||
binPath string
|
|
||||||
appExt string
|
|
||||||
binExt string
|
|
||||||
pkgName string
|
|
||||||
mode Mode
|
|
||||||
isMultiplePkgs bool
|
|
||||||
wantAppSuffix string
|
|
||||||
wantOrgAppDiff bool // true if orgApp should be different from app
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty outFile, single package",
|
|
||||||
outFile: "",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: "",
|
|
||||||
binExt: "",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "/usr/local/bin/hello",
|
|
||||||
wantOrgAppDiff: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty outFile with appExt",
|
|
||||||
outFile: "",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: "",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "/usr/local/bin/hello.exe",
|
|
||||||
wantOrgAppDiff: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "outFile without binExt",
|
|
||||||
outFile: "myapp",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: "",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "myapp.exe",
|
|
||||||
wantOrgAppDiff: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "outFile with existing extension, no binExt",
|
|
||||||
outFile: "myapp.exe",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: "",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "myapp.exe",
|
|
||||||
wantOrgAppDiff: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "outFile with binExt, different from existing extension",
|
|
||||||
outFile: "myapp",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: ".bin",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "myapp.bin",
|
|
||||||
wantOrgAppDiff: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "outFile already ends with binExt",
|
|
||||||
outFile: "t.bin",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: ".bin",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "t.bin",
|
|
||||||
wantOrgAppDiff: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "outFile with full path already ends with binExt",
|
|
||||||
outFile: "/path/to/t.bin",
|
|
||||||
binPath: "/usr/local/bin",
|
|
||||||
appExt: ".exe",
|
|
||||||
binExt: ".bin",
|
|
||||||
pkgName: "hello",
|
|
||||||
mode: ModeBuild,
|
|
||||||
isMultiplePkgs: false,
|
|
||||||
wantAppSuffix: "/path/to/t.bin",
|
|
||||||
wantOrgAppDiff: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
app, orgApp, err := generateOutputFilenames(
|
|
||||||
tt.outFile,
|
|
||||||
tt.binPath,
|
|
||||||
tt.appExt,
|
|
||||||
tt.binExt,
|
|
||||||
tt.pkgName,
|
|
||||||
tt.mode,
|
|
||||||
tt.isMultiplePkgs,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("generateOutputFilenames() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantAppSuffix != "" {
|
|
||||||
if app != tt.wantAppSuffix {
|
|
||||||
t.Errorf("generateOutputFilenames() app = %v, want %v", app, tt.wantAppSuffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantOrgAppDiff {
|
|
||||||
if app == orgApp {
|
|
||||||
t.Errorf("generateOutputFilenames() orgApp should be different from app, but both are %v", app)
|
|
||||||
}
|
|
||||||
// Clean up temp file
|
|
||||||
if orgApp != "" && strings.Contains(orgApp, "llgo-") {
|
|
||||||
os.Remove(orgApp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if app != orgApp {
|
|
||||||
t.Errorf("generateOutputFilenames() orgApp = %v, want %v (same as app)", orgApp, app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateOutputFilenames_EdgeCases(t *testing.T) {
|
|
||||||
// Test case where outFile has same extension as binExt
|
|
||||||
app, orgApp, err := generateOutputFilenames(
|
|
||||||
"firmware.bin",
|
|
||||||
"/usr/local/bin",
|
|
||||||
".exe",
|
|
||||||
".bin",
|
|
||||||
"esp32app",
|
|
||||||
ModeBuild,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if app != "firmware.bin" {
|
|
||||||
t.Errorf("Expected app to be 'firmware.bin', got '%s'", app)
|
|
||||||
}
|
|
||||||
|
|
||||||
if app == orgApp {
|
|
||||||
t.Errorf("Expected orgApp to be different from app when binExt is present, but both are '%s'", app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up temp file
|
|
||||||
if orgApp != "" && strings.Contains(orgApp, "llgo-") {
|
|
||||||
os.Remove(orgApp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
192
internal/build/outputs.go
Normal file
192
internal/build/outputs.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/internal/firmware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutputCfg contains the generated output paths and conversion configuration
|
||||||
|
type OutputCfg struct {
|
||||||
|
OutPath string // Final output file path
|
||||||
|
IntPath string // Intermediate file path (for two-stage conversion)
|
||||||
|
OutExt string // Output file extension
|
||||||
|
FileFmt string // File format (from conf.FileFormat or extracted from emulator)
|
||||||
|
BinFmt string // Binary format for firmware conversion (may have -img suffix)
|
||||||
|
NeedFwGen bool // Whether firmware image generation is needed
|
||||||
|
DirectGen bool // True if can generate firmware directly without intermediate file
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenOutputs generates appropriate output paths based on the configuration
|
||||||
|
func GenOutputs(conf *Config, pkgName string, multiPkg bool, emulator, binFmt string) (OutputCfg, error) {
|
||||||
|
var cfg OutputCfg
|
||||||
|
|
||||||
|
// Calculate binary extension and set up format info
|
||||||
|
binExt := firmware.BinaryExt(binFmt)
|
||||||
|
cfg.BinFmt = binFmt
|
||||||
|
|
||||||
|
// Determine output format and extension
|
||||||
|
cfg.FileFmt, cfg.OutExt = determineFormat(conf, emulator)
|
||||||
|
|
||||||
|
// Handle special .img case and set conversion flags
|
||||||
|
cfg.DirectGen = shouldDirectGen(cfg.OutExt, binExt)
|
||||||
|
if cfg.OutExt == ".img" {
|
||||||
|
cfg.BinFmt = binFmt + "-img"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if firmware generation is needed
|
||||||
|
cfg.NeedFwGen = needsFwGen(conf, cfg.OutExt, binExt)
|
||||||
|
|
||||||
|
// Generate paths based on mode
|
||||||
|
switch conf.Mode {
|
||||||
|
case ModeBuild:
|
||||||
|
return genBuildOutputs(conf, pkgName, multiPkg, cfg)
|
||||||
|
case ModeRun, ModeTest, ModeCmpTest:
|
||||||
|
return genRunOutputs(pkgName, cfg, conf.AppExt)
|
||||||
|
case ModeInstall:
|
||||||
|
return genInstallOutputs(conf, pkgName, cfg, binExt)
|
||||||
|
default:
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determineFormat determines the file format and extension
|
||||||
|
func determineFormat(conf *Config, emulator string) (format, ext string) {
|
||||||
|
if conf.FileFormat != "" {
|
||||||
|
// User specified file format
|
||||||
|
return conf.FileFormat, firmware.GetFileExtFromFormat(conf.FileFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Mode == ModeRun && conf.Emulator && emulator != "" {
|
||||||
|
// Emulator mode - extract format from emulator command
|
||||||
|
if emulatorFmt := firmware.ExtractFileFormatFromEmulator(emulator); emulatorFmt != "" {
|
||||||
|
return emulatorFmt, firmware.GetFileExtFromFormat(emulatorFmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldDirectGen determines if direct firmware generation is possible
|
||||||
|
func shouldDirectGen(outExt, binExt string) bool {
|
||||||
|
return outExt == "" || outExt == binExt || outExt == ".img"
|
||||||
|
}
|
||||||
|
|
||||||
|
// needsFwGen determines if firmware generation is needed
|
||||||
|
func needsFwGen(conf *Config, outExt, binExt string) bool {
|
||||||
|
switch conf.Mode {
|
||||||
|
case ModeBuild:
|
||||||
|
return conf.FileFormat != ""
|
||||||
|
case ModeRun, ModeTest, ModeCmpTest:
|
||||||
|
if conf.Emulator {
|
||||||
|
return outExt != ""
|
||||||
|
}
|
||||||
|
return binExt != ""
|
||||||
|
case ModeInstall:
|
||||||
|
return binExt != ""
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// genBuildOutputs generates output paths for build mode
|
||||||
|
func genBuildOutputs(conf *Config, pkgName string, multiPkg bool, cfg OutputCfg) (OutputCfg, error) {
|
||||||
|
if conf.OutFile == "" && multiPkg {
|
||||||
|
// Multiple packages, use temp file
|
||||||
|
return genTempOutputs(pkgName, cfg, conf.AppExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single package build
|
||||||
|
baseName := pkgName
|
||||||
|
if conf.OutFile != "" {
|
||||||
|
baseName = conf.OutFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.OutExt != "" {
|
||||||
|
// Need format conversion: ELF -> format
|
||||||
|
if err := setupTwoStageGen(&cfg, baseName, conf.AppExt); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Direct output
|
||||||
|
cfg.OutPath = baseName + conf.AppExt
|
||||||
|
cfg.IntPath = cfg.OutPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genRunOutputs generates output paths for run mode
|
||||||
|
func genRunOutputs(pkgName string, cfg OutputCfg, appExt string) (OutputCfg, error) {
|
||||||
|
// Always use temp files for run mode
|
||||||
|
return genTempOutputs(pkgName, cfg, appExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// genInstallOutputs generates output paths for install mode (flashing to device)
|
||||||
|
func genInstallOutputs(conf *Config, pkgName string, cfg OutputCfg, binExt string) (OutputCfg, error) {
|
||||||
|
// Install mode with target means flashing to device, use temp files like run mode
|
||||||
|
if binExt != "" || cfg.OutExt != "" {
|
||||||
|
// Flash to device - use temp files for firmware generation
|
||||||
|
return genTempOutputs(pkgName, cfg, conf.AppExt)
|
||||||
|
} else {
|
||||||
|
// Install to BinPath (traditional install without target)
|
||||||
|
cfg.OutPath = filepath.Join(conf.BinPath, pkgName+conf.AppExt)
|
||||||
|
cfg.IntPath = cfg.OutPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupTwoStageGen sets up paths for two-stage generation
|
||||||
|
func setupTwoStageGen(cfg *OutputCfg, baseName, appExt string) error {
|
||||||
|
// Create temp file for intermediate ELF
|
||||||
|
tmpFile, err := os.CreateTemp("", "llgo-*"+appExt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
cfg.IntPath = tmpFile.Name()
|
||||||
|
|
||||||
|
// Set final output path
|
||||||
|
if baseName != "" {
|
||||||
|
if filepath.Ext(baseName) == cfg.OutExt {
|
||||||
|
cfg.OutPath = baseName
|
||||||
|
} else {
|
||||||
|
cfg.OutPath = baseName + cfg.OutExt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genTempOutputs creates temporary output file paths
|
||||||
|
func genTempOutputs(pkgName string, cfg OutputCfg, appExt string) (OutputCfg, error) {
|
||||||
|
if cfg.OutExt != "" {
|
||||||
|
// Need format conversion: create temp ELF, then convert to final format
|
||||||
|
tmpFile, err := os.CreateTemp("", "llgo-*"+appExt)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
cfg.IntPath = tmpFile.Name()
|
||||||
|
|
||||||
|
finalTmp, err := os.CreateTemp("", pkgName+"-*"+cfg.OutExt)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
finalTmp.Close()
|
||||||
|
cfg.OutPath = finalTmp.Name()
|
||||||
|
} else {
|
||||||
|
// Direct output
|
||||||
|
tmpFile, err := os.CreateTemp("", pkgName+"-*"+appExt)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
cfg.OutPath = tmpFile.Name()
|
||||||
|
cfg.IntPath = cfg.OutPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
448
internal/build/outputs_test.go
Normal file
448
internal/build/outputs_test.go
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
//go:build !llgo
|
||||||
|
// +build !llgo
|
||||||
|
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenOutputs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf *Config
|
||||||
|
pkgName string
|
||||||
|
multiPkg bool
|
||||||
|
emulator string
|
||||||
|
wantOutPath string // use empty string to indicate temp file
|
||||||
|
wantIntPath string // use empty string to indicate same as outPath
|
||||||
|
wantOutExt string
|
||||||
|
wantFileFmt string
|
||||||
|
wantBinFmt string
|
||||||
|
wantDirectGen bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "build without target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "hello",
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build with -o",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
OutFile: "myapp",
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "myapp",
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build with target and file-format",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
FileFormat: "bin",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "hello.bin",
|
||||||
|
wantOutExt: ".bin",
|
||||||
|
wantFileFmt: "bin",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build with target, -o and file-format",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
OutFile: "myapp",
|
||||||
|
AppExt: "",
|
||||||
|
FileFormat: "hex",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "myapp.hex",
|
||||||
|
wantOutExt: ".hex",
|
||||||
|
wantFileFmt: "hex",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build with target, -o has correct extension and file-format",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
OutFile: "myapp.hex",
|
||||||
|
AppExt: "",
|
||||||
|
FileFormat: "hex",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "myapp.hex",
|
||||||
|
wantOutExt: ".hex",
|
||||||
|
wantFileFmt: "hex",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run without target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeRun,
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run with target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeRun,
|
||||||
|
AppExt: "",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run with target and emulator",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeRun,
|
||||||
|
AppExt: "",
|
||||||
|
Target: "esp32",
|
||||||
|
Emulator: true,
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
emulator: "qemu-system-xtensa -machine esp32 -drive file={hex},if=mtd,format=raw",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: ".hex",
|
||||||
|
wantFileFmt: "hex",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build with img file-format",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeBuild,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
FileFormat: "img",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "hello.img",
|
||||||
|
wantOutExt: ".img",
|
||||||
|
wantFileFmt: "img",
|
||||||
|
wantBinFmt: "esp32-img",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test without target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeTest,
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test with target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeTest,
|
||||||
|
AppExt: "",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cmptest without target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeCmpTest,
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "install without target",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeInstall,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "/go/bin/hello",
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "install with esp32 target (flash to device)",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeInstall,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file for flashing
|
||||||
|
wantOutExt: "",
|
||||||
|
wantFileFmt: "",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "install with file format (flash to device)",
|
||||||
|
conf: &Config{
|
||||||
|
Mode: ModeInstall,
|
||||||
|
BinPath: "/go/bin",
|
||||||
|
AppExt: "",
|
||||||
|
FileFormat: "hex",
|
||||||
|
Target: "esp32",
|
||||||
|
},
|
||||||
|
pkgName: "hello",
|
||||||
|
wantOutPath: "", // temp file for flashing
|
||||||
|
wantOutExt: ".hex",
|
||||||
|
wantFileFmt: "hex",
|
||||||
|
wantBinFmt: "esp32",
|
||||||
|
wantDirectGen: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Determine input binFmt - remove -img suffix if present as it will be added by the code
|
||||||
|
inputBinFmt := tt.wantBinFmt
|
||||||
|
if strings.HasSuffix(inputBinFmt, "-img") && tt.wantFileFmt == "img" {
|
||||||
|
inputBinFmt = strings.TrimSuffix(inputBinFmt, "-img")
|
||||||
|
}
|
||||||
|
result, err := GenOutputs(tt.conf, tt.pkgName, tt.multiPkg, tt.emulator, inputBinFmt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenOutputs() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantOutExt != result.OutExt {
|
||||||
|
t.Errorf("GenOutputs() OutExt = %v, want %v", result.OutExt, tt.wantOutExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantFileFmt != result.FileFmt {
|
||||||
|
t.Errorf("GenOutputs() FileFmt = %v, want %v", result.FileFmt, tt.wantFileFmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantBinFmt != result.BinFmt {
|
||||||
|
t.Errorf("GenOutputs() BinFmt = %v, want %v", result.BinFmt, tt.wantBinFmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantDirectGen != result.DirectGen {
|
||||||
|
t.Errorf("GenOutputs() DirectGen = %v, want %v", result.DirectGen, tt.wantDirectGen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantOutPath != "" {
|
||||||
|
// Check exact match for non-temp files
|
||||||
|
if result.OutPath != tt.wantOutPath {
|
||||||
|
t.Errorf("GenOutputs() OutPath = %v, want %v", result.OutPath, tt.wantOutPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check temp file pattern for temp files
|
||||||
|
if result.OutPath == "" {
|
||||||
|
t.Errorf("GenOutputs() OutPath should not be empty for temp file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IntPath logic
|
||||||
|
if tt.wantIntPath != "" {
|
||||||
|
// Check exact IntPath match when specified
|
||||||
|
if result.IntPath != tt.wantIntPath {
|
||||||
|
t.Errorf("GenOutputs() IntPath = %v, want %v", result.IntPath, tt.wantIntPath)
|
||||||
|
}
|
||||||
|
} else if tt.wantOutExt != "" && !tt.wantDirectGen {
|
||||||
|
// Should have different IntPath for format conversion
|
||||||
|
if result.IntPath == result.OutPath {
|
||||||
|
t.Errorf("GenOutputs() IntPath should be different from OutPath when format conversion is needed")
|
||||||
|
}
|
||||||
|
} else if tt.conf.Mode == ModeRun && tt.wantOutExt == "" {
|
||||||
|
// Run mode without conversion should have same IntPath and OutPath
|
||||||
|
if result.IntPath != result.OutPath {
|
||||||
|
t.Errorf("GenOutputs() IntPath should equal OutPath for run mode without conversion")
|
||||||
|
}
|
||||||
|
} else if tt.conf.Mode == ModeInstall {
|
||||||
|
// Install mode: check based on whether it's device flashing or traditional install
|
||||||
|
isDeviceFlash := tt.conf.Target != "" || tt.wantOutExt != ""
|
||||||
|
if isDeviceFlash {
|
||||||
|
// Device flashing - should use temp files (like run mode)
|
||||||
|
if result.OutPath == "" {
|
||||||
|
// This is expected for temp files, no additional check needed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Traditional install to BinPath - should have fixed paths
|
||||||
|
if result.IntPath != result.OutPath {
|
||||||
|
t.Errorf("GenOutputs() IntPath should equal OutPath for traditional install mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetermineFormat(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf *Config
|
||||||
|
emulator string
|
||||||
|
wantFmt string
|
||||||
|
wantExt string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user specified format",
|
||||||
|
conf: &Config{FileFormat: "hex"},
|
||||||
|
wantFmt: "hex",
|
||||||
|
wantExt: ".hex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emulator format extraction",
|
||||||
|
conf: &Config{Mode: ModeRun, Emulator: true},
|
||||||
|
emulator: "qemu-system-xtensa -machine esp32 -drive file={bin},if=mtd,format=raw",
|
||||||
|
wantFmt: "bin",
|
||||||
|
wantExt: ".bin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no format",
|
||||||
|
conf: &Config{},
|
||||||
|
wantFmt: "",
|
||||||
|
wantExt: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotFmt, gotExt := determineFormat(tt.conf, tt.emulator)
|
||||||
|
if gotFmt != tt.wantFmt {
|
||||||
|
t.Errorf("determineFormat() format = %v, want %v", gotFmt, tt.wantFmt)
|
||||||
|
}
|
||||||
|
if gotExt != tt.wantExt {
|
||||||
|
t.Errorf("determineFormat() ext = %v, want %v", gotExt, tt.wantExt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldDirectGen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
outExt string
|
||||||
|
binExt string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"no extension", "", ".bin", true},
|
||||||
|
{"same extension", ".bin", ".bin", true},
|
||||||
|
{"img format", ".img", ".bin", true},
|
||||||
|
{"different extension", ".hex", ".bin", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := shouldDirectGen(tt.outExt, tt.binExt); got != tt.want {
|
||||||
|
t.Errorf("shouldDirectGen() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNeedsFwGen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf *Config
|
||||||
|
outExt string
|
||||||
|
binExt string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "build mode with file format",
|
||||||
|
conf: &Config{Mode: ModeBuild, FileFormat: "hex"},
|
||||||
|
outExt: ".hex",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build mode without file format",
|
||||||
|
conf: &Config{Mode: ModeBuild},
|
||||||
|
outExt: "",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run mode with emulator",
|
||||||
|
conf: &Config{Mode: ModeRun, Emulator: true},
|
||||||
|
outExt: ".hex",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run mode with binExt",
|
||||||
|
conf: &Config{Mode: ModeRun},
|
||||||
|
outExt: "",
|
||||||
|
binExt: ".bin",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test mode with emulator",
|
||||||
|
conf: &Config{Mode: ModeTest, Emulator: true},
|
||||||
|
outExt: ".hex",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test mode with binExt",
|
||||||
|
conf: &Config{Mode: ModeTest},
|
||||||
|
outExt: "",
|
||||||
|
binExt: ".bin",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cmptest mode with binExt",
|
||||||
|
conf: &Config{Mode: ModeCmpTest},
|
||||||
|
outExt: "",
|
||||||
|
binExt: ".bin",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := needsFwGen(tt.conf, tt.outExt, tt.binExt); got != tt.want {
|
||||||
|
t.Errorf("needsFwGen() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ type Export struct {
|
|||||||
|
|
||||||
BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2")
|
BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2")
|
||||||
FormatDetail string // For uf2, it's uf2FamilyID
|
FormatDetail string // For uf2, it's uf2FamilyID
|
||||||
|
Emulator string // Emulator command template (e.g., "qemu-system-arm -M {} -kernel {}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLs and configuration that can be overridden for testing
|
// URLs and configuration that can be overridden for testing
|
||||||
@@ -499,6 +500,7 @@ func useTarget(targetName string) (export Export, err error) {
|
|||||||
export.ExtraFiles = config.ExtraFiles
|
export.ExtraFiles = config.ExtraFiles
|
||||||
export.BinaryFormat = config.BinaryFormat
|
export.BinaryFormat = config.BinaryFormat
|
||||||
export.FormatDetail = config.FormatDetail()
|
export.FormatDetail = config.FormatDetail()
|
||||||
|
export.Emulator = config.Emulator
|
||||||
|
|
||||||
// Build environment map for template variable expansion
|
// Build environment map for template variable expansion
|
||||||
envs := buildEnvMap(env.LLGoROOT())
|
envs := buildEnvMap(env.LLGoROOT())
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
|||||||
return fmt.Errorf("unsupported firmware format: %s", format)
|
return fmt.Errorf("unsupported firmware format: %s", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractFileFormatFromEmulator extracts file format from emulator command template
|
||||||
|
// Returns the format if found (e.g. "bin", "hex", "zip", "img"), empty string if not found
|
||||||
|
func ExtractFileFormatFromEmulator(emulatorCmd string) string {
|
||||||
|
formats := []string{"bin", "hex", "zip", "img", "uf2"}
|
||||||
|
for _, format := range formats {
|
||||||
|
if strings.Contains(emulatorCmd, "{"+format+"}") {
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// GetFileExtFromFormat converts file format to file extension
|
// GetFileExtFromFormat converts file format to file extension
|
||||||
func GetFileExtFromFormat(format string) string {
|
func GetFileExtFromFormat(format string) string {
|
||||||
switch format {
|
switch format {
|
||||||
@@ -33,6 +45,8 @@ func GetFileExtFromFormat(format string) string {
|
|||||||
return ".uf2"
|
return ".uf2"
|
||||||
case "zip":
|
case "zip":
|
||||||
return ".zip"
|
return ".zip"
|
||||||
|
case "img":
|
||||||
|
return ".img"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -47,7 +61,7 @@ func ConvertOutput(infile, outfile, binaryFormat, fileFormat string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only support conversion to hex format
|
// Only support conversion to hex and format
|
||||||
if fileFormat == "hex" {
|
if fileFormat == "hex" {
|
||||||
return convertToHex(infile, outfile)
|
return convertToHex(infile, outfile)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user