dap: support 'Env' attribute for launch requests (#2846)

* 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 https://github.com/go-delve/delve/pull/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
This commit is contained in:
Hyang-Ah Hana Kim 2022-01-06 12:01:09 -05:00 committed by GitHub
parent 79d5db24a5
commit 21bdb466f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 125 additions and 2 deletions

@ -7,7 +7,7 @@ import (
) )
func main() { func main() {
x := os.Getenv("SOMEVAR") x, y := os.LookupEnv("SOMEVAR")
runtime.Breakpoint() runtime.Breakpoint()
fmt.Printf("SOMEVAR=%s\n", x) fmt.Printf("SOMEVAR=%s\n%v", x, y)
} }

@ -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 == "" { if args.Mode == "" {
args.Mode = "debug" args.Mode = "debug"
} }

@ -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) { func TestAttachRequest(t *testing.T) {
if runtime.GOOS == "freebsd" { if runtime.GOOS == "freebsd" {
t.SkipNow() t.SkipNow()

@ -129,6 +129,15 @@ type LaunchConfig struct {
// directory. // directory.
DlvCwd string `json:"dlvCwd,omitempty"` 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 LaunchAttachCommonConfig
} }