Fix toolchain detection for worktree-local paths (#20229)

Reimplements `pet::EnvironmentApi`, trying to access the `project_env`
first
Closes #20177 

Release Notes:

- Fixed python toolchain detection when worktree local path is set
This commit is contained in:
Stanislav Alekseev 2024-11-05 15:25:18 +02:00 committed by GitHub
parent f8bd6c66f4
commit a26c0a8537
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 132 additions and 13 deletions

1
Cargo.lock generated
View file

@ -6403,6 +6403,7 @@ dependencies = [
"pet", "pet",
"pet-conda", "pet-conda",
"pet-core", "pet-core",
"pet-fs",
"pet-poetry", "pet-poetry",
"pet-reporter", "pet-reporter",
"project", "project",

View file

@ -381,6 +381,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
pathdiff = "0.2" pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }

View file

@ -7,6 +7,7 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use collections::HashMap;
use gpui::{AsyncAppContext, SharedString}; use gpui::{AsyncAppContext, SharedString};
use settings::WorktreeId; use settings::WorktreeId;
@ -23,7 +24,11 @@ pub struct Toolchain {
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait ToolchainLister: Send + Sync { pub trait ToolchainLister: Send + Sync {
async fn list(&self, _: PathBuf) -> ToolchainList; async fn list(
&self,
worktree_root: PathBuf,
project_env: Option<HashMap<String, String>>,
) -> ToolchainList;
} }
#[async_trait(?Send)] #[async_trait(?Send)]

View file

@ -47,6 +47,7 @@ lsp.workspace = true
node_runtime.workspace = true node_runtime.workspace = true
paths.workspace = true paths.workspace = true
pet.workspace = true pet.workspace = true
pet-fs.workspace = true
pet-core.workspace = true pet-core.workspace = true
pet-conda.workspace = true pet-conda.workspace = true
pet-poetry.workspace = true pet-poetry.workspace = true

View file

@ -11,11 +11,13 @@ use language::ToolchainLister;
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate}; use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use pet_core::os_environment::Environment;
use pet_core::python_environment::PythonEnvironmentKind; use pet_core::python_environment::PythonEnvironmentKind;
use pet_core::Configuration; use pet_core::Configuration;
use project::lsp_store::language_server_settings; use project::lsp_store::language_server_settings;
use serde_json::Value; use serde_json::Value;
use std::sync::Mutex;
use std::{ use std::{
any::Any, any::Any,
borrow::Cow, borrow::Cow,
@ -380,8 +382,13 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
#[async_trait(?Send)] #[async_trait(?Send)]
impl ToolchainLister for PythonToolchainProvider { impl ToolchainLister for PythonToolchainProvider {
async fn list(&self, worktree_root: PathBuf) -> ToolchainList { async fn list(
let environment = pet_core::os_environment::EnvironmentApi::new(); &self,
worktree_root: PathBuf,
project_env: Option<HashMap<String, String>>,
) -> ToolchainList {
let env = project_env.unwrap_or_default();
let environment = EnvironmentApi::from_env(&env);
let locators = pet::locators::create_locators( let locators = pet::locators::create_locators(
Arc::new(pet_conda::Conda::from(&environment)), Arc::new(pet_conda::Conda::from(&environment)),
Arc::new(pet_poetry::Poetry::from(&environment)), Arc::new(pet_poetry::Poetry::from(&environment)),
@ -427,6 +434,78 @@ impl ToolchainLister for PythonToolchainProvider {
} }
} }
pub struct EnvironmentApi<'a> {
global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
project_env: &'a HashMap<String, String>,
pet_env: pet_core::os_environment::EnvironmentApi,
}
impl<'a> EnvironmentApi<'a> {
pub fn from_env(project_env: &'a HashMap<String, String>) -> Self {
let paths = project_env
.get("PATH")
.map(|p| std::env::split_paths(p).collect())
.unwrap_or_default();
EnvironmentApi {
global_search_locations: Arc::new(Mutex::new(paths)),
project_env,
pet_env: pet_core::os_environment::EnvironmentApi::new(),
}
}
fn user_home(&self) -> Option<PathBuf> {
self.project_env
.get("HOME")
.or_else(|| self.project_env.get("USERPROFILE"))
.map(|home| pet_fs::path::norm_case(PathBuf::from(home)))
.or_else(|| self.pet_env.get_user_home())
}
}
impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> {
fn get_user_home(&self) -> Option<PathBuf> {
self.user_home()
}
fn get_root(&self) -> Option<PathBuf> {
None
}
fn get_env_var(&self, key: String) -> Option<String> {
self.project_env
.get(&key)
.cloned()
.or_else(|| self.pet_env.get_env_var(key))
}
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
if self.global_search_locations.lock().unwrap().is_empty() {
let mut paths =
std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default())
.collect::<Vec<PathBuf>>();
log::trace!("Env PATH: {:?}", paths);
for p in self.pet_env.get_know_global_search_locations() {
if !paths.contains(&p) {
paths.push(p);
}
}
let mut paths = paths
.into_iter()
.filter(|p| p.exists())
.collect::<Vec<PathBuf>>();
self.global_search_locations
.lock()
.unwrap()
.append(&mut paths);
}
self.global_search_locations.lock().unwrap().clone()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext}; use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};

View file

@ -639,7 +639,12 @@ impl Project {
cx.subscribe(&settings_observer, Self::on_settings_observer_event) cx.subscribe(&settings_observer, Self::on_settings_observer_event)
.detach(); .detach();
let toolchain_store = cx.new_model(|cx| { let toolchain_store = cx.new_model(|cx| {
ToolchainStore::local(languages.clone(), worktree_store.clone(), cx) ToolchainStore::local(
languages.clone(),
worktree_store.clone(),
environment.clone(),
cx,
)
}); });
let lsp_store = cx.new_model(|cx| { let lsp_store = cx.new_model(|cx| {
LspStore::new_local( LspStore::new_local(
@ -2369,10 +2374,16 @@ impl Project {
language_name: LanguageName, language_name: LanguageName,
cx: &AppContext, cx: &AppContext,
) -> Task<Option<ToolchainList>> { ) -> Task<Option<ToolchainList>> {
if let Some(toolchain_store) = self.toolchain_store.as_ref() { if let Some(toolchain_store) = self.toolchain_store.clone() {
toolchain_store cx.spawn(|cx| async move {
.read(cx) cx.update(|cx| {
.list_toolchains(worktree_id, language_name, cx) toolchain_store
.read(cx)
.list_toolchains(worktree_id, language_name, cx)
})
.unwrap_or(Task::Ready(None))
.await
})
} else { } else {
Task::ready(None) Task::ready(None)
} }

View file

@ -13,7 +13,7 @@ use rpc::{proto, AnyProtoClient, TypedEnvelope};
use settings::WorktreeId; use settings::WorktreeId;
use util::ResultExt as _; use util::ResultExt as _;
use crate::worktree_store::WorktreeStore; use crate::{worktree_store::WorktreeStore, ProjectEnvironment};
pub struct ToolchainStore(ToolchainStoreInner); pub struct ToolchainStore(ToolchainStoreInner);
enum ToolchainStoreInner { enum ToolchainStoreInner {
@ -32,11 +32,13 @@ impl ToolchainStore {
pub fn local( pub fn local(
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
worktree_store: Model<WorktreeStore>, worktree_store: Model<WorktreeStore>,
project_environment: Model<ProjectEnvironment>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
let model = cx.new_model(|_| LocalToolchainStore { let model = cx.new_model(|_| LocalToolchainStore {
languages, languages,
worktree_store, worktree_store,
project_environment,
active_toolchains: Default::default(), active_toolchains: Default::default(),
}); });
let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| { let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| {
@ -203,6 +205,7 @@ impl ToolchainStore {
struct LocalToolchainStore { struct LocalToolchainStore {
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
worktree_store: Model<WorktreeStore>, worktree_store: Model<WorktreeStore>,
project_environment: Model<ProjectEnvironment>,
active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>, active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
} }
@ -296,9 +299,20 @@ impl LocalToolchainStore {
else { else {
return Task::ready(None); return Task::ready(None);
}; };
cx.spawn(|_| async move {
let environment = self.project_environment.clone();
cx.spawn(|mut cx| async move {
let project_env = environment
.update(&mut cx, |environment, cx| {
environment.get_environment(Some(worktree_id), Some(root.clone()), cx)
})
.ok()?
.await;
let language = registry.language_for_name(&language_name.0).await.ok()?; let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language.toolchain_lister()?.list(root.to_path_buf()).await; let toolchains = language
.toolchain_lister()?
.list(root.to_path_buf(), project_env)
.await;
Some(toolchains) Some(toolchains)
}) })
} }
@ -345,6 +359,7 @@ impl RemoteToolchainStore {
Some(()) Some(())
}) })
} }
pub(crate) fn list_toolchains( pub(crate) fn list_toolchains(
&self, &self,
worktree_id: WorktreeId, worktree_id: WorktreeId,

View file

@ -108,8 +108,14 @@ impl HeadlessProject {
observer.shared(SSH_PROJECT_ID, session.clone().into(), cx); observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
observer observer
}); });
let toolchain_store = let toolchain_store = cx.new_model(|cx| {
cx.new_model(|cx| ToolchainStore::local(languages.clone(), worktree_store.clone(), cx)); ToolchainStore::local(
languages.clone(),
worktree_store.clone(),
environment.clone(),
cx,
)
});
let lsp_store = cx.new_model(|cx| { let lsp_store = cx.new_model(|cx| {
let mut lsp_store = LspStore::new_local( let mut lsp_store = LspStore::new_local(
buffer_store.clone(), buffer_store.clone(),