Skip to content

Support resolving Python environments #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/pet-cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ version = "0.1.0"
edition = "2021"

[dependencies]
pet-fs = { path = "../pet-fs" }
pet-python-utils = { path = "../pet-python-utils" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
pet-core = { path = "../pet-core" }
log = "0.4.21"
17 changes: 6 additions & 11 deletions crates/pet-cache/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#[cfg(test)]
mod tests {
use super::*;
use pet_core::python_environment::PythonEnvironment;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
pub trait Cache {
fn get<P: AsRef<P>>(&self, executable: P) -> Option<PythonEnvironment>;
fn set<P: AsRef<P>>(&self, environment: PythonEnvironment);
}
8 changes: 5 additions & 3 deletions crates/pet-conda/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ impl Conda {
}

impl Locator for Conda {
fn resolve(&self, _env: &PythonEnvironment) -> Option<PythonEnvironment> {
todo!()
}
fn from(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
if let Some(ref path) = env.prefix {
let mut environments = self.environments.lock().unwrap();
Expand Down Expand Up @@ -148,6 +145,11 @@ impl Locator for Conda {
}

fn find(&self, reporter: &dyn Reporter) {
// if we're calling this again, then clear what ever cache we have.
let mut environments = self.environments.lock().unwrap();
environments.clear();
drop(environments);

let env_vars = self.env_vars.clone();
thread::scope(|s| {
// 1. Get a list of all know conda environments file paths
Expand Down
16 changes: 16 additions & 0 deletions crates/pet-core/src/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ impl PartialOrd for Architecture {
Some(self.cmp(other))
}
}

impl std::fmt::Display for Architecture {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
if *self == Architecture::X64 {
"x64"
} else {
"x86"
}
)
.unwrap_or_default();
Ok(())
}
}
7 changes: 7 additions & 0 deletions crates/pet-core/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

pub trait Cache {
fn get<P: AsRef<P>>(&self, executable: P) -> Option<PythonEnvironment>;
fn set<P: AsRef<P>>(&self, environment: PythonEnvironment);
}
12 changes: 1 addition & 11 deletions crates/pet-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod manager;
pub mod os_environment;
pub mod python_environment;
pub mod reporter;
// pub mod telemetry;
pub mod telemetry;

#[derive(Debug, Clone)]
pub struct LocatorResult {
Expand All @@ -28,16 +28,6 @@ pub trait Locator: Send + Sync {
* This is because the `from` will do a best effort to get the environment information without spawning Python.
*/
fn from(&self, env: &PythonEnv) -> Option<PythonEnvironment>;
/**
* Given a Python environment, get all of the information by spawning the Python executable.
* E.g. version, sysprefix, etc ...
*
* I.e. use this to test whether an environment is of a specific type.
*/
fn resolve(&self, env: &PythonEnvironment) -> Option<PythonEnvironment> {
// TODO: Implement this.
Some(env.clone())
}
/**
* Finds all environments specific to this locator.
*/
Expand Down
16 changes: 16 additions & 0 deletions crates/pet-core/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,19 @@ impl EnvManager {
}
}
}

impl std::fmt::Display for EnvManager {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Manager ({:?})", self.tool).unwrap_or_default();
writeln!(
f,
" Executable : {}",
self.executable.to_str().unwrap_or_default()
)
.unwrap_or_default();
if let Some(version) = &self.version {
writeln!(f, " Version : {}", version).unwrap_or_default();
}
Ok(())
}
}
80 changes: 76 additions & 4 deletions crates/pet-core/src/python_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

use pet_fs::path::norm_case;
use pet_python_utils::executable::get_shortest_executable;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

Expand Down Expand Up @@ -67,6 +68,7 @@ pub struct PythonEnvironment {
// E.g. in the case of Homebrew there are a number of symlinks that are created.
pub symlinks: Option<Vec<PathBuf>>,
}

impl Ord for PythonEnvironment {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
format!(
Expand Down Expand Up @@ -126,6 +128,68 @@ impl PythonEnvironment {
}
}

impl std::fmt::Display for PythonEnvironment {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Environment ({:?})", self.category).unwrap_or_default();
if let Some(name) = &self.display_name {
writeln!(f, " Display-Name: {}", name).unwrap_or_default();
}
if let Some(name) = &self.name {
writeln!(f, " Name : {}", name).unwrap_or_default();
}
if let Some(exe) = &self.executable {
writeln!(f, " Executable : {}", exe.to_str().unwrap_or_default())
.unwrap_or_default();
}
if let Some(version) = &self.version {
writeln!(f, " Version : {}", version).unwrap_or_default();
}
if let Some(prefix) = &self.prefix {
writeln!(
f,
" Prefix : {}",
prefix.to_str().unwrap_or_default()
)
.unwrap_or_default();
}
if let Some(project) = &self.project {
writeln!(f, " Project : {}", project.to_str().unwrap()).unwrap_or_default();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor refactor

}
if let Some(arch) = &self.arch {
writeln!(f, " Architecture: {}", arch).unwrap_or_default();
}
if let Some(manager) = &self.manager {
writeln!(
f,
" Manager : {:?}, {}",
manager.tool,
manager.executable.to_str().unwrap_or_default()
)
.unwrap_or_default();
}
if let Some(symlinks) = &self.symlinks {
let mut symlinks = symlinks.clone();
symlinks.sort_by(|a, b| {
a.to_str()
.unwrap_or_default()
.len()
.cmp(&b.to_str().unwrap_or_default().len())
});

if !symlinks.is_empty() {
for (i, symlink) in symlinks.iter().enumerate() {
if i == 0 {
writeln!(f, " Symlinks : {:?}", symlink).unwrap_or_default();
} else {
writeln!(f, " : {:?}", symlink).unwrap_or_default();
}
}
}
}
Ok(())
}
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
Expand Down Expand Up @@ -177,7 +241,7 @@ impl PythonEnvironmentBuilder {
}
}
}
self.update_symlinks(self.symlinks.clone());
self.update_symlinks_and_exe(self.symlinks.clone());
self
}

Expand Down Expand Up @@ -213,11 +277,11 @@ impl PythonEnvironmentBuilder {
}

pub fn symlinks(mut self, symlinks: Option<Vec<PathBuf>>) -> Self {
self.update_symlinks(symlinks);
self.update_symlinks_and_exe(symlinks);
self
}

fn update_symlinks(&mut self, symlinks: Option<Vec<PathBuf>>) {
fn update_symlinks_and_exe(&mut self, symlinks: Option<Vec<PathBuf>>) {
let mut all = vec![];
if let Some(ref exe) = self.executable {
all.push(exe.clone());
Expand All @@ -228,7 +292,15 @@ impl PythonEnvironmentBuilder {
all.sort();
all.dedup();

self.symlinks = if all.is_empty() { None } else { Some(all) };
self.symlinks = if all.is_empty() {
None
} else {
Some(all.clone())
};
if let Some(executable) = &self.executable {
self.executable =
Some(get_shortest_executable(&Some(all.clone())).unwrap_or(executable.clone()));
}
}

pub fn build(self) -> PythonEnvironment {
Expand Down
44 changes: 44 additions & 0 deletions crates/pet-core/src/telemetry/inaccurate_python_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::python_environment::PythonEnvironmentCategory;

/// Information about an environment that was discovered to be inaccurate.
/// If the discovered information is None, then it means that the information was not found.
/// And we will not report that as an inaccuracy.
pub struct InaccuratePythonEnvironmentInfo {
/// Python Env category
pub category: PythonEnvironmentCategory,
/// Whether the actual exe is not what we expected.
pub invalid_executable: Option<bool>,
/// Whether the actual exe was not even in the list of symlinks that we expected.
pub executable_not_in_symlinks: Option<bool>,
/// Whether the prefix is not what we expected.
pub invalid_prefix: Option<bool>,
/// Whether the version is not what we expected.
pub invalid_version: Option<bool>,
/// Whether the architecture is not what we expected.
pub invalid_arch: Option<bool>,
}

impl std::fmt::Display for InaccuratePythonEnvironmentInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Environment {:?} incorrectly identified", self.category).unwrap_or_default();
if self.invalid_executable.unwrap_or_default() {
writeln!(f, " Executable is incorrect").unwrap_or_default();
}
if self.executable_not_in_symlinks.unwrap_or_default() {
writeln!(f, " Executable is not in the list of symlinks").unwrap_or_default();
}
if self.invalid_prefix.unwrap_or_default() {
writeln!(f, " Prefix is incorrect").unwrap_or_default();
}
if self.invalid_version.unwrap_or_default() {
writeln!(f, " Version is incorrect").unwrap_or_default();
}
if self.invalid_arch.unwrap_or_default() {
writeln!(f, " Architecture is incorrect").unwrap_or_default();
}
Ok(())
}
}
25 changes: 25 additions & 0 deletions crates/pet-core/src/telemetry/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use inaccurate_python_info::InaccuratePythonEnvironmentInfo;

pub mod inaccurate_python_info;

pub type NumberOfCustomSearchPaths = u32;

pub enum TelemetryEvent {
/// Total time taken to search for Global environments.
GlobalEnvironmentsSearchCompleted(std::time::Duration),
/// Total time taken to search for Global Virtual environments.
GlobalVirtualEnvironmentsSearchCompleted(std::time::Duration),
/// Total time taken to search for environments in the PATH environment variable.
GlobalPathVariableEnvironmentsSearchCompleted(std::time::Duration),
/// Total time taken to search for environments in specific paths provided by the user.
/// This generally maps to workspace folders in Python extension.
AllSearchPathsEnvironmentsSearchCompleted(std::time::Duration, NumberOfCustomSearchPaths),
/// Total time taken to search for all environments in all locations.
/// This is the max of all of the other `SearchCompleted` durations.
SearchCompleted(std::time::Duration),
/// Sent when an the information for an environment discovered is not accurate.
InaccuratePythonEnvironmentInfo(InaccuratePythonEnvironmentInfo),
}
1 change: 1 addition & 0 deletions crates/pet-fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"

[dependencies]
log = "0.4.21"
6 changes: 0 additions & 6 deletions crates/pet-hatch/Cargo.toml

This file was deleted.

Loading