diff --git a/compileopts/config.go b/compileopts/config.go index 9fb6a3bb..d7dbb41c 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -10,6 +10,7 @@ import ( "regexp" "strings" + "github.com/google/shlex" "github.com/tinygo-org/tinygo/goenv" ) @@ -494,13 +495,38 @@ func (c *Config) WasmAbi() string { return c.Target.WasmAbi } -// Emulator returns the emulator target config -func (c *Config) Emulator() []string { +// EmulatorName is a shorthand to get the command for this emulator, something +// like qemu-system-arm or simavr. +func (c *Config) EmulatorName() string { + parts := strings.SplitN(c.Target.Emulator, " ", 2) + if len(parts) > 1 { + return parts[0] + } + return "" +} + +// EmulatorFormat returns the binary format for the emulator and the associated +// file extension. An empty string means to pass directly whatever the linker +// produces directly without conversion. +func (c *Config) EmulatorFormat() (format, fileExt string) { + return "", "" +} + +// Emulator returns a ready-to-run command to run the given binary in an +// emulator. Give it the format (returned by EmulatorFormat()) and the path to +// the compiled binary. +func (c *Config) Emulator(format, binary string) ([]string, error) { + parts, err := shlex.Split(c.Target.Emulator) + if err != nil { + return nil, fmt.Errorf("could not parse emulator command: %w", err) + } var emulator []string - for _, s := range c.Target.Emulator { - emulator = append(emulator, strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT"))) + for _, s := range parts { + s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT")) + s = strings.ReplaceAll(s, "{"+format+"}", binary) + emulator = append(emulator, s) } - return emulator + return emulator, nil } type TestConfig struct { diff --git a/compileopts/target.go b/compileopts/target.go index 26f00753..6a1dbf12 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -44,8 +44,8 @@ type TargetSpec struct { LDFlags []string `json:"ldflags"` LinkerScript string `json:"linkerscript"` ExtraFiles []string `json:"extra-files"` - RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum - Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append + RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum + Emulator string `json:"emulator"` FlashCommand string `json:"flash-command"` GDB []string `json:"gdb"` PortReset string `json:"flash-1200-bps-reset"` @@ -90,19 +90,8 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) { if !src.IsNil() { dst.Set(src) } - case reflect.Slice: // for slices... - if src.Len() > 0 { // ... if not empty ... - switch tag := field.Tag.Get("override"); tag { - case "copy": - // copy the field of child to spec - dst.Set(src) - case "append", "": - // or append the field of child to spec - dst.Set(reflect.AppendSlice(dst, src)) - default: - panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.") - } - } + case reflect.Slice: // for slices, append the field + dst.Set(reflect.AppendSlice(dst, src)) default: panic("unknown field type : " + kind.String()) } @@ -335,20 +324,20 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { case "386": // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. if runtime.GOARCH != "amd64" { - spec.Emulator = []string{"qemu-i386"} + spec.Emulator = "qemu-i386 {}" } case "amd64": - spec.Emulator = []string{"qemu-x86_64"} + spec.Emulator = "qemu-x86_64 {}" case "arm": - spec.Emulator = []string{"qemu-arm"} + spec.Emulator = "qemu-arm {}" case "arm64": - spec.Emulator = []string{"qemu-aarch64"} + spec.Emulator = "qemu-aarch64 {}" } } } if goos != runtime.GOOS { if goos == "windows" { - spec.Emulator = []string{"wine"} + spec.Emulator = "wine {}" } } return &spec, nil diff --git a/compileopts/target_test.go b/compileopts/target_test.go index 6aea7e81..8f7b99b2 100644 --- a/compileopts/target_test.go +++ b/compileopts/target_test.go @@ -29,7 +29,6 @@ func TestOverrideProperties(t *testing.T) { CPU: "baseCpu", CFlags: []string{"-base-foo", "-base-bar"}, BuildTags: []string{"bt1", "bt2"}, - Emulator: []string{"be1", "be2"}, DefaultStackSize: 42, AutoStackSize: &baseAutoStackSize, } @@ -38,7 +37,6 @@ func TestOverrideProperties(t *testing.T) { GOOS: "", CPU: "chlidCpu", CFlags: []string{"-child-foo", "-child-bar"}, - Emulator: []string{"ce1", "ce2"}, AutoStackSize: &childAutoStackSize, DefaultStackSize: 64, } @@ -57,9 +55,6 @@ func TestOverrideProperties(t *testing.T) { if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) { t.Errorf("Overriding failed : got %v", base.BuildTags) } - if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) { - t.Errorf("Overriding failed : got %v", base.Emulator) - } if *base.AutoStackSize != false { t.Errorf("Overriding failed : got %v", base.AutoStackSize) } diff --git a/main.go b/main.go index b9b7626b..0cb8a42c 100644 --- a/main.go +++ b/main.go @@ -239,8 +239,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options cmd.Dir = result.MainDir // Wasmtime needs a few extra flags to work. - emulator := config.Emulator() - if len(emulator) != 0 && emulator[0] == "wasmtime" { + if config.EmulatorName() == "wasmtime" { // Add directories to the module root, but skip the current working // directory which is already added by buildAndRun. dirs := dirsToModuleRoot(result.MainDir, result.ModuleRoot) @@ -484,18 +483,19 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option return err } - return builder.Build(pkgName, "", config, func(result builder.BuildResult) error { + format, fileExt := config.EmulatorFormat() + return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { // Find a good way to run GDB. gdbInterface, openocdInterface := config.Programmer() switch gdbInterface { case "msd", "command", "": - emulator := config.Emulator() - if len(emulator) != 0 { - if emulator[0] == "mgba" { + emulator := config.EmulatorName() + if emulator != "" { + if emulator == "mgba" { gdbInterface = "mgba" - } else if emulator[0] == "simavr" { + } else if emulator == "simavr" { gdbInterface = "simavr" - } else if strings.HasPrefix(emulator[0], "qemu-system-") { + } else if strings.HasPrefix(emulator, "qemu-system-") { gdbInterface = "qemu" } else { // Assume QEMU as an emulator. @@ -514,6 +514,10 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option 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. @@ -563,33 +567,29 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option } case "qemu": port = ":1234" - emulator := config.Emulator() // Run in an emulator. - args := append(emulator[1:], result.Binary, "-s", "-S") + 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" - emulator := config.Emulator() // Run in an emulator. - args := append(emulator[1:], "-g", "1234", result.Binary) + 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" - emulator := config.Emulator() // Run in an emulator. - args := append(emulator[1:], result.Binary, "-g") + args := append(emulator[1:], "-g") daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "simavr": port = ":1234" - emulator := config.Emulator() // Run in an emulator. - args := append(emulator[1:], "-g", result.Binary) + args := append(emulator[1:], "-g") daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr @@ -666,7 +666,7 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Run() + err = cmd.Run() if err != nil { return &commandError{"failed to run " + cmdName + " with", result.Binary, err} } @@ -694,9 +694,6 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { // passes command line arguments and evironment variables in a way appropriate // for the given emulator. 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) error { - // make sure any special vars in the emulator definition are rewritten - emulator := config.Emulator() - // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, // WebAssembly in the browser). If we're on a system without an environment, @@ -728,7 +725,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c "runtime": runtimeGlobals, } } - } else if len(emulator) != 0 && emulator[0] == "wasmtime" { + } else if config.EmulatorName() == "wasmtime" { // Wasmtime needs some special flags to pass environment variables // and allow reading from the current directory. args = append(args, "--dir=.") @@ -747,7 +744,8 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c env = environmentVars } - return builder.Build(pkgName, "", config, func(result builder.BuildResult) error { + format, fileExt := config.EmulatorFormat() + return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { // If needed, set a timeout on the command. This is done in tests so // they don't waste resources on a stalled test. var ctx context.Context @@ -759,12 +757,15 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Set up the command. var name string - if len(emulator) == 0 { + if config.Target.Emulator == "" { name = result.Binary } else { + emulator, err := config.Emulator(format, result.Binary) + if err != nil { + return err + } name = emulator[0] emuArgs := append([]string(nil), emulator[1:]...) - emuArgs = append(emuArgs, result.Binary) args = append(emuArgs, args...) } var cmd *exec.Cmd @@ -779,7 +780,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // stdout. cmd.Stdout = stdout cmd.Stderr = os.Stderr - if len(emulator) != 0 && emulator[0] == "simavr" { + if config.EmulatorName() == "simavr" { cmd.Stdout = nil // don't print initial load commands cmd.Stderr = stdout } @@ -1572,7 +1573,7 @@ func main() { os.Exit(1) return } - if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == nil { + if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { // This doesn't look like a regular target file, but rather like // a parent target (such as targets/cortex-m.json). continue diff --git a/main_test.go b/main_test.go index efb4b514..3b088ada 100644 --- a/main_test.go +++ b/main_test.go @@ -223,7 +223,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest(name, options, t, nil, nil) }) } - if len(spec.Emulator) == 0 || spec.Emulator[0] != "simavr" { + if !strings.HasPrefix(spec.Emulator, "simavr ") { t.Run("env.go", func(t *testing.T) { t.Parallel() runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) @@ -257,8 +257,8 @@ func emuCheck(t *testing.T, options compileopts.Options) { if err != nil { t.Fatal("failed to load target spec:", err) } - if len(spec.Emulator) != 0 { - _, err := exec.LookPath(spec.Emulator[0]) + if spec.Emulator != "" { + _, err := exec.LookPath(strings.SplitN(spec.Emulator, " ", 2)[0]) if err != nil { if errors.Is(err, exec.ErrNotFound) { t.Skipf("emulator not installed: %q", spec.Emulator[0]) @@ -327,9 +327,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c t.Fatal(err) } - // make sure any special vars in the emulator definition are rewritten - emulator := config.Emulator() - // Build the test binary. stdout := &bytes.Buffer{} err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { @@ -345,7 +342,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1) expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows - if len(emulator) != 0 && emulator[0] == "simavr" { + if config.EmulatorName() == "simavr" { // Strip simavr log formatting. actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1) actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1) diff --git a/targets/arduino-nano.json b/targets/arduino-nano.json index 9f3a3b9f..548e87b1 100644 --- a/targets/arduino-nano.json +++ b/targets/arduino-nano.json @@ -6,5 +6,5 @@ "-Wl,--defsym=_stack_size=512" ], "flash-command": "avrdude -c arduino -p atmega328p -b 57600 -P {port} -U flash:w:{hex}:i", - "emulator": ["simavr", "-m", "atmega328p", "-f", "16000000"] + "emulator": "simavr -m atmega328p -f 16000000 {}" } diff --git a/targets/arduino.json b/targets/arduino.json index 91dc49b8..f2702304 100644 --- a/targets/arduino.json +++ b/targets/arduino.json @@ -7,5 +7,5 @@ ], "flash-command": "avrdude -c arduino -p atmega328p -P {port} -U flash:w:{hex}:i", "serial-port": ["acm:2341:0043", "acm:2341:0001", "acm:2a03:0043", "acm:2341:0243"], - "emulator": ["simavr", "-m", "atmega328p", "-f", "16000000"] + "emulator": "simavr -m atmega328p -f 16000000 {}" } diff --git a/targets/atmega1284p.json b/targets/atmega1284p.json index 7740ccf1..d78d6a2a 100644 --- a/targets/atmega1284p.json +++ b/targets/atmega1284p.json @@ -13,5 +13,5 @@ "targets/avr.S", "src/device/avr/atmega1284p.s" ], - "emulator": ["simavr", "-m", "atmega1284p", "-f", "20000000"] + "emulator": "simavr -m atmega1284p -f 20000000 {}" } diff --git a/targets/cortex-m-qemu.json b/targets/cortex-m-qemu.json index 1d1cfc89..80e087aa 100644 --- a/targets/cortex-m-qemu.json +++ b/targets/cortex-m-qemu.json @@ -5,5 +5,5 @@ "extra-files": [ "targets/cortex-m-qemu.s" ], - "emulator": ["qemu-system-arm", "-machine", "lm3s6965evb", "-semihosting", "-nographic", "-kernel"] + "emulator": "qemu-system-arm -machine lm3s6965evb -semihosting -nographic -kernel {}" } diff --git a/targets/digispark.json b/targets/digispark.json index 14606b04..ad9b1550 100644 --- a/targets/digispark.json +++ b/targets/digispark.json @@ -6,5 +6,5 @@ "-Wl,--defsym=_stack_size=128" ], "flash-command": "micronucleus --run {hex}", - "emulator": ["simavr", "-m", "attiny85", "-f", "16000000"] + "emulator": "simavr -m attiny85 -f 16000000 {}" } diff --git a/targets/gameboy-advance.json b/targets/gameboy-advance.json index 8f46c642..0ead0831 100644 --- a/targets/gameboy-advance.json +++ b/targets/gameboy-advance.json @@ -24,5 +24,5 @@ "src/runtime/gc_arm.S" ], "gdb": ["gdb-multiarch"], - "emulator": ["mgba", "-3"] + "emulator": "mgba -3 {}" } diff --git a/targets/hifive1-qemu.json b/targets/hifive1-qemu.json index 11518943..5d2d3143 100644 --- a/targets/hifive1-qemu.json +++ b/targets/hifive1-qemu.json @@ -4,5 +4,5 @@ "serial": "uart", "default-stack-size": 4096, "linkerscript": "targets/hifive1-qemu.ld", - "emulator": ["qemu-system-riscv32", "-machine", "sifive_e", "-nographic", "-kernel"] + "emulator": "qemu-system-riscv32 -machine sifive_e -nographic -kernel {}" } diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index ebc220e9..84050ff6 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -4,5 +4,5 @@ "build-tags": ["virt", "qemu"], "default-stack-size": 4096, "linkerscript": "targets/riscv-qemu.ld", - "emulator": ["qemu-system-riscv32", "-machine", "virt", "-nographic", "-bios", "none", "-kernel"] + "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}" } diff --git a/targets/wasi.json b/targets/wasi.json index 9ae96a33..8ff4c3a2 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -13,6 +13,6 @@ "--stack-first", "--no-demangle" ], - "emulator": ["wasmtime"], + "emulator": "wasmtime {}", "wasm-abi": "generic" } diff --git a/targets/wasm.json b/targets/wasm.json index 12857dc1..1cc3410d 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -13,6 +13,6 @@ "--stack-first", "--no-demangle" ], - "emulator": ["node", "{root}/targets/wasm_exec.js"], + "emulator": "node {root}/targets/wasm_exec.js {}", "wasm-abi": "js" }