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

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">
This commit is contained in:
Stanislav Alekseev 2024-09-17 22:49:12 +03:00 committed by GitHub
parent d3d3a093b4
commit 8a6c65c63b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 25 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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