Skip to content

Commit

Permalink
dap: support 'Env' attribute for launch requests (go-delve#2846)
Browse files Browse the repository at this point in the history
* dap: support 'Env' attribute for launch requests

Env is applied in addition to the delve process environment
variables. The env setting is done by calling os.Setenv
as early as possible when a Launch request is received.

Prior discussion is in go-delve#2582

In Visual Studio Code, setting null for an environment variable
in launch.json or tasks.json indicates users want to unset
the environment variable. Support the behavior by accepting
nil value.

* dap: Env field itself can be omitempty

* edit comment
  • Loading branch information
hyangah authored Jan 6, 2022
1 parent 79d5db2 commit 21bdb46
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 2 deletions.
4 changes: 2 additions & 2 deletions _fixtures/testenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func main() {
x := os.Getenv("SOMEVAR")
x, y := os.LookupEnv("SOMEVAR")
runtime.Breakpoint()
fmt.Printf("SOMEVAR=%s\n", x)
fmt.Printf("SOMEVAR=%s\n%v", x, y)
}
14 changes: 14 additions & 0 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,20 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
}
}

for k, v := range args.Env {
if v != nil {
if err := os.Setenv(k, *v); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to setenv(%v) - %v", k, err))
return
}
} else {
if err := os.Unsetenv(k); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to unsetenv(%v) - %v", k, err))
return
}
}
}

if args.Mode == "" {
args.Mode = "debug"
}
Expand Down
100 changes: 100 additions & 0 deletions service/dap/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5509,6 +5509,106 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) {
})
}

func TestLaunchRequestWithEnv(t *testing.T) {
// testenv fixture will lookup SOMEVAR with
// x, y := os.Lookup("SOMEVAR")
// before stopping at runtime.Breakpoint.

type envMap map[string]*string
strVar := func(s string) *string { return &s }

fixtures := protest.FindFixturesDir() // relative to current working directory.
testFile, _ := filepath.Abs(filepath.Join(fixtures, "testenv.go"))
for _, tc := range []struct {
name string
initEnv envMap
launchEnv envMap
wantX string
wantY bool
}{
{
name: "no env",
initEnv: envMap{"SOMEVAR": strVar("baz")},
wantX: "baz",
wantY: true,
},
{
name: "overwrite",
initEnv: envMap{"SOMEVAR": strVar("baz")},
launchEnv: envMap{"SOMEVAR": strVar("bar")},
wantX: "bar",
wantY: true,
},
{
name: "unset",
initEnv: envMap{"SOMEVAR": strVar("baz")},
launchEnv: envMap{"SOMEVAR": nil},
wantX: "",
wantY: false,
},
{
name: "empty value",
initEnv: envMap{"SOMEVAR": strVar("baz")},
launchEnv: envMap{"SOMEVAR": strVar("")},
wantX: "",
wantY: true,
},
{
name: "set",
launchEnv: envMap{"SOMEVAR": strVar("foo")},
wantX: "foo",
wantY: true,
},
{
name: "untouched",
initEnv: envMap{"SOMEVAR": strVar("baz")},
launchEnv: envMap{"SOMEVAR2": nil, "SOMEVAR3": strVar("foo")},
wantX: "baz",
wantY: true,
},
} {

t.Run(tc.name, func(t *testing.T) {
// cleanup
defer func() {
os.Unsetenv("SOMEVAR")
os.Unsetenv("SOMEVAR2")
os.Unsetenv("SOMEVAR3")
}()

for k, v := range tc.initEnv {
if v != nil {
os.Setenv(k, *v)
}
}

serverStopped := make(chan struct{})
client := startDAPServerWithClient(t, serverStopped)
defer client.Close()

runDebugSessionWithBPs(t, client, "launch", func() { // launch
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "debug",
"program": testFile,
"env": tc.launchEnv,
})
}, testFile, nil, // runtime.Breakpoint
[]onBreakpoint{{
execute: func() {
client.EvaluateRequest("x", 1000, "whatever")
gotX := client.ExpectEvaluateResponse(t)
checkEval(t, gotX, fmt.Sprintf("%q", tc.wantX), false)
client.EvaluateRequest("y", 1000, "whatever")
gotY := client.ExpectEvaluateResponse(t)
checkEval(t, gotY, fmt.Sprintf("%v", tc.wantY), false)
},
disconnect: true,
}})
<-serverStopped
})
}
}

func TestAttachRequest(t *testing.T) {
if runtime.GOOS == "freebsd" {
t.SkipNow()
Expand Down
9 changes: 9 additions & 0 deletions service/dap/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ type LaunchConfig struct {
// directory.
DlvCwd string `json:"dlvCwd,omitempty"`

// Env specifies optional environment variables for Delve server
// in addition to the environment variables Delve initially
// started with.
// Variables with 'nil' values can be used to unset the named
// environment variables.
// Values are interpreted verbatim. Variable substitution or
// reference to other environment variables is not supported.
Env map[string]*string `json:"env,omitempty"`

LaunchAttachCommonConfig
}

Expand Down

0 comments on commit 21bdb46

Please sign in to comment.