-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathversion.rs
More file actions
134 lines (127 loc) · 5.17 KB
/
version.rs
File metadata and controls
134 lines (127 loc) · 5.17 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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::headers::{self, Headers};
use log::{trace, warn};
use pet_core::pyvenv_cfg::PyVenvCfg;
use pet_fs::path::resolve_symlink;
use std::{
path::{Path, PathBuf},
time::UNIX_EPOCH,
};
pub fn from_header_files(prefix: &Path) -> Option<String> {
Headers::get_version(prefix)
}
pub fn from_pyvenv_cfg(prefix: &Path) -> Option<String> {
PyVenvCfg::find(prefix).map(|cfg| cfg.version)
}
pub fn from_creator_for_virtual_env(prefix: &Path) -> Option<String> {
if let Some(version) = Headers::get_version(prefix) {
return Some(version);
}
let mut bin = "bin";
let mut executable = prefix.join(bin).join("python");
if cfg!(windows) && !executable.exists() {
bin = "Scripts";
executable = prefix.join(bin).join("python.exe");
}
// Determine who created this virtual environment, and get version of that environment.
// Note, its unlikely conda envs were used to create virtual envs, thats a very bad idea (known to cause issues and not reccomended).
// Hence do not support conda envs when getting versio of the parent env.
if let Some(mut creator_executable) = get_python_exe_used_to_create_venv(executable) {
// Possible we got resolved to the same bin directory but python3.10
if creator_executable.starts_with(prefix) {
creator_executable = resolve_symlink(&creator_executable)?;
}
let parent_dir = creator_executable.parent()?;
if parent_dir.file_name().unwrap_or_default() != bin {
trace!("Creator of virtual environment found, but the creator of {:?} is located in {:?} , instead of a {:?} directory", prefix, creator_executable, bin);
None
} else {
// Assume the python environment used to create this virtual env is a regular install of Python.
// Try to get the version of that environment.
let sys_root = parent_dir.parent()?;
let pyver = if let Some(pyvenvcfg) = PyVenvCfg::find(prefix) {
Some((pyvenvcfg.version_major, pyvenvcfg.version_minor))
} else {
None
};
headers::get_version(sys_root, pyver)
}
} else if cfg!(windows) {
// Only on windows is it difficult to get the creator of the virtual environment.
get_version_from_pyvenv_if_pyvenv_cfg_and_exe_created_same_time(prefix)
} else {
None
}
}
pub fn from_prefix(prefix: &Path) -> Option<String> {
if let Some(version) = from_pyvenv_cfg(prefix) {
Some(version)
} else {
from_header_files(prefix)
}
}
/// When creating virtual envs using `python -m venv` or the like,
/// The executable in the new environment ends up being a symlink to the python executable used to create the env.
/// Using this information its possible to determine the version of the Python environment used to create the env.
fn get_python_exe_used_to_create_venv<T: AsRef<Path>>(executable: T) -> Option<PathBuf> {
let parent_dir = executable.as_ref().parent()?;
if cfg!(windows) {
if parent_dir.file_name().unwrap_or_default() != "bin"
&& parent_dir.file_name().unwrap_or_default() != "Scripts"
{
warn!("Attempted to determine creator of virtual environment, but the env executable ({:?}) is not in the expected location.", executable.as_ref());
return None;
}
} else if parent_dir.file_name().unwrap_or_default() != "bin" {
warn!("Attempted to determine creator of virtual environment, but the env executable ({:?}) is not in the expected location.", executable.as_ref());
return None;
}
let symlink = resolve_symlink(&executable)?;
if symlink.is_file() {
Some(symlink)
} else {
None
}
}
/// Use pyvenv.cfg to get the version of the virtual environment in windows.
/// If the creation/modified dates of the pyvenv.cfg and the Scripts/python.exe are in the same period (few minutes apart)
/// Then we can use the pyvenv.cfg to get the version of the virtual environment.
fn get_version_from_pyvenv_if_pyvenv_cfg_and_exe_created_same_time(
prefix: &Path,
) -> Option<String> {
let cfg = PyVenvCfg::find(prefix)?;
let pyvenv_cfg = prefix.join("pyvenv.cfg");
if !pyvenv_cfg.exists() {
return None;
}
let cfg_metadata = pyvenv_cfg.metadata().ok()?;
let mut bin = prefix.join("Scripts");
if !bin.exists() {
bin = prefix.join("bin");
}
let exe_metadata = bin.join("python.exe").metadata().ok()?;
let cfg_modified = cfg_metadata
.modified()
.ok()?
.duration_since(UNIX_EPOCH)
.ok()?
.as_secs();
let exe_modified = exe_metadata
.modified()
.ok()?
.duration_since(UNIX_EPOCH)
.ok()?
.as_secs();
// If they are just a few minutes apart,
// then we can assume the version in the pyvenv.cfg is the version of the virtual environment.
if cfg_modified.abs_diff(exe_modified) < 60 {
trace!(
"Using pyvenv.cfg to get version of virtual environment {:?}",
prefix
);
Some(cfg.version)
} else {
None
}
}