Skip to content

Commit 8cf3388

Browse files
Support resolving Python environments (#35)
Co-authored-by: Karthik Nadig <[email protected]>
1 parent 89a9cd4 commit 8cf3388

File tree

40 files changed

+1057
-720
lines changed

40 files changed

+1057
-720
lines changed

Cargo.lock

Lines changed: 25 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pet-cache/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
pet-fs = { path = "../pet-fs" }
8+
pet-python-utils = { path = "../pet-python-utils" }
9+
serde = { version = "1.0.152", features = ["derive"] }
10+
serde_json = "1.0.93"
11+
pet-core = { path = "../pet-core" }
12+
log = "0.4.21"

crates/pet-cache/src/lib.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
pub fn add(left: usize, right: usize) -> usize {
2-
left + right
3-
}
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
43

5-
#[cfg(test)]
6-
mod tests {
7-
use super::*;
4+
use pet_core::python_environment::PythonEnvironment;
85

9-
#[test]
10-
fn it_works() {
11-
let result = add(2, 2);
12-
assert_eq!(result, 4);
13-
}
6+
pub trait Cache {
7+
fn get<P: AsRef<P>>(&self, executable: P) -> Option<PythonEnvironment>;
8+
fn set<P: AsRef<P>>(&self, environment: PythonEnvironment);
149
}

crates/pet-conda/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ impl Conda {
104104
}
105105

106106
impl Locator for Conda {
107-
fn resolve(&self, _env: &PythonEnvironment) -> Option<PythonEnvironment> {
108-
todo!()
109-
}
110107
fn from(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
111108
if let Some(ref path) = env.prefix {
112109
let mut environments = self.environments.lock().unwrap();
@@ -148,6 +145,11 @@ impl Locator for Conda {
148145
}
149146

150147
fn find(&self, reporter: &dyn Reporter) {
148+
// if we're calling this again, then clear what ever cache we have.
149+
let mut environments = self.environments.lock().unwrap();
150+
environments.clear();
151+
drop(environments);
152+
151153
let env_vars = self.env_vars.clone();
152154
thread::scope(|s| {
153155
// 1. Get a list of all know conda environments file paths

crates/pet-core/src/arch.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,19 @@ impl PartialOrd for Architecture {
2020
Some(self.cmp(other))
2121
}
2222
}
23+
24+
impl std::fmt::Display for Architecture {
25+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26+
write!(
27+
f,
28+
"{}",
29+
if *self == Architecture::X64 {
30+
"x64"
31+
} else {
32+
"x86"
33+
}
34+
)
35+
.unwrap_or_default();
36+
Ok(())
37+
}
38+
}

crates/pet-core/src/cache.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
pub trait Cache {
5+
fn get<P: AsRef<P>>(&self, executable: P) -> Option<PythonEnvironment>;
6+
fn set<P: AsRef<P>>(&self, environment: PythonEnvironment);
7+
}

crates/pet-core/src/lib.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub mod manager;
1111
pub mod os_environment;
1212
pub mod python_environment;
1313
pub mod reporter;
14-
// pub mod telemetry;
14+
pub mod telemetry;
1515

1616
#[derive(Debug, Clone)]
1717
pub struct LocatorResult {
@@ -28,16 +28,6 @@ pub trait Locator: Send + Sync {
2828
* This is because the `from` will do a best effort to get the environment information without spawning Python.
2929
*/
3030
fn from(&self, env: &PythonEnv) -> Option<PythonEnvironment>;
31-
/**
32-
* Given a Python environment, get all of the information by spawning the Python executable.
33-
* E.g. version, sysprefix, etc ...
34-
*
35-
* I.e. use this to test whether an environment is of a specific type.
36-
*/
37-
fn resolve(&self, env: &PythonEnvironment) -> Option<PythonEnvironment> {
38-
// TODO: Implement this.
39-
Some(env.clone())
40-
}
4131
/**
4232
* Finds all environments specific to this locator.
4333
*/

crates/pet-core/src/manager.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,19 @@ impl EnvManager {
6060
}
6161
}
6262
}
63+
64+
impl std::fmt::Display for EnvManager {
65+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66+
writeln!(f, "Manager ({:?})", self.tool).unwrap_or_default();
67+
writeln!(
68+
f,
69+
" Executable : {}",
70+
self.executable.to_str().unwrap_or_default()
71+
)
72+
.unwrap_or_default();
73+
if let Some(version) = &self.version {
74+
writeln!(f, " Version : {}", version).unwrap_or_default();
75+
}
76+
Ok(())
77+
}
78+
}

crates/pet-core/src/python_environment.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use pet_fs::path::norm_case;
5+
use pet_python_utils::executable::get_shortest_executable;
56
use serde::{Deserialize, Serialize};
67
use std::path::PathBuf;
78

@@ -67,6 +68,7 @@ pub struct PythonEnvironment {
6768
// E.g. in the case of Homebrew there are a number of symlinks that are created.
6869
pub symlinks: Option<Vec<PathBuf>>,
6970
}
71+
7072
impl Ord for PythonEnvironment {
7173
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
7274
format!(
@@ -126,6 +128,68 @@ impl PythonEnvironment {
126128
}
127129
}
128130

131+
impl std::fmt::Display for PythonEnvironment {
132+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
133+
writeln!(f, "Environment ({:?})", self.category).unwrap_or_default();
134+
if let Some(name) = &self.display_name {
135+
writeln!(f, " Display-Name: {}", name).unwrap_or_default();
136+
}
137+
if let Some(name) = &self.name {
138+
writeln!(f, " Name : {}", name).unwrap_or_default();
139+
}
140+
if let Some(exe) = &self.executable {
141+
writeln!(f, " Executable : {}", exe.to_str().unwrap_or_default())
142+
.unwrap_or_default();
143+
}
144+
if let Some(version) = &self.version {
145+
writeln!(f, " Version : {}", version).unwrap_or_default();
146+
}
147+
if let Some(prefix) = &self.prefix {
148+
writeln!(
149+
f,
150+
" Prefix : {}",
151+
prefix.to_str().unwrap_or_default()
152+
)
153+
.unwrap_or_default();
154+
}
155+
if let Some(project) = &self.project {
156+
writeln!(f, " Project : {}", project.to_str().unwrap()).unwrap_or_default();
157+
}
158+
if let Some(arch) = &self.arch {
159+
writeln!(f, " Architecture: {}", arch).unwrap_or_default();
160+
}
161+
if let Some(manager) = &self.manager {
162+
writeln!(
163+
f,
164+
" Manager : {:?}, {}",
165+
manager.tool,
166+
manager.executable.to_str().unwrap_or_default()
167+
)
168+
.unwrap_or_default();
169+
}
170+
if let Some(symlinks) = &self.symlinks {
171+
let mut symlinks = symlinks.clone();
172+
symlinks.sort_by(|a, b| {
173+
a.to_str()
174+
.unwrap_or_default()
175+
.len()
176+
.cmp(&b.to_str().unwrap_or_default().len())
177+
});
178+
179+
if !symlinks.is_empty() {
180+
for (i, symlink) in symlinks.iter().enumerate() {
181+
if i == 0 {
182+
writeln!(f, " Symlinks : {:?}", symlink).unwrap_or_default();
183+
} else {
184+
writeln!(f, " : {:?}", symlink).unwrap_or_default();
185+
}
186+
}
187+
}
188+
}
189+
Ok(())
190+
}
191+
}
192+
129193
#[derive(Serialize, Deserialize, Clone)]
130194
#[serde(rename_all = "camelCase")]
131195
#[derive(Debug)]
@@ -177,7 +241,7 @@ impl PythonEnvironmentBuilder {
177241
}
178242
}
179243
}
180-
self.update_symlinks(self.symlinks.clone());
244+
self.update_symlinks_and_exe(self.symlinks.clone());
181245
self
182246
}
183247

@@ -213,11 +277,11 @@ impl PythonEnvironmentBuilder {
213277
}
214278

215279
pub fn symlinks(mut self, symlinks: Option<Vec<PathBuf>>) -> Self {
216-
self.update_symlinks(symlinks);
280+
self.update_symlinks_and_exe(symlinks);
217281
self
218282
}
219283

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

231-
self.symlinks = if all.is_empty() { None } else { Some(all) };
295+
self.symlinks = if all.is_empty() {
296+
None
297+
} else {
298+
Some(all.clone())
299+
};
300+
if let Some(executable) = &self.executable {
301+
self.executable =
302+
Some(get_shortest_executable(&Some(all.clone())).unwrap_or(executable.clone()));
303+
}
232304
}
233305

234306
pub fn build(self) -> PythonEnvironment {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::python_environment::PythonEnvironmentCategory;
5+
6+
/// Information about an environment that was discovered to be inaccurate.
7+
/// If the discovered information is None, then it means that the information was not found.
8+
/// And we will not report that as an inaccuracy.
9+
pub struct InaccuratePythonEnvironmentInfo {
10+
/// Python Env category
11+
pub category: PythonEnvironmentCategory,
12+
/// Whether the actual exe is not what we expected.
13+
pub invalid_executable: Option<bool>,
14+
/// Whether the actual exe was not even in the list of symlinks that we expected.
15+
pub executable_not_in_symlinks: Option<bool>,
16+
/// Whether the prefix is not what we expected.
17+
pub invalid_prefix: Option<bool>,
18+
/// Whether the version is not what we expected.
19+
pub invalid_version: Option<bool>,
20+
/// Whether the architecture is not what we expected.
21+
pub invalid_arch: Option<bool>,
22+
}
23+
24+
impl std::fmt::Display for InaccuratePythonEnvironmentInfo {
25+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26+
writeln!(f, "Environment {:?} incorrectly identified", self.category).unwrap_or_default();
27+
if self.invalid_executable.unwrap_or_default() {
28+
writeln!(f, " Executable is incorrect").unwrap_or_default();
29+
}
30+
if self.executable_not_in_symlinks.unwrap_or_default() {
31+
writeln!(f, " Executable is not in the list of symlinks").unwrap_or_default();
32+
}
33+
if self.invalid_prefix.unwrap_or_default() {
34+
writeln!(f, " Prefix is incorrect").unwrap_or_default();
35+
}
36+
if self.invalid_version.unwrap_or_default() {
37+
writeln!(f, " Version is incorrect").unwrap_or_default();
38+
}
39+
if self.invalid_arch.unwrap_or_default() {
40+
writeln!(f, " Architecture is incorrect").unwrap_or_default();
41+
}
42+
Ok(())
43+
}
44+
}

0 commit comments

Comments
 (0)