github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/json" 8 "errors" 9 "flag" 10 "fmt" 11 "go/scanner" 12 "go/types" 13 "io" 14 "os" 15 "os/exec" 16 "os/signal" 17 "path/filepath" 18 "regexp" 19 "runtime" 20 "runtime/pprof" 21 "sort" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/google/shlex" 29 "github.com/inhies/go-bytesize" 30 "github.com/mattn/go-colorable" 31 "github.com/tinygo-org/tinygo/builder" 32 "github.com/tinygo-org/tinygo/compileopts" 33 "github.com/tinygo-org/tinygo/goenv" 34 "github.com/tinygo-org/tinygo/interp" 35 "github.com/tinygo-org/tinygo/loader" 36 "golang.org/x/tools/go/buildutil" 37 "tinygo.org/x/go-llvm" 38 39 "go.bug.st/serial" 40 "go.bug.st/serial/enumerator" 41 ) 42 43 // commandError is an error type to wrap os/exec.Command errors. This provides 44 // some more information regarding what went wrong while running a command. 45 type commandError struct { 46 Msg string 47 File string 48 Err error 49 } 50 51 func (e *commandError) Error() string { 52 return e.Msg + " " + e.File + ": " + e.Err.Error() 53 } 54 55 // moveFile renames the file from src to dst. If renaming doesn't work (for 56 // example, the rename crosses a filesystem boundary), the file is copied and 57 // the old file is removed. 58 func moveFile(src, dst string) error { 59 err := os.Rename(src, dst) 60 if err == nil { 61 // Success! 62 return nil 63 } 64 // Failed to move, probably a different filesystem. 65 // Do a copy + remove. 66 err = copyFile(src, dst) 67 if err != nil { 68 return err 69 } 70 return os.Remove(src) 71 } 72 73 // copyFile copies the given file or directory from src to dst. It can copy over 74 // a possibly already existing file (but not directory) at the destination. 75 func copyFile(src, dst string) error { 76 source, err := os.Open(src) 77 if err != nil { 78 return err 79 } 80 defer source.Close() 81 82 st, err := source.Stat() 83 if err != nil { 84 return err 85 } 86 87 if st.IsDir() { 88 err := os.Mkdir(dst, st.Mode().Perm()) 89 if err != nil { 90 return err 91 } 92 names, err := source.Readdirnames(0) 93 if err != nil { 94 return err 95 } 96 for _, name := range names { 97 err := copyFile(filepath.Join(src, name), filepath.Join(dst, name)) 98 if err != nil { 99 return err 100 } 101 } 102 return nil 103 } else { 104 destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, st.Mode()) 105 if err != nil { 106 return err 107 } 108 defer destination.Close() 109 110 _, err = io.Copy(destination, source) 111 return err 112 } 113 } 114 115 // executeCommand is a simple wrapper to exec.Cmd 116 func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd { 117 if options.PrintCommands != nil { 118 options.PrintCommands(name, arg...) 119 } 120 return exec.Command(name, arg...) 121 } 122 123 // printCommand prints a command to stdout while formatting it like a real 124 // command (escaping characters etc). The resulting command should be easy to 125 // run directly in a shell, although it is not guaranteed to be a safe shell 126 // escape. That's not a problem as the primary use case is printing the command, 127 // not running it. 128 func printCommand(cmd string, args ...string) { 129 command := append([]string{cmd}, args...) 130 for i, arg := range command { 131 // Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html 132 const specialChars = "~`#$&*()\\|[]{};'\"<>?! " 133 if strings.ContainsAny(arg, specialChars) { 134 // See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash 135 arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'" 136 command[i] = arg 137 } 138 } 139 fmt.Fprintln(os.Stderr, strings.Join(command, " ")) 140 } 141 142 // Build compiles and links the given package and writes it to outpath. 143 func Build(pkgName, outpath string, options *compileopts.Options) error { 144 config, err := builder.NewConfig(options) 145 if err != nil { 146 return err 147 } 148 149 if options.PrintJSON { 150 b, err := json.MarshalIndent(config, "", " ") 151 if err != nil { 152 handleCompilerError(err) 153 } 154 fmt.Printf("%s\n", string(b)) 155 return nil 156 } 157 158 // Create a temporary directory for intermediary files. 159 tmpdir, err := os.MkdirTemp("", "tinygo") 160 if err != nil { 161 return err 162 } 163 if !options.Work { 164 defer os.RemoveAll(tmpdir) 165 } 166 167 // Do the build. 168 result, err := builder.Build(pkgName, outpath, tmpdir, config) 169 if err != nil { 170 return err 171 } 172 173 if result.Binary != "" { 174 // If result.Binary is set, it means there is a build output (elf, hex, 175 // etc) that we need to move to the outpath. If it isn't set, it means 176 // the build output was a .ll, .bc or .o file that has already been 177 // written to outpath and so we don't need to do anything. 178 179 if outpath == "" { 180 if strings.HasSuffix(pkgName, ".go") { 181 // A Go file was specified directly on the command line. 182 // Base the binary name off of it. 183 outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension() 184 } else { 185 // Pick a default output path based on the main directory. 186 outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() 187 } 188 } 189 190 if err := os.Rename(result.Binary, outpath); err != nil { 191 // Moving failed. Do a file copy. 192 inf, err := os.Open(result.Binary) 193 if err != nil { 194 return err 195 } 196 defer inf.Close() 197 outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 198 if err != nil { 199 return err 200 } 201 202 // Copy data to output file. 203 _, err = io.Copy(outf, inf) 204 if err != nil { 205 return err 206 } 207 208 // Check whether file writing was successful. 209 return outf.Close() 210 } 211 } 212 213 // Move was successful. 214 return nil 215 } 216 217 // Test runs the tests in the given package. Returns whether the test passed and 218 // possibly an error if the test failed to run. 219 func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) { 220 options.TestConfig.CompileTestBinary = true 221 config, err := builder.NewConfig(options) 222 if err != nil { 223 return false, err 224 } 225 226 testConfig := &options.TestConfig 227 228 // Pass test flags to the test binary. 229 var flags []string 230 if testConfig.Verbose { 231 flags = append(flags, "-test.v") 232 } 233 if testConfig.Short { 234 flags = append(flags, "-test.short") 235 } 236 if testConfig.RunRegexp != "" { 237 flags = append(flags, "-test.run="+testConfig.RunRegexp) 238 } 239 if testConfig.SkipRegexp != "" { 240 flags = append(flags, "-test.skip="+testConfig.SkipRegexp) 241 } 242 if testConfig.BenchRegexp != "" { 243 flags = append(flags, "-test.bench="+testConfig.BenchRegexp) 244 } 245 if testConfig.BenchTime != "" { 246 flags = append(flags, "-test.benchtime="+testConfig.BenchTime) 247 } 248 if testConfig.BenchMem { 249 flags = append(flags, "-test.benchmem") 250 } 251 if testConfig.Count != nil && *testConfig.Count != 1 { 252 flags = append(flags, "-test.count="+strconv.Itoa(*testConfig.Count)) 253 } 254 if testConfig.Shuffle != "" { 255 flags = append(flags, "-test.shuffle="+testConfig.Shuffle) 256 } 257 258 logToStdout := testConfig.Verbose || testConfig.BenchRegexp != "" 259 260 var buf bytes.Buffer 261 var output io.Writer = &buf 262 // Send the test output to stdout if -v or -bench 263 if logToStdout { 264 output = os.Stdout 265 } 266 267 passed := false 268 var duration time.Duration 269 result, err := buildAndRun(pkgName, config, output, flags, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { 270 if testConfig.CompileOnly || outpath != "" { 271 // Write test binary to the specified file name. 272 if outpath == "" { 273 // No -o path was given, so create one now. 274 // This matches the behavior of go test. 275 outpath = filepath.Base(result.MainDir) + ".test" 276 } 277 copyFile(result.Binary, outpath) 278 } 279 if testConfig.CompileOnly { 280 // Do not run the test. 281 passed = true 282 return nil 283 } 284 285 // Tests are always run in the package directory. 286 cmd.Dir = result.MainDir 287 288 // wasmtime is the default emulator used for `-target=wasip1`. wasmtime 289 // is a WebAssembly runtime CLI with WASI enabled by default. However, 290 // only stdio are allowed by default. For example, while STDOUT routes 291 // to the host, other files don't. It also does not inherit environment 292 // variables from the host. Some tests read testdata files, often from 293 // outside the package directory. Other tests require temporary 294 // writeable directories. We allow this by adding wasmtime flags below. 295 if config.EmulatorName() == "wasmtime" { 296 // At this point, The current working directory is at the package 297 // directory. Ex. $GOROOT/src/compress/flate for compress/flate. 298 // buildAndRun has already added arguments for wasmtime, that allow 299 // read-access to files such as "testdata/huffman-zero.in". 300 // 301 // Ex. main(.wasm) --dir=. -- -test.v 302 303 // Below adds additional wasmtime flags in case a test reads files 304 // outside its directory, like "../testdata/e.txt". This allows any 305 // relative directory up to the module root, even if the test never 306 // reads any files. 307 // 308 // Ex. run --dir=.. --dir=../.. --dir=../../.. 309 dirs := dirsToModuleRoot(result.MainDir, result.ModuleRoot) 310 args := []string{"run"} 311 for _, d := range dirs[1:] { 312 args = append(args, "--dir="+d) 313 } 314 315 // The below re-organizes the arguments so that the current 316 // directory is added last. 317 args = append(args, cmd.Args[1:]...) 318 cmd.Args = append(cmd.Args[:1:1], args...) 319 } 320 321 // Run the test. 322 start := time.Now() 323 err = cmd.Run() 324 duration = time.Since(start) 325 passed = err == nil 326 327 // if verbose or benchmarks, then output is already going to stdout 328 // However, if we failed and weren't printing to stdout, print the output we accumulated. 329 if !passed && !logToStdout { 330 buf.WriteTo(stdout) 331 } 332 333 if _, ok := err.(*exec.ExitError); ok { 334 // Binary exited with a non-zero exit code, which means the test 335 // failed. Return nil to avoid printing a useless "exited with 336 // error" error message. 337 return nil 338 } 339 return err 340 }) 341 importPath := strings.TrimSuffix(result.ImportPath, ".test") 342 343 var w io.Writer = stdout 344 if logToStdout { 345 w = os.Stdout 346 } 347 if err, ok := err.(loader.NoTestFilesError); ok { 348 fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath) 349 // Pretend the test passed - it at least didn't fail. 350 return true, nil 351 } else if passed && !testConfig.CompileOnly { 352 fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) 353 } else { 354 fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) 355 } 356 return passed, err 357 } 358 359 func dirsToModuleRoot(maindir, modroot string) []string { 360 var dirs = []string{"."} 361 last := ".." 362 // strip off path elements until we hit the module root 363 // adding `..`, `../..`, `../../..` until we're done 364 for maindir != modroot { 365 dirs = append(dirs, last) 366 last = filepath.Join(last, "..") 367 maindir = filepath.Dir(maindir) 368 } 369 return dirs 370 } 371 372 // Flash builds and flashes the built binary to the given serial port. 373 func Flash(pkgName, port string, options *compileopts.Options) error { 374 config, err := builder.NewConfig(options) 375 if err != nil { 376 return err 377 } 378 379 // determine the type of file to compile 380 var fileExt string 381 382 flashMethod, _ := config.Programmer() 383 switch flashMethod { 384 case "command", "": 385 switch { 386 case strings.Contains(config.Target.FlashCommand, "{hex}"): 387 fileExt = ".hex" 388 case strings.Contains(config.Target.FlashCommand, "{elf}"): 389 fileExt = ".elf" 390 case strings.Contains(config.Target.FlashCommand, "{bin}"): 391 fileExt = ".bin" 392 case strings.Contains(config.Target.FlashCommand, "{uf2}"): 393 fileExt = ".uf2" 394 case strings.Contains(config.Target.FlashCommand, "{zip}"): 395 fileExt = ".zip" 396 default: 397 return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?") 398 } 399 case "msd": 400 if config.Target.FlashFilename == "" { 401 return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set") 402 } 403 fileExt = filepath.Ext(config.Target.FlashFilename) 404 case "openocd": 405 fileExt = ".hex" 406 case "bmp": 407 fileExt = ".elf" 408 case "native": 409 return errors.New("unknown flash method \"native\" - did you miss a -target flag?") 410 default: 411 return errors.New("unknown flash method: " + flashMethod) 412 } 413 414 // Create a temporary directory for intermediary files. 415 tmpdir, err := os.MkdirTemp("", "tinygo") 416 if err != nil { 417 return err 418 } 419 if !options.Work { 420 defer os.RemoveAll(tmpdir) 421 } 422 423 // Build the binary. 424 result, err := builder.Build(pkgName, fileExt, tmpdir, config) 425 if err != nil { 426 return err 427 } 428 429 // do we need port reset to put MCU into bootloader mode? 430 if config.Target.PortReset == "true" && flashMethod != "openocd" { 431 port, err := getDefaultPort(port, config.Target.SerialPort) 432 if err == nil { 433 err = touchSerialPortAt1200bps(port) 434 if err != nil { 435 return &commandError{"failed to reset port", port, err} 436 } 437 // give the target MCU a chance to restart into bootloader 438 time.Sleep(3 * time.Second) 439 } 440 } 441 442 // Flash the binary to the MCU. 443 switch flashMethod { 444 case "", "command": 445 // Create the command. 446 flashCmd := config.Target.FlashCommand 447 flashCmdList, err := shlex.Split(flashCmd) 448 if err != nil { 449 return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err) 450 } 451 452 if strings.Contains(flashCmd, "{port}") { 453 var err error 454 port, err = getDefaultPort(port, config.Target.SerialPort) 455 if err != nil { 456 return err 457 } 458 } 459 460 // Fill in fields in the command template. 461 fileToken := "{" + fileExt[1:] + "}" 462 for i, arg := range flashCmdList { 463 arg = strings.ReplaceAll(arg, fileToken, result.Binary) 464 arg = strings.ReplaceAll(arg, "{port}", port) 465 flashCmdList[i] = arg 466 } 467 468 // Execute the command. 469 if len(flashCmdList) < 2 { 470 return fmt.Errorf("invalid flash command: %#v", flashCmd) 471 } 472 cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...) 473 cmd.Stdout = os.Stdout 474 cmd.Stderr = os.Stderr 475 cmd.Dir = goenv.Get("TINYGOROOT") 476 err = cmd.Run() 477 if err != nil { 478 return &commandError{"failed to flash", result.Binary, err} 479 } 480 case "msd": 481 // this flashing method copies the binary data to a Mass Storage Device (msd) 482 switch fileExt { 483 case ".uf2": 484 err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options) 485 if err != nil { 486 return &commandError{"failed to flash", result.Binary, err} 487 } 488 case ".hex": 489 err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options) 490 if err != nil { 491 return &commandError{"failed to flash", result.Binary, err} 492 } 493 default: 494 return errors.New("mass storage device flashing currently only supports uf2 and hex") 495 } 496 case "openocd": 497 args, err := config.OpenOCDConfiguration() 498 if err != nil { 499 return err 500 } 501 exit := " reset exit" 502 if config.Target.OpenOCDVerify != nil && *config.Target.OpenOCDVerify { 503 exit = " verify" + exit 504 } 505 args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+exit) 506 cmd := executeCommand(config.Options, "openocd", args...) 507 cmd.Stdout = os.Stdout 508 cmd.Stderr = os.Stderr 509 err = cmd.Run() 510 if err != nil { 511 return &commandError{"failed to flash", result.Binary, err} 512 } 513 case "bmp": 514 gdb, err := config.Target.LookupGDB() 515 if err != nil { 516 return err 517 } 518 var bmpGDBPort string 519 bmpGDBPort, _, err = getBMPPorts() 520 if err != nil { 521 return err 522 } 523 args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)} 524 cmd := executeCommand(config.Options, gdb, args...) 525 cmd.Stdout = os.Stdout 526 cmd.Stderr = os.Stderr 527 err = cmd.Run() 528 if err != nil { 529 return &commandError{"failed to flash", result.Binary, err} 530 } 531 default: 532 return fmt.Errorf("unknown flash method: %s", flashMethod) 533 } 534 if options.Monitor { 535 return Monitor(result.Executable, "", config) 536 } 537 return nil 538 } 539 540 // Debug compiles and flashes a program to a microcontroller (just like Flash) 541 // but instead of resetting the target, it will drop into a debug shell like GDB 542 // or LLDB. You can then set breakpoints, run the `continue` command to start, 543 // hit Ctrl+C to break the running program, etc. 544 // 545 // Note: this command is expected to execute just before exiting, as it 546 // modifies global state. 547 func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Options) error { 548 config, err := builder.NewConfig(options) 549 if err != nil { 550 return err 551 } 552 var cmdName string 553 switch debugger { 554 case "gdb": 555 cmdName, err = config.Target.LookupGDB() 556 case "lldb": 557 cmdName, err = builder.LookupCommand("lldb") 558 } 559 if err != nil { 560 return err 561 } 562 563 // Create a temporary directory for intermediary files. 564 tmpdir, err := os.MkdirTemp("", "tinygo") 565 if err != nil { 566 return err 567 } 568 if !options.Work { 569 defer os.RemoveAll(tmpdir) 570 } 571 572 // Build the binary to debug. 573 format, fileExt := config.EmulatorFormat() 574 result, err := builder.Build(pkgName, fileExt, tmpdir, config) 575 if err != nil { 576 return err 577 } 578 579 // Find a good way to run GDB. 580 gdbInterface, openocdInterface := config.Programmer() 581 switch gdbInterface { 582 case "msd", "command", "": 583 emulator := config.EmulatorName() 584 if emulator != "" { 585 if emulator == "mgba" { 586 gdbInterface = "mgba" 587 } else if emulator == "simavr" { 588 gdbInterface = "simavr" 589 } else if strings.HasPrefix(emulator, "qemu-system-") { 590 gdbInterface = "qemu" 591 } else { 592 // Assume QEMU as an emulator. 593 gdbInterface = "qemu-user" 594 } 595 } else if openocdInterface != "" && config.Target.OpenOCDTarget != "" { 596 gdbInterface = "openocd" 597 } else if config.Target.JLinkDevice != "" { 598 gdbInterface = "jlink" 599 } else { 600 gdbInterface = "native" 601 } 602 } 603 604 // Run the GDB server, if necessary. 605 port := "" 606 var gdbCommands []string 607 var daemon *exec.Cmd 608 emulator, err := config.Emulator(format, result.Binary) 609 if err != nil { 610 return err 611 } 612 switch gdbInterface { 613 case "native": 614 // Run GDB directly. 615 case "bmp": 616 var bmpGDBPort string 617 bmpGDBPort, _, err = getBMPPorts() 618 if err != nil { 619 return err 620 } 621 port = bmpGDBPort 622 gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load") 623 case "openocd": 624 port = ":3333" 625 gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt") 626 627 // We need a separate debugging daemon for on-chip debugging. 628 args, err := config.OpenOCDConfiguration() 629 if err != nil { 630 return err 631 } 632 daemon = executeCommand(config.Options, "openocd", args...) 633 if ocdOutput { 634 // Make it clear which output is from the daemon. 635 w := &ColorWriter{ 636 Out: colorable.NewColorableStderr(), 637 Prefix: "openocd: ", 638 Color: TermColorYellow, 639 } 640 daemon.Stdout = w 641 daemon.Stderr = w 642 } 643 case "jlink": 644 port = ":2331" 645 gdbCommands = append(gdbCommands, "load", "monitor reset halt") 646 647 // We need a separate debugging daemon for on-chip debugging. 648 daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) 649 if ocdOutput { 650 // Make it clear which output is from the daemon. 651 w := &ColorWriter{ 652 Out: colorable.NewColorableStderr(), 653 Prefix: "jlink: ", 654 Color: TermColorYellow, 655 } 656 daemon.Stdout = w 657 daemon.Stderr = w 658 } 659 case "qemu": 660 port = ":1234" 661 // Run in an emulator. 662 args := append(emulator[1:], "-s", "-S") 663 daemon = executeCommand(config.Options, emulator[0], args...) 664 daemon.Stdout = os.Stdout 665 daemon.Stderr = os.Stderr 666 case "qemu-user": 667 port = ":1234" 668 // Run in an emulator. 669 args := append([]string{"-g", "1234"}, emulator[1:]...) 670 daemon = executeCommand(config.Options, emulator[0], args...) 671 daemon.Stdout = os.Stdout 672 daemon.Stderr = os.Stderr 673 case "mgba": 674 port = ":2345" 675 // Run in an emulator. 676 args := append(emulator[1:], "-g") 677 daemon = executeCommand(config.Options, emulator[0], args...) 678 daemon.Stdout = os.Stdout 679 daemon.Stderr = os.Stderr 680 case "simavr": 681 port = ":1234" 682 // Run in an emulator. 683 args := append(emulator[1:], "-g") 684 daemon = executeCommand(config.Options, emulator[0], args...) 685 daemon.Stdout = os.Stdout 686 daemon.Stderr = os.Stderr 687 case "msd": 688 return errors.New("gdb is not supported for drag-and-drop programmable devices") 689 default: 690 return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface) 691 } 692 693 if daemon != nil { 694 // Make sure the daemon doesn't receive Ctrl-C that is intended for 695 // GDB (to break the currently executing program). 696 setCommandAsDaemon(daemon) 697 698 // Start now, and kill it on exit. 699 err = daemon.Start() 700 if err != nil { 701 return &commandError{"failed to run", daemon.Path, err} 702 } 703 defer func() { 704 daemon.Process.Signal(os.Interrupt) 705 var stopped uint32 706 go func() { 707 time.Sleep(time.Millisecond * 100) 708 if atomic.LoadUint32(&stopped) == 0 { 709 daemon.Process.Kill() 710 } 711 }() 712 daemon.Wait() 713 atomic.StoreUint32(&stopped, 1) 714 }() 715 } 716 717 // Ignore Ctrl-C, it must be passed on to GDB. 718 c := make(chan os.Signal, 1) 719 signal.Notify(c, os.Interrupt) 720 go func() { 721 for range c { 722 } 723 }() 724 725 // Construct and execute a gdb or lldb command. 726 // By default: gdb -ex run <binary> 727 // Exit the debugger with Ctrl-D. 728 params := []string{result.Executable} 729 switch debugger { 730 case "gdb": 731 if port != "" { 732 params = append(params, "-ex", "target extended-remote "+port) 733 } 734 for _, cmd := range gdbCommands { 735 params = append(params, "-ex", cmd) 736 } 737 case "lldb": 738 params = append(params, "--arch", config.Triple()) 739 if port != "" { 740 if strings.HasPrefix(port, ":") { 741 params = append(params, "-o", "gdb-remote "+port[1:]) 742 } else { 743 return fmt.Errorf("cannot use LLDB over a gdb-remote that isn't a TCP port: %s", port) 744 } 745 } 746 for _, cmd := range gdbCommands { 747 if strings.HasPrefix(cmd, "monitor ") { 748 params = append(params, "-o", "process plugin packet "+cmd) 749 } else if cmd == "load" { 750 params = append(params, "-o", "target modules load --load --slide 0") 751 } else { 752 return fmt.Errorf("don't know how to convert GDB command %#v to LLDB", cmd) 753 } 754 } 755 } 756 cmd := executeCommand(config.Options, cmdName, params...) 757 cmd.Stdin = os.Stdin 758 cmd.Stdout = os.Stdout 759 cmd.Stderr = os.Stderr 760 err = cmd.Run() 761 if err != nil { 762 return &commandError{"failed to run " + cmdName + " with", result.Executable, err} 763 } 764 return nil 765 } 766 767 // Run compiles and runs the given program. Depending on the target provided in 768 // the options, it will run the program directly on the host or will run it in 769 // an emulator. For example, -target=wasm will cause the binary to be run inside 770 // of a WebAssembly VM. 771 func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { 772 config, err := builder.NewConfig(options) 773 if err != nil { 774 return err 775 } 776 777 _, err = buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { 778 return cmd.Run() 779 }) 780 return err 781 } 782 783 // buildAndRun builds and runs the given program, writing output to stdout and 784 // errors to os.Stderr. It takes care of emulators (qemu, wasmtime, etc) and 785 // passes command line arguments and evironment variables in a way appropriate 786 // for the given emulator. 787 func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { 788 // Determine whether we're on a system that supports environment variables 789 // and command line parameters (operating systems, WASI) or not (baremetal, 790 // WebAssembly in the browser). If we're on a system without an environment, 791 // we need to pass command line arguments and environment variables through 792 // global variables (built into the binary directly) instead of the 793 // conventional way. 794 needsEnvInVars := config.GOOS() == "js" 795 for _, tag := range config.BuildTags() { 796 if tag == "baremetal" { 797 needsEnvInVars = true 798 } 799 } 800 var args, emuArgs, env []string 801 var extraCmdEnv []string 802 if needsEnvInVars { 803 runtimeGlobals := make(map[string]string) 804 if len(cmdArgs) != 0 { 805 runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00") 806 } 807 if len(environmentVars) != 0 { 808 runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00") 809 } 810 if len(runtimeGlobals) != 0 { 811 // This sets the global variables like they would be set with 812 // `-ldflags="-X=runtime.osArgs=first\x00second`. 813 // The runtime package has two variables (osArgs and osEnv) that are 814 // both strings, from which the parameters and environment variables 815 // are read. 816 config.Options.GlobalValues = map[string]map[string]string{ 817 "runtime": runtimeGlobals, 818 } 819 } 820 } else if config.EmulatorName() == "wasmtime" { 821 // Wasmtime needs some special flags to pass environment variables 822 // and allow reading from the current directory. 823 emuArgs = append(emuArgs, "--dir=.") 824 for _, v := range environmentVars { 825 emuArgs = append(emuArgs, "--env", v) 826 } 827 if len(cmdArgs) != 0 { 828 // Use of '--' argument no longer necessary as of Wasmtime v14: 829 // https://github.com/bytecodealliance/wasmtime/pull/6946 830 // args = append(args, "--") 831 args = append(args, cmdArgs...) 832 } 833 834 // Set this for nicer backtraces during tests, but don't override the user. 835 if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { 836 extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") 837 } 838 } else { 839 // Pass environment variables and command line parameters as usual. 840 // This also works on qemu-aarch64 etc. 841 args = cmdArgs 842 env = environmentVars 843 } 844 845 // Create a temporary directory for intermediary files. 846 tmpdir, err := os.MkdirTemp("", "tinygo") 847 if err != nil { 848 return builder.BuildResult{}, err 849 } 850 if !config.Options.Work { 851 defer os.RemoveAll(tmpdir) 852 } 853 854 // Build the binary to be run. 855 format, fileExt := config.EmulatorFormat() 856 result, err := builder.Build(pkgName, fileExt, tmpdir, config) 857 if err != nil { 858 return result, err 859 } 860 861 // If needed, set a timeout on the command. This is done in tests so 862 // they don't waste resources on a stalled test. 863 var ctx context.Context 864 if timeout != 0 { 865 var cancel context.CancelFunc 866 ctx, cancel = context.WithTimeout(context.Background(), timeout) 867 defer cancel() 868 } 869 870 // Set up the command. 871 var name string 872 if config.Target.Emulator == "" { 873 name = result.Binary 874 } else { 875 emulator, err := config.Emulator(format, result.Binary) 876 if err != nil { 877 return result, err 878 } 879 name = emulator[0] 880 emuArgs = append(emuArgs, emulator[1:]...) 881 args = append(emuArgs, args...) 882 } 883 var cmd *exec.Cmd 884 if ctx != nil { 885 cmd = exec.CommandContext(ctx, name, args...) 886 } else { 887 cmd = exec.Command(name, args...) 888 } 889 cmd.Env = append(cmd.Env, env...) 890 cmd.Env = append(cmd.Env, extraCmdEnv...) 891 892 // Configure stdout/stderr. The stdout may go to a buffer, not a real 893 // stdout. 894 cmd.Stdout = stdout 895 cmd.Stderr = os.Stderr 896 if config.EmulatorName() == "simavr" { 897 cmd.Stdout = nil // don't print initial load commands 898 cmd.Stderr = stdout 899 } 900 901 // If this is a test, reserve CPU time for it so that increased 902 // parallelism doesn't blow up memory usage. If this isn't a test but 903 // simply `tinygo run`, then it is practically a no-op. 904 config.Options.Semaphore <- struct{}{} 905 defer func() { 906 <-config.Options.Semaphore 907 }() 908 909 // Run binary. 910 if config.Options.PrintCommands != nil { 911 config.Options.PrintCommands(cmd.Path, cmd.Args...) 912 } 913 err = run(cmd, result) 914 if err != nil { 915 if ctx != nil && ctx.Err() == context.DeadlineExceeded { 916 stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) 917 err = ctx.Err() 918 } 919 return result, &commandError{"failed to run compiled binary", result.Binary, err} 920 } 921 return result, nil 922 } 923 924 func touchSerialPortAt1200bps(port string) (err error) { 925 retryCount := 3 926 for i := 0; i < retryCount; i++ { 927 // Open port 928 p, e := serial.Open(port, &serial.Mode{BaudRate: 1200}) 929 if e != nil { 930 if runtime.GOOS == `windows` { 931 se, ok := e.(*serial.PortError) 932 if ok && se.Code() == serial.InvalidSerialPort { 933 // InvalidSerialPort error occurs when transitioning to boot 934 return nil 935 } 936 } 937 time.Sleep(1 * time.Second) 938 err = e 939 continue 940 } 941 defer p.Close() 942 943 p.SetDTR(false) 944 return nil 945 } 946 return fmt.Errorf("opening port: %s", err) 947 } 948 949 func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { 950 for start := time.Now(); time.Since(start) < options.Timeout; { 951 // Find a UF2 mount point. 952 mounts, err := findFATMounts(options) 953 if err != nil { 954 return err 955 } 956 for _, mount := range mounts { 957 for _, volume := range volumes { 958 if mount.name != volume { 959 continue 960 } 961 if _, err := os.Stat(filepath.Join(mount.path, "INFO_UF2.TXT")); err != nil { 962 // No INFO_UF2.TXT found, which is expected on a UF2 963 // filesystem. 964 continue 965 } 966 // Found the filesystem, so flash the device! 967 return moveFile(tmppath, filepath.Join(mount.path, "flash.uf2")) 968 } 969 } 970 time.Sleep(500 * time.Millisecond) 971 } 972 return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") 973 } 974 975 func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { 976 for start := time.Now(); time.Since(start) < options.Timeout; { 977 // Find all mount points. 978 mounts, err := findFATMounts(options) 979 if err != nil { 980 return err 981 } 982 for _, mount := range mounts { 983 for _, volume := range volumes { 984 if mount.name != volume { 985 continue 986 } 987 // Found the filesystem, so flash the device! 988 return moveFile(tmppath, filepath.Join(mount.path, "flash.hex")) 989 } 990 } 991 time.Sleep(500 * time.Millisecond) 992 } 993 return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") 994 } 995 996 type mountPoint struct { 997 name string 998 path string 999 } 1000 1001 // Find all the mount points on the system that use the FAT filesystem. 1002 func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { 1003 var points []mountPoint 1004 switch runtime.GOOS { 1005 case "darwin": 1006 list, err := os.ReadDir("/Volumes") 1007 if err != nil { 1008 return nil, fmt.Errorf("could not list mount points: %w", err) 1009 } 1010 for _, elem := range list { 1011 // TODO: find a way to check for the filesystem type. 1012 // (Only return FAT filesystems). 1013 points = append(points, mountPoint{ 1014 name: elem.Name(), 1015 path: filepath.Join("/Volumes", elem.Name()), 1016 }) 1017 } 1018 sort.Slice(points, func(i, j int) bool { 1019 return points[i].path < points[j].name 1020 }) 1021 return points, nil 1022 case "linux": 1023 tab, err := os.ReadFile("/proc/mounts") // symlink to /proc/self/mounts on my system 1024 if err != nil { 1025 return nil, fmt.Errorf("could not list mount points: %w", err) 1026 } 1027 for _, line := range strings.Split(string(tab), "\n") { 1028 fields := strings.Fields(line) 1029 if len(fields) <= 2 { 1030 continue 1031 } 1032 fstype := fields[2] 1033 if fstype != "vfat" { 1034 continue 1035 } 1036 points = append(points, mountPoint{ 1037 name: filepath.Base(fields[1]), 1038 path: fields[1], 1039 }) 1040 } 1041 return points, nil 1042 case "windows": 1043 // Obtain a list of all currently mounted volumes. 1044 cmd := executeCommand(options, "wmic", 1045 "PATH", "Win32_LogicalDisk", 1046 "get", "DeviceID,VolumeName,FileSystem,DriveType") 1047 var out bytes.Buffer 1048 cmd.Stdout = &out 1049 err := cmd.Run() 1050 if err != nil { 1051 return nil, fmt.Errorf("could not list mount points: %w", err) 1052 } 1053 1054 // Extract data to convert to a []mountPoint slice. 1055 for _, line := range strings.Split(out.String(), "\n") { 1056 words := strings.Fields(line) 1057 if len(words) < 3 { 1058 continue 1059 } 1060 if words[1] != "2" || words[2] != "FAT" { 1061 // - DriveType 2 is removable (which we're looking for). 1062 // - We only want to return FAT filesystems. 1063 continue 1064 } 1065 points = append(points, mountPoint{ 1066 name: words[3], 1067 path: words[0], 1068 }) 1069 } 1070 return points, nil 1071 default: 1072 return nil, fmt.Errorf("unknown GOOS for listing mount points: %s", runtime.GOOS) 1073 } 1074 } 1075 1076 // getDefaultPort returns the default serial port depending on the operating system. 1077 func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) { 1078 portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' }) 1079 if len(portCandidates) == 1 { 1080 return portCandidates[0], nil 1081 } 1082 1083 var ports []string 1084 switch runtime.GOOS { 1085 case "freebsd": 1086 ports, err = filepath.Glob("/dev/cuaU*") 1087 case "darwin", "linux", "windows": 1088 var portsList []*enumerator.PortDetails 1089 portsList, err = enumerator.GetDetailedPortsList() 1090 if err != nil { 1091 return "", err 1092 } 1093 1094 var preferredPortIDs [][2]uint16 1095 for _, s := range usbInterfaces { 1096 parts := strings.Split(s, ":") 1097 if len(parts) != 2 { 1098 return "", fmt.Errorf("could not parse USB VID/PID pair %q", s) 1099 } 1100 vid, err := strconv.ParseUint(parts[0], 16, 16) 1101 if err != nil { 1102 return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err) 1103 } 1104 pid, err := strconv.ParseUint(parts[1], 16, 16) 1105 if err != nil { 1106 return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err) 1107 } 1108 preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)}) 1109 } 1110 1111 var primaryPorts []string // ports picked from preferred USB VID/PID 1112 var secondaryPorts []string // other ports (as a fallback) 1113 for _, p := range portsList { 1114 if !p.IsUSB { 1115 continue 1116 } 1117 if p.VID != "" && p.PID != "" { 1118 foundPort := false 1119 vid, vidErr := strconv.ParseUint(p.VID, 16, 16) 1120 pid, pidErr := strconv.ParseUint(p.PID, 16, 16) 1121 if vidErr == nil && pidErr == nil { 1122 for _, id := range preferredPortIDs { 1123 if uint16(vid) == id[0] && uint16(pid) == id[1] { 1124 primaryPorts = append(primaryPorts, p.Name) 1125 foundPort = true 1126 continue 1127 } 1128 } 1129 } 1130 if foundPort { 1131 continue 1132 } 1133 } 1134 1135 secondaryPorts = append(secondaryPorts, p.Name) 1136 } 1137 if len(primaryPorts) == 1 { 1138 // There is exactly one match in the set of preferred ports. Use 1139 // this port, even if there may be others available. This allows 1140 // flashing a specific board even if there are multiple available. 1141 return primaryPorts[0], nil 1142 } else if len(primaryPorts) > 1 { 1143 // There are multiple preferred ports, probably because more than 1144 // one device of the same type are connected (e.g. two Arduino 1145 // Unos). 1146 ports = primaryPorts 1147 } else { 1148 // No preferred ports found. Fall back to other serial ports 1149 // available in the system. 1150 ports = secondaryPorts 1151 } 1152 default: 1153 return "", errors.New("unable to search for a default USB device to be flashed on this OS") 1154 } 1155 1156 if err != nil { 1157 return "", err 1158 } else if ports == nil { 1159 return "", errors.New("unable to locate a serial port") 1160 } else if len(ports) == 0 { 1161 return "", errors.New("no serial ports available") 1162 } 1163 1164 if len(portCandidates) == 0 { 1165 if len(usbInterfaces) > 0 { 1166 return "", errors.New("unable to search for a default USB device - use -port flag, available ports are " + strings.Join(ports, ", ")) 1167 } else if len(ports) == 1 { 1168 return ports[0], nil 1169 } else { 1170 return "", errors.New("multiple serial ports available - use -port flag, available ports are " + strings.Join(ports, ", ")) 1171 } 1172 } 1173 1174 for _, ps := range portCandidates { 1175 for _, p := range ports { 1176 if p == ps { 1177 return p, nil 1178 } 1179 } 1180 } 1181 1182 return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", ")) 1183 } 1184 1185 // getBMPPorts returns BlackMagicProbe's serial ports if any 1186 func getBMPPorts() (gdbPort, uartPort string, err error) { 1187 var portsList []*enumerator.PortDetails 1188 portsList, err = enumerator.GetDetailedPortsList() 1189 if err != nil { 1190 return "", "", err 1191 } 1192 var ports []string 1193 for _, p := range portsList { 1194 if !p.IsUSB { 1195 continue 1196 } 1197 if p.VID != "" && p.PID != "" { 1198 vid, vidErr := strconv.ParseUint(p.VID, 16, 16) 1199 pid, pidErr := strconv.ParseUint(p.PID, 16, 16) 1200 if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 { 1201 ports = append(ports, p.Name) 1202 } 1203 } 1204 } 1205 if len(ports) == 2 { 1206 return ports[0], ports[1], nil 1207 } else if len(ports) == 0 { 1208 return "", "", errors.New("no BMP detected") 1209 } else { 1210 return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports)) 1211 } 1212 } 1213 1214 func usage(command string) { 1215 switch command { 1216 default: 1217 fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") 1218 fmt.Fprintln(os.Stderr, "version:", goenv.Version()) 1219 fmt.Fprintf(os.Stderr, "usage: %s <command> [arguments]\n", os.Args[0]) 1220 fmt.Fprintln(os.Stderr, "\ncommands:") 1221 fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") 1222 fmt.Fprintln(os.Stderr, " run: compile and run immediately") 1223 fmt.Fprintln(os.Stderr, " test: test packages") 1224 fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") 1225 fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") 1226 fmt.Fprintln(os.Stderr, " lldb: run/flash and immediately enter LLDB") 1227 fmt.Fprintln(os.Stderr, " monitor: open communication port") 1228 fmt.Fprintln(os.Stderr, " ports: list available serial ports") 1229 fmt.Fprintln(os.Stderr, " env: list environment variables used during build") 1230 fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root") 1231 fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")") 1232 fmt.Fprintln(os.Stderr, " targets: list targets") 1233 fmt.Fprintln(os.Stderr, " info: show info for specified target") 1234 fmt.Fprintln(os.Stderr, " version: show version") 1235 fmt.Fprintln(os.Stderr, " help: print this help text") 1236 1237 if flag.Parsed() { 1238 fmt.Fprintln(os.Stderr, "\nflags:") 1239 flag.PrintDefaults() 1240 } 1241 1242 fmt.Fprintln(os.Stderr, "\nfor more details, see https://tinygo.org/docs/reference/usage/") 1243 } 1244 } 1245 1246 // try to make the path relative to the current working directory. If any error 1247 // occurs, this error is ignored and the absolute path is returned instead. 1248 func tryToMakePathRelative(dir string) string { 1249 wd, err := os.Getwd() 1250 if err != nil { 1251 return dir 1252 } 1253 relpath, err := filepath.Rel(wd, dir) 1254 if err != nil { 1255 return dir 1256 } 1257 return relpath 1258 } 1259 1260 // printCompilerError prints compiler errors using the provided logger function 1261 // (similar to fmt.Println). 1262 // 1263 // There is one exception: interp errors may print to stderr unconditionally due 1264 // to limitations in the LLVM bindings. 1265 func printCompilerError(logln func(...interface{}), err error) { 1266 switch err := err.(type) { 1267 case types.Error: 1268 printCompilerError(logln, scanner.Error{ 1269 Pos: err.Fset.Position(err.Pos), 1270 Msg: err.Msg, 1271 }) 1272 case scanner.Error: 1273 if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { 1274 // This file is not from the standard library (either the GOROOT or 1275 // the TINYGOROOT). Make the path relative, for easier reading. 1276 // Ignore any errors in the process (falling back to the absolute 1277 // path). 1278 err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename) 1279 } 1280 logln(err) 1281 case scanner.ErrorList: 1282 for _, scannerErr := range err { 1283 printCompilerError(logln, *scannerErr) 1284 } 1285 case *interp.Error: 1286 logln("#", err.ImportPath) 1287 logln(err.Error()) 1288 if len(err.Inst) != 0 { 1289 logln(err.Inst) 1290 } 1291 if len(err.Traceback) > 0 { 1292 logln("\ntraceback:") 1293 for _, line := range err.Traceback { 1294 logln(line.Pos.String() + ":") 1295 logln(line.Inst) 1296 } 1297 } 1298 case loader.Errors: 1299 logln("#", err.Pkg.ImportPath) 1300 for _, err := range err.Errs { 1301 printCompilerError(logln, err) 1302 } 1303 case loader.Error: 1304 logln(err.Err.Error()) 1305 logln("package", err.ImportStack[0]) 1306 for _, pkgPath := range err.ImportStack[1:] { 1307 logln("\timports", pkgPath) 1308 } 1309 case *builder.MultiError: 1310 for _, err := range err.Errs { 1311 printCompilerError(logln, err) 1312 } 1313 default: 1314 logln("error:", err) 1315 } 1316 } 1317 1318 func handleCompilerError(err error) { 1319 if err != nil { 1320 printCompilerError(func(args ...interface{}) { 1321 fmt.Fprintln(os.Stderr, args...) 1322 }, err) 1323 os.Exit(1) 1324 } 1325 } 1326 1327 // This is a special type for the -X flag to parse the pkgpath.Var=stringVal 1328 // format. It has to be a special type to allow multiple variables to be defined 1329 // this way. 1330 type globalValuesFlag map[string]map[string]string 1331 1332 func (m globalValuesFlag) String() string { 1333 return "pkgpath.Var=value" 1334 } 1335 1336 func (m globalValuesFlag) Set(value string) error { 1337 equalsIndex := strings.IndexByte(value, '=') 1338 if equalsIndex < 0 { 1339 return errors.New("expected format pkgpath.Var=value") 1340 } 1341 pathAndName := value[:equalsIndex] 1342 pointIndex := strings.LastIndexByte(pathAndName, '.') 1343 if pointIndex < 0 { 1344 return errors.New("expected format pkgpath.Var=value") 1345 } 1346 path := pathAndName[:pointIndex] 1347 name := pathAndName[pointIndex+1:] 1348 stringValue := value[equalsIndex+1:] 1349 if m[path] == nil { 1350 m[path] = make(map[string]string) 1351 } 1352 m[path][name] = stringValue 1353 return nil 1354 } 1355 1356 // parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now 1357 // is the -X flag, for setting the value of global string variables. 1358 func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) { 1359 set := flag.NewFlagSet("link", flag.ExitOnError) 1360 globalVarValues := make(globalValuesFlag) 1361 set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.") 1362 flags, err := shlex.Split(flagsString) 1363 if err != nil { 1364 return nil, err 1365 } 1366 err = set.Parse(flags) 1367 if err != nil { 1368 return nil, err 1369 } 1370 return map[string]map[string]string(globalVarValues), nil 1371 } 1372 1373 // getListOfPackages returns a standard list of packages for a given list that might 1374 // include wildards using `go list`. 1375 // For example [./...] => ["pkg1", "pkg1/pkg12", "pkg2"] 1376 func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, error) { 1377 config, err := builder.NewConfig(options) 1378 if err != nil { 1379 return nil, err 1380 } 1381 cmd, err := loader.List(config, nil, pkgs) 1382 if err != nil { 1383 return nil, fmt.Errorf("failed to run `go list`: %w", err) 1384 } 1385 outputBuf := bytes.NewBuffer(nil) 1386 cmd.Stdout = outputBuf 1387 cmd.Stderr = os.Stderr 1388 err = cmd.Run() 1389 if err != nil { 1390 return nil, err 1391 } 1392 1393 var pkgNames []string 1394 sc := bufio.NewScanner(outputBuf) 1395 for sc.Scan() { 1396 pkgNames = append(pkgNames, sc.Text()) 1397 } 1398 1399 return pkgNames, nil 1400 } 1401 1402 func main() { 1403 if len(os.Args) < 2 { 1404 fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") 1405 usage("") 1406 os.Exit(1) 1407 } 1408 command := os.Args[1] 1409 1410 opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") 1411 gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") 1412 panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") 1413 scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)") 1414 serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") 1415 work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") 1416 interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") 1417 var tags buildutil.TagsFlag 1418 flag.Var(&tags, "tags", "a space-separated list of extra build tags") 1419 target := flag.String("target", "", "chip/board name or JSON target specification file") 1420 var stackSize uint64 1421 flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { 1422 size, err := bytesize.Parse(s) 1423 stackSize = uint64(size) 1424 return err 1425 }) 1426 printSize := flag.String("size", "", "print sizes (none, short, full)") 1427 printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") 1428 printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed") 1429 printCommands := flag.Bool("x", false, "Print commands") 1430 parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") 1431 nodebug := flag.Bool("no-debug", false, "strip debug information") 1432 ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)") 1433 ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") 1434 port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)") 1435 timeout := flag.Duration("timeout", 20*time.Second, "the length of time to retry locating the MSD volume to be used for flashing") 1436 programmer := flag.String("programmer", "", "which hardware programmer to use") 1437 ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") 1438 llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") 1439 cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") 1440 monitor := flag.Bool("monitor", false, "enable serial monitor") 1441 baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") 1442 1443 // Internal flags, that are only intended for TinyGo development. 1444 printIR := flag.Bool("internal-printir", false, "print LLVM IR") 1445 dumpSSA := flag.Bool("internal-dumpssa", false, "dump internal Go SSA") 1446 verifyIR := flag.Bool("internal-verifyir", false, "run extra verification steps on LLVM IR") 1447 // Don't generate debug information in the IR, to make IR more readable. 1448 // You generally want debug information in IR for various features, like 1449 // stack size calculation and features like -size=short, -print-allocs=, 1450 // etc. The -no-debug flag is used to strip it at link time. But for TinyGo 1451 // development it can be useful to not emit debug information at all. 1452 skipDwarf := flag.Bool("internal-nodwarf", false, "internal flag, use -no-debug instead") 1453 1454 var flagJSON, flagDeps, flagTest bool 1455 if command == "help" || command == "list" || command == "info" || command == "build" { 1456 flag.BoolVar(&flagJSON, "json", false, "print data in JSON format") 1457 } 1458 if command == "help" || command == "list" { 1459 flag.BoolVar(&flagDeps, "deps", false, "supply -deps flag to go list") 1460 flag.BoolVar(&flagTest, "test", false, "supply -test flag to go list") 1461 } 1462 var outpath string 1463 if command == "help" || command == "build" || command == "build-library" || command == "test" { 1464 flag.StringVar(&outpath, "o", "", "output filename") 1465 } 1466 1467 var testConfig compileopts.TestConfig 1468 if command == "help" || command == "test" { 1469 flag.BoolVar(&testConfig.CompileOnly, "c", false, "compile the test binary but do not run it") 1470 flag.BoolVar(&testConfig.Verbose, "v", false, "verbose: print additional output") 1471 flag.BoolVar(&testConfig.Short, "short", false, "short: run smaller test suite to save time") 1472 flag.StringVar(&testConfig.RunRegexp, "run", "", "run: regexp of tests to run") 1473 flag.StringVar(&testConfig.SkipRegexp, "skip", "", "skip: regexp of tests to skip") 1474 testConfig.Count = flag.Int("count", 1, "count: number of times to run tests/benchmarks `count` times") 1475 flag.StringVar(&testConfig.BenchRegexp, "bench", "", "bench: regexp of benchmarks to run") 1476 flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration `d`") 1477 flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks") 1478 flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run") 1479 } 1480 1481 // Early command processing, before commands are interpreted by the Go flag 1482 // library. 1483 switch command { 1484 case "clang", "ld.lld", "wasm-ld": 1485 err := builder.RunTool(command, os.Args[2:]...) 1486 if err != nil { 1487 fmt.Fprintln(os.Stderr, err) 1488 os.Exit(1) 1489 } 1490 os.Exit(0) 1491 } 1492 1493 flag.CommandLine.Parse(os.Args[2:]) 1494 globalVarValues, err := parseGoLinkFlag(*ldflags) 1495 if err != nil { 1496 fmt.Fprintln(os.Stderr, err) 1497 os.Exit(1) 1498 } 1499 1500 var printAllocs *regexp.Regexp 1501 if *printAllocsString != "" { 1502 printAllocs, err = regexp.Compile(*printAllocsString) 1503 if err != nil { 1504 fmt.Fprintln(os.Stderr, err) 1505 os.Exit(1) 1506 } 1507 } 1508 1509 var ocdCommands []string 1510 if *ocdCommandsString != "" { 1511 ocdCommands = strings.Split(*ocdCommandsString, ",") 1512 } 1513 1514 options := &compileopts.Options{ 1515 GOOS: goenv.Get("GOOS"), 1516 GOARCH: goenv.Get("GOARCH"), 1517 GOARM: goenv.Get("GOARM"), 1518 Target: *target, 1519 StackSize: stackSize, 1520 Opt: *opt, 1521 GC: *gc, 1522 PanicStrategy: *panicStrategy, 1523 Scheduler: *scheduler, 1524 Serial: *serial, 1525 Work: *work, 1526 InterpTimeout: *interpTimeout, 1527 PrintIR: *printIR, 1528 DumpSSA: *dumpSSA, 1529 VerifyIR: *verifyIR, 1530 SkipDWARF: *skipDwarf, 1531 Semaphore: make(chan struct{}, *parallelism), 1532 Debug: !*nodebug, 1533 PrintSizes: *printSize, 1534 PrintStacks: *printStacks, 1535 PrintAllocs: printAllocs, 1536 Tags: []string(tags), 1537 TestConfig: testConfig, 1538 GlobalValues: globalVarValues, 1539 Programmer: *programmer, 1540 OpenOCDCommands: ocdCommands, 1541 LLVMFeatures: *llvmFeatures, 1542 PrintJSON: flagJSON, 1543 Monitor: *monitor, 1544 BaudRate: *baudrate, 1545 Timeout: *timeout, 1546 } 1547 if *printCommands { 1548 options.PrintCommands = printCommand 1549 } 1550 1551 err = options.Verify() 1552 if err != nil { 1553 fmt.Fprintln(os.Stderr, err.Error()) 1554 usage(command) 1555 os.Exit(1) 1556 } 1557 1558 if *cpuprofile != "" { 1559 f, err := os.Create(*cpuprofile) 1560 if err != nil { 1561 fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err) 1562 os.Exit(1) 1563 } 1564 defer f.Close() 1565 if err := pprof.StartCPUProfile(f); err != nil { 1566 fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err) 1567 os.Exit(1) 1568 } 1569 defer pprof.StopCPUProfile() 1570 } 1571 1572 switch command { 1573 case "build": 1574 pkgName := "." 1575 if flag.NArg() == 1 { 1576 pkgName = filepath.ToSlash(flag.Arg(0)) 1577 } else if flag.NArg() > 1 { 1578 fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") 1579 usage(command) 1580 os.Exit(1) 1581 } 1582 if options.Target == "" && filepath.Ext(outpath) == ".wasm" { 1583 options.Target = "wasm" 1584 } 1585 1586 err := Build(pkgName, outpath, options) 1587 handleCompilerError(err) 1588 case "build-library": 1589 // Note: this command is only meant to be used while making a release! 1590 if outpath == "" { 1591 fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") 1592 usage(command) 1593 os.Exit(1) 1594 } 1595 if *target == "" { 1596 fmt.Fprintln(os.Stderr, "No target (-target).") 1597 } 1598 if flag.NArg() != 1 { 1599 fmt.Fprintf(os.Stderr, "Build-library only accepts exactly one library name as argument, %d given\n", flag.NArg()) 1600 usage(command) 1601 os.Exit(1) 1602 } 1603 var lib *builder.Library 1604 switch name := flag.Arg(0); name { 1605 case "compiler-rt": 1606 lib = &builder.CompilerRT 1607 case "picolibc": 1608 lib = &builder.Picolibc 1609 default: 1610 fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name) 1611 os.Exit(1) 1612 } 1613 tmpdir, err := os.MkdirTemp("", "tinygo*") 1614 if err != nil { 1615 handleCompilerError(err) 1616 } 1617 defer os.RemoveAll(tmpdir) 1618 spec, err := compileopts.LoadTarget(options) 1619 if err != nil { 1620 handleCompilerError(err) 1621 } 1622 config := &compileopts.Config{ 1623 Options: options, 1624 Target: spec, 1625 } 1626 path, err := lib.Load(config, tmpdir) 1627 handleCompilerError(err) 1628 err = copyFile(path, outpath) 1629 if err != nil { 1630 handleCompilerError(err) 1631 } 1632 case "flash", "gdb", "lldb": 1633 pkgName := filepath.ToSlash(flag.Arg(0)) 1634 if command == "flash" { 1635 err := Flash(pkgName, *port, options) 1636 handleCompilerError(err) 1637 } else { 1638 if !options.Debug { 1639 fmt.Fprintln(os.Stderr, "Debug disabled while running debugger?") 1640 usage(command) 1641 os.Exit(1) 1642 } 1643 err := Debug(command, pkgName, *ocdOutput, options) 1644 handleCompilerError(err) 1645 } 1646 case "run": 1647 if flag.NArg() < 1 { 1648 fmt.Fprintln(os.Stderr, "No package specified.") 1649 usage(command) 1650 os.Exit(1) 1651 } 1652 pkgName := filepath.ToSlash(flag.Arg(0)) 1653 err := Run(pkgName, options, flag.Args()[1:]) 1654 handleCompilerError(err) 1655 case "test": 1656 var pkgNames []string 1657 for i := 0; i < flag.NArg(); i++ { 1658 pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i))) 1659 } 1660 if len(pkgNames) == 0 { 1661 pkgNames = []string{"."} 1662 } 1663 1664 explicitPkgNames, err := getListOfPackages(pkgNames, options) 1665 if err != nil { 1666 fmt.Printf("cannot resolve packages: %v\n", err) 1667 os.Exit(1) 1668 } 1669 1670 if outpath != "" && len(explicitPkgNames) > 1 { 1671 fmt.Println("cannot use -o flag with multiple packages") 1672 os.Exit(1) 1673 } 1674 1675 fail := make(chan struct{}, 1) 1676 var wg sync.WaitGroup 1677 bufs := make([]testOutputBuf, len(explicitPkgNames)) 1678 for i := range bufs { 1679 bufs[i].done = make(chan struct{}) 1680 } 1681 1682 wg.Add(1) 1683 go func() { 1684 defer wg.Done() 1685 1686 // Flush the output one test at a time. 1687 // This ensures that outputs from different tests are not mixed together. 1688 for i := range bufs { 1689 err := bufs[i].flush(os.Stdout, os.Stderr) 1690 if err != nil { 1691 // There was an error writing to stdout or stderr, so we probbably cannot print this. 1692 select { 1693 case fail <- struct{}{}: 1694 default: 1695 } 1696 } 1697 } 1698 }() 1699 1700 // Build and run the tests concurrently. 1701 // This uses an additional semaphore to reduce the memory usage. 1702 testSema := make(chan struct{}, cap(options.Semaphore)) 1703 for i, pkgName := range explicitPkgNames { 1704 pkgName := pkgName 1705 buf := &bufs[i] 1706 testSema <- struct{}{} 1707 wg.Add(1) 1708 go func() { 1709 defer wg.Done() 1710 defer func() { <-testSema }() 1711 defer close(buf.done) 1712 stdout := (*testStdout)(buf) 1713 stderr := (*testStderr)(buf) 1714 passed, err := Test(pkgName, stdout, stderr, options, outpath) 1715 if err != nil { 1716 printCompilerError(func(args ...interface{}) { 1717 fmt.Fprintln(stderr, args...) 1718 }, err) 1719 } 1720 if !passed { 1721 select { 1722 case fail <- struct{}{}: 1723 default: 1724 } 1725 } 1726 }() 1727 } 1728 1729 // Wait for all tests to finish. 1730 wg.Wait() 1731 close(fail) 1732 if _, fail := <-fail; fail { 1733 os.Exit(1) 1734 } 1735 case "monitor": 1736 config, err := builder.NewConfig(options) 1737 handleCompilerError(err) 1738 err = Monitor("", *port, config) 1739 handleCompilerError(err) 1740 case "ports": 1741 serialPortInfo, err := ListSerialPorts() 1742 handleCompilerError(err) 1743 if len(serialPortInfo) == 0 { 1744 fmt.Println("No serial ports found.") 1745 } 1746 fmt.Printf("%-20s %-9s %s\n", "Port", "ID", "Boards") 1747 for _, s := range serialPortInfo { 1748 fmt.Printf("%-20s %4s:%4s %s\n", s.Name, s.VID, s.PID, s.Target) 1749 } 1750 case "targets": 1751 specs, err := compileopts.GetTargetSpecs() 1752 if err != nil { 1753 fmt.Fprintln(os.Stderr, "could not list targets:", err) 1754 os.Exit(1) 1755 return 1756 } 1757 names := []string{} 1758 for key := range specs { 1759 names = append(names, key) 1760 } 1761 sort.Strings(names) 1762 for _, name := range names { 1763 fmt.Println(name) 1764 } 1765 case "info": 1766 if flag.NArg() == 1 { 1767 options.Target = flag.Arg(0) 1768 } else if flag.NArg() > 1 { 1769 fmt.Fprintln(os.Stderr, "only one target name is accepted") 1770 usage(command) 1771 os.Exit(1) 1772 } 1773 config, err := builder.NewConfig(options) 1774 if err != nil { 1775 fmt.Fprintln(os.Stderr, err) 1776 usage(command) 1777 os.Exit(1) 1778 } 1779 config.GoMinorVersion = 0 // this avoids creating the list of Go1.x build tags. 1780 if err != nil { 1781 fmt.Fprintln(os.Stderr, err) 1782 os.Exit(1) 1783 } 1784 cachedGOROOT, err := loader.GetCachedGoroot(config) 1785 if err != nil { 1786 fmt.Fprintln(os.Stderr, err) 1787 os.Exit(1) 1788 } 1789 if flagJSON { 1790 json, _ := json.MarshalIndent(struct { 1791 Target *compileopts.TargetSpec `json:"target"` 1792 GOROOT string `json:"goroot"` 1793 GOOS string `json:"goos"` 1794 GOARCH string `json:"goarch"` 1795 GOARM string `json:"goarm"` 1796 BuildTags []string `json:"build_tags"` 1797 GC string `json:"garbage_collector"` 1798 Scheduler string `json:"scheduler"` 1799 LLVMTriple string `json:"llvm_triple"` 1800 }{ 1801 Target: config.Target, 1802 GOROOT: cachedGOROOT, 1803 GOOS: config.GOOS(), 1804 GOARCH: config.GOARCH(), 1805 GOARM: config.GOARM(), 1806 BuildTags: config.BuildTags(), 1807 GC: config.GC(), 1808 Scheduler: config.Scheduler(), 1809 LLVMTriple: config.Triple(), 1810 }, "", " ") 1811 fmt.Println(string(json)) 1812 } else { 1813 fmt.Printf("LLVM triple: %s\n", config.Triple()) 1814 fmt.Printf("GOOS: %s\n", config.GOOS()) 1815 fmt.Printf("GOARCH: %s\n", config.GOARCH()) 1816 fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) 1817 fmt.Printf("garbage collector: %s\n", config.GC()) 1818 fmt.Printf("scheduler: %s\n", config.Scheduler()) 1819 fmt.Printf("cached GOROOT: %s\n", cachedGOROOT) 1820 } 1821 case "list": 1822 config, err := builder.NewConfig(options) 1823 if err != nil { 1824 fmt.Fprintln(os.Stderr, err) 1825 usage(command) 1826 os.Exit(1) 1827 } 1828 var extraArgs []string 1829 if flagJSON { 1830 extraArgs = append(extraArgs, "-json") 1831 } 1832 if flagDeps { 1833 extraArgs = append(extraArgs, "-deps") 1834 } 1835 if flagTest { 1836 extraArgs = append(extraArgs, "-test") 1837 } 1838 cmd, err := loader.List(config, extraArgs, flag.Args()) 1839 if err != nil { 1840 fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) 1841 os.Exit(1) 1842 } 1843 cmd.Stdout = os.Stdout 1844 cmd.Stderr = os.Stderr 1845 err = cmd.Run() 1846 if err != nil { 1847 if exitErr, ok := err.(*exec.ExitError); ok { 1848 os.Exit(exitErr.ExitCode()) 1849 } 1850 fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) 1851 os.Exit(1) 1852 } 1853 case "clean": 1854 // remove cache directory 1855 err := os.RemoveAll(goenv.Get("GOCACHE")) 1856 if err != nil { 1857 fmt.Fprintln(os.Stderr, "cannot clean cache:", err) 1858 os.Exit(1) 1859 } 1860 case "help": 1861 command := "" 1862 if flag.NArg() >= 1 { 1863 command = flag.Arg(0) 1864 } 1865 usage(command) 1866 case "version": 1867 goversion := "<unknown>" 1868 if s, err := goenv.GorootVersionString(); err == nil { 1869 goversion = s 1870 } 1871 fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version(), runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) 1872 case "env": 1873 if flag.NArg() == 0 { 1874 // Show all environment variables. 1875 for _, key := range goenv.Keys { 1876 fmt.Printf("%s=%#v\n", key, goenv.Get(key)) 1877 } 1878 } else { 1879 // Show only one (or a few) environment variables. 1880 for i := 0; i < flag.NArg(); i++ { 1881 fmt.Println(goenv.Get(flag.Arg(i))) 1882 } 1883 } 1884 default: 1885 fmt.Fprintln(os.Stderr, "Unknown command:", command) 1886 usage("") 1887 os.Exit(1) 1888 } 1889 } 1890 1891 // testOutputBuf is used to buffer the output of concurrent tests. 1892 type testOutputBuf struct { 1893 mu sync.Mutex 1894 output []outputEntry 1895 stdout, stderr io.Writer 1896 outerr, errerr error 1897 done chan struct{} 1898 } 1899 1900 // flush the output to stdout and stderr. 1901 // This waits until done is closed. 1902 func (b *testOutputBuf) flush(stdout, stderr io.Writer) error { 1903 b.mu.Lock() 1904 1905 var err error 1906 b.stdout = stdout 1907 b.stderr = stderr 1908 for _, e := range b.output { 1909 var w io.Writer 1910 var errDst *error 1911 if e.stderr { 1912 w = stderr 1913 errDst = &b.errerr 1914 } else { 1915 w = stdout 1916 errDst = &b.outerr 1917 } 1918 if *errDst != nil { 1919 continue 1920 } 1921 1922 _, werr := w.Write(e.data) 1923 if werr != nil { 1924 if err == nil { 1925 err = werr 1926 } 1927 *errDst = err 1928 } 1929 } 1930 1931 b.mu.Unlock() 1932 1933 <-b.done 1934 1935 return err 1936 } 1937 1938 // testStdout writes stdout from a test to the output buffer. 1939 type testStdout testOutputBuf 1940 1941 func (out *testStdout) Write(data []byte) (int, error) { 1942 buf := (*testOutputBuf)(out) 1943 buf.mu.Lock() 1944 1945 if buf.stdout != nil { 1946 // Write the output directly. 1947 err := out.outerr 1948 buf.mu.Unlock() 1949 if err != nil { 1950 return 0, err 1951 } 1952 return buf.stdout.Write(data) 1953 } 1954 1955 defer buf.mu.Unlock() 1956 1957 // Append the output. 1958 if len(buf.output) == 0 || buf.output[len(buf.output)-1].stderr { 1959 buf.output = append(buf.output, outputEntry{ 1960 stderr: false, 1961 }) 1962 } 1963 last := &buf.output[len(buf.output)-1] 1964 last.data = append(last.data, data...) 1965 1966 return len(data), nil 1967 } 1968 1969 // testStderr writes stderr from a test to the output buffer. 1970 type testStderr testOutputBuf 1971 1972 func (out *testStderr) Write(data []byte) (int, error) { 1973 buf := (*testOutputBuf)(out) 1974 buf.mu.Lock() 1975 1976 if buf.stderr != nil { 1977 // Write the output directly. 1978 err := out.errerr 1979 buf.mu.Unlock() 1980 if err != nil { 1981 return 0, err 1982 } 1983 return buf.stderr.Write(data) 1984 } 1985 1986 defer buf.mu.Unlock() 1987 1988 // Append the output. 1989 if len(buf.output) == 0 || !buf.output[len(buf.output)-1].stderr { 1990 buf.output = append(buf.output, outputEntry{ 1991 stderr: true, 1992 }) 1993 } 1994 last := &buf.output[len(buf.output)-1] 1995 last.data = append(last.data, data...) 1996 1997 return len(data), nil 1998 } 1999 2000 type outputEntry struct { 2001 stderr bool 2002 data []byte 2003 }