Skip to content

Commit 59bb4bb

Browse files
authored
Merge branch 'main' into deduplicate-global-paths
2 parents 078c70b + 845945b commit 59bb4bb

File tree

3 files changed

+78
-34
lines changed

3 files changed

+78
-34
lines changed

crates/pet-core/src/pyvenv_cfg.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ const PYVENV_CONFIG_FILE: &str = "pyvenv.cfg";
2020
#[derive(Debug)]
2121
pub struct PyVenvCfg {
2222
pub version: String,
23+
pub version_major: u64,
24+
pub version_minor: u64,
2325
}
2426

2527
impl PyVenvCfg {
26-
fn new(version: String) -> Self {
27-
Self { version }
28+
fn new(version: String, version_major: u64, version_minor: u64) -> Self {
29+
Self {
30+
version,
31+
version_major,
32+
version_minor,
33+
}
2834
}
2935
pub fn find(path: &Path) -> Option<Self> {
3036
if let Some(ref file) = find(path) {
@@ -77,16 +83,36 @@ fn parse(file: &Path) -> Option<PyVenvCfg> {
7783
if !line.contains("version") {
7884
continue;
7985
}
80-
if let Some(captures) = VERSION.captures(line) {
81-
if let Some(value) = captures.get(1) {
82-
return Some(PyVenvCfg::new(value.as_str().to_string()));
83-
}
86+
if let Some(cfg) = parse_version(line, &VERSION) {
87+
return Some(cfg);
8488
}
85-
if let Some(captures) = VERSION_INFO.captures(line) {
86-
if let Some(value) = captures.get(1) {
87-
return Some(PyVenvCfg::new(value.as_str().to_string()));
88-
}
89+
if let Some(cfg) = parse_version(line, &VERSION_INFO) {
90+
return Some(cfg);
8991
}
9092
}
9193
None
9294
}
95+
96+
fn parse_version(line: &str, regex: &Regex) -> Option<PyVenvCfg> {
97+
if let Some(captures) = regex.captures(line) {
98+
if let Some(value) = captures.get(1) {
99+
let version = value.as_str();
100+
let parts: Vec<&str> = version.splitn(3, ".").take(2).collect();
101+
// .expect() below is OK because the version regex
102+
// guarantees there are at least two digits.
103+
let version_major = parts[0]
104+
.parse()
105+
.expect("python major version to be an integer");
106+
let version_minor = parts[1]
107+
.parse()
108+
.expect("python minor version to be an integer");
109+
return Some(PyVenvCfg::new(
110+
version.to_string(),
111+
version_major,
112+
version_minor,
113+
));
114+
}
115+
}
116+
117+
None
118+
}

crates/pet-python-utils/src/headers.rs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ pub struct Headers {
1818

1919
impl Headers {
2020
pub fn get_version(path: &Path) -> Option<String> {
21-
get_version(path)
21+
let mut path = path.to_path_buf();
22+
let bin = if cfg!(windows) { "Scripts" } else { "bin" };
23+
if path.ends_with(bin) {
24+
path.pop();
25+
}
26+
get_version(&path, None)
2227
}
2328
}
2429

@@ -28,23 +33,16 @@ impl Headers {
2833
// /* Version as a string */
2934
// #define PY_VERSION "3.10.2"
3035
// /*--end constants--*/
31-
pub fn get_version(path: &Path) -> Option<String> {
32-
let mut path = path.to_path_buf();
33-
let bin = if cfg!(windows) { "Scripts" } else { "bin" };
34-
if path.ends_with(bin) {
35-
path.pop();
36-
}
36+
pub fn get_version(sys_prefix: &Path, pyver: Option<(u64, u64)>) -> Option<String> {
3737
// Generally the files are in Headers in windows and include in unix
3838
// However they can also be in Headers on Mac (command line tools python, hence make no assumptions)
39-
for headers_path in [path.join("Headers"), path.join("include")] {
40-
let patchlevel_h = headers_path.join("patchlevel.h");
41-
let mut contents = "".to_string();
42-
if let Ok(result) = fs::read_to_string(patchlevel_h) {
43-
contents = result;
44-
} else if !headers_path.exists() {
45-
// TODO: Remove this check, unnecessary, as we try to read the dir below.
46-
// Such a path does not exist, get out.
39+
for headers_path in [sys_prefix.join("Headers"), sys_prefix.join("include")] {
40+
if !headers_path.exists() {
4741
continue;
42+
}
43+
let patchlevel_h = headers_path.join("patchlevel.h");
44+
if let Some(version) = valid_version_from_header(&patchlevel_h, pyver) {
45+
return Some(version);
4846
} else {
4947
// Try the other path
5048
// Sometimes we have it in a sub directory such as `python3.10` or `pypy3.9`
@@ -57,18 +55,32 @@ pub fn get_version(path: &Path) -> Option<String> {
5755
}
5856
let path = path.path();
5957
let patchlevel_h = path.join("patchlevel.h");
60-
if let Ok(result) = fs::read_to_string(patchlevel_h) {
61-
contents = result;
62-
break;
58+
if let Some(version) = valid_version_from_header(&patchlevel_h, pyver) {
59+
return Some(version);
6360
}
6461
}
6562
}
6663
}
67-
for line in contents.lines() {
68-
if let Some(captures) = VERSION.captures(line) {
69-
if let Some(value) = captures.get(1) {
70-
return Some(value.as_str().to_string());
64+
}
65+
None
66+
}
67+
68+
fn valid_version_from_header(header: &Path, pyver: Option<(u64, u64)>) -> Option<String> {
69+
let contents = fs::read_to_string(header).ok()?;
70+
for line in contents.lines() {
71+
if let Some(captures) = VERSION.captures(line) {
72+
let version = captures.get(1)?.as_str();
73+
if let Some(pyver) = pyver {
74+
let parts: Vec<u64> = version
75+
.splitn(3, ".")
76+
.take(2)
77+
.flat_map(str::parse::<u64>)
78+
.collect();
79+
if parts.len() == 2 && (parts[0], parts[1]) == pyver {
80+
return Some(version.to_string());
7181
}
82+
} else {
83+
return Some(version.to_string());
7284
}
7385
}
7486
}

crates/pet-python-utils/src/version.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::headers::Headers;
4+
use crate::headers::{self, Headers};
55
use log::{trace, warn};
66
use pet_core::pyvenv_cfg::PyVenvCfg;
77
use pet_fs::path::resolve_symlink;
@@ -38,7 +38,13 @@ pub fn from_creator_for_virtual_env(prefix: &Path) -> Option<String> {
3838
} else {
3939
// Assume the python environment used to create this virtual env is a regular install of Python.
4040
// Try to get the version of that environment.
41-
from_header_files(parent_dir)
41+
let sys_root = parent_dir.parent()?;
42+
let pyver = if let Some(pyvenvcfg) = PyVenvCfg::find(prefix) {
43+
Some((pyvenvcfg.version_major, pyvenvcfg.version_minor))
44+
} else {
45+
None
46+
};
47+
headers::get_version(sys_root, pyver)
4248
}
4349
} else if cfg!(windows) {
4450
// Only on windows is it difficult to get the creator of the virtual environment.

0 commit comments

Comments
 (0)