debugger: Handle the `envFile` setting for Go (#33666)

Julia Ryan created

Fixes #32984

Release Notes:

- The Go debugger now respects the `envFile` setting.

Change summary

Cargo.lock                     | 10 +---
Cargo.toml                     |  2 
crates/dap_adapters/Cargo.toml |  2 
crates/dap_adapters/src/go.rs  | 82 +++++++++++++++++++++++++++++++----
crates/eval/Cargo.toml         |  2 
crates/eval/src/eval.rs        |  2 
6 files changed, 80 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4147,6 +4147,8 @@ dependencies = [
  "async-trait",
  "collections",
  "dap",
+ "dotenvy",
+ "fs",
  "futures 0.3.31",
  "gpui",
  "json_dotpath",
@@ -4675,12 +4677,6 @@ dependencies = [
  "syn 2.0.101",
 ]
 
-[[package]]
-name = "dotenv"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
-
 [[package]]
 name = "dotenvy"
 version = "0.15.7"
@@ -5114,7 +5110,7 @@ dependencies = [
  "collections",
  "debug_adapter_extension",
  "dirs 4.0.0",
- "dotenv",
+ "dotenvy",
  "env_logger 0.11.8",
  "extension",
  "fs",

Cargo.toml 🔗

@@ -449,7 +449,7 @@ dashmap = "6.0"
 derive_more = "0.99.17"
 dirs = "4.0"
 documented = "0.9.1"
-dotenv = "0.15.0"
+dotenvy = "0.15.0"
 ec4rs = "1.1"
 emojis = "0.6.1"
 env_logger = "0.11"

crates/dap_adapters/Cargo.toml 🔗

@@ -25,7 +25,9 @@ anyhow.workspace = true
 async-trait.workspace = true
 collections.workspace = true
 dap.workspace = true
+dotenvy.workspace = true
 futures.workspace = true
+fs.workspace = true
 gpui.workspace = true
 json_dotpath.workspace = true
 language.workspace = true

crates/dap_adapters/src/go.rs 🔗

@@ -7,13 +7,22 @@ use dap::{
         latest_github_release,
     },
 };
-
+use fs::Fs;
 use gpui::{AsyncApp, SharedString};
 use language::LanguageName;
-use std::{env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock};
+use log::warn;
+use serde_json::{Map, Value};
 use task::TcpArgumentsTemplate;
 use util;
 
+use std::{
+    env::consts,
+    ffi::OsStr,
+    path::{Path, PathBuf},
+    str::FromStr,
+    sync::OnceLock,
+};
+
 use crate::*;
 
 #[derive(Default, Debug)]
@@ -437,22 +446,34 @@ impl DebugAdapter for GoDebugAdapter {
             adapter_path.join("dlv").to_string_lossy().to_string()
         };
 
-        let cwd = task_definition
-            .config
-            .get("cwd")
-            .and_then(|s| s.as_str())
-            .map(PathBuf::from)
-            .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf());
+        let cwd = Some(
+            task_definition
+                .config
+                .get("cwd")
+                .and_then(|s| s.as_str())
+                .map(PathBuf::from)
+                .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()),
+        );
 
         let arguments;
         let command;
         let connection;
 
         let mut configuration = task_definition.config.clone();
+        let mut envs = HashMap::default();
+
         if let Some(configuration) = configuration.as_object_mut() {
             configuration
                 .entry("cwd")
                 .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
+
+            handle_envs(
+                configuration,
+                &mut envs,
+                cwd.as_deref(),
+                delegate.fs().clone(),
+            )
+            .await;
         }
 
         if let Some(connection_options) = &task_definition.tcp_connection {
@@ -494,8 +515,8 @@ impl DebugAdapter for GoDebugAdapter {
         Ok(DebugAdapterBinary {
             command,
             arguments,
-            cwd: Some(cwd),
-            envs: HashMap::default(),
+            cwd,
+            envs,
             connection,
             request_args: StartDebuggingRequestArguments {
                 configuration,
@@ -504,3 +525,44 @@ impl DebugAdapter for GoDebugAdapter {
         })
     }
 }
+
+// delve doesn't do anything with the envFile setting, so we intercept it
+async fn handle_envs(
+    config: &mut Map<String, Value>,
+    envs: &mut HashMap<String, String>,
+    cwd: Option<&Path>,
+    fs: Arc<dyn Fs>,
+) -> Option<()> {
+    let env_files = match config.get("envFile")? {
+        Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
+        Value::String(s) => vec![Some(s.as_str())],
+        _ => return None,
+    };
+
+    let rebase_path = |path: PathBuf| {
+        if path.is_absolute() {
+            Some(path)
+        } else {
+            cwd.map(|p| p.join(path))
+        }
+    };
+
+    for path in env_files {
+        let Some(path) = path
+            .and_then(|s| PathBuf::from_str(s).ok())
+            .and_then(rebase_path)
+        else {
+            continue;
+        };
+
+        if let Ok(file) = fs.open_sync(&path).await {
+            envs.extend(dotenvy::from_read_iter(file).filter_map(Result::ok))
+        } else {
+            warn!("While starting Go debug session: failed to read env file {path:?}");
+        };
+    }
+
+    // remove envFile now that it's been handled
+    config.remove("entry");
+    Some(())
+}

crates/eval/Cargo.toml 🔗

@@ -32,7 +32,7 @@ client.workspace = true
 collections.workspace = true
 debug_adapter_extension.workspace = true
 dirs.workspace = true
-dotenv.workspace = true
+dotenvy.workspace = true
 env_logger.workspace = true
 extension.workspace = true
 fs.workspace = true

crates/eval/src/eval.rs 🔗

@@ -63,7 +63,7 @@ struct Args {
 }
 
 fn main() {
-    dotenv::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok();
+    dotenvy::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok();
 
     env_logger::init();