Build and run for embeded
This commit is contained in:
@@ -19,6 +19,7 @@ var Verbose bool
|
||||
var BuildEnv string
|
||||
var Tags string
|
||||
var Target string
|
||||
var Emulator bool
|
||||
var AbiMode int
|
||||
var CheckLinkArgs bool
|
||||
var CheckLLFiles bool
|
||||
@@ -39,6 +40,10 @@ func AddBuildFlags(fs *flag.FlagSet) {
|
||||
|
||||
var Gen bool
|
||||
|
||||
func AddRunFlags(fs *flag.FlagSet) {
|
||||
fs.BoolVar(&Emulator, "emulator", false, "Run in emulator mode")
|
||||
}
|
||||
|
||||
func AddCmpTestFlags(fs *flag.FlagSet) {
|
||||
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
|
||||
}
|
||||
@@ -51,6 +56,8 @@ func UpdateConfig(conf *build.Config) {
|
||||
case build.ModeBuild:
|
||||
conf.OutFile = OutputFile
|
||||
conf.FileFormat = FileFormat
|
||||
case build.ModeRun:
|
||||
conf.Emulator = Emulator
|
||||
case build.ModeCmpTest:
|
||||
conf.GenExpect = Gen
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func init() {
|
||||
base.PassBuildFlags(Cmd)
|
||||
flags.AddBuildFlags(&Cmd.Flag)
|
||||
flags.AddBuildFlags(&CmpTestCmd.Flag)
|
||||
flags.AddRunFlags(&Cmd.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
|
||||
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
|
||||
Emulator bool // only valid for ModeRun - run in emulator mode
|
||||
RunArgs []string // only valid for ModeRun
|
||||
Mode Mode
|
||||
AbiMode AbiMode
|
||||
@@ -628,83 +629,18 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
|
||||
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) {
|
||||
pkgPath := pkg.PkgPath
|
||||
name := path.Base(pkgPath)
|
||||
binFmt := ctx.crossCompile.BinaryFormat
|
||||
binExt := firmware.BinaryExt(binFmt)
|
||||
|
||||
// Determine final output extension from user-specified file format
|
||||
outExt := binExt
|
||||
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,
|
||||
)
|
||||
// Generate output configuration using the centralized function
|
||||
outputCfg, err := GenOutputs(conf, name, len(ctx.initial) > 1, ctx.crossCompile.Emulator, binFmt)
|
||||
check(err)
|
||||
|
||||
app := outputCfg.OutPath
|
||||
orgApp := outputCfg.IntPath
|
||||
|
||||
needRuntime := false
|
||||
needPyInit := false
|
||||
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
|
||||
currentApp := orgApp
|
||||
|
||||
// Determine if firmware conversion is needed based on mode
|
||||
needFirmwareConversion := false
|
||||
if mode == ModeBuild {
|
||||
// For build command, do firmware conversion if file-format is specified
|
||||
needFirmwareConversion = conf.FileFormat != ""
|
||||
} else {
|
||||
// For run and install commands, do firmware conversion if binExt is set
|
||||
needFirmwareConversion = binExt != ""
|
||||
useEmulator := false
|
||||
if mode == ModeRun && conf.Emulator {
|
||||
if ctx.crossCompile.Emulator == "" {
|
||||
panic(fmt.Errorf("target %s does not have emulator configured", conf.Target))
|
||||
}
|
||||
useEmulator = true
|
||||
}
|
||||
|
||||
// Step 1: Firmware conversion if needed
|
||||
if needFirmwareConversion {
|
||||
if outExt == binExt {
|
||||
// Direct conversion to final output
|
||||
if outputCfg.NeedFwGen {
|
||||
if outputCfg.DirectGen {
|
||||
// Direct conversion to final output (including .img case)
|
||||
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)
|
||||
currentApp = app
|
||||
} else {
|
||||
// Convert to intermediate file first
|
||||
binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat)
|
||||
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
|
||||
check(err)
|
||||
tmpFile.Close()
|
||||
@@ -813,12 +748,12 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
|
||||
// Step 2: File format conversion if needed
|
||||
if currentApp != app {
|
||||
if conf.FileFormat != "" {
|
||||
if outputCfg.FileFmt != "" {
|
||||
// File format conversion
|
||||
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)
|
||||
} else {
|
||||
// Just move/copy the file
|
||||
@@ -845,39 +780,46 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
}
|
||||
}
|
||||
case ModeRun:
|
||||
args := make([]string, 0, len(conf.RunArgs)+1)
|
||||
copy(args, conf.RunArgs)
|
||||
if isWasmTarget(conf.Goos) {
|
||||
wasmer := os.ExpandEnv(WasmRuntime())
|
||||
wasmerArgs := strings.Split(wasmer, " ")
|
||||
wasmerCmd := wasmerArgs[0]
|
||||
wasmerArgs = wasmerArgs[1:]
|
||||
switch wasmer {
|
||||
case "wasmtime":
|
||||
args = append(args, "--wasm", "multi-memory=true", app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
case "iwasm":
|
||||
args = append(args, "--stack-size=819200000", "--heap-size=800000000", app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
default:
|
||||
args = append(args, wasmerArgs...)
|
||||
args = append(args, app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
if useEmulator {
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "Using emulator: %s\n", ctx.crossCompile.Emulator)
|
||||
}
|
||||
app = wasmerCmd
|
||||
runInEmulator(app, ctx.crossCompile.Emulator, conf.RunArgs, verbose)
|
||||
} else {
|
||||
args = conf.RunArgs
|
||||
}
|
||||
cmd := exec.Command(app, args...)
|
||||
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())
|
||||
args := make([]string, 0, len(conf.RunArgs)+1)
|
||||
copy(args, conf.RunArgs)
|
||||
if isWasmTarget(conf.Goos) {
|
||||
wasmer := os.ExpandEnv(WasmRuntime())
|
||||
wasmerArgs := strings.Split(wasmer, " ")
|
||||
wasmerCmd := wasmerArgs[0]
|
||||
wasmerArgs = wasmerArgs[1:]
|
||||
switch wasmer {
|
||||
case "wasmtime":
|
||||
args = append(args, "--wasm", "multi-memory=true", app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
case "iwasm":
|
||||
args = append(args, "--stack-size=819200000", "--heap-size=800000000", app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
default:
|
||||
args = append(args, wasmerArgs...)
|
||||
args = append(args, app)
|
||||
args = append(args, conf.RunArgs...)
|
||||
}
|
||||
app = wasmerCmd
|
||||
} else {
|
||||
args = conf.RunArgs
|
||||
}
|
||||
cmd := exec.Command(app, args...)
|
||||
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())
|
||||
}
|
||||
}
|
||||
case ModeCmpTest:
|
||||
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{}
|
||||
|
||||
// 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) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/llgo/internal/mockable"
|
||||
@@ -95,172 +94,4 @@ func TestCmpTest(t *testing.T) {
|
||||
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
|
||||
}
|
||||
|
||||
func TestGenerateOutputFilenames(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
// TestGenerateOutputFilenames removed - functionality moved to filename_test.go
|
||||
|
||||
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")
|
||||
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
|
||||
@@ -499,6 +500,7 @@ func useTarget(targetName string) (export Export, err error) {
|
||||
export.ExtraFiles = config.ExtraFiles
|
||||
export.BinaryFormat = config.BinaryFormat
|
||||
export.FormatDetail = config.FormatDetail()
|
||||
export.Emulator = config.Emulator
|
||||
|
||||
// Build environment map for template variable expansion
|
||||
envs := buildEnvMap(env.LLGoROOT())
|
||||
|
||||
@@ -20,6 +20,18 @@ func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
||||
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
|
||||
func GetFileExtFromFormat(format string) string {
|
||||
switch format {
|
||||
@@ -33,6 +45,8 @@ func GetFileExtFromFormat(format string) string {
|
||||
return ".uf2"
|
||||
case "zip":
|
||||
return ".zip"
|
||||
case "img":
|
||||
return ".img"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -47,7 +61,7 @@ func ConvertOutput(infile, outfile, binaryFormat, fileFormat string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only support conversion to hex format
|
||||
// Only support conversion to hex and format
|
||||
if fileFormat == "hex" {
|
||||
return convertToHex(infile, outfile)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user