Skip to content

Commit 528aeaa

Browse files
authored
Determine failures to find poetry envs (#100)
1 parent e360a27 commit 528aeaa

17 files changed

Lines changed: 471 additions & 125 deletions
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Telemetry sent when
7+
/// 1. We are able to spawn poetry
8+
/// 2. We have found some new envs after spawning poetry
9+
#[derive(Serialize, Deserialize)]
10+
#[serde(rename_all = "camelCase")]
11+
#[derive(Debug, Clone, Copy)]
12+
pub struct MissingPoetryEnvironments {
13+
/// Total number of missing envs.
14+
pub missing: u16,
15+
/// Total number of missing envs, where the envs are created in the virtualenvs_path directory.
16+
pub missing_in_path: u16,
17+
/// Whether the user provided a executable.
18+
pub user_provided_poetry_exe: Option<bool>,
19+
/// Whether we managed to find the poetry exe or not.
20+
pub poetry_exe_not_found: Option<bool>,
21+
/// Whether we failed to find the global config file.
22+
pub global_config_not_found: Option<bool>,
23+
/// Whether the cache-dir returned by Poetry exe was not found by us
24+
/// This indicated the fact that we are unable to parse the poetry config file or something else.
25+
pub cache_dir_not_found: Option<bool>,
26+
/// Whether the cache-dir we found is different from what is returned by Poetry exe
27+
pub cache_dir_is_different: Option<bool>,
28+
/// Whether the virtualenvs path returned by Poetry exe was not found by us
29+
/// This indicated the fact that we are unable to parse the poetry config file or something else.
30+
pub virtualenvs_path_not_found: Option<bool>,
31+
/// Whether the virtualenvs_path we found is different from what is returned by Poetry exe
32+
pub virtualenvs_path_is_different: Option<bool>,
33+
/// Whether the virtualenvs.in-project setting value is differnt from what is returned by Poetry exe
34+
pub in_project_is_different: Option<bool>,
35+
}

crates/pet-core/src/telemetry/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
use inaccurate_python_info::InaccuratePythonEnvironmentInfo;
55
use missing_conda_info::MissingCondaEnvironments;
6+
use missing_poetry_info::MissingPoetryEnvironments;
67
use serde::{Deserialize, Serialize};
78

89
pub mod inaccurate_python_info;
910
pub mod missing_conda_info;
11+
pub mod missing_poetry_info;
1012

1113
pub type NumberOfCustomSearchPaths = u32;
1214

@@ -30,6 +32,8 @@ pub enum TelemetryEvent {
3032
InaccuratePythonEnvironmentInfo(InaccuratePythonEnvironmentInfo),
3133
/// Sent when an environment is discovered by spawning conda and not found otherwise.
3234
MissingCondaEnvironments(MissingCondaEnvironments),
35+
/// Sent when an environment is discovered by spawning poetry and not found otherwise.
36+
MissingPoetryEnvironments(MissingPoetryEnvironments),
3337
}
3438

3539
pub fn get_telemetry_event_name(event: &TelemetryEvent) -> &'static str {
@@ -47,5 +51,6 @@ pub fn get_telemetry_event_name(event: &TelemetryEvent) -> &'static str {
4751
TelemetryEvent::SearchCompleted(_) => "SearchCompleted",
4852
TelemetryEvent::InaccuratePythonEnvironmentInfo(_) => "InaccuratePythonEnvironmentInfo",
4953
TelemetryEvent::MissingCondaEnvironments(_) => "MissingCondaEnvironments",
54+
TelemetryEvent::MissingPoetryEnvironments(_) => "MissingPoetryEnvironments",
5055
}
5156
}

crates/pet-poetry/src/config.rs

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,28 @@ static _APP_NAME: &str = "pypoetry";
1717
pub struct Config {
1818
pub virtualenvs_in_project: Option<bool>,
1919
pub virtualenvs_path: PathBuf,
20+
pub cache_dir: Option<PathBuf>,
2021
pub file: Option<PathBuf>,
2122
}
2223

2324
impl Config {
2425
fn new(
2526
file: Option<PathBuf>,
2627
virtualenvs_path: PathBuf,
28+
cache_dir: Option<PathBuf>,
2729
virtualenvs_in_project: Option<bool>,
2830
) -> Self {
2931
trace!(
30-
"Poetry config file => {:?}, virtualenv.path => {:?}, virtualenvs_in_project => {:?}",
32+
"Poetry config file => {:?}, virtualenv.path => {:?}, cache_dir => {:?}, virtualenvs_in_project => {:?}",
3133
file,
3234
virtualenvs_path,
35+
cache_dir,
3336
virtualenvs_in_project
3437
);
3538
Config {
3639
file,
3740
virtualenvs_path,
41+
cache_dir,
3842
virtualenvs_in_project,
3943
}
4044
}
@@ -54,44 +58,79 @@ impl Config {
5458

5559
fn create_config(file: Option<PathBuf>, env: &EnvVariables) -> Option<Config> {
5660
let cfg = file.clone().and_then(|f| parse(&f));
61+
let cache_dir = get_cache_dir(&cfg, env);
62+
let virtualenvs_path_from_env_var = env
63+
.poetry_virtualenvs_path
64+
.clone()
65+
.map(|p| resolve_virtualenvs_path(&p, &cache_dir));
66+
5767
if let Some(virtualenvs_path) = &cfg.clone().and_then(|cfg| cfg.virtualenvs_path) {
58-
let mut virtualenvs_path = virtualenvs_path.clone();
5968
trace!("Poetry virtualenvs path => {:?}", virtualenvs_path);
60-
if virtualenvs_path
61-
.to_string_lossy()
62-
.to_lowercase()
63-
.contains("{cache-dir}")
64-
{
65-
if let Some(cache_dir) = &get_default_cache_dir(env) {
66-
virtualenvs_path = PathBuf::from(
67-
virtualenvs_path
68-
.to_string_lossy()
69-
.replace("{cache-dir}", cache_dir.to_string_lossy().as_ref()),
70-
);
71-
trace!(
72-
"Poetry virtualenvs path after replacing cache-dir => {:?}",
73-
virtualenvs_path
74-
);
75-
}
76-
}
69+
let virtualenvs_path = resolve_virtualenvs_path(&virtualenvs_path.clone(), &cache_dir);
7770

7871
return Some(Config::new(
7972
file.clone(),
80-
virtualenvs_path.clone(),
73+
// Give preference to the virtualenvs path from the env var
74+
virtualenvs_path_from_env_var.unwrap_or(virtualenvs_path.clone()),
75+
cache_dir,
8176
cfg.and_then(|cfg| cfg.virtualenvs_in_project),
8277
));
8378
}
8479

85-
get_default_cache_dir(env)
86-
.map(|cache_dir| Config::new(file, cache_dir.join("virtualenvs"), None))
80+
// Give preference to the virtualenvs path from the env var
81+
if let Some(virtualenvs_path_from_env_var) = virtualenvs_path_from_env_var {
82+
if virtualenvs_path_from_env_var.exists() {
83+
return Some(Config::new(
84+
file,
85+
virtualenvs_path_from_env_var,
86+
cache_dir,
87+
cfg.and_then(|cfg| cfg.virtualenvs_in_project),
88+
));
89+
}
90+
}
91+
92+
cache_dir
93+
.map(|cache_dir| Config::new(file, cache_dir.join("virtualenvs"), Some(cache_dir), None))
94+
}
95+
96+
/// Replaces {cache-dir} in virtualenvs path with the cache dir
97+
fn resolve_virtualenvs_path(virtualenvs_path: &Path, cache_dir: &Option<PathBuf>) -> PathBuf {
98+
if virtualenvs_path
99+
.to_string_lossy()
100+
.to_lowercase()
101+
.contains("{cache-dir}")
102+
{
103+
if let Some(cache_dir) = &cache_dir {
104+
let virtualenvs_path = PathBuf::from(
105+
virtualenvs_path
106+
.to_string_lossy()
107+
.replace("{cache-dir}", cache_dir.to_string_lossy().as_ref()),
108+
);
109+
trace!(
110+
"Poetry virtualenvs path after replacing cache-dir => {:?}",
111+
virtualenvs_path
112+
);
113+
return virtualenvs_path;
114+
}
115+
}
116+
virtualenvs_path.to_path_buf()
87117
}
88118
/// Maps to DEFAULT_CACHE_DIR in poetry
89-
fn get_default_cache_dir(env: &EnvVariables) -> Option<PathBuf> {
119+
fn get_cache_dir(cfg: &Option<ConfigToml>, env: &EnvVariables) -> Option<PathBuf> {
120+
// Cache dir in env variables takes precedence
90121
if let Some(cache_dir) = env.poetry_cache_dir.clone() {
91-
Some(cache_dir)
92-
} else {
93-
Platformdirs::new(_APP_NAME.into(), false).user_cache_path()
122+
if cache_dir.is_dir() {
123+
return Some(cache_dir);
124+
}
125+
}
126+
// Check cache dir in config.
127+
if let Some(cache_dir) = cfg.as_ref().and_then(|cfg| cfg.cache_dir.clone()) {
128+
if cache_dir.is_dir() {
129+
return Some(cache_dir);
130+
}
94131
}
132+
133+
Platformdirs::new(_APP_NAME.into(), false).user_cache_path()
95134
}
96135

97136
/// Maps to CONFIG_DIR in poetry
@@ -105,7 +144,7 @@ fn get_config_dir(env: &EnvVariables) -> Option<PathBuf> {
105144
Platformdirs::new(_APP_NAME.into(), true).user_config_path()
106145
}
107146

108-
fn find_config_file(env: &EnvVariables) -> Option<PathBuf> {
147+
pub fn find_config_file(env: &EnvVariables) -> Option<PathBuf> {
109148
let config_dir = get_config_dir(env)?;
110149
let file = config_dir.join("config.toml");
111150
if file.exists() {

crates/pet-poetry/src/env_variables.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct EnvVariables {
1313
pub root: Option<PathBuf>,
1414
/// Maps to env var `APPDATA`
1515
pub app_data: Option<PathBuf>,
16+
/// Maps to env var `POETRY_VIRTUALENVS_PATH`
17+
pub poetry_virtualenvs_path: Option<PathBuf>,
1618
/// Maps to env var `POETRY_HOME`
1719
pub poetry_home: Option<PathBuf>,
1820
/// Maps to env var `POETRY_CONFIG_DIR`
@@ -46,6 +48,9 @@ impl EnvVariables {
4648
path: env.get_env_var("PATH".to_string()),
4749
root: env.get_root(),
4850
app_data: env.get_env_var("APPDATA".to_string()).map(PathBuf::from),
51+
poetry_virtualenvs_path: env
52+
.get_env_var("POETRY_VIRTUALENVS_PATH".to_string())
53+
.map(PathBuf::from),
4954
poetry_cache_dir: env
5055
.get_env_var("POETRY_CACHE_DIR".to_string())
5156
.map(PathBuf::from),

crates/pet-poetry/src/environment_locations.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,21 @@ fn should_use_local_venv_as_poetry_env(
122122
local: &Option<Config>,
123123
env: &EnvVariables,
124124
) -> bool {
125-
// Give preference to setting in local config file.
126-
if let Some(poetry_virtualenvs_in_project) =
127-
local.clone().and_then(|c| c.virtualenvs_in_project)
128-
{
125+
// Given preference to env variable.
126+
if let Some(poetry_virtualenvs_in_project) = env.poetry_virtualenvs_in_project {
129127
trace!(
130-
"Poetry virtualenvs_in_project from local config file: {}",
128+
"Poetry virtualenvs_in_project from Env Variable: {}",
131129
poetry_virtualenvs_in_project
132130
);
133131
return poetry_virtualenvs_in_project;
134132
}
135133

136-
// Given preference to env variable.
137-
if let Some(poetry_virtualenvs_in_project) = env.poetry_virtualenvs_in_project {
134+
// Give preference to setting in local config file.
135+
if let Some(poetry_virtualenvs_in_project) =
136+
local.clone().and_then(|c| c.virtualenvs_in_project)
137+
{
138138
trace!(
139-
"Poetry virtualenvs_in_project from Env Variable: {}",
139+
"Poetry virtualenvs_in_project from local config file: {}",
140140
poetry_virtualenvs_in_project
141141
);
142142
return poetry_virtualenvs_in_project;

crates/pet-poetry/src/environment_locations_spawn.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ lazy_static! {
1616

1717
pub fn list_environments(
1818
executable: &PathBuf,
19-
project_dirs: Vec<PathBuf>,
19+
project_dirs: &Vec<PathBuf>,
2020
manager: &PoetryManager,
2121
) -> Vec<PythonEnvironment> {
2222
let mut envs = vec![];
2323
for project_dir in project_dirs {
24-
if let Some(project_envs) = get_environments(executable, &project_dir) {
24+
if let Some(project_envs) = get_environments(executable, project_dir) {
2525
for project_env in project_envs {
2626
if let Some(env) =
2727
create_poetry_env(&project_env, project_dir.clone(), Some(manager.clone()))
@@ -81,3 +81,72 @@ fn get_environments(executable: &PathBuf, project_dir: &PathBuf) -> Option<Vec<P
8181
}
8282
}
8383
}
84+
85+
#[derive(Clone, Debug)]
86+
pub struct PoetryConfig {
87+
pub cache_dir: Option<PathBuf>,
88+
pub virtualenvs_in_project: Option<bool>,
89+
pub virtualenvs_path: Option<PathBuf>,
90+
}
91+
92+
pub fn get_config(executable: &PathBuf, project_dir: &PathBuf) -> PoetryConfig {
93+
let cache_dir = get_config_path(executable, project_dir, "cache-dir");
94+
let virtualenvs_path = get_config_path(executable, project_dir, "virtualenvs.path");
95+
let virtualenvs_in_project = get_config_bool(executable, project_dir, "virtualenvs.in-project");
96+
PoetryConfig {
97+
cache_dir,
98+
virtualenvs_in_project,
99+
virtualenvs_path,
100+
}
101+
}
102+
103+
fn get_config_bool(executable: &PathBuf, project_dir: &PathBuf, setting: &str) -> Option<bool> {
104+
match get_config_value(executable, project_dir, setting) {
105+
Some(output) => {
106+
let output = output.trim();
107+
if output.starts_with("true") {
108+
Some(true)
109+
} else if output.starts_with("false") {
110+
Some(false)
111+
} else {
112+
None
113+
}
114+
}
115+
None => None,
116+
}
117+
}
118+
fn get_config_path(executable: &PathBuf, project_dir: &PathBuf, setting: &str) -> Option<PathBuf> {
119+
get_config_value(executable, project_dir, setting).map(|output| PathBuf::from(output.trim()))
120+
}
121+
122+
fn get_config_value(executable: &PathBuf, project_dir: &PathBuf, setting: &str) -> Option<String> {
123+
let start = SystemTime::now();
124+
let result = std::process::Command::new(executable)
125+
.arg("config")
126+
.arg(setting)
127+
.current_dir(project_dir)
128+
.output();
129+
trace!(
130+
"Executed Poetry ({}ms): {executable:?} config {setting} {project_dir:?}",
131+
start.elapsed().unwrap_or_default().as_millis(),
132+
);
133+
match result {
134+
Ok(output) => {
135+
if output.status.success() {
136+
Some(String::from_utf8_lossy(&output.stdout).to_string())
137+
} else {
138+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
139+
trace!(
140+
"Failed to get Poetry config {setting} using exe {executable:?} in {project_dir:?}, due to ({}) {}",
141+
output.status.code().unwrap_or_default(),
142+
stderr
143+
);
144+
None
145+
}
146+
}
147+
Err(err) => {
148+
error!("Failed to execute Poetry env list {:?}", err);
149+
None
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)