Allow task context providers to access project env (#17964)

Stanislav Alekseev created

Closes #13106

Release Notes:

- Task context providers now have access to the local shell environment,
allowing local rust tool installations to work

Before:
<img width="1136" alt="Screenshot 2024-09-17 at 22 09 38"
src="https://github.com/user-attachments/assets/7d6c5606-4820-4f6f-92d1-c3d314b9ab42">

After:
<img width="1136" alt="Screenshot 2024-09-17 at 22 09 58"
src="https://github.com/user-attachments/assets/a962e607-15f5-44ce-b53e-a0dbe135f2d8">

Change summary

crates/language/src/task_context.rs  |  1 
crates/languages/src/go.rs           |  2 +
crates/languages/src/python.rs       |  2 +
crates/languages/src/rust.rs         | 31 +++++++++++++++++++++++------
crates/project/src/project.rs        | 26 +++++++++++++-----------
crates/project/src/task_inventory.rs |  3 +
6 files changed, 45 insertions(+), 20 deletions(-)

Detailed changes

crates/language/src/task_context.rs 🔗

@@ -25,6 +25,7 @@ pub trait ContextProvider: Send + Sync {
         &self,
         _variables: &TaskVariables,
         _location: &Location,
+        _project_env: Option<&HashMap<String, String>>,
         _cx: &mut AppContext,
     ) -> Result<TaskVariables> {
         Ok(TaskVariables::default())

crates/languages/src/go.rs 🔗

@@ -1,5 +1,6 @@
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
+use collections::HashMap;
 use futures::StreamExt;
 use gpui::{AppContext, AsyncAppContext, Task};
 use http_client::github::latest_github_release;
@@ -454,6 +455,7 @@ impl ContextProvider for GoContextProvider {
         &self,
         variables: &TaskVariables,
         location: &Location,
+        _: Option<&HashMap<String, String>>,
         cx: &mut gpui::AppContext,
     ) -> Result<TaskVariables> {
         let local_abs_path = location

crates/languages/src/python.rs 🔗

@@ -1,5 +1,6 @@
 use anyhow::Result;
 use async_trait::async_trait;
+use collections::HashMap;
 use gpui::AppContext;
 use gpui::AsyncAppContext;
 use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
@@ -215,6 +216,7 @@ impl ContextProvider for PythonContextProvider {
         &self,
         variables: &task::TaskVariables,
         _location: &project::Location,
+        _: Option<&HashMap<String, String>>,
         _cx: &mut gpui::AppContext,
     ) -> Result<task::TaskVariables> {
         let python_module_name = python_module_name_from_relative_path(

crates/languages/src/rust.rs 🔗

@@ -1,6 +1,7 @@
 use anyhow::{anyhow, bail, Context, Result};
 use async_compression::futures::bufread::GzipDecoder;
 use async_trait::async_trait;
+use collections::HashMap;
 use futures::{io::BufReader, StreamExt};
 use gpui::{AppContext, AsyncAppContext};
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
@@ -434,6 +435,7 @@ impl ContextProvider for RustContextProvider {
         &self,
         task_variables: &TaskVariables,
         location: &Location,
+        project_env: Option<&HashMap<String, String>>,
         cx: &mut gpui::AppContext,
     ) -> Result<TaskVariables> {
         let local_abs_path = location
@@ -449,8 +451,8 @@ impl ContextProvider for RustContextProvider {
             .is_some();
 
         if is_main_function {
-            if let Some((package_name, bin_name)) =
-                local_abs_path.and_then(package_name_and_bin_name_from_abs_path)
+            if let Some((package_name, bin_name)) = local_abs_path
+                .and_then(|path| package_name_and_bin_name_from_abs_path(path, project_env))
             {
                 return Ok(TaskVariables::from_iter([
                     (RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
@@ -461,7 +463,7 @@ impl ContextProvider for RustContextProvider {
 
         if let Some(package_name) = local_abs_path
             .and_then(|local_abs_path| local_abs_path.parent())
-            .and_then(human_readable_package_name)
+            .and_then(|path| human_readable_package_name(path, project_env))
         {
             return Ok(TaskVariables::from_iter([(
                 RUST_PACKAGE_TASK_VARIABLE.clone(),
@@ -615,8 +617,15 @@ struct CargoTarget {
     src_path: String,
 }
 
-fn package_name_and_bin_name_from_abs_path(abs_path: &Path) -> Option<(String, String)> {
-    let output = std::process::Command::new("cargo")
+fn package_name_and_bin_name_from_abs_path(
+    abs_path: &Path,
+    project_env: Option<&HashMap<String, String>>,
+) -> Option<(String, String)> {
+    let mut command = std::process::Command::new("cargo");
+    if let Some(envs) = project_env {
+        command.envs(envs);
+    }
+    let output = command
         .current_dir(abs_path.parent()?)
         .arg("metadata")
         .arg("--no-deps")
@@ -654,9 +663,17 @@ fn retrieve_package_id_and_bin_name_from_metadata(
     None
 }
 
-fn human_readable_package_name(package_directory: &Path) -> Option<String> {
+fn human_readable_package_name(
+    package_directory: &Path,
+    project_env: Option<&HashMap<String, String>>,
+) -> Option<String> {
+    let mut command = std::process::Command::new("cargo");
+    if let Some(envs) = project_env {
+        command.envs(envs);
+    }
+
     let pkgid = String::from_utf8(
-        std::process::Command::new("cargo")
+        command
             .current_dir(package_directory)
             .arg("pkgid")
             .output()

crates/project/src/project.rs 🔗

@@ -4890,11 +4890,22 @@ impl Project {
             };
 
             cx.spawn(|project, mut cx| async move {
+                let project_env = project
+                    .update(&mut cx, |project, cx| {
+                        let worktree_abs_path = worktree_abs_path.clone();
+                        project.environment.update(cx, |environment, cx| {
+                            environment.get_environment(worktree_id, worktree_abs_path, cx)
+                        })
+                    })
+                    .ok()?
+                    .await;
+
                 let mut task_variables = cx
                     .update(|cx| {
                         combine_task_variables(
                             captured_variables,
                             location,
+                            project_env.as_ref(),
                             BasicContextProvider::new(project.upgrade()?),
                             cx,
                         )
@@ -4905,16 +4916,6 @@ impl Project {
                 // Remove all custom entries starting with _, as they're not intended for use by the end user.
                 task_variables.sweep();
 
-                let project_env = project
-                    .update(&mut cx, |project, cx| {
-                        let worktree_abs_path = worktree_abs_path.clone();
-                        project.environment.update(cx, |environment, cx| {
-                            environment.get_environment(worktree_id, worktree_abs_path, cx)
-                        })
-                    })
-                    .ok()?
-                    .await;
-
                 Some(TaskContext {
                     project_env: project_env.unwrap_or_default(),
                     cwd: worktree_abs_path.map(|p| p.to_path_buf()),
@@ -5111,6 +5112,7 @@ impl Project {
 fn combine_task_variables(
     mut captured_variables: TaskVariables,
     location: Location,
+    project_env: Option<&HashMap<String, String>>,
     baseline: BasicContextProvider,
     cx: &mut AppContext,
 ) -> anyhow::Result<TaskVariables> {
@@ -5120,13 +5122,13 @@ fn combine_task_variables(
         .language()
         .and_then(|language| language.context_provider());
     let baseline = baseline
-        .build_context(&captured_variables, &location, cx)
+        .build_context(&captured_variables, &location, project_env, cx)
         .context("building basic default context")?;
     captured_variables.extend(baseline);
     if let Some(provider) = language_context_provider {
         captured_variables.extend(
             provider
-                .build_context(&captured_variables, &location, cx)
+                .build_context(&captured_variables, &location, project_env, cx)
                 .context("building provider context")?,
         );
     }

crates/project/src/task_inventory.rs 🔗

@@ -8,7 +8,7 @@ use std::{
 };
 
 use anyhow::Result;
-use collections::{btree_map, BTreeMap, VecDeque};
+use collections::{btree_map, BTreeMap, HashMap, VecDeque};
 use futures::{
     channel::mpsc::{unbounded, UnboundedSender},
     StreamExt,
@@ -543,6 +543,7 @@ impl ContextProvider for BasicContextProvider {
         &self,
         _: &TaskVariables,
         location: &Location,
+        _: Option<&HashMap<String, String>>,
         cx: &mut AppContext,
     ) -> Result<TaskVariables> {
         let buffer = location.buffer.read(cx);