-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathenv.rs
More file actions
145 lines (138 loc) · 5.37 KB
/
env.rs
File metadata and controls
145 lines (138 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use log::{error, trace};
use pet_core::{arch::Architecture, env::PythonEnv, python_environment::PythonEnvironment};
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
time::SystemTime,
};
use crate::{cache::create_cache, executable::new_silent_command};
const PYTHON_INFO_JSON_SEPARATOR: &str = "093385e9-59f7-4a16-a604-14bf206256fe";
const PYTHON_INFO_CMD:&str = "import json, sys; print('093385e9-59f7-4a16-a604-14bf206256fe');print(json.dumps({'version': '.'.join(str(n) for n in sys.version_info), 'sys_prefix': sys.prefix, 'executable': sys.executable, 'is64_bit': sys.maxsize > 2**32}))";
#[derive(Debug, Deserialize, Clone)]
pub struct InterpreterInfo {
pub version: String,
pub sys_prefix: String,
pub executable: String,
pub is64_bit: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResolvedPythonEnv {
pub executable: PathBuf,
pub prefix: PathBuf,
pub version: String,
pub is64_bit: bool,
pub symlinks: Option<Vec<PathBuf>>,
}
impl ResolvedPythonEnv {
pub fn to_python_env(&self) -> PythonEnv {
let mut env = PythonEnv::new(
self.executable.clone(),
Some(self.prefix.clone()),
Some(self.version.clone()),
);
env.symlinks.clone_from(&self.symlinks);
env
}
pub fn add_to_cache(&self, environment: PythonEnvironment) {
// Verify whether we have been given the right exe.
let arch = Some(if self.is64_bit {
Architecture::X64
} else {
Architecture::X86
});
let symlinks = environment.symlinks.clone().unwrap_or_default();
if symlinks.contains(&self.executable)
&& environment.version.clone().unwrap_or_default() == self.version
&& environment.prefix.clone().unwrap_or_default() == self.prefix
&& environment.arch == arch
{
let cache = create_cache(self.executable.clone());
let entry = cache.lock().expect("cache mutex poisoned");
entry.track_symlinks(symlinks)
} else {
error!(
"Invalid Python environment being cached: {:?} expected {:?}",
environment, self
);
}
}
/// Given the executable path, resolve the python environment by spawning python.
/// If we had previously spawned Python and we have the symlinks to this as well,
/// & all of them are the same as when this exe was previously spawned,
/// & mtime & ctimes of none of the exes (symlinks) have changed, then we can use the cached info.
pub fn from(
executable: &Path,
// known_symlinks: &Vec<PathBuf>,
// cache: &dyn Cache,
) -> Option<Self> {
let cache = create_cache(executable.to_path_buf());
let entry = cache.lock().expect("cache mutex poisoned");
if let Some(env) = entry.get() {
Some(env)
} else if let Some(env) = get_interpreter_details(executable) {
entry.store(env.clone());
Some(env)
} else {
None
}
}
}
fn get_interpreter_details(executable: &Path) -> Option<ResolvedPythonEnv> {
// Spawn the python exe and get the version, sys.prefix and sys.executable.
let executable = executable.to_str()?;
let start = SystemTime::now();
trace!("Executing Python: {} -c {}", executable, PYTHON_INFO_CMD);
let result = new_silent_command(executable)
.args(["-c", PYTHON_INFO_CMD])
.output();
match result {
Ok(output) => {
let output = String::from_utf8(output.stdout).unwrap().trim().to_string();
trace!(
"Executed Python {:?} in {:?} & produced an output {:?}",
executable,
start.elapsed(),
output
);
if let Some((_, output)) = output.split_once(PYTHON_INFO_JSON_SEPARATOR) {
if let Ok(info) = serde_json::from_str::<InterpreterInfo>(output) {
let mut symlinks = vec![
PathBuf::from(executable),
PathBuf::from(info.executable.clone()),
];
symlinks.sort();
symlinks.dedup();
Some(ResolvedPythonEnv {
executable: PathBuf::from(info.executable.clone()),
prefix: PathBuf::from(info.sys_prefix),
version: info.version.trim().to_string(),
is64_bit: info.is64_bit,
symlinks: Some(symlinks),
})
} else {
error!(
"Python Execution for {:?} produced an output {:?} that could not be parsed as JSON",
executable, output,
);
None
}
} else {
error!(
"Python Execution for {:?} produced an output {:?} without a separator",
executable, output,
);
None
}
}
Err(err) => {
error!(
"Failed to execute Python to resolve info {:?}: {}",
executable, err
);
None
}
}
}