diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 7317b9c52c7f..5144c4ade2fa 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -25,13 +25,14 @@ fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: let version = utils::get_version(path); let env_path = get_env_path(path); dispatcher.report_environment(messaging::PythonEnvironment::new( - "Python".to_string(), - vec![path.to_string()], + None, + Some(path.to_string()), messaging::PythonEnvironmentCategory::System, version, - None, env_path.clone(), env_path, + None, + Some(vec![path.to_string()]), )); } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 35dee6dc5cac..2610f53778c7 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -375,7 +375,7 @@ pub fn find_and_report( match conda_binary { Some(conda_binary) => { let params = messaging::EnvManager::new( - vec![conda_binary.to_string_lossy().to_string()], + conda_binary.to_string_lossy().to_string(), get_conda_version(&conda_binary), ); dispatcher.report_environment_manager(params); @@ -385,13 +385,16 @@ pub fn find_and_report( let executable = find_python_binary_path(Path::new(&env.path)); let env_path = env.path.to_string_lossy().to_string(); let params = messaging::PythonEnvironment::new( - env.name.to_string(), + Some(env.name.to_string()), match executable { - Some(executable) => vec![executable.to_string_lossy().to_string()], - None => vec![], + Some(executable) => Some(executable.to_string_lossy().to_string()), + None => None, }, messaging::PythonEnvironmentCategory::Conda, get_conda_python_version(&env.path), + Some(env_path.clone()), + Some(env_path), + None, if env.named { Some(vec![ conda_binary.to_string_lossy().to_string(), @@ -409,8 +412,6 @@ pub fn find_and_report( "python".to_string(), ]) }, - Some(env_path.clone()), - Some(env_path), ); dispatcher.report_environment(params); } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index 3e5d2c92f66c..37b52720aed6 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -45,15 +45,16 @@ pub fn find_and_report( continue; } reported.insert(exe.to_string_lossy().to_string()); - + let executable = exe.to_string_lossy().to_string(); let env = crate::messaging::PythonEnvironment::new( - "Python".to_string(), - vec![exe.to_string_lossy().to_string()], + None, + Some(executable.clone()), crate::messaging::PythonEnvironmentCategory::Homebrew, version, None, None, None, + Some(vec![executable]), ); dispatcher.report_environment(env); } diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 0ae0b90b02c6..858f74c59e8a 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -17,12 +17,12 @@ pub trait MessageDispatcher { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManager { - pub executable_path: Vec, + pub executable_path: String, pub version: Option, } impl EnvManager { - pub fn new(executable_path: Vec, version: Option) -> Self { + pub fn new(executable_path: String, version: Option) -> Self { Self { executable_path, version, @@ -30,6 +30,15 @@ impl EnvManager { } } +impl Clone for EnvManager { + fn clone(&self) -> Self { + Self { + executable_path: self.executable_path.clone(), + version: self.version.clone(), + } + } +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnvManagerMessage { @@ -62,33 +71,36 @@ pub enum PythonEnvironmentCategory { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { - pub name: String, - pub python_executable_path: Vec, + pub name: Option, + pub python_executable_path: Option, pub category: PythonEnvironmentCategory, pub version: Option, - pub activated_run: Option>, pub env_path: Option, pub sys_prefix_path: Option, + pub env_manager: Option, + pub python_run_command: Option>, } impl PythonEnvironment { pub fn new( - name: String, - python_executable_path: Vec, + name: Option, + python_executable_path: Option, category: PythonEnvironmentCategory, version: Option, - activated_run: Option>, env_path: Option, sys_prefix_path: Option, + env_manager: Option, + python_run_command: Option>, ) -> Self { Self { name, python_executable_path, category, version, - activated_run, env_path, sys_prefix_path, + env_manager, + python_run_command, } } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index fbad817108c4..5365818dd318 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use crate::known; use crate::messaging; +use crate::messaging::EnvManager; use crate::utils::find_python_binary_path; #[cfg(windows)] @@ -102,25 +103,21 @@ fn get_pyenv_version(folder_name: String) -> Option { fn report_if_pure_python_environment( executable: PathBuf, path: &PathBuf, - pyenv_binary_for_activation: String, + manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { let version = get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string())?; - + let executable = executable.into_os_string().into_string().unwrap(); let env_path = path.to_string_lossy().to_string(); - let activated_run = Some(vec![ - pyenv_binary_for_activation, - "shell".to_string(), - version.clone(), - ]); dispatcher.report_environment(messaging::PythonEnvironment::new( - version.clone(), - vec![executable.into_os_string().into_string().unwrap()], + None, + Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, Some(version), - activated_run, Some(env_path.clone()), Some(env_path), + manager, + Some(vec![executable]), )); Some(()) @@ -154,26 +151,22 @@ fn parse_pyenv_cfg(path: &PathBuf) -> Option { fn report_if_virtual_env_environment( executable: PathBuf, path: &PathBuf, - pyenv_binary_for_activation: String, + manager: Option, dispatcher: &mut impl messaging::MessageDispatcher, ) -> Option<()> { let pyenv_cfg = parse_pyenv_cfg(path)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); - + let executable = executable.into_os_string().into_string().unwrap(); let env_path = path.to_string_lossy().to_string(); - let activated_run = Some(vec![ - pyenv_binary_for_activation, - "activate".to_string(), - folder_name.clone(), - ]); dispatcher.report_environment(messaging::PythonEnvironment::new( - folder_name, - vec![executable.into_os_string().into_string().unwrap()], + Some(folder_name), + Some(executable.clone()), messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), - activated_run, Some(env_path.clone()), Some(env_path), + manager, + Some(vec![executable]), )); Some(()) @@ -185,9 +178,14 @@ pub fn find_and_report( ) -> Option<()> { let pyenv_dir = get_pyenv_dir(environment)?; - if let Some(pyenv_binary) = get_pyenv_binary(environment) { - dispatcher.report_environment_manager(messaging::EnvManager::new(vec![pyenv_binary], None)); - } + let manager = match get_pyenv_binary(environment) { + Some(pyenv_binary) => { + let manager = messaging::EnvManager::new(pyenv_binary, None); + dispatcher.report_environment_manager(manager.clone()); + Some(manager) + } + None => None, + }; let versions_dir = PathBuf::from(&pyenv_dir) .join("versions") @@ -195,10 +193,6 @@ pub fn find_and_report( .into_string() .ok()?; - let pyenv_binary_for_activation = match get_pyenv_binary(environment) { - Some(binary) => binary, - None => "pyenv".to_string(), - }; for entry in fs::read_dir(&versions_dir).ok()? { if let Ok(path) = entry { let path = path.path(); @@ -209,7 +203,7 @@ pub fn find_and_report( if report_if_pure_python_environment( executable.clone(), &path, - pyenv_binary_for_activation.clone(), + manager.clone(), dispatcher, ) .is_some() @@ -220,7 +214,7 @@ pub fn find_and_report( report_if_virtual_env_environment( executable.clone(), &path, - pyenv_binary_for_activation.clone(), + manager.clone(), dispatcher, ); } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index c4c9ae8f388c..0494066eef0c 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -9,13 +9,14 @@ use std::path::Path; fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { let version = utils::get_version(path); dispatcher.report_environment(messaging::PythonEnvironment::new( - "Python".to_string(), - vec![path.to_string()], + None, + Some(path.to_string()), messaging::PythonEnvironmentCategory::WindowsStore, version, None, None, None, + None, )); } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 31d8b8c52f63..f8841ee9b6fa 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -27,6 +27,6 @@ fn find_python_in_path_this() { common_python::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"name":"Python","pythonExecutablePath":[unix_python_exe.clone()],"category":"system","version":null,"activatedRun":null,"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); + let expected_json = json!({"envManager":null,"name":null,"pythonExecutablePath":unix_python_exe.clone(),"category":"system","version":null,"pythonRunCommand":[unix_python_exe.clone()],"envPath":unix_python.clone(),"sysPrefixPath":unix_python.clone()}); assert_messages(&[expected_json], &dispatcher); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 54f16eef4f5b..94274c8f7db7 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -44,7 +44,7 @@ fn find_conda_exe_and_empty_envs() { conda::find_and_report(&mut dispatcher, &known); let conda_exe = join_test_paths(&[conda_dir.clone().as_str(), "conda"]); - let expected_json = json!({"executablePath":[conda_exe.clone()],"version":null}); + let expected_json = json!({"executablePath":conda_exe.clone(),"version":null}); assert_messages(&[expected_json], &dispatcher) } #[test] @@ -80,9 +80,9 @@ fn finds_two_conda_envs_from_txt() { let conda_1_exe = join_test_paths(&[conda_1.clone().as_str(), "python"]); let conda_2_exe = join_test_paths(&[conda_2.clone().as_str(), "python"]); - let expected_conda_env = json!({"executablePath":[conda_exe.clone()],"version":null}); - let expected_conda_1 = json!({"name":"one","pythonExecutablePath":[conda_1_exe.clone()],"category":"conda","version":"10.0.1","activatedRun":[conda_exe.clone(),"run","-n","one","python"],"envPath":conda_1.clone(), "sysPrefixPath":conda_1.clone()}); - let expected_conda_2 = json!({"name":"two","pythonExecutablePath":[conda_2_exe.clone()],"category":"conda","version":null,"activatedRun":[conda_exe.clone(),"run","-n","two","python"],"envPath":conda_2.clone(), "sysPrefixPath":conda_2.clone()}); + let expected_conda_env = json!({ "executablePath": conda_exe.clone(), "version": null}); + let expected_conda_1 = json!({ "name": "one", "pythonExecutablePath": conda_1_exe.clone(), "category": "conda", "version": "10.0.1", "envPath": conda_1.clone(), "sysPrefixPath": conda_1.clone(), "envManager": null, "pythonRunCommand": [conda_exe.clone(), "run", "-n", "one", "python"]}); + let expected_conda_2 = json!({ "name": "two", "pythonExecutablePath": conda_2_exe.clone(), "category": "conda", "version": null, "envPath": conda_2.clone(), "sysPrefixPath": conda_2.clone(), "envManager": null,"pythonRunCommand": [conda_exe.clone(),"run","-n","two","python"]}); assert_messages( &[expected_conda_env, expected_conda_1, expected_conda_2], &dispatcher, diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index a901a1c13ca6..d5327b9bd275 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -46,7 +46,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 1); - let expected_json = json!({"executablePath":[pyenv_exe],"version":null}); + let expected_json = json!({"executablePath":pyenv_exe,"version":null}); assert_messages(&[expected_json], &dispatcher) } @@ -74,12 +74,12 @@ fn find_pyenv_envs() { pyenv::find_and_report(&mut dispatcher, &known); assert_eq!(dispatcher.messages.len(), 6); - let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null }); - let expected_3_9_9 = json!({"name": "3.9.9","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "shell", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"])}); - let expected_virtual_env = json!( {"name": "my-virtual-env", "version": "3.10.13", "activatedRun": [pyenv_exe.clone(), "activate", "my-virtual-env"], "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"])}); - let expected_3_12_1 = json!({"name": "3.12.1","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"])}); - let expected_3_13_dev = json!({"name": "3.13-dev","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "shell", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"])}); - let expected_3_12_1a3 = json!({"name": "3.12.1a3","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "shell", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"])}); + let expected_manager = json!({ "executablePath": pyenv_exe.clone(), "version": null }); + let expected_3_9_9 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])], "category": "pyenv","version": "3.9.9","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]), "envManager": expected_manager}); + let expected_virtual_env = json!({"name": "my-virtual-env", "version": "3.10.13", "category": "pyenvVirtualEnv", "envPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonExecutablePath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"]), "sysPrefixPath": join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env"]), "pythonRunCommand": [join_test_paths(&[home.as_str(),".pyenv/versions/my-virtual-env/bin/python"])], "envManager": expected_manager}); + let expected_3_12_1 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])], "category": "pyenv","version": "3.12.1","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]), "envManager": expected_manager}); + let expected_3_13_dev = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])], "category": "pyenv","version": "3.13-dev","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]), "envManager": expected_manager}); + let expected_3_12_1a3 = json!({"name": null,"pythonExecutablePath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"]), "pythonRunCommand": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])], "category": "pyenv","version": "3.12.1a3","envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]), "envManager": expected_manager}); assert_messages( &[ expected_manager,