diff --git a/service/dap/server.go b/service/dap/server.go index ba8d345113bfe3d2258614f78468c5694fd6baf3..69bf59e38f929940bb7ae0d318e61dce5a0a7875 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -16,10 +16,13 @@ import ( "io" "net" "os" + "os/exec" "path/filepath" "reflect" "regexp" + "runtime" "strings" + "sync" "github.com/go-delve/delve/pkg/gobuild" "github.com/go-delve/delve/pkg/logflags" @@ -67,6 +70,11 @@ type Server struct { variableHandles *variablesHandlesMap // args tracks special settings for handling debug session requests. args launchAttachArgs + + mu sync.Mutex + + // noDebugProcess is set for the noDebug launch process. + noDebugProcess *exec.Cmd } // launchAttachArgs captures arguments from launch/attach request that @@ -431,6 +439,13 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) { // Output path for the compiled binary in debug or test modes. const debugBinary string = "./__debug_bin" +func cleanExeName(name string) string { + if runtime.GOOS == "windows" && filepath.Ext(name) != ".exe" { + return name + ".exe" + } + return name +} + func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { // Validate launch request mode mode, ok := request.Arguments["mode"] @@ -456,7 +471,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { if mode == "debug" || mode == "test" { output, ok := request.Arguments["output"].(string) if !ok || output == "" { - output = debugBinary + output = cleanExeName(debugBinary) } debugname, err := filepath.Abs(output) if err != nil { @@ -532,6 +547,17 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { s.config.Debugger.WorkingDir = wdParsed } + noDebug, ok := request.Arguments["noDebug"] + if ok { + if v, ok := noDebug.(bool); ok && v { // noDebug == true + if err := s.runWithoutDebug(program, targetArgs, s.config.Debugger.WorkingDir); err != nil { + s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error()) + } else { // program terminated. + s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")}) + } + return + } + } var err error if s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs); err != nil { s.sendErrorResponse(request.Request, @@ -546,6 +572,52 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)}) } +func (s *Server) runWithoutDebug(program string, targetArgs []string, wd string) error { + s.log.Println("Running without debug: ", program) + + cmd := exec.Command(program, targetArgs...) + // TODO: send stdin/out/err as OutputEvent messages + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Dir = s.config.Debugger.WorkingDir + + s.mu.Lock() + defer s.mu.Unlock() + if s.noDebugProcess != nil { + return fmt.Errorf("previous process (pid=%v) is still active", s.noDebugProcess.Process.Pid) + } + if err := cmd.Start(); err != nil { + return err + } + s.noDebugProcess = cmd + s.mu.Unlock() // allow disconnect or restart requests to call stopNoDebugProcess. + + // block until the process terminates. + if err := cmd.Wait(); err != nil { + s.log.Errorf("process exited with %v", err) + } + + s.mu.Lock() + if s.noDebugProcess == cmd { + s.noDebugProcess = nil + } + return nil // Program ran and terminated. +} + +func (s *Server) stopNoDebugProcess() { + s.mu.Lock() + defer s.mu.Unlock() + if s.noDebugProcess == nil { + return + } + + // TODO(hyangah): gracefully terminate the process and its children processes. + if err := s.noDebugProcess.Process.Kill(); err != nil { + s.log.Errorf("killing process (pid=%v) failed: %v", s.noDebugProcess.Process.Pid, err) + } +} + // TODO(polina): support "remote" mode func isValidLaunchMode(launchMode interface{}) bool { switch launchMode { @@ -578,6 +650,8 @@ func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) { if err != nil { s.log.Error(err) } + } else { + s.stopNoDebugProcess() } // TODO(polina): make thread-safe when handlers become asynchronous. s.signalDisconnect() diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 9290b305ed5479a2e1d661b940806e8881d72604..6f1d640dc3fa2f68dadb5b461a859e1f819416c4 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -1693,7 +1693,7 @@ func TestWorkingDir(t *testing.T) { "mode": "exec", "program": fixture.Path, "stopOnEntry": false, - "wd": wd, + "wd": wd, }) }, // Set breakpoints @@ -2366,6 +2366,48 @@ func TestLaunchRequestDefaults(t *testing.T) { }) } +func TestLaunchRequestDefaultsNoDebug(t *testing.T) { + runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { + runNoDebugDebugSession(t, client, "launch", func() { + client.LaunchRequestWithArgs(map[string]interface{}{ + "noDebug": true, + "mode": "", /*"debug" by default*/ + "program": fixture.Source, + "output": cleanExeName("__mydir")}) + }, fixture.Source) + }) + runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { + runNoDebugDebugSession(t, client, "launch", func() { + // Use the default output directory. + client.LaunchRequestWithArgs(map[string]interface{}{ + "noDebug": true, + /*"mode":"debug" by default*/ + "program": fixture.Source, + "output": cleanExeName("__mydir")}) + }, fixture.Source) + }) + runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { + runNoDebugDebugSession(t, client, "launch", func() { + // Use the default output directory. + client.LaunchRequestWithArgs(map[string]interface{}{ + "noDebug": true, + "mode": "debug", + "program": fixture.Source}) + // writes to default output dir __debug_bin + }, fixture.Source) + }) +} + +func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string) { + client.InitializeRequest() + client.ExpectInitializeResponse(t) + + cmdRequest() + // ! client.InitializedEvent. + // ! client.ExpectLaunchResponse + client.ExpectTerminatedEvent(t) +} + func TestLaunchTestRequest(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, "launch", func() {