refactor: multi format generation and llgo build flags
This commit is contained in:
@@ -39,6 +39,8 @@ func init() {
|
||||
|
||||
flags.AddCommonFlags(&Cmd.Flag)
|
||||
flags.AddBuildFlags(&Cmd.Flag)
|
||||
flags.AddEmulatorFlags(&Cmd.Flag)
|
||||
flags.AddEmbeddedFlags(&Cmd.Flag)
|
||||
flags.AddOutputFlags(&Cmd.Flag)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,19 @@ import (
|
||||
)
|
||||
|
||||
var OutputFile string
|
||||
var FileFormat string
|
||||
var OutBin bool
|
||||
var OutHex bool
|
||||
var OutImg bool
|
||||
var OutUf2 bool
|
||||
var OutZip bool
|
||||
|
||||
func AddOutputFlags(fs *flag.FlagSet) {
|
||||
fs.StringVar(&OutputFile, "o", "", "Output file")
|
||||
fs.StringVar(&FileFormat, "file-format", "", "File format for target output (e.g., bin, hex, elf, uf2, zip)")
|
||||
fs.BoolVar(&OutBin, "obin", false, "Generate binary output (.bin)")
|
||||
fs.BoolVar(&OutHex, "ohex", false, "Generate Intel hex output (.hex)")
|
||||
fs.BoolVar(&OutImg, "oimg", false, "Generate image output (.img)")
|
||||
fs.BoolVar(&OutUf2, "ouf2", false, "Generate UF2 output (.uf2)")
|
||||
fs.BoolVar(&OutZip, "ozip", false, "Generate ZIP/DFU output (.zip)")
|
||||
}
|
||||
|
||||
var Verbose bool
|
||||
@@ -67,7 +75,13 @@ func UpdateConfig(conf *build.Config) {
|
||||
switch conf.Mode {
|
||||
case build.ModeBuild:
|
||||
conf.OutFile = OutputFile
|
||||
conf.FileFormat = FileFormat
|
||||
conf.OutFmts = build.OutFmts{
|
||||
Bin: OutBin,
|
||||
Hex: OutHex,
|
||||
Img: OutImg,
|
||||
Uf2: OutUf2,
|
||||
Zip: OutZip,
|
||||
}
|
||||
case build.ModeRun, build.ModeTest:
|
||||
conf.Emulator = Emulator
|
||||
case build.ModeInstall:
|
||||
|
||||
@@ -72,17 +72,36 @@ const (
|
||||
debugBuild = packages.DebugPackagesLoad
|
||||
)
|
||||
|
||||
// OutFmts contains output format specifications for embedded targets
|
||||
type OutFmts struct {
|
||||
Bin bool // Generate binary output (.bin)
|
||||
Hex bool // Generate Intel hex output (.hex)
|
||||
Img bool // Generate image output (.img)
|
||||
Uf2 bool // Generate UF2 output (.uf2)
|
||||
Zip bool // Generate ZIP/DFU output (.zip)
|
||||
}
|
||||
|
||||
// OutFmtDetails contains detailed output file paths for each format
|
||||
type OutFmtDetails struct {
|
||||
Out string // Base output file path
|
||||
Bin string // Binary output file path (.bin)
|
||||
Hex string // Intel hex output file path (.hex)
|
||||
Img string // Image output file path (.img)
|
||||
Uf2 string // UF2 output file path (.uf2)
|
||||
Zip string // ZIP/DFU output file path (.zip)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Goos string
|
||||
Goarch string
|
||||
Target string // target name (e.g., "rp2040", "wasi") - takes precedence over Goos/Goarch
|
||||
BinPath string
|
||||
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 // run in emulator mode
|
||||
Port string // target port for flashing
|
||||
BaudRate int // baudrate for serial communication
|
||||
AppExt string // ".exe" on Windows, empty on Unix
|
||||
OutFile string // only valid for ModeBuild when len(pkgs) == 1
|
||||
OutFmts OutFmts // Output format specifications (only for Target != "")
|
||||
Emulator bool // run in emulator mode
|
||||
Port string // target port for flashing
|
||||
BaudRate int // baudrate for serial communication
|
||||
RunArgs []string
|
||||
Mode Mode
|
||||
AbiMode AbiMode
|
||||
@@ -121,7 +140,6 @@ func NewDefaultConf(mode Mode) *Config {
|
||||
BinPath: bin,
|
||||
Mode: mode,
|
||||
AbiMode: cabi.ModeAllFunc,
|
||||
AppExt: DefaultAppExt(goos),
|
||||
}
|
||||
return conf
|
||||
}
|
||||
@@ -137,8 +155,15 @@ func envGOPATH() (string, error) {
|
||||
return filepath.Join(home, "go"), nil
|
||||
}
|
||||
|
||||
func DefaultAppExt(goos string) string {
|
||||
switch goos {
|
||||
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":
|
||||
@@ -163,6 +188,9 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
if conf.Goarch == "" {
|
||||
conf.Goarch = runtime.GOARCH
|
||||
}
|
||||
if conf.AppExt == "" {
|
||||
conf.AppExt = defaultAppExt(conf)
|
||||
}
|
||||
// Handle crosscompile configuration first to set correct GOOS/GOARCH
|
||||
export, err := crosscompile.Use(conf.Goos, conf.Goarch, IsWasiThreadsEnabled(), conf.Target)
|
||||
if err != nil {
|
||||
@@ -241,6 +269,10 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
if conf.OutFile != "" {
|
||||
return nil, fmt.Errorf("cannot build multiple packages with -o")
|
||||
}
|
||||
case ModeInstall:
|
||||
if conf.Target != "" {
|
||||
return nil, fmt.Errorf("cannot install multiple packages to embedded target")
|
||||
}
|
||||
case ModeRun:
|
||||
return nil, fmt.Errorf("cannot run multiple packages")
|
||||
case ModeTest:
|
||||
@@ -319,19 +351,24 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
if needLink(pkg, mode) {
|
||||
name := path.Base(pkg.PkgPath)
|
||||
|
||||
outputCfg, err := genOutputs(conf, name, len(ctx.initial) > 1, &ctx.crossCompile)
|
||||
// Create output format details
|
||||
outFmts, err := buildOutFmts(name, conf, len(ctx.initial) > 1, &ctx.crossCompile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = linkMainPkg(ctx, pkg, allPkgs, global, outputCfg.IntPath, verbose)
|
||||
// Link main package using the base output path
|
||||
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalApp := outputCfg.IntPath
|
||||
if outputCfg.NeedFwGen || outputCfg.FileFmt != "" {
|
||||
finalApp, err = convertFormat(ctx, finalApp, &outputCfg)
|
||||
envMap := outFmts.ToEnvMap()
|
||||
|
||||
// Only convert formats when Target is specified
|
||||
if conf.Target != "" {
|
||||
// Process format conversions for embedded targets
|
||||
err = firmware.ConvertFormats(ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail, envMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -344,7 +381,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
case ModeInstall:
|
||||
// Native already installed in linkMainPkg
|
||||
if conf.Target != "" {
|
||||
err = flash.FlashDevice(ctx.crossCompile.Device, finalApp, ctx.buildConf.Port, verbose)
|
||||
err = flash.FlashDevice(ctx.crossCompile.Device, envMap, ctx.buildConf.Port, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -352,18 +389,18 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
|
||||
case ModeRun, ModeTest, ModeCmpTest:
|
||||
if conf.Target == "" {
|
||||
err = runNative(ctx, finalApp, pkg.Dir, pkg.PkgPath, conf, mode)
|
||||
err = runNative(ctx, outFmts.Out, pkg.Dir, pkg.PkgPath, conf, mode)
|
||||
} else if conf.Emulator {
|
||||
err = runInEmulator(ctx.crossCompile.Emulator, finalApp, pkg.Dir, pkg.PkgPath, conf, mode, verbose)
|
||||
err = runInEmulator(ctx.crossCompile.Emulator, envMap, pkg.Dir, pkg.PkgPath, conf, mode, verbose)
|
||||
} else {
|
||||
err = flash.FlashDevice(ctx.crossCompile.Device, finalApp, ctx.buildConf.Port, verbose)
|
||||
err = flash.FlashDevice(ctx.crossCompile.Device, envMap, ctx.buildConf.Port, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
monitorConfig := monitor.MonitorConfig{
|
||||
Port: ctx.buildConf.Port,
|
||||
Target: conf.Target,
|
||||
Executable: finalApp,
|
||||
Executable: outFmts.Out,
|
||||
BaudRate: conf.BaudRate,
|
||||
SerialPort: ctx.crossCompile.Device.SerialPort,
|
||||
}
|
||||
@@ -756,60 +793,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
return linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose)
|
||||
}
|
||||
|
||||
func convertFormat(ctx *context, inputFile string, outputCfg *OutputCfg) (string, error) {
|
||||
app := outputCfg.OutPath
|
||||
currentApp := inputFile
|
||||
if ctx.buildConf.Verbose {
|
||||
fmt.Fprintf(os.Stderr, "Converting output format: %s -> %s\n", currentApp, app)
|
||||
}
|
||||
|
||||
if outputCfg.NeedFwGen {
|
||||
if outputCfg.DirectGen {
|
||||
err := firmware.MakeFirmwareImage(currentApp, app, outputCfg.BinFmt, ctx.crossCompile.FormatDetail)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
currentApp = app
|
||||
} else {
|
||||
binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat)
|
||||
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFile.Close()
|
||||
intermediateApp := tmpFile.Name()
|
||||
|
||||
err = firmware.MakeFirmwareImage(currentApp, intermediateApp, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
currentApp = intermediateApp
|
||||
defer func() {
|
||||
if _, err := os.Stat(intermediateApp); err == nil {
|
||||
os.Remove(intermediateApp)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if currentApp != app {
|
||||
if outputCfg.FileFmt != "" {
|
||||
binFmt := ctx.crossCompile.BinaryFormat
|
||||
err := firmware.ConvertOutput(currentApp, app, binFmt, outputCfg.FileFmt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
err := os.Rename(currentApp, app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
|
||||
buildArgs := []string{"-o", app}
|
||||
buildArgs = append(buildArgs, linkArgs...)
|
||||
|
||||
@@ -3,6 +3,7 @@ package build
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/goplus/llgo/internal/crosscompile"
|
||||
@@ -20,227 +21,143 @@ type OutputCfg struct {
|
||||
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, crossCompile *crosscompile.Export) (OutputCfg, error) {
|
||||
var cfg OutputCfg
|
||||
|
||||
// Calculate binary extension and set up format info
|
||||
binFmt := crossCompile.BinaryFormat
|
||||
binExt := firmware.BinaryExt(binFmt)
|
||||
cfg.BinFmt = binFmt
|
||||
|
||||
// Determine output format and extension
|
||||
cfg.FileFmt, cfg.OutExt = determineFormat(conf, crossCompile)
|
||||
|
||||
// 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, crossCompile *crosscompile.Export) (format, ext string) {
|
||||
if conf.FileFormat != "" {
|
||||
// User specified file format
|
||||
return conf.FileFormat, firmware.GetFileExtFromFormat(conf.FileFormat)
|
||||
}
|
||||
|
||||
if conf.Mode == ModeRun && conf.Emulator && crossCompile.Emulator != "" {
|
||||
// Emulator mode - extract format from emulator command
|
||||
if emulatorFmt := firmware.ExtractFileFormatFromEmulator(crossCompile.Emulator); emulatorFmt != "" {
|
||||
return emulatorFmt, firmware.GetFileExtFromFormat(emulatorFmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Device flashing - determine format based on flash method and target
|
||||
if conf.Target != "" && (conf.Mode == ModeInstall || conf.Mode == ModeRun || conf.Mode == ModeTest || conf.Mode == ModeCmpTest) {
|
||||
if flashExt := determineFlashFormat(crossCompile); flashExt != "" {
|
||||
return flashExt[1:], flashExt // Remove the dot for format, keep for ext
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// determineFlashFormat determines the required file format for flashing based on flash method
|
||||
func determineFlashFormat(crossCompile *crosscompile.Export) string {
|
||||
if crossCompile == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
flashMethod := crossCompile.Device.Flash.Method
|
||||
switch flashMethod {
|
||||
case "command", "":
|
||||
// Extract format from flash command tokens
|
||||
flashCommand := crossCompile.Device.Flash.Command
|
||||
switch {
|
||||
case strings.Contains(flashCommand, "{hex}"):
|
||||
return ".hex"
|
||||
case strings.Contains(flashCommand, "{elf}"):
|
||||
return ".elf"
|
||||
case strings.Contains(flashCommand, "{bin}"):
|
||||
return ".bin"
|
||||
case strings.Contains(flashCommand, "{uf2}"):
|
||||
return ".uf2"
|
||||
case strings.Contains(flashCommand, "{zip}"):
|
||||
return ".zip"
|
||||
case strings.Contains(flashCommand, "{img}"):
|
||||
return ".img"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
case "msd":
|
||||
if crossCompile.Device.MSD.FirmwareName == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Ext(crossCompile.Device.MSD.FirmwareName)
|
||||
case "openocd":
|
||||
return ".hex"
|
||||
case "bmp":
|
||||
return ".elf"
|
||||
default:
|
||||
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
|
||||
if filepath.Ext(cfg.OutPath) != conf.AppExt {
|
||||
cfg.OutPath += 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)
|
||||
func genTempOutputFile(prefix, ext string) (string, error) {
|
||||
tmpFile, err := os.CreateTemp("", prefix+"-*"+ext)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
tmpFile.Close()
|
||||
cfg.IntPath = tmpFile.Name()
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
|
||||
// Set final output path
|
||||
if baseName != "" {
|
||||
if filepath.Ext(baseName) == cfg.OutExt {
|
||||
cfg.OutPath = baseName
|
||||
// setOutFmt sets the appropriate OutFmt based on format name
|
||||
func setOutFmt(conf *Config, formatName string) {
|
||||
switch formatName {
|
||||
case "bin":
|
||||
conf.OutFmts.Bin = true
|
||||
case "hex":
|
||||
conf.OutFmts.Hex = true
|
||||
case "img":
|
||||
conf.OutFmts.Img = true
|
||||
case "uf2":
|
||||
conf.OutFmts.Uf2 = true
|
||||
case "zip":
|
||||
conf.OutFmts.Zip = true
|
||||
}
|
||||
}
|
||||
|
||||
// buildOutFmts creates OutFmtDetails based on package, configuration and multi-package status
|
||||
func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) {
|
||||
details := &OutFmtDetails{}
|
||||
var err error
|
||||
if conf.Target == "" {
|
||||
// Native target
|
||||
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 {
|
||||
cfg.OutPath = baseName + cfg.OutExt
|
||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return details, nil
|
||||
}
|
||||
|
||||
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 multiPkg {
|
||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
return nil, 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 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 {
|
||||
// Direct output
|
||||
tmpFile, err := os.CreateTemp("", pkgName+"-*"+appExt)
|
||||
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
return nil, err
|
||||
}
|
||||
tmpFile.Close()
|
||||
cfg.OutPath = tmpFile.Name()
|
||||
cfg.IntPath = cfg.OutPath
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
// Check emulator format if emulator mode is enabled
|
||||
outFmt := ""
|
||||
if conf.Emulator {
|
||||
if crossCompile.Emulator != "" {
|
||||
outFmt = firmware.ExtractFileFormatFromCommand(crossCompile.Emulator)
|
||||
}
|
||||
} else {
|
||||
if crossCompile.Device.Flash.Method == "command" {
|
||||
outFmt = firmware.ExtractFileFormatFromCommand(crossCompile.Device.Flash.Command)
|
||||
}
|
||||
}
|
||||
if outFmt != "" {
|
||||
setOutFmt(conf, outFmt)
|
||||
}
|
||||
|
||||
// Check binary format and set corresponding format
|
||||
if crossCompile.BinaryFormat != "" && slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode) {
|
||||
envName := firmware.BinaryFormatToEnvName(crossCompile.BinaryFormat)
|
||||
if envName != "" {
|
||||
setOutFmt(conf, envName)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate format-specific paths based on base output
|
||||
if details.Out != "" {
|
||||
base := strings.TrimSuffix(details.Out, filepath.Ext(details.Out))
|
||||
|
||||
if conf.OutFmts.Bin || conf.OutFmts.Img {
|
||||
details.Bin = base + ".bin"
|
||||
}
|
||||
if conf.OutFmts.Hex {
|
||||
details.Bin = base + ".bin" // hex depends on bin
|
||||
details.Hex = base + ".hex"
|
||||
}
|
||||
if conf.OutFmts.Img {
|
||||
details.Bin = base + ".bin" // img depends on bin
|
||||
details.Img = base + ".img"
|
||||
}
|
||||
if conf.OutFmts.Uf2 {
|
||||
details.Uf2 = base + ".uf2"
|
||||
}
|
||||
if conf.OutFmts.Zip {
|
||||
details.Zip = base + ".zip"
|
||||
}
|
||||
}
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
// ToEnvMap converts OutFmtDetails to a map for template substitution
|
||||
func (details *OutFmtDetails) ToEnvMap() map[string]string {
|
||||
envMap := make(map[string]string)
|
||||
|
||||
if details.Out != "" {
|
||||
envMap[""] = details.Out
|
||||
envMap["out"] = details.Out
|
||||
envMap["elf"] = details.Out // alias for compatibility
|
||||
}
|
||||
if details.Bin != "" {
|
||||
envMap["bin"] = details.Bin
|
||||
}
|
||||
if details.Hex != "" {
|
||||
envMap["hex"] = details.Hex
|
||||
}
|
||||
if details.Img != "" {
|
||||
envMap["img"] = details.Img
|
||||
}
|
||||
if details.Uf2 != "" {
|
||||
envMap["uf2"] = details.Uf2
|
||||
}
|
||||
if details.Zip != "" {
|
||||
envMap["zip"] = details.Zip
|
||||
}
|
||||
|
||||
return envMap
|
||||
}
|
||||
|
||||
@@ -4,43 +4,26 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/llgo/internal/crosscompile"
|
||||
"github.com/goplus/llgo/internal/flash"
|
||||
)
|
||||
|
||||
func TestGenOutputs(t *testing.T) {
|
||||
func TestBuildOutFmtsWithTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conf *Config
|
||||
pkgName string
|
||||
multiPkg bool
|
||||
crossCompile *crosscompile.Export
|
||||
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 string
|
||||
conf *Config
|
||||
pkgName string
|
||||
crossCompile crosscompile.Export
|
||||
wantOut string // use empty string to indicate temp file
|
||||
wantBin string
|
||||
wantHex string
|
||||
wantImg string
|
||||
wantUf2 string
|
||||
wantZip string
|
||||
}{
|
||||
{
|
||||
name: "build without target",
|
||||
conf: &Config{
|
||||
Mode: ModeBuild,
|
||||
BinPath: "/go/bin",
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
wantOutPath: "hello",
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "build with -o",
|
||||
conf: &Config{
|
||||
@@ -49,273 +32,53 @@ func TestGenOutputs(t *testing.T) {
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
wantOutPath: "myapp",
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "",
|
||||
wantDirectGen: true,
|
||||
wantOut: "myapp",
|
||||
},
|
||||
{
|
||||
name: "build with target and file-format",
|
||||
name: "build hex format",
|
||||
conf: &Config{
|
||||
Mode: ModeBuild,
|
||||
BinPath: "/go/bin",
|
||||
AppExt: "",
|
||||
FileFormat: "bin",
|
||||
Target: "esp32",
|
||||
Mode: ModeBuild,
|
||||
Target: "esp32",
|
||||
AppExt: ".elf",
|
||||
OutFmts: OutFmts{Hex: true},
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "esp32", // This will auto-set Bin: true
|
||||
},
|
||||
wantOutPath: "hello.bin",
|
||||
wantOutExt: ".bin",
|
||||
wantFileFmt: "bin",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: true,
|
||||
wantOut: "hello.elf",
|
||||
wantBin: "hello.bin", // Now expected due to esp32 BinaryFormat
|
||||
wantHex: "hello.hex",
|
||||
},
|
||||
{
|
||||
name: "build with target, -o and file-format",
|
||||
conf: &Config{
|
||||
Mode: ModeBuild,
|
||||
OutFile: "myapp",
|
||||
AppExt: "",
|
||||
FileFormat: "hex",
|
||||
Target: "esp32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
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",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "myapp.hex",
|
||||
wantOutExt: ".hex",
|
||||
wantFileFmt: "hex",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: false,
|
||||
},
|
||||
{
|
||||
name: "run without target",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "run with target",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "esp32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "run with target and emulator",
|
||||
name: "emulator mode with bin format",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "esp32",
|
||||
AppExt: ".elf",
|
||||
Emulator: true,
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
Emulator: "qemu-system-xtensa -machine esp32 -drive file={hex},if=mtd,format=raw",
|
||||
Emulator: "qemu-system-xtensa -machine esp32 -kernel {bin}",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".hex",
|
||||
wantFileFmt: "hex",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: false,
|
||||
wantBin: ".bin", // Should be temp file with .bin extension
|
||||
},
|
||||
{
|
||||
name: "build with img file-format",
|
||||
name: "flash command with hex format",
|
||||
conf: &Config{
|
||||
Mode: ModeBuild,
|
||||
BinPath: "/go/bin",
|
||||
AppExt: "",
|
||||
FileFormat: "img",
|
||||
Target: "esp32",
|
||||
Mode: ModeInstall,
|
||||
Target: "esp32",
|
||||
AppExt: ".elf",
|
||||
Emulator: false,
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "hello.img",
|
||||
wantOutExt: ".img",
|
||||
wantFileFmt: "img",
|
||||
wantBinFmt: "esp32-img",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "test without target",
|
||||
conf: &Config{
|
||||
Mode: ModeTest,
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "test with target",
|
||||
conf: &Config{
|
||||
Mode: ModeTest,
|
||||
AppExt: "",
|
||||
Target: "esp32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "cmptest without target",
|
||||
conf: &Config{
|
||||
Mode: ModeCmpTest,
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "install without target",
|
||||
conf: &Config{
|
||||
Mode: ModeInstall,
|
||||
BinPath: "/go/bin",
|
||||
AppExt: "",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "",
|
||||
},
|
||||
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",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
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",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "", // temp file for flashing
|
||||
wantOutExt: ".hex",
|
||||
wantFileFmt: "hex",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: false,
|
||||
},
|
||||
{
|
||||
name: "run with target non-emulator (should use .bin from binary format)",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "esp32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: "",
|
||||
wantFileFmt: "",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "run with flash method command - extract hex from command",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "esp32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "esp32", // This will auto-set Bin: true
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "command",
|
||||
@@ -323,373 +86,280 @@ func TestGenOutputs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".hex",
|
||||
wantFileFmt: "hex",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: false,
|
||||
wantBin: ".bin", // Expected due to esp32 BinaryFormat
|
||||
wantHex: ".hex", // Should be temp file with .hex extension
|
||||
},
|
||||
{
|
||||
name: "run with flash method command - extract bin from command",
|
||||
name: "multiple formats specified",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Mode: ModeBuild,
|
||||
Target: "esp32",
|
||||
AppExt: ".elf",
|
||||
OutFmts: OutFmts{
|
||||
Bin: true,
|
||||
Hex: true,
|
||||
Img: true,
|
||||
Uf2: true,
|
||||
},
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "command",
|
||||
Command: "esptool.py --chip esp32 write_flash 0x10000 {bin}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".bin",
|
||||
wantFileFmt: "bin",
|
||||
wantBinFmt: "esp32",
|
||||
wantDirectGen: true,
|
||||
wantOut: "hello.elf",
|
||||
wantBin: "hello.bin",
|
||||
wantHex: "hello.hex",
|
||||
wantImg: "hello.img",
|
||||
wantUf2: "hello.uf2",
|
||||
wantZip: "", // Not specified
|
||||
},
|
||||
{
|
||||
name: "run with flash method openocd - should use .hex",
|
||||
name: "no formats specified",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "stm32",
|
||||
Mode: ModeBuild,
|
||||
Target: "esp32",
|
||||
AppExt: ".elf",
|
||||
OutFmts: OutFmts{}, // No formats specified
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "arm",
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "openocd",
|
||||
},
|
||||
},
|
||||
crossCompile: crosscompile.Export{
|
||||
BinaryFormat: "esp32",
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".hex",
|
||||
wantFileFmt: "hex",
|
||||
wantBinFmt: "arm",
|
||||
wantDirectGen: false,
|
||||
},
|
||||
{
|
||||
name: "run with flash method msd - extract extension from firmware name",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "rp2040",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "uf2",
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "msd",
|
||||
},
|
||||
MSD: flash.MSD{
|
||||
FirmwareName: "firmware.uf2",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".uf2",
|
||||
wantFileFmt: "uf2",
|
||||
wantBinFmt: "uf2",
|
||||
wantDirectGen: true,
|
||||
},
|
||||
{
|
||||
name: "run with flash method bmp - should use .elf",
|
||||
conf: &Config{
|
||||
Mode: ModeRun,
|
||||
AppExt: "",
|
||||
Target: "stm32",
|
||||
},
|
||||
pkgName: "hello",
|
||||
crossCompile: &crosscompile.Export{
|
||||
BinaryFormat: "arm",
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "bmp",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutPath: "", // temp file
|
||||
wantOutExt: ".elf",
|
||||
wantFileFmt: "elf",
|
||||
wantBinFmt: "arm",
|
||||
wantDirectGen: false,
|
||||
wantOut: "hello.elf",
|
||||
wantBin: "", // No bin file should be generated
|
||||
wantHex: "", // No hex file should be generated
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := genOutputs(tt.conf, tt.pkgName, tt.multiPkg, tt.crossCompile)
|
||||
// Determine if multi-package (mock single package for simplicity)
|
||||
multiPkg := false
|
||||
|
||||
result, err := buildOutFmts(tt.pkgName, tt.conf, multiPkg, &tt.crossCompile)
|
||||
if err != nil {
|
||||
t.Fatalf("GenOutputs() error = %v", err)
|
||||
t.Errorf("buildOutFmts() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
// Check base output path
|
||||
if tt.wantOut != "" {
|
||||
if result.Out != tt.wantOut {
|
||||
t.Errorf("buildOutFmts().Out = %v, want %v", result.Out, tt.wantOut)
|
||||
}
|
||||
} else {
|
||||
// Check temp file pattern for temp files
|
||||
if result.OutPath == "" {
|
||||
t.Errorf("GenOutputs() OutPath should not be empty for temp file")
|
||||
// Should be a temp file
|
||||
if result.Out == "" || !strings.Contains(result.Out, tt.pkgName) {
|
||||
t.Errorf("buildOutFmts().Out should be temp file containing %v, got %v", tt.pkgName, result.Out)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Check format-specific paths
|
||||
checkFormatPath := func(actual, expected, formatName string) {
|
||||
if expected == "" {
|
||||
// Empty means no file should be generated
|
||||
if actual != "" {
|
||||
t.Errorf("buildOutFmts().%s = %v, want empty (no file generated)", formatName, actual)
|
||||
}
|
||||
} else if strings.HasPrefix(expected, ".") && !strings.Contains(expected[1:], ".") {
|
||||
// ".xxx" means temp file with .xxx extension
|
||||
if actual == "" {
|
||||
t.Errorf("buildOutFmts().%s = empty, want temp file with %s extension", formatName, expected)
|
||||
} else if !strings.HasSuffix(actual, expected) || !strings.Contains(actual, tt.pkgName) {
|
||||
t.Errorf("buildOutFmts().%s should be temp file with %s extension containing %v, got %v", formatName, expected, tt.pkgName, actual)
|
||||
}
|
||||
} 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")
|
||||
// "aaa.xxx" means exact file name
|
||||
if actual != expected {
|
||||
t.Errorf("buildOutFmts().%s = %v, want %v", formatName, actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkFormatPath(result.Bin, tt.wantBin, "Bin")
|
||||
checkFormatPath(result.Hex, tt.wantHex, "Hex")
|
||||
checkFormatPath(result.Img, tt.wantImg, "Img")
|
||||
checkFormatPath(result.Uf2, tt.wantUf2, "Uf2")
|
||||
checkFormatPath(result.Zip, tt.wantZip, "Zip")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineFormat(t *testing.T) {
|
||||
func TestBuildOutFmtsNativeTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conf *Config
|
||||
crossCompile *crosscompile.Export
|
||||
wantFmt string
|
||||
wantExt string
|
||||
name string
|
||||
mode Mode
|
||||
multiPkg bool
|
||||
outFile string
|
||||
binPath string
|
||||
appExt string
|
||||
goos string
|
||||
pkgName string
|
||||
wantOut string
|
||||
}{
|
||||
// ModeBuild scenarios
|
||||
{
|
||||
name: "user specified format",
|
||||
conf: &Config{FileFormat: "hex"},
|
||||
crossCompile: &crosscompile.Export{},
|
||||
wantFmt: "hex",
|
||||
wantExt: ".hex",
|
||||
name: "build single pkg no outfile macos",
|
||||
mode: ModeBuild,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
appExt: "",
|
||||
goos: "darwin",
|
||||
pkgName: "hello",
|
||||
wantOut: "hello",
|
||||
},
|
||||
{
|
||||
name: "emulator format extraction",
|
||||
conf: &Config{Mode: ModeRun, Emulator: true},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Emulator: "qemu-system-xtensa -machine esp32 -drive file={bin},if=mtd,format=raw",
|
||||
},
|
||||
wantFmt: "bin",
|
||||
wantExt: ".bin",
|
||||
name: "build single pkg no outfile linux",
|
||||
mode: ModeBuild,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "hello",
|
||||
},
|
||||
{
|
||||
name: "flash method command - extract hex",
|
||||
conf: &Config{Mode: ModeRun, Target: "esp32"},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "command",
|
||||
Command: "esptool.py --chip esp32 write_flash 0x10000 {hex}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFmt: "hex",
|
||||
wantExt: ".hex",
|
||||
name: "build single pkg no outfile windows",
|
||||
mode: ModeBuild,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
appExt: ".exe",
|
||||
goos: "windows",
|
||||
pkgName: "hello",
|
||||
wantOut: "hello.exe",
|
||||
},
|
||||
{
|
||||
name: "flash method command - extract bin",
|
||||
conf: &Config{Mode: ModeRun, Target: "esp32"},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "command",
|
||||
Command: "esptool.py --chip esp32 write_flash 0x10000 {bin}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFmt: "bin",
|
||||
wantExt: ".bin",
|
||||
name: "build single pkg with outfile",
|
||||
mode: ModeBuild,
|
||||
multiPkg: false,
|
||||
outFile: "myapp",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "myapp",
|
||||
},
|
||||
{
|
||||
name: "flash method openocd",
|
||||
conf: &Config{Mode: ModeRun, Target: "stm32"},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "openocd",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFmt: "hex",
|
||||
wantExt: ".hex",
|
||||
name: "build single pkg with outfile and ext",
|
||||
mode: ModeBuild,
|
||||
multiPkg: false,
|
||||
outFile: "myapp.exe",
|
||||
appExt: ".exe",
|
||||
goos: "windows",
|
||||
pkgName: "hello",
|
||||
wantOut: "myapp.exe",
|
||||
},
|
||||
{
|
||||
name: "flash method msd - extract from firmware name",
|
||||
conf: &Config{Mode: ModeRun, Target: "rp2040"},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "msd",
|
||||
},
|
||||
MSD: flash.MSD{
|
||||
FirmwareName: "firmware.uf2",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFmt: "uf2",
|
||||
wantExt: ".uf2",
|
||||
name: "build multi pkg",
|
||||
mode: ModeBuild,
|
||||
multiPkg: true,
|
||||
outFile: "",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "", // Should be temp file
|
||||
},
|
||||
// ModeInstall scenarios
|
||||
{
|
||||
name: "install single pkg macos",
|
||||
mode: ModeInstall,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
binPath: "/go/bin",
|
||||
appExt: "",
|
||||
goos: "darwin",
|
||||
pkgName: "hello",
|
||||
wantOut: "/go/bin/hello",
|
||||
},
|
||||
{
|
||||
name: "flash method bmp",
|
||||
conf: &Config{Mode: ModeRun, Target: "stm32"},
|
||||
crossCompile: &crosscompile.Export{
|
||||
Device: flash.Device{
|
||||
Flash: flash.Flash{
|
||||
Method: "bmp",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFmt: "elf",
|
||||
wantExt: ".elf",
|
||||
name: "install single pkg windows",
|
||||
mode: ModeInstall,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
binPath: "C:/go/bin", // Use forward slashes for cross-platform compatibility
|
||||
appExt: ".exe",
|
||||
goos: "windows",
|
||||
pkgName: "hello",
|
||||
wantOut: "C:/go/bin/hello.exe",
|
||||
},
|
||||
{
|
||||
name: "no format",
|
||||
conf: &Config{},
|
||||
crossCompile: &crosscompile.Export{},
|
||||
wantFmt: "",
|
||||
wantExt: "",
|
||||
name: "install multi pkg",
|
||||
mode: ModeInstall,
|
||||
multiPkg: true,
|
||||
outFile: "",
|
||||
binPath: "/go/bin",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "/go/bin/hello",
|
||||
},
|
||||
// Other modes should use temp files
|
||||
{
|
||||
name: "run mode",
|
||||
mode: ModeRun,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "", // Should be temp file
|
||||
},
|
||||
{
|
||||
name: "test mode",
|
||||
mode: ModeTest,
|
||||
multiPkg: false,
|
||||
outFile: "",
|
||||
appExt: "",
|
||||
goos: "linux",
|
||||
pkgName: "hello",
|
||||
wantOut: "", // Should be temp file
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotFmt, gotExt := determineFormat(tt.conf, tt.crossCompile)
|
||||
if gotFmt != tt.wantFmt {
|
||||
t.Errorf("determineFormat() format = %v, want %v", gotFmt, tt.wantFmt)
|
||||
conf := &Config{
|
||||
Mode: tt.mode,
|
||||
OutFile: tt.outFile,
|
||||
BinPath: tt.binPath,
|
||||
AppExt: tt.appExt,
|
||||
Target: "", // Native target
|
||||
}
|
||||
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)
|
||||
|
||||
crossCompile := &crosscompile.Export{}
|
||||
|
||||
result, err := buildOutFmts(tt.pkgName, conf, tt.multiPkg, crossCompile)
|
||||
if err != nil {
|
||||
t.Errorf("buildOutFmts() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check base output path
|
||||
if tt.wantOut != "" {
|
||||
if result.Out != tt.wantOut {
|
||||
t.Errorf("buildOutFmts().Out = %v, want %v", result.Out, tt.wantOut)
|
||||
}
|
||||
} else {
|
||||
// Should be a temp file
|
||||
if result.Out == "" || !strings.Contains(result.Out, tt.pkgName) {
|
||||
t.Errorf("buildOutFmts().Out should be temp file containing %v, got %v", tt.pkgName, result.Out)
|
||||
}
|
||||
}
|
||||
|
||||
// For native targets, no format files should be generated
|
||||
if result.Bin != "" {
|
||||
t.Errorf("buildOutFmts().Bin = %v, want empty for native target", result.Bin)
|
||||
}
|
||||
if result.Hex != "" {
|
||||
t.Errorf("buildOutFmts().Hex = %v, want empty for native target", result.Hex)
|
||||
}
|
||||
if result.Img != "" {
|
||||
t.Errorf("buildOutFmts().Img = %v, want empty for native target", result.Img)
|
||||
}
|
||||
if result.Uf2 != "" {
|
||||
t.Errorf("buildOutFmts().Uf2 = %v, want empty for native target", result.Uf2)
|
||||
}
|
||||
if result.Zip != "" {
|
||||
t.Errorf("buildOutFmts().Zip = %v, want empty for native target", result.Zip)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func runNative(ctx *context, app, pkgDir, pkgName string, conf *Config, mode Mod
|
||||
return nil
|
||||
}
|
||||
|
||||
func runInEmulator(emulator, app, pkgDir, pkgName string, conf *Config, mode Mode, verbose bool) error {
|
||||
func runInEmulator(emulator string, envMap map[string]string, pkgDir, pkgName string, conf *Config, mode Mode, verbose bool) error {
|
||||
if emulator == "" {
|
||||
return fmt.Errorf("target %s does not have emulator configured", conf.Target)
|
||||
}
|
||||
@@ -95,31 +95,21 @@ func runInEmulator(emulator, app, pkgDir, pkgName string, conf *Config, mode Mod
|
||||
|
||||
switch mode {
|
||||
case ModeRun:
|
||||
return runEmuCmd(app, emulator, conf.RunArgs, verbose)
|
||||
return runEmuCmd(envMap, emulator, conf.RunArgs, verbose)
|
||||
case ModeTest:
|
||||
return runEmuCmd(app, emulator, conf.RunArgs, verbose)
|
||||
return runEmuCmd(envMap, emulator, conf.RunArgs, verbose)
|
||||
case ModeCmpTest:
|
||||
cmpTest(pkgDir, pkgName, app, conf.GenExpect, conf.RunArgs)
|
||||
cmpTest(pkgDir, pkgName, envMap["out"], conf.GenExpect, conf.RunArgs)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runEmuCmd runs the application in emulator by formatting the emulator command template
|
||||
func runEmuCmd(appPath, emulatorTemplate string, runArgs []string, verbose bool) error {
|
||||
// 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,
|
||||
}
|
||||
|
||||
func runEmuCmd(envMap map[string]string, emulatorTemplate string, runArgs []string, verbose bool) error {
|
||||
// Expand the emulator command template
|
||||
emulatorCmd := emulatorTemplate
|
||||
for placeholder, path := range envs {
|
||||
for placeholder, path := range envMap {
|
||||
var target string
|
||||
if placeholder == "" {
|
||||
target = "{}"
|
||||
|
||||
16
internal/firmware/env.go
Normal file
16
internal/firmware/env.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package firmware
|
||||
|
||||
import "strings"
|
||||
|
||||
// BinaryFormatToEnvName returns the environment variable name based on the binary format
|
||||
// Returns the format name for template expansion (e.g., "bin", "uf2", "zip")
|
||||
func BinaryFormatToEnvName(binaryFormat string) string {
|
||||
if strings.HasPrefix(binaryFormat, "esp") {
|
||||
return "bin"
|
||||
} else if strings.HasPrefix(binaryFormat, "uf2") {
|
||||
return "uf2"
|
||||
} else if strings.HasPrefix(binaryFormat, "nrf-dfu") {
|
||||
return "zip"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package firmware
|
||||
|
||||
import "strings"
|
||||
|
||||
// BinaryExt returns the binary file extension based on the binary format
|
||||
// Returns ".bin" for ESP-based formats, "" for others
|
||||
func BinaryExt(binaryFormat string) string {
|
||||
if strings.HasPrefix(binaryFormat, "esp") {
|
||||
return ".bin"
|
||||
} else if strings.HasPrefix(binaryFormat, "uf2") {
|
||||
return ".uf2"
|
||||
} else if strings.HasPrefix(binaryFormat, "nrf-dfu") {
|
||||
return ".zip"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
//go:build !llgo
|
||||
// +build !llgo
|
||||
|
||||
package firmware
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBinaryExt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
binaryFormat string
|
||||
expected string
|
||||
}{
|
||||
{"ESP32", "esp32", ".bin"},
|
||||
{"ESP8266", "esp8266", ".bin"},
|
||||
{"ESP32C3", "esp32c3", ".bin"},
|
||||
{"UF2", "uf2", ".uf2"},
|
||||
{"ELF", "elf", ""},
|
||||
{"Empty", "", ""},
|
||||
{"NRF-DFU", "nrf-dfu", ".zip"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := BinaryExt(tt.binaryFormat)
|
||||
if result != tt.expected {
|
||||
t.Errorf("BinaryExt() = %q, want %q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,12 @@ func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
||||
return fmt.Errorf("unsupported firmware format: %s", format)
|
||||
}
|
||||
|
||||
// ExtractFileFormatFromEmulator extracts file format from emulator command template
|
||||
// ExtractFileFormatFromCommand extracts file format from command template
|
||||
// Returns the format if found (e.g. "bin", "hex", "zip", "img"), empty string if not found
|
||||
func ExtractFileFormatFromEmulator(emulatorCmd string) string {
|
||||
func ExtractFileFormatFromCommand(cmd string) string {
|
||||
formats := []string{"bin", "hex", "zip", "img", "uf2"}
|
||||
for _, format := range formats {
|
||||
if strings.Contains(emulatorCmd, "{"+format+"}") {
|
||||
if strings.Contains(cmd, "{"+format+"}") {
|
||||
return format
|
||||
}
|
||||
}
|
||||
@@ -52,21 +52,52 @@ func GetFileExtFromFormat(format string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertOutput converts a binary file to the specified format.
|
||||
// If binaryFormat == fileFormat, no conversion is needed.
|
||||
// Otherwise, only hex format conversion is supported.
|
||||
func ConvertOutput(infile, outfile, binaryFormat, fileFormat string) error {
|
||||
// If formats match, no conversion needed
|
||||
if binaryFormat == fileFormat {
|
||||
return nil
|
||||
// ConvertFormats processes format conversions for embedded targets only
|
||||
func ConvertFormats(binFmt, fmtDetail string, envMap map[string]string) error {
|
||||
fmt.Printf("Converting formats based on binary format: %s\n", binFmt)
|
||||
fmt.Printf("Format details: %s\n", fmtDetail)
|
||||
fmt.Printf("Environment map: %+v\n", envMap)
|
||||
// Convert to bin format first (needed for img)
|
||||
if envMap["bin"] != "" {
|
||||
err := MakeFirmwareImage(envMap["out"], envMap["bin"], binFmt, fmtDetail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert to bin format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Only support conversion to hex and format
|
||||
if fileFormat == "hex" {
|
||||
return convertToHex(infile, outfile)
|
||||
// Convert to hex format
|
||||
if envMap["hex"] != "" {
|
||||
err := MakeFirmwareImage(envMap["out"], envMap["hex"], binFmt, fmtDetail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert to hex format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported format conversion from %s to %s", binaryFormat, fileFormat)
|
||||
// Convert to img format (depends on bin)
|
||||
if envMap["img"] != "" {
|
||||
err := MakeFirmwareImage(envMap["out"], envMap["img"], binFmt+"-img", fmtDetail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert to img format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to uf2 format
|
||||
if envMap["uf2"] != "" {
|
||||
err := MakeFirmwareImage(envMap["out"], envMap["uf2"], binFmt, fmtDetail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert to uf2 format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to zip format
|
||||
if envMap["zip"] != "" {
|
||||
err := MakeFirmwareImage(envMap["out"], envMap["zip"], binFmt, fmtDetail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert to zip format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertToHex converts binary file to hex format (each byte as two hex characters)
|
||||
|
||||
@@ -192,7 +192,7 @@ func touchSerialPortAt1200bps(port string) (err error) {
|
||||
}
|
||||
|
||||
// FlashDevice flashes firmware to a device based on the device configuration
|
||||
func FlashDevice(device Device, app string, port string, verbose bool) error {
|
||||
func FlashDevice(device Device, envMap map[string]string, port string, verbose bool) error {
|
||||
method := device.Flash.Method
|
||||
if method == "" {
|
||||
method = "command"
|
||||
@@ -208,7 +208,7 @@ func FlashDevice(device Device, app string, port string, verbose bool) error {
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "Flashing %s using method: %s\n", app, method)
|
||||
fmt.Fprintf(os.Stderr, "Flashing using method: %s\n", method)
|
||||
fmt.Fprintf(os.Stderr, "Using port: %s\n", port)
|
||||
}
|
||||
|
||||
@@ -226,26 +226,26 @@ func FlashDevice(device Device, app string, port string, verbose bool) error {
|
||||
|
||||
switch method {
|
||||
case "command":
|
||||
return flashCommand(device.Flash, app, port, verbose)
|
||||
return flashCommand(device.Flash, envMap, port, verbose)
|
||||
case "openocd":
|
||||
return flashOpenOCD(device.OpenOCD, app, verbose)
|
||||
return flashOpenOCD(device.OpenOCD, envMap, verbose)
|
||||
case "msd":
|
||||
return flashMSD(device.MSD, app, verbose)
|
||||
return flashMSD(device.MSD, envMap, verbose)
|
||||
case "bmp":
|
||||
return flashBMP(app, verbose)
|
||||
return flashBMP(envMap, port, verbose)
|
||||
default:
|
||||
return fmt.Errorf("unsupported flash method: %s", method)
|
||||
}
|
||||
}
|
||||
|
||||
// flashCommand handles command-based flashing
|
||||
func flashCommand(flash Flash, app string, port string, verbose bool) error {
|
||||
func flashCommand(flash Flash, envMap map[string]string, port string, verbose bool) error {
|
||||
if flash.Command == "" {
|
||||
return fmt.Errorf("flash command not specified")
|
||||
}
|
||||
|
||||
// Build environment map for template variable expansion
|
||||
envs := buildFlashEnvMap(app, port)
|
||||
envs := buildFlashEnvMap(envMap, port)
|
||||
|
||||
// Expand template variables in command
|
||||
expandedCommand := expandEnv(flash.Command, envs)
|
||||
@@ -269,7 +269,7 @@ func flashCommand(flash Flash, app string, port string, verbose bool) error {
|
||||
}
|
||||
|
||||
// flashOpenOCD handles OpenOCD-based flashing
|
||||
func flashOpenOCD(openocd OpenOCD, app string, verbose bool) error {
|
||||
func flashOpenOCD(openocd OpenOCD, envMap map[string]string, verbose bool) error {
|
||||
if openocd.Interface == "" {
|
||||
return fmt.Errorf("OpenOCD interface not specified")
|
||||
}
|
||||
@@ -290,7 +290,7 @@ func flashOpenOCD(openocd OpenOCD, app string, verbose bool) error {
|
||||
args = append(args,
|
||||
"-c", "init",
|
||||
"-c", "reset init",
|
||||
"-c", fmt.Sprintf("flash write_image erase %s", app),
|
||||
"-c", fmt.Sprintf("flash write_image erase %s", envMap["elf"]),
|
||||
"-c", "reset",
|
||||
"-c", "shutdown",
|
||||
)
|
||||
@@ -307,7 +307,7 @@ func flashOpenOCD(openocd OpenOCD, app string, verbose bool) error {
|
||||
}
|
||||
|
||||
// flashMSD handles Mass Storage Device flashing
|
||||
func flashMSD(msd MSD, app string, verbose bool) error {
|
||||
func flashMSD(msd MSD, envMap map[string]string, verbose bool) error {
|
||||
if len(msd.VolumeName) == 0 {
|
||||
return fmt.Errorf("MSD volume names not specified")
|
||||
}
|
||||
@@ -363,24 +363,24 @@ func flashMSD(msd MSD, app string, verbose bool) error {
|
||||
destPath := filepath.Join(mountPoint, msd.FirmwareName)
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "Copying %s to %s\n", app, destPath)
|
||||
fmt.Fprintf(os.Stderr, "Copying %s to %s\n", envMap["uf2"], destPath)
|
||||
}
|
||||
|
||||
return copyFile(app, destPath)
|
||||
return copyFile(envMap["uf2"], destPath)
|
||||
}
|
||||
|
||||
// flashBMP handles Black Magic Probe flashing
|
||||
func flashBMP(app string, verbose bool) error {
|
||||
func flashBMP(envMap map[string]string, port string, verbose bool) error {
|
||||
// BMP typically uses GDB for flashing
|
||||
args := []string{
|
||||
"-ex", "target extended-remote /dev/ttyACM0", // Default BMP port
|
||||
"-ex", "target extended-remote " + port,
|
||||
"-ex", "monitor swdp_scan",
|
||||
"-ex", "attach 1",
|
||||
"-ex", "load",
|
||||
"-ex", "compare-sections",
|
||||
"-ex", "kill",
|
||||
"-ex", "quit",
|
||||
app,
|
||||
envMap["elf"],
|
||||
}
|
||||
|
||||
if verbose {
|
||||
@@ -395,7 +395,7 @@ func flashBMP(app string, verbose bool) error {
|
||||
}
|
||||
|
||||
// buildFlashEnvMap creates environment map for template expansion
|
||||
func buildFlashEnvMap(app string, port string) map[string]string {
|
||||
func buildFlashEnvMap(envMap map[string]string, port string) map[string]string {
|
||||
envs := make(map[string]string)
|
||||
|
||||
// Basic paths
|
||||
@@ -407,24 +407,11 @@ func buildFlashEnvMap(app string, port string) map[string]string {
|
||||
envs["port"] = port
|
||||
}
|
||||
|
||||
// File paths based on extension
|
||||
ext := strings.ToLower(filepath.Ext(app))
|
||||
switch ext {
|
||||
case ".hex":
|
||||
envs["hex"] = app
|
||||
case ".bin":
|
||||
envs["bin"] = app
|
||||
case ".elf":
|
||||
envs["elf"] = app
|
||||
case ".uf2":
|
||||
envs["uf2"] = app
|
||||
case ".zip":
|
||||
envs["zip"] = app
|
||||
case ".img":
|
||||
envs["img"] = app
|
||||
default:
|
||||
// Default to binary for unknown extensions
|
||||
envs["bin"] = app
|
||||
// Copy all format paths from envMap
|
||||
for key, value := range envMap {
|
||||
if value != "" {
|
||||
envs[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return envs
|
||||
@@ -433,6 +420,7 @@ func buildFlashEnvMap(app string, port string) map[string]string {
|
||||
// expandEnv expands template variables in a string
|
||||
// Supports variables like {port}, {hex}, {bin}, {root}, {tmpDir}, etc.
|
||||
func expandEnv(template string, envs map[string]string) string {
|
||||
fmt.Fprintf(os.Stderr, "Expanding template: %s with envs: %v\n", template, envs)
|
||||
if template == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/goplus/llgo/internal/build"
|
||||
@@ -48,7 +47,6 @@ func genFrom(pkgPath string, abiMode build.AbiMode) (build.Package, error) {
|
||||
conf := &build.Config{
|
||||
Mode: build.ModeGen,
|
||||
AbiMode: abiMode,
|
||||
AppExt: build.DefaultAppExt(runtime.GOOS),
|
||||
}
|
||||
pkgs, err := build.Do([]string{pkgPath}, conf)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user