Skip to content

Commit 6aa0968

Browse files
CopilotDonJayamanne
andcommitted
Fix PET failures with pyenv shims by detecting and skipping them
Co-authored-by: DonJayamanne <1948812+DonJayamanne@users.noreply.github.com>
1 parent fa18516 commit 6aa0968

File tree

6 files changed

+230
-3
lines changed

6 files changed

+230
-3
lines changed

Cargo.lock

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

crates/pet-python-utils/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ ci-homebrew-container = []
2525
ci-poetry-global = []
2626
ci-poetry-project = []
2727
ci-poetry-custom = []
28+
29+
[dev-dependencies]
30+
tempfile = "3.8"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use pet_python_utils::is_pyenv_shim;
5+
use std::path::PathBuf;
6+
7+
fn main() {
8+
env_logger::init();
9+
10+
let shim_path = PathBuf::from("/tmp/fake_pyenv/shims/python3.10");
11+
println!("Testing path: {:?}", shim_path);
12+
println!("Is pyenv shim: {}", is_pyenv_shim(&shim_path));
13+
14+
let regular_path = PathBuf::from("/usr/bin/python3");
15+
println!("Testing path: {:?}", regular_path);
16+
println!("Is regular path shim: {}", is_pyenv_shim(&regular_path));
17+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use log::{error, trace};
55
use pet_core::{arch::Architecture, env::PythonEnv, python_environment::PythonEnvironment};
66
use serde::{Deserialize, Serialize};
77
use std::{
8+
fs,
89
path::{Path, PathBuf},
910
time::SystemTime,
1011
};
@@ -88,6 +89,12 @@ impl ResolvedPythonEnv {
8889
}
8990

9091
fn get_interpreter_details(executable: &Path) -> Option<ResolvedPythonEnv> {
92+
// Skip pyenv shims as they don't work when executed directly
93+
if is_pyenv_shim(executable) {
94+
trace!("Skipping pyenv shim: {:?}", executable);
95+
return None;
96+
}
97+
9198
// Spawn the python exe and get the version, sys.prefix and sys.executable.
9299
let executable = executable.to_str()?;
93100
let start = SystemTime::now();
@@ -143,3 +150,25 @@ fn get_interpreter_details(executable: &Path) -> Option<ResolvedPythonEnv> {
143150
}
144151
}
145152
}
153+
154+
/// Detect if an executable is a pyenv shim by checking if its path contains .pyenv/shims
155+
/// or by reading the file content to look for pyenv-specific patterns
156+
pub fn is_pyenv_shim(executable: &Path) -> bool {
157+
// Check if the path contains .pyenv/shims
158+
if let Some(path_str) = executable.to_str() {
159+
if path_str.contains("/.pyenv/shims/") {
160+
return true;
161+
}
162+
}
163+
164+
// Additionally, check if it's a shell script with pyenv content
165+
if let Ok(content) = fs::read_to_string(executable) {
166+
// Look for common pyenv shim patterns
167+
if content.contains("PYENV_ROOT") && content.contains("pyenv") && content.contains("exec") {
168+
trace!("Detected pyenv shim by content: {:?}", executable);
169+
return true;
170+
}
171+
}
172+
173+
false
174+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ pub mod fs_cache;
88
mod headers;
99
pub mod platform_dirs;
1010
pub mod version;
11+
12+
pub use env::is_pyenv_shim;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use std::fs;
5+
use std::io::Write;
6+
use std::path::PathBuf;
7+
use tempfile::NamedTempFile;
8+
9+
use pet_python_utils::{env::ResolvedPythonEnv, is_pyenv_shim};
10+
11+
#[test]
12+
fn test_pyenv_shim_detection_by_path() {
13+
// Test path-based detection
14+
let shim_path = PathBuf::from("/home/user/.pyenv/shims/python3.10");
15+
assert!(is_pyenv_shim(&shim_path));
16+
17+
// Test that regular paths are not detected as shims
18+
let regular_path = PathBuf::from("/usr/bin/python3");
19+
assert!(!is_pyenv_shim(&regular_path));
20+
}
21+
22+
#[test]
23+
fn test_pyenv_shim_detection_by_content() {
24+
// Create a temporary file with pyenv shim content
25+
let mut shim_file = NamedTempFile::new().unwrap();
26+
let shim_content = r#"#!/usr/bin/env bash
27+
set -e
28+
[ -n "$PYENV_DEBUG" ] && set -x
29+
30+
program="${0##*/}"
31+
32+
export PYENV_ROOT="/home/user/.pyenv"
33+
exec "/home/user/.pyenv/libexec/pyenv" exec "$program" "$@"
34+
"#;
35+
shim_file.write_all(shim_content.as_bytes()).unwrap();
36+
37+
// Make it executable
38+
#[cfg(unix)]
39+
{
40+
use std::os::unix::fs::PermissionsExt;
41+
let mut perms = fs::metadata(shim_file.path()).unwrap().permissions();
42+
perms.set_mode(0o755);
43+
fs::set_permissions(shim_file.path(), perms).unwrap();
44+
}
45+
46+
// Test that this is detected as a pyenv shim
47+
assert!(is_pyenv_shim(shim_file.path()));
48+
}
49+
50+
#[test]
51+
fn test_pyenv_shim_resolve_returns_none() {
52+
// Create a temporary file that looks like a pyenv shim
53+
let mut shim_file = NamedTempFile::new().unwrap();
54+
let shim_content = r#"#!/usr/bin/env bash
55+
export PYENV_ROOT="/home/user/.pyenv"
56+
exec "/home/user/.pyenv/libexec/pyenv" exec "$program" "$@"
57+
"#;
58+
shim_file.write_all(shim_content.as_bytes()).unwrap();
59+
60+
// Make it executable
61+
#[cfg(unix)]
62+
{
63+
use std::os::unix::fs::PermissionsExt;
64+
let mut perms = fs::metadata(shim_file.path()).unwrap().permissions();
65+
perms.set_mode(0o755);
66+
fs::set_permissions(shim_file.path(), perms).unwrap();
67+
}
68+
69+
// Test that we cannot resolve this pyenv shim
70+
let result = ResolvedPythonEnv::from(shim_file.path());
71+
72+
// The result should be None since pyenv shims should be skipped
73+
assert!(result.is_none());
74+
}
75+
76+
#[test]
77+
fn test_regular_script_not_detected_as_shim() {
78+
// Create a regular script that shouldn't be detected as a pyenv shim
79+
let mut script_file = NamedTempFile::new().unwrap();
80+
let script_content = r#"#!/usr/bin/env bash
81+
echo "Hello World"
82+
python3 --version
83+
"#;
84+
script_file.write_all(script_content.as_bytes()).unwrap();
85+
86+
// Test that this is not detected as a pyenv shim
87+
assert!(!is_pyenv_shim(script_file.path()));
88+
}

0 commit comments

Comments
 (0)