Files
llgo/internal/build/outputs.go

247 lines
7.0 KiB
Go

package build
import (
"os"
"path/filepath"
"strings"
"github.com/goplus/llgo/internal/crosscompile"
"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, 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)
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
}