feat: add -file-format flag for flexible output formats
Add support for -file-format flag to llgo build command allowing users to specify output format independently of target configuration. Changes: - Add -file-format flag supporting bin, hex, elf, uf2, zip formats - Implement two-stage conversion: firmware format → file format - Add ConvertOutput function with hex format conversion support - Update build logic to handle different modes (build vs run/install) - Add verbose logging for conversion operations For build command: only convert firmware when -file-format is specified For run/install commands: always convert firmware when target requires it 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var OutputFile string
|
var OutputFile string
|
||||||
|
var FileFormat string
|
||||||
|
|
||||||
func AddOutputFlags(fs *flag.FlagSet) {
|
func AddOutputFlags(fs *flag.FlagSet) {
|
||||||
fs.StringVar(&OutputFile, "o", "", "Output file")
|
fs.StringVar(&OutputFile, "o", "", "Output file")
|
||||||
|
fs.StringVar(&FileFormat, "file-format", "", "File format for target output (e.g., bin, hex, elf, uf2, zip)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var Verbose bool
|
var Verbose bool
|
||||||
@@ -48,6 +50,7 @@ func UpdateConfig(conf *build.Config) {
|
|||||||
switch conf.Mode {
|
switch conf.Mode {
|
||||||
case build.ModeBuild:
|
case build.ModeBuild:
|
||||||
conf.OutFile = OutputFile
|
conf.OutFile = OutputFile
|
||||||
|
conf.FileFormat = FileFormat
|
||||||
case build.ModeCmpTest:
|
case build.ModeCmpTest:
|
||||||
conf.GenExpect = Gen
|
conf.GenExpect = Gen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ type Config struct {
|
|||||||
BinPath string
|
BinPath string
|
||||||
AppExt string // ".exe" on Windows, empty on Unix
|
AppExt string // ".exe" on Windows, empty on Unix
|
||||||
OutFile string // only valid for ModeBuild when len(pkgs) == 1
|
OutFile string // only valid for ModeBuild when len(pkgs) == 1
|
||||||
|
FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default
|
||||||
RunArgs []string // only valid for ModeRun
|
RunArgs []string // only valid for ModeRun
|
||||||
Mode Mode
|
Mode Mode
|
||||||
AbiMode AbiMode
|
AbiMode AbiMode
|
||||||
@@ -685,13 +686,19 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
binFmt := ctx.crossCompile.BinaryFormat
|
binFmt := ctx.crossCompile.BinaryFormat
|
||||||
binExt := firmware.BinaryExt(binFmt)
|
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
|
// app: converted firmware output file or executable file
|
||||||
// orgApp: before converted output file
|
// orgApp: before converted output file
|
||||||
app, orgApp, err := generateOutputFilenames(
|
app, orgApp, err := generateOutputFilenames(
|
||||||
conf.OutFile,
|
conf.OutFile,
|
||||||
conf.BinPath,
|
conf.BinPath,
|
||||||
conf.AppExt,
|
conf.AppExt,
|
||||||
binExt,
|
outExt,
|
||||||
name,
|
name,
|
||||||
mode,
|
mode,
|
||||||
len(ctx.initial) > 1,
|
len(ctx.initial) > 1,
|
||||||
@@ -759,10 +766,69 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
err = linkObjFiles(ctx, orgApp, objFiles, linkArgs, verbose)
|
err = linkObjFiles(ctx, orgApp, objFiles, linkArgs, verbose)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
if orgApp != app {
|
// Handle firmware conversion and file format conversion
|
||||||
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
|
currentApp := orgApp
|
||||||
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
|
||||||
|
// 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 != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Firmware conversion if needed
|
||||||
|
if needFirmwareConversion {
|
||||||
|
if outExt == binExt {
|
||||||
|
// Direct conversion to final output
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", ctx.crossCompile.BinaryFormat, currentApp, app)
|
||||||
|
}
|
||||||
|
err = firmware.MakeFirmwareImage(currentApp, app, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
||||||
check(err)
|
check(err)
|
||||||
|
currentApp = app
|
||||||
|
} else {
|
||||||
|
// Convert to intermediate file first
|
||||||
|
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
|
||||||
|
check(err)
|
||||||
|
tmpFile.Close()
|
||||||
|
intermediateApp := tmpFile.Name()
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", ctx.crossCompile.BinaryFormat, currentApp, intermediateApp)
|
||||||
|
}
|
||||||
|
err = firmware.MakeFirmwareImage(currentApp, intermediateApp, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
||||||
|
check(err)
|
||||||
|
currentApp = intermediateApp
|
||||||
|
defer func() {
|
||||||
|
// Only remove if the intermediate file still exists (wasn't moved)
|
||||||
|
if _, err := os.Stat(intermediateApp); err == nil {
|
||||||
|
os.Remove(intermediateApp)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: File format conversion if needed
|
||||||
|
if currentApp != app {
|
||||||
|
if conf.FileFormat != "" {
|
||||||
|
// File format conversion
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Converting to file format: %s (%s -> %s)\n", conf.FileFormat, currentApp, app)
|
||||||
|
}
|
||||||
|
err = firmware.ConvertOutput(currentApp, app, binFmt, conf.FileFormat)
|
||||||
|
check(err)
|
||||||
|
} else {
|
||||||
|
// Just move/copy the file
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Moving file: %s -> %s\n", currentApp, app)
|
||||||
|
}
|
||||||
|
err = os.Rename(currentApp, app)
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package firmware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,3 +19,73 @@ func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("unsupported firmware format: %s", format)
|
return fmt.Errorf("unsupported firmware format: %s", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileExtFromFormat converts file format to file extension
|
||||||
|
func GetFileExtFromFormat(format string) string {
|
||||||
|
switch format {
|
||||||
|
case "bin":
|
||||||
|
return ".bin"
|
||||||
|
case "hex":
|
||||||
|
return ".hex"
|
||||||
|
case "elf":
|
||||||
|
return ""
|
||||||
|
case "uf2":
|
||||||
|
return ".uf2"
|
||||||
|
case "zip":
|
||||||
|
return ".zip"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only support conversion to hex format
|
||||||
|
if fileFormat == "hex" {
|
||||||
|
return convertToHex(infile, outfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unsupported format conversion from %s to %s", binaryFormat, fileFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToHex converts binary file to hex format (each byte as two hex characters)
|
||||||
|
func convertToHex(infile, outfile string) error {
|
||||||
|
srcFile, err := os.Open(infile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
dstFile, err := os.Create(outfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
// Read input file and convert each byte to two hex characters
|
||||||
|
buf := make([]byte, 4096) // Read in chunks
|
||||||
|
for {
|
||||||
|
n, err := srcFile.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if _, writeErr := fmt.Fprintf(dstFile, "%02x", buf[i]); writeErr != nil {
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user