builder: refactor Build function to not use a callback
The only reason a callback was used, was so that the temporary directory gets removed once `Build` returns. But that is honestly a really bad reason: the parent function can simply create a temporary function and remove it when it returns. It wasn't worth the code complexity that this callback created. This change should not cause any observable differences in behavior (it should be a non-functional change). I have no reason to do this now, but this unclean code has been bugging me and I just wanted to get it fixed.
Этот коммит содержится в:
родитель
c153239682
коммит
f866d5cc38
2 изменённых файлов: 463 добавлений и 425 удалений
103
builder/build.go
103
builder/build.go
|
@ -42,8 +42,8 @@ type BuildResult struct {
|
||||||
// information. Used for GDB for example.
|
// information. Used for GDB for example.
|
||||||
Executable string
|
Executable string
|
||||||
|
|
||||||
// A path to the output binary. It will be removed after Build returns, so
|
// A path to the output binary. It is stored in the tmpdir directory of the
|
||||||
// if it should be kept it must be copied or moved away.
|
// 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
|
// It is often the same as Executable, but differs if the output format is
|
||||||
// .hex for example (instead of the usual ELF).
|
// .hex for example (instead of the usual ELF).
|
||||||
Binary string
|
Binary string
|
||||||
|
@ -94,23 +94,16 @@ type packageAction struct {
|
||||||
//
|
//
|
||||||
// The error value may be of type *MultiError. Callers will likely want to check
|
// The error value may be of type *MultiError. Callers will likely want to check
|
||||||
// for this case and print such errors individually.
|
// 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.
|
// Read the build ID of the tinygo binary.
|
||||||
// Used as a cache key for package builds.
|
// Used as a cache key for package builds.
|
||||||
compilerBuildID, err := ReadBuildID()
|
compilerBuildID, err := ReadBuildID()
|
||||||
if err != nil {
|
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 {
|
if config.Options.Work {
|
||||||
fmt.Printf("WORK=%s\n", dir)
|
fmt.Printf("WORK=%s\n", tmpdir)
|
||||||
} else {
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the build cache directory, which is used to speed up incremental
|
// 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" {
|
if cacheDir == "off" {
|
||||||
// Use temporary build directory instead, effectively disabling the
|
// Use temporary build directory instead, effectively disabling the
|
||||||
// build cache.
|
// build cache.
|
||||||
cacheDir = dir
|
cacheDir = tmpdir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a libc dependency.
|
// Check for a libc dependency.
|
||||||
|
@ -129,40 +122,40 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
var libcDependencies []*compileJob
|
var libcDependencies []*compileJob
|
||||||
switch config.Target.Libc {
|
switch config.Target.Libc {
|
||||||
case "darwin-libSystem":
|
case "darwin-libSystem":
|
||||||
job := makeDarwinLibSystemJob(config, dir)
|
job := makeDarwinLibSystemJob(config, tmpdir)
|
||||||
libcDependencies = append(libcDependencies, job)
|
libcDependencies = append(libcDependencies, job)
|
||||||
case "musl":
|
case "musl":
|
||||||
job, unlock, err := Musl.load(config, dir)
|
job, unlock, err := Musl.load(config, tmpdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
|
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
|
||||||
libcDependencies = append(libcDependencies, job)
|
libcDependencies = append(libcDependencies, job)
|
||||||
case "picolibc":
|
case "picolibc":
|
||||||
libcJob, unlock, err := Picolibc.load(config, dir)
|
libcJob, unlock, err := Picolibc.load(config, tmpdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
libcDependencies = append(libcDependencies, libcJob)
|
libcDependencies = append(libcDependencies, libcJob)
|
||||||
case "wasi-libc":
|
case "wasi-libc":
|
||||||
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
|
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
|
||||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
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))
|
libcDependencies = append(libcDependencies, dummyCompileJob(path))
|
||||||
case "mingw-w64":
|
case "mingw-w64":
|
||||||
_, unlock, err := MinGW.load(config, dir)
|
_, unlock, err := MinGW.load(config, tmpdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
unlock()
|
unlock()
|
||||||
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(dir)...)
|
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir)...)
|
||||||
case "":
|
case "":
|
||||||
// no library specified, so nothing to do
|
// no library specified, so nothing to do
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
||||||
}
|
}
|
||||||
|
|
||||||
optLevel, sizeLevel, _ := config.OptLevels()
|
optLevel, sizeLevel, _ := config.OptLevels()
|
||||||
|
@ -188,7 +181,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
// address spaces, etc).
|
// address spaces, etc).
|
||||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
defer machine.Dispose()
|
defer machine.Dispose()
|
||||||
|
|
||||||
|
@ -197,11 +190,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
Sizes: compiler.Sizes(machine),
|
Sizes: compiler.Sizes(machine),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
err = lprogram.Parse()
|
err = lprogram.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the *ssa.Program. This does not yet build the entire SSA of the
|
// 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
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -284,7 +277,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
for _, imported := range pkg.Pkg.Imports() {
|
for _, imported := range pkg.Pkg.Imports() {
|
||||||
job, ok := packageActionIDJobs[imported.Path()]
|
job, ok := packageActionIDJobs[imported.Path()]
|
||||||
if !ok {
|
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)
|
importedPackages = append(importedPackages, job)
|
||||||
actionIDDependencies = append(actionIDDependencies, 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.
|
// Packages are compiled independently anyway.
|
||||||
for _, cgoHeader := range pkg.CGoHeaders {
|
for _, cgoHeader := range pkg.CGoHeaders {
|
||||||
// Store the header text in a temporary file.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -579,17 +572,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
// Run jobs to produce the LLVM module.
|
// Run jobs to produce the LLVM module.
|
||||||
err := runJobs(programJob, config.Options.Semaphore)
|
err := runJobs(programJob, config.Options.Semaphore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
// Generate output.
|
// Generate output.
|
||||||
switch outext {
|
switch outext {
|
||||||
case ".o":
|
case ".o":
|
||||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
defer llvmBuf.Dispose()
|
defer llvmBuf.Dispose()
|
||||||
return os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
return BuildResult{}, os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
||||||
case ".bc":
|
case ".bc":
|
||||||
var buf llvm.MemoryBuffer
|
var buf llvm.MemoryBuffer
|
||||||
if config.UseThinLTO() {
|
if config.UseThinLTO() {
|
||||||
|
@ -598,10 +591,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
|
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||||
}
|
}
|
||||||
defer buf.Dispose()
|
defer buf.Dispose()
|
||||||
return os.WriteFile(outpath, buf.Bytes(), 0666)
|
return BuildResult{}, os.WriteFile(outpath, buf.Bytes(), 0666)
|
||||||
case ".ll":
|
case ".ll":
|
||||||
data := []byte(mod.String())
|
data := []byte(mod.String())
|
||||||
return os.WriteFile(outpath, data, 0666)
|
return BuildResult{}, os.WriteFile(outpath, data, 0666)
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
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.
|
// run all jobs in parallel as far as possible.
|
||||||
|
|
||||||
// Add job to write the output object file.
|
// Add job to write the output object file.
|
||||||
objfile := filepath.Join(dir, "main.o")
|
objfile := filepath.Join(tmpdir, "main.o")
|
||||||
outputObjectFileJob := &compileJob{
|
outputObjectFileJob := &compileJob{
|
||||||
description: "generate output file",
|
description: "generate output file",
|
||||||
dependencies: []*compileJob{programJob},
|
dependencies: []*compileJob{programJob},
|
||||||
|
@ -635,7 +628,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
|
|
||||||
// Prepare link command.
|
// Prepare link command.
|
||||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||||
executable := filepath.Join(dir, "main")
|
executable := filepath.Join(tmpdir, "main")
|
||||||
if config.GOOS() == "windows" {
|
if config.GOOS() == "windows" {
|
||||||
executable += ".exe"
|
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
|
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
||||||
// a cache.
|
// a cache.
|
||||||
if config.Target.RTLib == "compiler-rt" {
|
if config.Target.RTLib == "compiler-rt" {
|
||||||
job, unlock, err := CompilerRT.load(config, dir)
|
job, unlock, err := CompilerRT.load(config, tmpdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
linkerDependencies = append(linkerDependencies, job)
|
linkerDependencies = append(linkerDependencies, job)
|
||||||
|
@ -661,7 +654,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
job := &compileJob{
|
job := &compileJob{
|
||||||
description: "compile extra file " + path,
|
description: "compile extra file " + path,
|
||||||
run: func(job *compileJob) error {
|
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
|
job.result = result
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
@ -679,7 +672,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
job := &compileJob{
|
job := &compileJob{
|
||||||
description: "compile CGo file " + abspath,
|
description: "compile CGo file " + abspath,
|
||||||
run: func(job *compileJob) error {
|
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
|
job.result = result
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
@ -722,7 +715,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
ldflags = append(ldflags, "--strip-debug")
|
ldflags = append(ldflags, "--strip-debug")
|
||||||
} else {
|
} else {
|
||||||
// Other linkers may have different flags.
|
// 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.
|
// is simpler and cannot be parallelized.
|
||||||
err = runJobs(linkJob, config.Options.Semaphore)
|
err = runJobs(linkJob, config.Options.Semaphore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an Intel .hex file or .bin file from the .elf file.
|
// 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":
|
case "hex", "bin":
|
||||||
// Extract raw binary, either encoding it as a hex file or as a raw
|
// Extract raw binary, either encoding it as a hex file or as a raw
|
||||||
// firmware file.
|
// firmware file.
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
tmppath = filepath.Join(tmpdir, "main"+outext)
|
||||||
err := objcopy(executable, tmppath, outputBinaryFormat)
|
err := objcopy(executable, tmppath, outputBinaryFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
case "uf2":
|
case "uf2":
|
||||||
// Get UF2 from the .elf file.
|
// 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)
|
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
case "esp32", "esp32-img", "esp32c3", "esp8266":
|
case "esp32", "esp32-img", "esp32c3", "esp8266":
|
||||||
// Special format for the ESP family of chips (parsed by the ROM
|
// Special format for the ESP family of chips (parsed by the ROM
|
||||||
// bootloader).
|
// bootloader).
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
tmppath = filepath.Join(tmpdir, "main"+outext)
|
||||||
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
|
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
case "nrf-dfu":
|
case "nrf-dfu":
|
||||||
// special format for nrfutil for Nordic chips
|
// special format for nrfutil for Nordic chips
|
||||||
tmphexpath := filepath.Join(dir, "main.hex")
|
tmphexpath := filepath.Join(tmpdir, "main.hex")
|
||||||
err := objcopy(executable, tmphexpath, "hex")
|
err := objcopy(executable, tmphexpath, "hex")
|
||||||
if err != nil {
|
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)
|
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
default:
|
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.
|
// 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
|
moduleroot = lprogram.MainPkg().Root
|
||||||
}
|
}
|
||||||
|
|
||||||
return action(BuildResult{
|
return BuildResult{
|
||||||
Executable: executable,
|
Executable: executable,
|
||||||
Binary: tmppath,
|
Binary: tmppath,
|
||||||
MainDir: lprogram.MainPkg().Dir,
|
MainDir: lprogram.MainPkg().Dir,
|
||||||
ModuleRoot: moduleroot,
|
ModuleRoot: moduleroot,
|
||||||
ImportPath: lprogram.MainPkg().ImportPath,
|
ImportPath: lprogram.MainPkg().ImportPath,
|
||||||
})
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createEmbedObjectFile creates a new object file with the given contents, for
|
// createEmbedObjectFile creates a new object file with the given contents, for
|
||||||
|
|
785
main.go
785
main.go
|
@ -155,43 +155,54 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
|
// Create a temporary directory for intermediary files.
|
||||||
if outpath == "" {
|
tmpdir, err := os.MkdirTemp("", "tinygo")
|
||||||
if strings.HasSuffix(pkgName, ".go") {
|
if err != nil {
|
||||||
// A Go file was specified directly on the command line.
|
return err
|
||||||
// Base the binary name off of it.
|
}
|
||||||
outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
|
defer os.RemoveAll(tmpdir)
|
||||||
} 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 {
|
// Do the build.
|
||||||
// Moving failed. Do a file copy.
|
result, err := builder.Build(pkgName, outpath, tmpdir, config)
|
||||||
inf, err := os.Open(result.Binary)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
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.
|
if outpath == "" {
|
||||||
_, err = io.Copy(outf, inf)
|
if strings.HasSuffix(pkgName, ".go") {
|
||||||
if err != nil {
|
// A Go file was specified directly on the command line.
|
||||||
return err
|
// Base the binary name off of it.
|
||||||
}
|
outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
|
||||||
|
|
||||||
// Check whether file writing was successful.
|
|
||||||
return outf.Close()
|
|
||||||
} else {
|
} else {
|
||||||
// Move was successful.
|
// Pick a default output path based on the main directory.
|
||||||
return nil
|
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
|
// 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 errors.New("unknown flash method: " + flashMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
|
// Create a temporary directory for intermediary files.
|
||||||
// do we need port reset to put MCU into bootloader mode?
|
tmpdir, err := os.MkdirTemp("", "tinygo")
|
||||||
if config.Target.PortReset == "true" && flashMethod != "openocd" {
|
if err != nil {
|
||||||
port, err := getDefaultPort(port, config.Target.SerialPort)
|
return err
|
||||||
if err == nil {
|
}
|
||||||
err = touchSerialPortAt1200bps(port)
|
defer os.RemoveAll(tmpdir)
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to reset port", result.Binary, err}
|
// Build the binary.
|
||||||
}
|
result, err := builder.Build(pkgName, fileExt, tmpdir, config)
|
||||||
// give the target MCU a chance to restart into bootloader
|
if err != nil {
|
||||||
time.Sleep(3 * time.Second)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(flashCmd, "{port}") {
|
||||||
|
var err error
|
||||||
|
port, err = getDefaultPort(port, config.Target.SerialPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
// this flashing method copies the binary data to a Mass Storage Device (msd)
|
||||||
switch flashMethod {
|
switch fileExt {
|
||||||
case "", "command":
|
case ".uf2":
|
||||||
// Create the command.
|
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options)
|
||||||
flashCmd := config.Target.FlashCommand
|
|
||||||
flashCmdList, err := shlex.Split(flashCmd)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(flashCmd, "{port}") {
|
|
||||||
var err error
|
|
||||||
port, err = getDefaultPort(port, config.Target.SerialPort)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
if err != nil {
|
||||||
return &commandError{"failed to flash", result.Binary, err}
|
return &commandError{"failed to flash", result.Binary, err}
|
||||||
}
|
}
|
||||||
case "msd":
|
case ".hex":
|
||||||
switch fileExt {
|
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options)
|
||||||
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 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()
|
|
||||||
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 {
|
if err != nil {
|
||||||
return &commandError{"failed to flash", result.Binary, err}
|
return &commandError{"failed to flash", result.Binary, err}
|
||||||
}
|
}
|
||||||
default:
|
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 {
|
case "openocd":
|
||||||
return Monitor("", options)
|
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)
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format, fileExt := config.EmulatorFormat()
|
// Create a temporary directory for intermediary files.
|
||||||
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
|
tmpdir, err := os.MkdirTemp("", "tinygo")
|
||||||
// Find a good way to run GDB.
|
if err != nil {
|
||||||
gdbInterface, openocdInterface := config.Programmer()
|
return err
|
||||||
switch gdbInterface {
|
}
|
||||||
case "msd", "command", "":
|
defer os.RemoveAll(tmpdir)
|
||||||
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"
|
|
||||||
} else {
|
|
||||||
gdbInterface = "native"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the GDB server, if necessary.
|
// Build the binary to debug.
|
||||||
port := ""
|
format, fileExt := config.EmulatorFormat()
|
||||||
var gdbCommands []string
|
result, err := builder.Build(pkgName, fileExt, tmpdir, config)
|
||||||
var daemon *exec.Cmd
|
if err != nil {
|
||||||
emulator, err := config.Emulator(format, result.Binary)
|
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 {
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch gdbInterface {
|
||||||
|
case "native":
|
||||||
|
// Run GDB directly.
|
||||||
|
case "bmp":
|
||||||
|
var bmpGDBPort string
|
||||||
|
bmpGDBPort, _, err = getBMPPorts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch gdbInterface {
|
port = bmpGDBPort
|
||||||
case "native":
|
gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load")
|
||||||
// Run GDB directly.
|
case "openocd":
|
||||||
case "bmp":
|
port = ":3333"
|
||||||
var bmpGDBPort string
|
gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt")
|
||||||
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.
|
// We need a separate debugging daemon for on-chip debugging.
|
||||||
args, err := config.OpenOCDConfiguration()
|
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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 <binary>
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd := executeCommand(config.Options, cmdName, params...)
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to run " + cmdName + " with", result.Executable, err}
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 <binary>
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// 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
|
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()
|
format, fileExt := config.EmulatorFormat()
|
||||||
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
|
result, err := builder.Build(pkgName, fileExt, tmpdir, config)
|
||||||
// If needed, set a timeout on the command. This is done in tests so
|
if err != nil {
|
||||||
// they don't waste resources on a stalled test.
|
return err
|
||||||
var ctx context.Context
|
}
|
||||||
if timeout != 0 {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the command.
|
// If needed, set a timeout on the command. This is done in tests so
|
||||||
var name string
|
// they don't waste resources on a stalled test.
|
||||||
if config.Target.Emulator == "" {
|
var ctx context.Context
|
||||||
name = result.Binary
|
if timeout != 0 {
|
||||||
} else {
|
var cancel context.CancelFunc
|
||||||
emulator, err := config.Emulator(format, result.Binary)
|
ctx, cancel = context.WithTimeout(context.Background(), timeout)
|
||||||
if err != nil {
|
defer cancel()
|
||||||
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
|
|
||||||
|
|
||||||
// Configure stdout/stderr. The stdout may go to a buffer, not a real
|
// Set up the command.
|
||||||
// stdout.
|
var name string
|
||||||
cmd.Stdout = stdout
|
if config.Target.Emulator == "" {
|
||||||
cmd.Stderr = os.Stderr
|
name = result.Binary
|
||||||
if config.EmulatorName() == "simavr" {
|
} else {
|
||||||
cmd.Stdout = nil // don't print initial load commands
|
emulator, err := config.Emulator(format, result.Binary)
|
||||||
cmd.Stderr = stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 err != nil {
|
||||||
if ctx != nil && ctx.Err() == context.DeadlineExceeded {
|
return err
|
||||||
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}
|
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
|
||||||
|
// 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 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 &commandError{"failed to run compiled binary", result.Binary, err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func touchSerialPortAt1200bps(port string) (err error) {
|
func touchSerialPortAt1200bps(port string) (err error) {
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче