diff --git a/builder/build.go b/builder/build.go index 478d8a73..8095a6b6 100644 --- a/builder/build.go +++ b/builder/build.go @@ -42,8 +42,8 @@ type BuildResult struct { // information. Used for GDB for example. Executable string - // A path to the output binary. It will be removed after Build returns, so - // if it should be kept it must be copied or moved away. + // A path to the output binary. It is stored in the tmpdir directory of the + // Build function, so if it should be kept it must be copied or moved away. // It is often the same as Executable, but differs if the output format is // .hex for example (instead of the usual ELF). Binary string @@ -94,23 +94,16 @@ type packageAction struct { // // The error value may be of type *MultiError. Callers will likely want to check // for this case and print such errors individually. -func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error { +func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildResult, error) { // Read the build ID of the tinygo binary. // Used as a cache key for package builds. compilerBuildID, err := ReadBuildID() if err != nil { - return err + return BuildResult{}, err } - // Create a temporary directory for intermediary files. - dir, err := os.MkdirTemp("", "tinygo") - if err != nil { - return err - } if config.Options.Work { - fmt.Printf("WORK=%s\n", dir) - } else { - defer os.RemoveAll(dir) + fmt.Printf("WORK=%s\n", tmpdir) } // Look up the build cache directory, which is used to speed up incremental @@ -119,7 +112,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if cacheDir == "off" { // Use temporary build directory instead, effectively disabling the // build cache. - cacheDir = dir + cacheDir = tmpdir } // Check for a libc dependency. @@ -129,40 +122,40 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil var libcDependencies []*compileJob switch config.Target.Libc { case "darwin-libSystem": - job := makeDarwinLibSystemJob(config, dir) + job := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, job) case "musl": - job, unlock, err := Musl.load(config, dir) + job, unlock, err := Musl.load(config, tmpdir) if err != nil { - return err + return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) libcDependencies = append(libcDependencies, job) case "picolibc": - libcJob, unlock, err := Picolibc.load(config, dir) + libcJob, unlock, err := Picolibc.load(config, tmpdir) if err != nil { - return err + return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "wasi-libc": path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") + return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") } libcDependencies = append(libcDependencies, dummyCompileJob(path)) case "mingw-w64": - _, unlock, err := MinGW.load(config, dir) + _, unlock, err := MinGW.load(config, tmpdir) if err != nil { - return err + return BuildResult{}, err } unlock() - libcDependencies = append(libcDependencies, makeMinGWExtraLibs(dir)...) + libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir)...) case "": // no library specified, so nothing to do default: - return fmt.Errorf("unknown libc: %s", config.Target.Libc) + return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) } optLevel, sizeLevel, _ := config.OptLevels() @@ -188,7 +181,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // address spaces, etc). machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { - return err + return BuildResult{}, err } defer machine.Dispose() @@ -197,11 +190,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil Sizes: compiler.Sizes(machine), }) if err != nil { - return err + return BuildResult{}, err } err = lprogram.Parse() if err != nil { - return err + return BuildResult{}, err } // Create the *ssa.Program. This does not yet build the entire SSA of the @@ -270,7 +263,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil } } - job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), dir, compilerConfig) + job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), tmpdir, compilerConfig) return err }, } @@ -284,7 +277,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil for _, imported := range pkg.Pkg.Imports() { job, ok := packageActionIDJobs[imported.Path()] if !ok { - return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) + return BuildResult{}, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) } importedPackages = append(importedPackages, job) actionIDDependencies = append(actionIDDependencies, job) @@ -370,7 +363,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // Packages are compiled independently anyway. for _, cgoHeader := range pkg.CGoHeaders { // Store the header text in a temporary file. - f, err := os.CreateTemp(dir, "cgosnippet-*.c") + f, err := os.CreateTemp(tmpdir, "cgosnippet-*.c") if err != nil { return err } @@ -579,17 +572,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // Run jobs to produce the LLVM module. err := runJobs(programJob, config.Options.Semaphore) if err != nil { - return err + return BuildResult{}, err } // Generate output. switch outext { case ".o": llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) if err != nil { - return err + return BuildResult{}, err } defer llvmBuf.Dispose() - return os.WriteFile(outpath, llvmBuf.Bytes(), 0666) + return BuildResult{}, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) case ".bc": var buf llvm.MemoryBuffer if config.UseThinLTO() { @@ -598,10 +591,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil buf = llvm.WriteBitcodeToMemoryBuffer(mod) } defer buf.Dispose() - return os.WriteFile(outpath, buf.Bytes(), 0666) + return BuildResult{}, os.WriteFile(outpath, buf.Bytes(), 0666) case ".ll": data := []byte(mod.String()) - return os.WriteFile(outpath, data, 0666) + return BuildResult{}, os.WriteFile(outpath, data, 0666) default: panic("unreachable") } @@ -612,7 +605,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // run all jobs in parallel as far as possible. // Add job to write the output object file. - objfile := filepath.Join(dir, "main.o") + objfile := filepath.Join(tmpdir, "main.o") outputObjectFileJob := &compileJob{ description: "generate output file", dependencies: []*compileJob{programJob}, @@ -635,7 +628,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // Prepare link command. linkerDependencies := []*compileJob{outputObjectFileJob} - executable := filepath.Join(dir, "main") + executable := filepath.Join(tmpdir, "main") if config.GOOS() == "windows" { executable += ".exe" } @@ -645,9 +638,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { - job, unlock, err := CompilerRT.load(config, dir) + job, unlock, err := CompilerRT.load(config, tmpdir) if err != nil { - return err + return BuildResult{}, err } defer unlock() linkerDependencies = append(linkerDependencies, job) @@ -661,7 +654,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil job := &compileJob{ description: "compile extra file " + path, run: func(job *compileJob) error { - result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands) + result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands) job.result = result return err }, @@ -679,7 +672,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil job := &compileJob{ description: "compile CGo file " + abspath, run: func(job *compileJob) error { - result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.UseThinLTO(), config.Options.PrintCommands) + result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.UseThinLTO(), config.Options.PrintCommands) job.result = result return err }, @@ -722,7 +715,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil ldflags = append(ldflags, "--strip-debug") } else { // Other linkers may have different flags. - return errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) + return BuildResult{}, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) } } @@ -879,7 +872,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // is simpler and cannot be parallelized. err = runJobs(linkJob, config.Options.Semaphore) if err != nil { - return err + return BuildResult{}, err } // Get an Intel .hex file or .bin file from the .elf file. @@ -890,40 +883,40 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil case "hex", "bin": // Extract raw binary, either encoding it as a hex file or as a raw // firmware file. - tmppath = filepath.Join(dir, "main"+outext) + tmppath = filepath.Join(tmpdir, "main"+outext) err := objcopy(executable, tmppath, outputBinaryFormat) if err != nil { - return err + return BuildResult{}, err } case "uf2": // Get UF2 from the .elf file. - tmppath = filepath.Join(dir, "main"+outext) + tmppath = filepath.Join(tmpdir, "main"+outext) err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID) if err != nil { - return err + return BuildResult{}, err } case "esp32", "esp32-img", "esp32c3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). - tmppath = filepath.Join(dir, "main"+outext) + tmppath = filepath.Join(tmpdir, "main"+outext) err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat) if err != nil { - return err + return BuildResult{}, err } case "nrf-dfu": // special format for nrfutil for Nordic chips - tmphexpath := filepath.Join(dir, "main.hex") + tmphexpath := filepath.Join(tmpdir, "main.hex") err := objcopy(executable, tmphexpath, "hex") if err != nil { - return err + return BuildResult{}, err } - tmppath = filepath.Join(dir, "main"+outext) + tmppath = filepath.Join(tmpdir, "main"+outext) err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath) if err != nil { - return err + return BuildResult{}, err } default: - return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) + return BuildResult{}, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) } // If there's a module root, use that. @@ -933,13 +926,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil moduleroot = lprogram.MainPkg().Root } - return action(BuildResult{ + return BuildResult{ Executable: executable, Binary: tmppath, MainDir: lprogram.MainPkg().Dir, ModuleRoot: moduleroot, ImportPath: lprogram.MainPkg().ImportPath, - }) + }, nil } // createEmbedObjectFile creates a new object file with the given contents, for diff --git a/main.go b/main.go index 944a3156..662e7033 100644 --- a/main.go +++ b/main.go @@ -155,43 +155,54 @@ func Build(pkgName, outpath string, options *compileopts.Options) error { return nil } - return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error { - if outpath == "" { - if strings.HasSuffix(pkgName, ".go") { - // A Go file was specified directly on the command line. - // Base the binary name off of it. - outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension() - } else { - // Pick a default output path based on the main directory. - outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() - } - } - - if err := os.Rename(result.Binary, outpath); err != nil { - // Moving failed. Do a file copy. - inf, err := os.Open(result.Binary) - if err != nil { - return err - } - defer inf.Close() - outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) - if err != nil { - return err - } + // Create a temporary directory for intermediary files. + tmpdir, err := os.MkdirTemp("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) - // Copy data to output file. - _, err = io.Copy(outf, inf) - if err != nil { - return err - } + // Do the build. + result, err := builder.Build(pkgName, outpath, tmpdir, config) + if err != nil { + return err + } - // Check whether file writing was successful. - return outf.Close() + if outpath == "" { + if strings.HasSuffix(pkgName, ".go") { + // A Go file was specified directly on the command line. + // Base the binary name off of it. + outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension() } else { - // Move was successful. - return nil + // Pick a default output path based on the main directory. + outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() } - }) + } + + if err := os.Rename(result.Binary, outpath); err != nil { + // Moving failed. Do a file copy. + inf, err := os.Open(result.Binary) + if err != nil { + return err + } + defer inf.Close() + outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) + if err != nil { + return err + } + + // Copy data to output file. + _, err = io.Copy(outf, inf) + if err != nil { + return err + } + + // Check whether file writing was successful. + return outf.Close() + } + + // Move was successful. + return nil } // Test runs the tests in the given package. Returns whether the test passed and @@ -371,116 +382,128 @@ func Flash(pkgName, port string, options *compileopts.Options) error { return errors.New("unknown flash method: " + flashMethod) } - return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { - // do we need port reset to put MCU into bootloader mode? - if config.Target.PortReset == "true" && flashMethod != "openocd" { - port, err := getDefaultPort(port, config.Target.SerialPort) - if err == nil { - err = touchSerialPortAt1200bps(port) - if err != nil { - return &commandError{"failed to reset port", result.Binary, err} - } - // give the target MCU a chance to restart into bootloader - time.Sleep(3 * time.Second) - } - } + // Create a temporary directory for intermediary files. + tmpdir, err := os.MkdirTemp("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) - // this flashing method copies the binary data to a Mass Storage Device (msd) - switch flashMethod { - case "", "command": - // Create the command. - flashCmd := config.Target.FlashCommand - flashCmdList, err := shlex.Split(flashCmd) - if err != nil { - return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err) - } + // Build the binary. + result, err := builder.Build(pkgName, fileExt, tmpdir, config) + if err != nil { + return err + } - if strings.Contains(flashCmd, "{port}") { - var err error - port, err = getDefaultPort(port, config.Target.SerialPort) - if err != nil { - return err - } + // do we need port reset to put MCU into bootloader mode? + if config.Target.PortReset == "true" && flashMethod != "openocd" { + port, err := getDefaultPort(port, config.Target.SerialPort) + if err == nil { + err = touchSerialPortAt1200bps(port) + if err != nil { + return &commandError{"failed to reset port", result.Binary, err} } + // give the target MCU a chance to restart into bootloader + time.Sleep(3 * time.Second) + } + } - // Fill in fields in the command template. - fileToken := "{" + fileExt[1:] + "}" - for i, arg := range flashCmdList { - arg = strings.ReplaceAll(arg, fileToken, result.Binary) - arg = strings.ReplaceAll(arg, "{port}", port) - flashCmdList[i] = arg - } + // Flash the binary to the MCU. + switch flashMethod { + case "", "command": + // Create the command. + flashCmd := config.Target.FlashCommand + flashCmdList, err := shlex.Split(flashCmd) + if err != nil { + return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err) + } - // Execute the command. - if len(flashCmdList) < 2 { - return fmt.Errorf("invalid flash command: %#v", flashCmd) - } - cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = goenv.Get("TINYGOROOT") - err = cmd.Run() - if err != nil { - return &commandError{"failed to flash", result.Binary, err} - } - case "msd": - switch fileExt { - case ".uf2": - err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options) - if err != nil { - return &commandError{"failed to flash", result.Binary, err} - } - case ".hex": - err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options) - if err != nil { - return &commandError{"failed to flash", result.Binary, err} - } - default: - return errors.New("mass storage device flashing currently only supports uf2 and hex") - } - case "openocd": - args, err := config.OpenOCDConfiguration() + if strings.Contains(flashCmd, "{port}") { + var err error + port, err = getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } - exit := " reset exit" - if config.Target.OpenOCDVerify != nil && *config.Target.OpenOCDVerify { - exit = " verify" + exit - } - args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+exit) - cmd := executeCommand(config.Options, "openocd", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() + } + + // Fill in fields in the command template. + fileToken := "{" + fileExt[1:] + "}" + for i, arg := range flashCmdList { + arg = strings.ReplaceAll(arg, fileToken, result.Binary) + arg = strings.ReplaceAll(arg, "{port}", port) + flashCmdList[i] = arg + } + + // Execute the command. + if len(flashCmdList) < 2 { + return fmt.Errorf("invalid flash command: %#v", flashCmd) + } + cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = goenv.Get("TINYGOROOT") + err = cmd.Run() + if err != nil { + return &commandError{"failed to flash", result.Binary, err} + } + case "msd": + // this flashing method copies the binary data to a Mass Storage Device (msd) + switch fileExt { + case ".uf2": + err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options) if err != nil { return &commandError{"failed to flash", result.Binary, err} } - case "bmp": - gdb, err := config.Target.LookupGDB() - if err != nil { - return err - } - var bmpGDBPort string - bmpGDBPort, _, err = getBMPPorts() - if err != nil { - return err - } - args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)} - cmd := executeCommand(config.Options, gdb, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() + case ".hex": + err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options) if err != nil { return &commandError{"failed to flash", result.Binary, err} } default: - return fmt.Errorf("unknown flash method: %s", flashMethod) + return errors.New("mass storage device flashing currently only supports uf2 and hex") } - if options.Monitor { - return Monitor("", options) + case "openocd": + args, err := config.OpenOCDConfiguration() + if err != nil { + return err } - return nil - }) + exit := " reset exit" + if config.Target.OpenOCDVerify != nil && *config.Target.OpenOCDVerify { + exit = " verify" + exit + } + args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+exit) + cmd := executeCommand(config.Options, "openocd", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return &commandError{"failed to flash", result.Binary, err} + } + case "bmp": + gdb, err := config.Target.LookupGDB() + if err != nil { + return err + } + var bmpGDBPort string + bmpGDBPort, _, err = getBMPPorts() + if err != nil { + return err + } + args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)} + cmd := executeCommand(config.Options, gdb, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return &commandError{"failed to flash", result.Binary, err} + } + default: + return fmt.Errorf("unknown flash method: %s", flashMethod) + } + if options.Monitor { + return Monitor("", options) + } + return nil } // Debug compiles and flashes a program to a microcontroller (just like Flash) @@ -506,195 +529,206 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option return err } + // Create a temporary directory for intermediary files. + tmpdir, err := os.MkdirTemp("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) + + // Build the binary to debug. format, fileExt := config.EmulatorFormat() - return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { - // Find a good way to run GDB. - gdbInterface, openocdInterface := config.Programmer() - switch gdbInterface { - case "msd", "command", "": - emulator := config.EmulatorName() - if emulator != "" { - if emulator == "mgba" { - gdbInterface = "mgba" - } else if emulator == "simavr" { - gdbInterface = "simavr" - } else if strings.HasPrefix(emulator, "qemu-system-") { - gdbInterface = "qemu" - } else { - // Assume QEMU as an emulator. - gdbInterface = "qemu-user" - } - } else if openocdInterface != "" && config.Target.OpenOCDTarget != "" { - gdbInterface = "openocd" - } else if config.Target.JLinkDevice != "" { - gdbInterface = "jlink" + result, err := builder.Build(pkgName, fileExt, tmpdir, config) + if err != nil { + return err + } + + // Find a good way to run GDB. + gdbInterface, openocdInterface := config.Programmer() + switch gdbInterface { + case "msd", "command", "": + emulator := config.EmulatorName() + if emulator != "" { + if emulator == "mgba" { + gdbInterface = "mgba" + } else if emulator == "simavr" { + gdbInterface = "simavr" + } else if strings.HasPrefix(emulator, "qemu-system-") { + gdbInterface = "qemu" } else { - gdbInterface = "native" + // Assume QEMU as an emulator. + gdbInterface = "qemu-user" } + } else if openocdInterface != "" && config.Target.OpenOCDTarget != "" { + gdbInterface = "openocd" + } else if config.Target.JLinkDevice != "" { + gdbInterface = "jlink" + } else { + gdbInterface = "native" } + } - // Run the GDB server, if necessary. - port := "" - var gdbCommands []string - var daemon *exec.Cmd - emulator, err := config.Emulator(format, result.Binary) + // Run the GDB server, if necessary. + port := "" + var gdbCommands []string + var daemon *exec.Cmd + emulator, err := config.Emulator(format, result.Binary) + if err != nil { + return err + } + switch gdbInterface { + case "native": + // Run GDB directly. + case "bmp": + var bmpGDBPort string + bmpGDBPort, _, err = getBMPPorts() if err != nil { return err } - switch gdbInterface { - case "native": - // Run GDB directly. - case "bmp": - var bmpGDBPort string - bmpGDBPort, _, err = getBMPPorts() - if err != nil { - return err - } - port = bmpGDBPort - gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load") - case "openocd": - port = ":3333" - gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt") - - // We need a separate debugging daemon for on-chip debugging. - args, err := config.OpenOCDConfiguration() - if err != nil { - return err - } - daemon = executeCommand(config.Options, "openocd", args...) - if ocdOutput { - // Make it clear which output is from the daemon. - w := &ColorWriter{ - Out: colorable.NewColorableStderr(), - Prefix: "openocd: ", - Color: TermColorYellow, - } - daemon.Stdout = w - daemon.Stderr = w + port = bmpGDBPort + gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load") + case "openocd": + port = ":3333" + gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt") + + // We need a separate debugging daemon for on-chip debugging. + args, err := config.OpenOCDConfiguration() + if err != nil { + return err + } + daemon = executeCommand(config.Options, "openocd", args...) + if ocdOutput { + // Make it clear which output is from the daemon. + w := &ColorWriter{ + Out: colorable.NewColorableStderr(), + Prefix: "openocd: ", + Color: TermColorYellow, } - case "jlink": - port = ":2331" - gdbCommands = append(gdbCommands, "load", "monitor reset halt") - - // We need a separate debugging daemon for on-chip debugging. - daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) - if ocdOutput { - // Make it clear which output is from the daemon. - w := &ColorWriter{ - Out: colorable.NewColorableStderr(), - Prefix: "jlink: ", - Color: TermColorYellow, - } - daemon.Stdout = w - daemon.Stderr = w + daemon.Stdout = w + daemon.Stderr = w + } + case "jlink": + port = ":2331" + gdbCommands = append(gdbCommands, "load", "monitor reset halt") + + // We need a separate debugging daemon for on-chip debugging. + daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) + if ocdOutput { + // Make it clear which output is from the daemon. + w := &ColorWriter{ + Out: colorable.NewColorableStderr(), + Prefix: "jlink: ", + Color: TermColorYellow, } - case "qemu": - port = ":1234" - // Run in an emulator. - args := append(emulator[1:], "-s", "-S") - daemon = executeCommand(config.Options, emulator[0], args...) - daemon.Stdout = os.Stdout - daemon.Stderr = os.Stderr - case "qemu-user": - port = ":1234" - // Run in an emulator. - args := append(emulator[1:], "-g", "1234") - daemon = executeCommand(config.Options, emulator[0], args...) - daemon.Stdout = os.Stdout - daemon.Stderr = os.Stderr - case "mgba": - port = ":2345" - // Run in an emulator. - args := append(emulator[1:], "-g") - daemon = executeCommand(config.Options, emulator[0], args...) - daemon.Stdout = os.Stdout - daemon.Stderr = os.Stderr - case "simavr": - port = ":1234" - // Run in an emulator. - args := append(emulator[1:], "-g") - daemon = executeCommand(config.Options, emulator[0], args...) - daemon.Stdout = os.Stdout - daemon.Stderr = os.Stderr - case "msd": - return errors.New("gdb is not supported for drag-and-drop programmable devices") - default: - return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface) - } + daemon.Stdout = w + daemon.Stderr = w + } + case "qemu": + port = ":1234" + // Run in an emulator. + args := append(emulator[1:], "-s", "-S") + daemon = executeCommand(config.Options, emulator[0], args...) + daemon.Stdout = os.Stdout + daemon.Stderr = os.Stderr + case "qemu-user": + port = ":1234" + // Run in an emulator. + args := append(emulator[1:], "-g", "1234") + daemon = executeCommand(config.Options, emulator[0], args...) + daemon.Stdout = os.Stdout + daemon.Stderr = os.Stderr + case "mgba": + port = ":2345" + // Run in an emulator. + args := append(emulator[1:], "-g") + daemon = executeCommand(config.Options, emulator[0], args...) + daemon.Stdout = os.Stdout + daemon.Stderr = os.Stderr + case "simavr": + port = ":1234" + // Run in an emulator. + args := append(emulator[1:], "-g") + daemon = executeCommand(config.Options, emulator[0], args...) + daemon.Stdout = os.Stdout + daemon.Stderr = os.Stderr + case "msd": + return errors.New("gdb is not supported for drag-and-drop programmable devices") + default: + return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface) + } - if daemon != nil { - // Make sure the daemon doesn't receive Ctrl-C that is intended for - // GDB (to break the currently executing program). - setCommandAsDaemon(daemon) + if daemon != nil { + // Make sure the daemon doesn't receive Ctrl-C that is intended for + // GDB (to break the currently executing program). + setCommandAsDaemon(daemon) - // Start now, and kill it on exit. - err = daemon.Start() - if err != nil { - return &commandError{"failed to run", daemon.Path, err} - } - defer func() { - daemon.Process.Signal(os.Interrupt) - var stopped uint32 - go func() { - time.Sleep(time.Millisecond * 100) - if atomic.LoadUint32(&stopped) == 0 { - daemon.Process.Kill() - } - }() - daemon.Wait() - atomic.StoreUint32(&stopped, 1) - }() + // Start now, and kill it on exit. + err = daemon.Start() + if err != nil { + return &commandError{"failed to run", daemon.Path, err} } - - // Ignore Ctrl-C, it must be passed on to GDB. - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - for range c { - } + defer func() { + daemon.Process.Signal(os.Interrupt) + var stopped uint32 + go func() { + time.Sleep(time.Millisecond * 100) + if atomic.LoadUint32(&stopped) == 0 { + daemon.Process.Kill() + } + }() + daemon.Wait() + atomic.StoreUint32(&stopped, 1) }() + } - // Construct and execute a gdb or lldb command. - // By default: gdb -ex run - // Exit the debugger with Ctrl-D. - params := []string{result.Executable} - switch debugger { - case "gdb": - if port != "" { - params = append(params, "-ex", "target extended-remote "+port) - } - for _, cmd := range gdbCommands { - params = append(params, "-ex", cmd) - } - case "lldb": - params = append(params, "--arch", config.Triple()) - if port != "" { - if strings.HasPrefix(port, ":") { - params = append(params, "-o", "gdb-remote "+port[1:]) - } else { - return fmt.Errorf("cannot use LLDB over a gdb-remote that isn't a TCP port: %s", port) - } - } - for _, cmd := range gdbCommands { - if strings.HasPrefix(cmd, "monitor ") { - params = append(params, "-o", "process plugin packet "+cmd) - } else if cmd == "load" { - params = append(params, "-o", "target modules load --load --slide 0") - } else { - return fmt.Errorf("don't know how to convert GDB command %#v to LLDB", cmd) - } + // Ignore Ctrl-C, it must be passed on to GDB. + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + } + }() + + // Construct and execute a gdb or lldb command. + // By default: gdb -ex run + // Exit the debugger with Ctrl-D. + params := []string{result.Executable} + switch debugger { + case "gdb": + if port != "" { + params = append(params, "-ex", "target extended-remote "+port) + } + for _, cmd := range gdbCommands { + params = append(params, "-ex", cmd) + } + case "lldb": + params = append(params, "--arch", config.Triple()) + if port != "" { + if strings.HasPrefix(port, ":") { + params = append(params, "-o", "gdb-remote "+port[1:]) + } else { + return fmt.Errorf("cannot use LLDB over a gdb-remote that isn't a TCP port: %s", port) } } - cmd := executeCommand(config.Options, cmdName, params...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return &commandError{"failed to run " + cmdName + " with", result.Executable, err} + for _, cmd := range gdbCommands { + if strings.HasPrefix(cmd, "monitor ") { + params = append(params, "-o", "process plugin packet "+cmd) + } else if cmd == "load" { + params = append(params, "-o", "target modules load --load --slide 0") + } else { + return fmt.Errorf("don't know how to convert GDB command %#v to LLDB", cmd) + } } - return nil - }) + } + cmd := executeCommand(config.Options, cmdName, params...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return &commandError{"failed to run " + cmdName + " with", result.Executable, err} + } + return nil } // Run compiles and runs the given program. Depending on the target provided in @@ -767,69 +801,80 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c env = environmentVars } + // Create a temporary directory for intermediary files. + tmpdir, err := os.MkdirTemp("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) + + // Build the binary to be run. format, fileExt := config.EmulatorFormat() - return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { - // If needed, set a timeout on the command. This is done in tests so - // they don't waste resources on a stalled test. - var ctx context.Context - if timeout != 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(context.Background(), timeout) - defer cancel() - } - - // Set up the command. - var name string - if config.Target.Emulator == "" { - name = result.Binary - } else { - emulator, err := config.Emulator(format, result.Binary) - if err != nil { - return err - } - name = emulator[0] - emuArgs := append([]string(nil), emulator[1:]...) - args = append(emuArgs, args...) - } - var cmd *exec.Cmd - if ctx != nil { - cmd = exec.CommandContext(ctx, name, args...) - } else { - cmd = exec.Command(name, args...) - } - cmd.Env = env + result, err := builder.Build(pkgName, fileExt, tmpdir, config) + if err != nil { + return err + } - // Configure stdout/stderr. The stdout may go to a buffer, not a real - // stdout. - cmd.Stdout = stdout - cmd.Stderr = os.Stderr - if config.EmulatorName() == "simavr" { - cmd.Stdout = nil // don't print initial load commands - cmd.Stderr = stdout + // If needed, set a timeout on the command. This is done in tests so + // they don't waste resources on a stalled test. + var ctx context.Context + if timeout != 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), timeout) + defer cancel() + } + + // Set up the command. + var name string + if config.Target.Emulator == "" { + name = result.Binary + } else { + emulator, err := config.Emulator(format, result.Binary) + if err != nil { + return err } + name = emulator[0] + emuArgs := append([]string(nil), emulator[1:]...) + args = append(emuArgs, args...) + } + var cmd *exec.Cmd + if ctx != nil { + cmd = exec.CommandContext(ctx, name, args...) + } else { + cmd = exec.Command(name, args...) + } + cmd.Env = env - // If this is a test, reserve CPU time for it so that increased - // parallelism doesn't blow up memory usage. If this isn't a test but - // simply `tinygo run`, then it is practically a no-op. - config.Options.Semaphore <- struct{}{} - defer func() { - <-config.Options.Semaphore - }() + // Configure stdout/stderr. The stdout may go to a buffer, not a real + // stdout. + cmd.Stdout = stdout + cmd.Stderr = os.Stderr + if config.EmulatorName() == "simavr" { + cmd.Stdout = nil // don't print initial load commands + cmd.Stderr = stdout + } - // Run binary. - if config.Options.PrintCommands != nil { - config.Options.PrintCommands(cmd.Path, cmd.Args...) - } - err := run(cmd, result) - if err != nil { - if ctx != nil && ctx.Err() == context.DeadlineExceeded { - stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) - err = ctx.Err() - } - return &commandError{"failed to run compiled binary", result.Binary, err} + // If this is a test, reserve CPU time for it so that increased + // parallelism doesn't blow up memory usage. If this isn't a test but + // simply `tinygo run`, then it is practically a no-op. + config.Options.Semaphore <- struct{}{} + defer func() { + <-config.Options.Semaphore + }() + + // Run binary. + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(cmd.Path, cmd.Args...) + } + err = run(cmd, result) + if err != nil { + if ctx != nil && ctx.Err() == context.DeadlineExceeded { + stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) + err = ctx.Err() } - return nil - }) + return &commandError{"failed to run compiled binary", result.Binary, err} + } + return nil } func touchSerialPortAt1200bps(port string) (err error) {