From 1033452e8f19e452b794c42f561535cae2cb6b4a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 5 Sep 2025 18:39:47 +0800 Subject: [PATCH] extract run from linkMainPkg, add flash scaffold --- cmd/internal/flags/flags.go | 14 +++- cmd/internal/install/install.go | 1 + cmd/internal/run/run.go | 7 +- cmd/internal/test/test.go | 2 + doc/Embedded_Cmd.md | 16 ++-- internal/build/build.go | 133 ++++++++++++++++++++++++-------- internal/build/outputs.go | 5 +- internal/build/outputs_test.go | 10 +-- 8 files changed, 137 insertions(+), 51 deletions(-) diff --git a/cmd/internal/flags/flags.go b/cmd/internal/flags/flags.go index c06bb7f1..adb7c219 100644 --- a/cmd/internal/flags/flags.go +++ b/cmd/internal/flags/flags.go @@ -20,6 +20,7 @@ var BuildEnv string var Tags string var Target string var Emulator bool +var Port string var AbiMode int var CheckLinkArgs bool var CheckLLFiles bool @@ -40,10 +41,14 @@ func AddBuildFlags(fs *flag.FlagSet) { var Gen bool -func AddRunFlags(fs *flag.FlagSet) { +func AddEmulatorFlags(fs *flag.FlagSet) { 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) { fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file") } @@ -56,9 +61,14 @@ func UpdateConfig(conf *build.Config) { case build.ModeBuild: conf.OutFile = OutputFile conf.FileFormat = FileFormat - case build.ModeRun: + case build.ModeRun, build.ModeTest: conf.Emulator = Emulator + conf.Port = Port + case build.ModeInstall: + conf.Port = Port case build.ModeCmpTest: + conf.Emulator = Emulator + conf.Port = Port conf.GenExpect = Gen } if buildenv.Dev { diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go index 7c50c509..d986853c 100644 --- a/cmd/internal/install/install.go +++ b/cmd/internal/install/install.go @@ -36,6 +36,7 @@ var Cmd = &base.Command{ func init() { Cmd.Run = runCmd flags.AddBuildFlags(&Cmd.Flag) + flags.AddEmbeddedFlags(&Cmd.Flag) } func runCmd(cmd *base.Command, args []string) { diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go index b90add5e..b1614715 100644 --- a/cmd/internal/run/run.go +++ b/cmd/internal/run/run.go @@ -49,8 +49,13 @@ func init() { CmpTestCmd.Run = runCmpTest base.PassBuildFlags(Cmd) flags.AddBuildFlags(&Cmd.Flag) + flags.AddEmulatorFlags(&Cmd.Flag) + flags.AddEmbeddedFlags(&Cmd.Flag) // for -target support + + base.PassBuildFlags(CmpTestCmd) flags.AddBuildFlags(&CmpTestCmd.Flag) - flags.AddRunFlags(&Cmd.Flag) + flags.AddEmulatorFlags(&CmpTestCmd.Flag) + flags.AddEmbeddedFlags(&CmpTestCmd.Flag) // for -target support flags.AddCmpTestFlags(&CmpTestCmd.Flag) } diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go index b05f1e69..5a259922 100644 --- a/cmd/internal/test/test.go +++ b/cmd/internal/test/test.go @@ -18,6 +18,8 @@ var Cmd = &base.Command{ func init() { Cmd.Run = runCmd flags.AddBuildFlags(&Cmd.Flag) + flags.AddEmulatorFlags(&Cmd.Flag) + flags.AddEmbeddedFlags(&Cmd.Flag) } func runCmd(cmd *base.Command, args []string) { diff --git a/doc/Embedded_Cmd.md b/doc/Embedded_Cmd.md index 1909893c..d4616484 100644 --- a/doc/Embedded_Cmd.md +++ b/doc/Embedded_Cmd.md @@ -7,7 +7,7 @@ - `-file-format ` - Convert to specified format (**requires `-target`**) - Supported: `elf` (default), `bin`, `hex`, `uf2`, `zip`, `img` - `-emulator` - Run using emulator (auto-detects required format) -- `-d ` - Target device for flashing or testing +- `-port ` - Target port for flashing or testing ## Commands @@ -16,7 +16,7 @@ Compile program to output file. - No `-target`: Native executable - With `-target`: ELF executable (or `-file-format` if specified) -### llgo run +### llgo run Compile and run program. - No `-target`: Run locally - With `-target`: Run on device or emulator @@ -25,12 +25,12 @@ Compile and run program. Compile and run tests. - No `-target`: Run tests locally - With `-target`: Run tests on device or emulator -- Supports `-emulator` and `-d` flags +- Supports `-emulator` and `-port` flags ### llgo install Install program or flash to device. - No `-target`: Install to `$GOPATH/bin` -- With `-target`: Flash to device (use `-d` to specify device) +- With `-target`: Flash to device (use `-port` to specify port) ## 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 run -target esp32 hello.go # run on ESP32 llgo run -target esp32 -emulator hello.go # run in emulator -llgo test -target esp32 -d /dev/ttyUSB0 # run tests on device -llgo test -target esp32 -emulator # run tests in emulator -llgo install -target esp32 -d /dev/ttyUSB0 hello.go # flash to specific device -``` \ No newline at end of file +llgo test -target esp32 -port /dev/ttyUSB0 . # run tests on device +llgo test -target esp32 -emulator . # run tests in emulator +llgo install -target esp32 -port /dev/ttyUSB0 hello.go # flash to specific port +``` diff --git a/internal/build/build.go b/internal/build/build.go index c8a80579..fabb54e1 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -79,7 +79,8 @@ type Config struct { AppExt string // ".exe" on Windows, empty on Unix OutFile string // only valid for ModeBuild when len(pkgs) == 1 FileFormat string // File format override (e.g., "bin", "hex", "elf", "uf2", "zip") - takes precedence over target's default - Emulator bool // only valid for ModeRun - run in emulator mode + 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 Mode Mode AbiMode AbiMode @@ -314,7 +315,25 @@ func Do(args []string, conf *Config) ([]Package, error) { for _, pkg := range initial { 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 } -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 name := path.Base(pkgPath) binFmt := ctx.crossCompile.BinaryFormat // Generate output configuration using the centralized function outputCfg, err := GenOutputs(conf, name, len(ctx.initial) > 1, ctx.crossCompile.Emulator, binFmt) - check(err) + if err != nil { + return "", err + } app := outputCfg.OutPath 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) - check(err) + if err != nil { + return "", err + } // defer os.Remove(entryLLFile) objFiles = append(objFiles, entryObjFile) // Compile extra files from target configuration extraObjFiles, err := compileExtraFiles(ctx, verbose) - check(err) + if err != nil { + return "", err + } objFiles = append(objFiles, extraObjFiles...) if global != nil { export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String())) - check(err) + if err != nil { + return "", err + } 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) - check(err) + if err != nil { + return "", err + } // Handle firmware conversion and file format conversion 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 if outputCfg.NeedFwGen { 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) } err = firmware.MakeFirmwareImage(currentApp, app, outputCfg.BinFmt, ctx.crossCompile.FormatDetail) - check(err) + if err != nil { + return "", err + } currentApp = app } else { // Convert to intermediate file first binExt := firmware.BinaryExt(ctx.crossCompile.BinaryFormat) tmpFile, err := os.CreateTemp("", "llgo-*"+binExt) - check(err) + if err != nil { + return "", err + } tmpFile.Close() 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) } err = firmware.MakeFirmwareImage(currentApp, intermediateApp, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail) - check(err) + if err != nil { + return "", err + } currentApp = intermediateApp defer func() { // Only remove if the intermediate file still exists (wasn't moved) @@ -754,29 +781,52 @@ 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) } err = firmware.ConvertOutput(currentApp, app, binFmt, outputCfg.FileFmt) - check(err) + if err != nil { + return "", 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) + 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 { case ModeTest: cmd := exec.Command(app, conf.RunArgs...) - cmd.Dir = pkg.Dir + cmd.Dir = pkgDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - cmd.Run() - if s := cmd.ProcessState; s != nil { - exitCode := s.ExitCode() - fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, exitCode) - if !ctx.testFail && exitCode != 0 { - ctx.testFail = true + if err := cmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + // The command ran and exited with a non-zero status. + fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, exitErr.ExitCode()) + if !ctx.testFail { + 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: @@ -784,7 +834,10 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l if verbose { 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 { args := make([]string, 0, len(conf.RunArgs)+1) copy(args, conf.RunArgs) @@ -813,17 +866,28 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err = cmd.Run() + err := cmd.Run() if err != nil { - panic(err) + return err } if s := cmd.ProcessState; s != nil { mockable.Exit(s.ExitCode()) } } case ModeCmpTest: - cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs) + 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 { @@ -1314,7 +1378,7 @@ func findDylibDep(exe, lib string) string { type none struct{} // runInEmulator runs the application in emulator by formatting the emulator command template -func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose bool) { +func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose bool) error { // Build environment map for template variable expansion envs := map[string]string{ "": appPath, // {} expands to app path @@ -1357,11 +1421,12 @@ func runInEmulator(appPath, emulatorTemplate string, runArgs []string, verbose b cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { - panic(err) + return err } if s := cmd.ProcessState; s != nil { mockable.Exit(s.ExitCode()) } + return nil } func check(err error) { diff --git a/internal/build/outputs.go b/internal/build/outputs.go index dd67e069..0fae21b0 100644 --- a/internal/build/outputs.go +++ b/internal/build/outputs.go @@ -110,7 +110,10 @@ func genBuildOutputs(conf *Config, pkgName string, multiPkg bool, cfg OutputCfg) } } else { // 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 } diff --git a/internal/build/outputs_test.go b/internal/build/outputs_test.go index 66cb1330..c677c8ee 100644 --- a/internal/build/outputs_test.go +++ b/internal/build/outputs_test.go @@ -33,7 +33,7 @@ func TestGenOutputs(t *testing.T) { wantOutPath: "hello", wantOutExt: "", wantFileFmt: "", - wantBinFmt: "esp32", + wantBinFmt: "", wantDirectGen: true, }, { @@ -47,7 +47,7 @@ func TestGenOutputs(t *testing.T) { wantOutPath: "myapp", wantOutExt: "", wantFileFmt: "", - wantBinFmt: "esp32", + wantBinFmt: "", wantDirectGen: true, }, { @@ -108,7 +108,7 @@ func TestGenOutputs(t *testing.T) { wantOutPath: "", // temp file wantOutExt: "", wantFileFmt: "", - wantBinFmt: "esp32", + wantBinFmt: "", wantDirectGen: true, }, { @@ -167,7 +167,7 @@ func TestGenOutputs(t *testing.T) { wantOutPath: "", // temp file wantOutExt: "", wantFileFmt: "", - wantBinFmt: "esp32", + wantBinFmt: "", wantDirectGen: true, }, { @@ -194,7 +194,7 @@ func TestGenOutputs(t *testing.T) { wantOutPath: "", // temp file wantOutExt: "", wantFileFmt: "", - wantBinFmt: "esp32", + wantBinFmt: "", wantDirectGen: true, }, {