extract run from linkMainPkg, add flash scaffold

This commit is contained in:
Li Jie
2025-09-05 18:39:47 +08:00
parent 5e5d5c2a83
commit 1033452e8f
8 changed files with 137 additions and 51 deletions

View File

@@ -20,6 +20,7 @@ var BuildEnv string
var Tags string var Tags string
var Target string var Target string
var Emulator bool var Emulator bool
var Port string
var AbiMode int var AbiMode int
var CheckLinkArgs bool var CheckLinkArgs bool
var CheckLLFiles bool var CheckLLFiles bool
@@ -40,10 +41,14 @@ func AddBuildFlags(fs *flag.FlagSet) {
var Gen bool var Gen bool
func AddRunFlags(fs *flag.FlagSet) { func AddEmulatorFlags(fs *flag.FlagSet) {
fs.BoolVar(&Emulator, "emulator", false, "Run in emulator mode") fs.BoolVar(&Emulator, "emulator", false, "Run in emulator mode")
} }
func AddEmbeddedFlags(fs *flag.FlagSet) {
fs.StringVar(&Port, "port", "", "Target port for flashing")
}
func AddCmpTestFlags(fs *flag.FlagSet) { func AddCmpTestFlags(fs *flag.FlagSet) {
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file") fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
} }
@@ -56,9 +61,14 @@ func UpdateConfig(conf *build.Config) {
case build.ModeBuild: case build.ModeBuild:
conf.OutFile = OutputFile conf.OutFile = OutputFile
conf.FileFormat = FileFormat conf.FileFormat = FileFormat
case build.ModeRun: case build.ModeRun, build.ModeTest:
conf.Emulator = Emulator conf.Emulator = Emulator
conf.Port = Port
case build.ModeInstall:
conf.Port = Port
case build.ModeCmpTest: case build.ModeCmpTest:
conf.Emulator = Emulator
conf.Port = Port
conf.GenExpect = Gen conf.GenExpect = Gen
} }
if buildenv.Dev { if buildenv.Dev {

View File

@@ -36,6 +36,7 @@ var Cmd = &base.Command{
func init() { func init() {
Cmd.Run = runCmd Cmd.Run = runCmd
flags.AddBuildFlags(&Cmd.Flag) flags.AddBuildFlags(&Cmd.Flag)
flags.AddEmbeddedFlags(&Cmd.Flag)
} }
func runCmd(cmd *base.Command, args []string) { func runCmd(cmd *base.Command, args []string) {

View File

@@ -49,8 +49,13 @@ func init() {
CmpTestCmd.Run = runCmpTest CmpTestCmd.Run = runCmpTest
base.PassBuildFlags(Cmd) base.PassBuildFlags(Cmd)
flags.AddBuildFlags(&Cmd.Flag) flags.AddBuildFlags(&Cmd.Flag)
flags.AddEmulatorFlags(&Cmd.Flag)
flags.AddEmbeddedFlags(&Cmd.Flag) // for -target support
base.PassBuildFlags(CmpTestCmd)
flags.AddBuildFlags(&CmpTestCmd.Flag) flags.AddBuildFlags(&CmpTestCmd.Flag)
flags.AddRunFlags(&Cmd.Flag) flags.AddEmulatorFlags(&CmpTestCmd.Flag)
flags.AddEmbeddedFlags(&CmpTestCmd.Flag) // for -target support
flags.AddCmpTestFlags(&CmpTestCmd.Flag) flags.AddCmpTestFlags(&CmpTestCmd.Flag)
} }

View File

@@ -18,6 +18,8 @@ var Cmd = &base.Command{
func init() { func init() {
Cmd.Run = runCmd Cmd.Run = runCmd
flags.AddBuildFlags(&Cmd.Flag) flags.AddBuildFlags(&Cmd.Flag)
flags.AddEmulatorFlags(&Cmd.Flag)
flags.AddEmbeddedFlags(&Cmd.Flag)
} }
func runCmd(cmd *base.Command, args []string) { func runCmd(cmd *base.Command, args []string) {

View File

@@ -7,7 +7,7 @@
- `-file-format <format>` - Convert to specified format (**requires `-target`**) - `-file-format <format>` - Convert to specified format (**requires `-target`**)
- Supported: `elf` (default), `bin`, `hex`, `uf2`, `zip`, `img` - Supported: `elf` (default), `bin`, `hex`, `uf2`, `zip`, `img`
- `-emulator` - Run using emulator (auto-detects required format) - `-emulator` - Run using emulator (auto-detects required format)
- `-d <device>` - Target device for flashing or testing - `-port <port>` - Target port for flashing or testing
## Commands ## Commands
@@ -25,12 +25,12 @@ Compile and run program.
Compile and run tests. Compile and run tests.
- No `-target`: Run tests locally - No `-target`: Run tests locally
- With `-target`: Run tests on device or emulator - With `-target`: Run tests on device or emulator
- Supports `-emulator` and `-d` flags - Supports `-emulator` and `-port` flags
### llgo install ### llgo install
Install program or flash to device. Install program or flash to device.
- No `-target`: Install to `$GOPATH/bin` - No `-target`: Install to `$GOPATH/bin`
- With `-target`: Flash to device (use `-d` to specify device) - With `-target`: Flash to device (use `-port` to specify port)
## Examples ## Examples
@@ -46,7 +46,7 @@ llgo build -target esp32 hello.go # -> hello (ELF)
llgo build -target esp32 -file-format bin hello.go # -> hello.bin llgo build -target esp32 -file-format bin hello.go # -> hello.bin
llgo run -target esp32 hello.go # run on ESP32 llgo run -target esp32 hello.go # run on ESP32
llgo run -target esp32 -emulator hello.go # run in emulator 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 -port /dev/ttyUSB0 . # run tests on device
llgo test -target esp32 -emulator # run tests in emulator llgo test -target esp32 -emulator . # run tests in emulator
llgo install -target esp32 -d /dev/ttyUSB0 hello.go # flash to specific device llgo install -target esp32 -port /dev/ttyUSB0 hello.go # flash to specific port
``` ```

View File

@@ -79,7 +79,8 @@ type Config struct {
AppExt string // ".exe" on Windows, empty on Unix AppExt string // ".exe" on Windows, empty on Unix
OutFile string // only valid for ModeBuild when len(pkgs) == 1 OutFile string // only valid for ModeBuild when len(pkgs) == 1
FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default
Emulator bool // only valid for ModeRun - run in emulator mode Emulator bool // only valid for ModeRun/ModeTest - run in emulator mode
Port string // only valid for ModeRun/ModeTest/ModeInstall/ModeCmpTest - target port for flashing
RunArgs []string // only valid for ModeRun RunArgs []string // only valid for ModeRun
Mode Mode Mode Mode
AbiMode AbiMode AbiMode AbiMode
@@ -314,7 +315,25 @@ func Do(args []string, conf *Config) ([]Package, error) {
for _, pkg := range initial { for _, pkg := range initial {
if needLink(pkg, mode) { if needLink(pkg, mode) {
linkMainPkg(ctx, pkg, allPkgs, global, conf, mode, verbose) app, err := linkMainPkg(ctx, pkg, allPkgs, global, conf, mode, verbose)
if err != nil {
return nil, err
}
if slices.Contains([]Mode{ModeRun, ModeCmpTest, ModeTest, ModeInstall}, mode) {
// Flash to device if needed (for embedded targets)
if !conf.Emulator && conf.Target != "" {
err = flash(ctx, app, verbose)
if err != nil {
return nil, err
}
} else if mode != ModeInstall {
err = run(ctx, app, pkg.PkgPath, pkg.Dir, conf, mode, verbose)
if err != nil {
return nil, err
}
}
}
} }
} }
@@ -629,14 +648,16 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
return objFiles, nil return objFiles, nil
} }
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) { func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) (string, error) {
pkgPath := pkg.PkgPath pkgPath := pkg.PkgPath
name := path.Base(pkgPath) name := path.Base(pkgPath)
binFmt := ctx.crossCompile.BinaryFormat binFmt := ctx.crossCompile.BinaryFormat
// Generate output configuration using the centralized function // Generate output configuration using the centralized function
outputCfg, err := GenOutputs(conf, name, len(ctx.initial) > 1, ctx.crossCompile.Emulator, binFmt) outputCfg, err := GenOutputs(conf, name, len(ctx.initial) > 1, ctx.crossCompile.Emulator, binFmt)
check(err) if err != nil {
return "", err
}
app := outputCfg.OutPath app := outputCfg.OutPath
orgApp := outputCfg.IntPath orgApp := outputCfg.IntPath
@@ -667,18 +688,24 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
} }
}) })
entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
check(err) if err != nil {
return "", err
}
// defer os.Remove(entryLLFile) // defer os.Remove(entryLLFile)
objFiles = append(objFiles, entryObjFile) objFiles = append(objFiles, entryObjFile)
// Compile extra files from target configuration // Compile extra files from target configuration
extraObjFiles, err := compileExtraFiles(ctx, verbose) extraObjFiles, err := compileExtraFiles(ctx, verbose)
check(err) if err != nil {
return "", err
}
objFiles = append(objFiles, extraObjFiles...) objFiles = append(objFiles, extraObjFiles...)
if global != nil { if global != nil {
export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String())) export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String()))
check(err) if err != nil {
return "", err
}
objFiles = append(objFiles, export) objFiles = append(objFiles, export)
} }
@@ -700,19 +727,13 @@ 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) if err != nil {
return "", err
}
// Handle firmware conversion and file format conversion // Handle firmware conversion and file format conversion
currentApp := orgApp currentApp := orgApp
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 // Step 1: Firmware conversion if needed
if outputCfg.NeedFwGen { if outputCfg.NeedFwGen {
if outputCfg.DirectGen { if outputCfg.DirectGen {
@@ -721,13 +742,17 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", outputCfg.BinFmt, currentApp, app) fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", outputCfg.BinFmt, currentApp, app)
} }
err = firmware.MakeFirmwareImage(currentApp, app, outputCfg.BinFmt, ctx.crossCompile.FormatDetail) err = firmware.MakeFirmwareImage(currentApp, app, outputCfg.BinFmt, ctx.crossCompile.FormatDetail)
check(err) if err != nil {
return "", err
}
currentApp = app currentApp = app
} else { } else {
// Convert to intermediate file first // Convert to intermediate file first
binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat) binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat)
tmpFile, err := os.CreateTemp("", "llgo-*"+binExt) tmpFile, err := os.CreateTemp("", "llgo-*"+binExt)
check(err) if err != nil {
return "", err
}
tmpFile.Close() tmpFile.Close()
intermediateApp := tmpFile.Name() intermediateApp := tmpFile.Name()
@@ -735,7 +760,9 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
fmt.Fprintf(os.Stderr, "Converting to firmware format: %s (%s -> %s)\n", ctx.crossCompile.BinaryFormat, currentApp, intermediateApp) 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) err = firmware.MakeFirmwareImage(currentApp, intermediateApp, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
check(err) if err != nil {
return "", err
}
currentApp = intermediateApp currentApp = intermediateApp
defer func() { defer func() {
// Only remove if the intermediate file still exists (wasn't moved) // Only remove if the intermediate file still exists (wasn't moved)
@@ -754,37 +781,63 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
fmt.Fprintf(os.Stderr, "Converting to file format: %s (%s -> %s)\n", outputCfg.FileFmt, currentApp, app) fmt.Fprintf(os.Stderr, "Converting to file format: %s (%s -> %s)\n", outputCfg.FileFmt, currentApp, app)
} }
err = firmware.ConvertOutput(currentApp, app, binFmt, outputCfg.FileFmt) err = firmware.ConvertOutput(currentApp, app, binFmt, outputCfg.FileFmt)
check(err) if err != nil {
return "", err
}
} else { } else {
// Just move/copy the file // Just move/copy the file
if verbose { if verbose {
fmt.Fprintf(os.Stderr, "Moving file: %s -> %s\n", currentApp, app) fmt.Fprintf(os.Stderr, "Moving file: %s -> %s\n", currentApp, app)
} }
err = os.Rename(currentApp, app) err = os.Rename(currentApp, app)
check(err) if err != nil {
return "", err
} }
} }
}
return app, nil
}
func run(ctx *context, app string, pkgPath, pkgDir string, conf *Config, mode Mode, verbose bool) error {
useEmulator := false
if mode == ModeRun && conf.Emulator {
if ctx.crossCompile.Emulator == "" {
return fmt.Errorf("target %s does not have emulator configured", conf.Target)
}
useEmulator = true
}
switch mode { switch mode {
case ModeTest: case ModeTest:
cmd := exec.Command(app, conf.RunArgs...) cmd := exec.Command(app, conf.RunArgs...)
cmd.Dir = pkg.Dir cmd.Dir = pkgDir
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Run() if err := cmd.Run(); err != nil {
if s := cmd.ProcessState; s != nil { if exitErr, ok := err.(*exec.ExitError); ok {
exitCode := s.ExitCode() // The command ran and exited with a non-zero status.
fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, exitCode) fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, exitErr.ExitCode())
if !ctx.testFail && exitCode != 0 { if !ctx.testFail {
ctx.testFail = true ctx.testFail = true
} }
} else {
// The command failed to start or other error.
fmt.Fprintf(os.Stderr, "failed to run test %s: %v\n", app, err)
if !ctx.testFail {
ctx.testFail = true
}
}
} }
case ModeRun: case ModeRun:
if useEmulator { if useEmulator {
if verbose { if verbose {
fmt.Fprintf(os.Stderr, "Using emulator: %s\n", ctx.crossCompile.Emulator) fmt.Fprintf(os.Stderr, "Using emulator: %s\n", ctx.crossCompile.Emulator)
} }
runInEmulator(app, ctx.crossCompile.Emulator, conf.RunArgs, verbose) err := runInEmulator(app, ctx.crossCompile.Emulator, conf.RunArgs, verbose)
if err != nil {
return err
}
} else { } else {
args := make([]string, 0, len(conf.RunArgs)+1) args := make([]string, 0, len(conf.RunArgs)+1)
copy(args, conf.RunArgs) copy(args, conf.RunArgs)
@@ -813,17 +866,28 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
panic(err) return err
} }
if s := cmd.ProcessState; s != nil { if s := cmd.ProcessState; s != nil {
mockable.Exit(s.ExitCode()) mockable.Exit(s.ExitCode())
} }
} }
case ModeCmpTest: case ModeCmpTest:
cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs) cmpTest(pkgDir, pkgPath, app, conf.GenExpect, conf.RunArgs)
} }
return nil
}
func flash(ctx *context, app string, verbose bool) error {
// TODO: Implement device flashing logic
if verbose {
fmt.Fprintf(os.Stderr, "Flashing %s to port %s\n", app, ctx.buildConf.Port)
}
fmt.Printf("conf: %#v\n", ctx.buildConf)
fmt.Printf("crosscompile: %#v\n", ctx.crossCompile)
return nil
} }
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error { func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
@@ -1314,7 +1378,7 @@ func findDylibDep(exe, lib string) string {
type none struct{} type none struct{}
// runInEmulator runs the application in emulator by formatting the emulator command template // runInEmulator runs the application in emulator by formatting the emulator command template
func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose bool) { func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose bool) error {
// Build environment map for template variable expansion // Build environment map for template variable expansion
envs := map[string]string{ envs := map[string]string{
"": appPath, // {} expands to app path "": appPath, // {} expands to app path
@@ -1357,11 +1421,12 @@ func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose b
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
panic(err) return err
} }
if s := cmd.ProcessState; s != nil { if s := cmd.ProcessState; s != nil {
mockable.Exit(s.ExitCode()) mockable.Exit(s.ExitCode())
} }
return nil
} }
func check(err error) { func check(err error) {

View File

@@ -110,7 +110,10 @@ func genBuildOutputs(conf *Config, pkgName string, multiPkg bool, cfg OutputCfg)
} }
} else { } else {
// Direct output // Direct output
cfg.OutPath = baseName + conf.AppExt cfg.OutPath = baseName
if filepath.Ext(cfg.OutPath) != conf.AppExt {
cfg.OutPath += conf.AppExt
}
cfg.IntPath = cfg.OutPath cfg.IntPath = cfg.OutPath
} }

View File

@@ -33,7 +33,7 @@ func TestGenOutputs(t *testing.T) {
wantOutPath: "hello", wantOutPath: "hello",
wantOutExt: "", wantOutExt: "",
wantFileFmt: "", wantFileFmt: "",
wantBinFmt: "esp32", wantBinFmt: "",
wantDirectGen: true, wantDirectGen: true,
}, },
{ {
@@ -47,7 +47,7 @@ func TestGenOutputs(t *testing.T) {
wantOutPath: "myapp", wantOutPath: "myapp",
wantOutExt: "", wantOutExt: "",
wantFileFmt: "", wantFileFmt: "",
wantBinFmt: "esp32", wantBinFmt: "",
wantDirectGen: true, wantDirectGen: true,
}, },
{ {
@@ -108,7 +108,7 @@ func TestGenOutputs(t *testing.T) {
wantOutPath: "", // temp file wantOutPath: "", // temp file
wantOutExt: "", wantOutExt: "",
wantFileFmt: "", wantFileFmt: "",
wantBinFmt: "esp32", wantBinFmt: "",
wantDirectGen: true, wantDirectGen: true,
}, },
{ {
@@ -167,7 +167,7 @@ func TestGenOutputs(t *testing.T) {
wantOutPath: "", // temp file wantOutPath: "", // temp file
wantOutExt: "", wantOutExt: "",
wantFileFmt: "", wantFileFmt: "",
wantBinFmt: "esp32", wantBinFmt: "",
wantDirectGen: true, wantDirectGen: true,
}, },
{ {
@@ -194,7 +194,7 @@ func TestGenOutputs(t *testing.T) {
wantOutPath: "", // temp file wantOutPath: "", // temp file
wantOutExt: "", wantOutExt: "",
wantFileFmt: "", wantFileFmt: "",
wantBinFmt: "esp32", wantBinFmt: "",
wantDirectGen: true, wantDirectGen: true,
}, },
{ {