Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 88dc2ca

Browse files
committed
venvlauncher
1 parent 61ddd98 commit 88dc2ca

File tree

9 files changed

+230
-8
lines changed

9 files changed

+230
-8
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
263 KB
Binary file not shown.
263 KB
Binary file not shown.
263 KB
Binary file not shown.
263 KB
Binary file not shown.

crates/venvlauncher/Cargo.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[package]
2+
name = "rustpython-venvlauncher"
3+
description = "Lightweight venv launcher for RustPython"
4+
version.workspace = true
5+
authors.workspace = true
6+
edition.workspace = true
7+
rust-version.workspace = true
8+
repository.workspace = true
9+
license.workspace = true
10+
11+
[[bin]]
12+
name = "venvlauncher"
13+
path = "src/main.rs"
14+
15+
[[bin]]
16+
name = "venvwlauncher"
17+
path = "src/main.rs"
18+
19+
# Free-threaded variants (RustPython uses Py_GIL_DISABLED=true)
20+
[[bin]]
21+
name = "venvlaunchert"
22+
path = "src/main.rs"
23+
24+
[[bin]]
25+
name = "venvwlaunchert"
26+
path = "src/main.rs"
27+
28+
[target.'cfg(windows)'.dependencies]
29+
windows-sys = { workspace = true, features = [
30+
"Win32_Foundation",
31+
"Win32_System_Threading",
32+
"Win32_System_Environment",
33+
"Win32_Storage_FileSystem",
34+
"Win32_System_Console",
35+
"Win32_Security",
36+
] }
37+
38+
[lints]
39+
workspace = true

crates/venvlauncher/build.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! Build script for venvlauncher
2+
//!
3+
//! Sets the Windows subsystem to GUI for venvwlauncher variants.
4+
5+
fn main() {
6+
// Only apply on Windows
7+
if std::env::var("CARGO_CFG_WINDOWS").is_ok() {
8+
let exe_name = std::env::var("CARGO_BIN_NAME").unwrap_or_default();
9+
10+
// venvwlauncher and venvwlaunchert should be Windows GUI applications
11+
// (no console window)
12+
if exe_name.contains("venvw") {
13+
// MSVC linker flags
14+
println!("cargo:rustc-link-arg=/SUBSYSTEM:WINDOWS");
15+
println!("cargo:rustc-link-arg=/ENTRY:mainCRTStartup");
16+
}
17+
}
18+
}

crates/venvlauncher/src/main.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! RustPython venv launcher
2+
//!
3+
//! A lightweight launcher that reads pyvenv.cfg and delegates execution
4+
//! to the actual Python interpreter. This mimics CPython's venvlauncher.c.
5+
6+
use std::env;
7+
use std::fs;
8+
use std::path::{Path, PathBuf};
9+
use std::process::ExitCode;
10+
11+
fn main() -> ExitCode {
12+
match run() {
13+
Ok(code) => ExitCode::from(code as u8),
14+
Err(e) => {
15+
eprintln!("venvlauncher error: {}", e);
16+
ExitCode::from(1)
17+
}
18+
}
19+
}
20+
21+
fn run() -> Result<u32, Box<dyn std::error::Error>> {
22+
// 1. Get own executable path
23+
let exe_path = env::current_exe()?;
24+
let exe_name = exe_path
25+
.file_name()
26+
.ok_or("Failed to get executable name")?
27+
.to_string_lossy();
28+
29+
// 2. Determine target executable name based on launcher name
30+
// pythonw.exe / venvwlauncher -> pythonw.exe (GUI, no console)
31+
// python.exe / venvlauncher -> python.exe (console)
32+
let exe_name_lower = exe_name.to_lowercase();
33+
let target_exe = if exe_name_lower.contains("pythonw") || exe_name_lower.contains("venvw") {
34+
"pythonw.exe"
35+
} else {
36+
"python.exe"
37+
};
38+
39+
// 3. Find pyvenv.cfg
40+
// The launcher is in Scripts/ directory, pyvenv.cfg is in parent (venv root)
41+
let scripts_dir = exe_path.parent().ok_or("Failed to get Scripts directory")?;
42+
let venv_dir = scripts_dir.parent().ok_or("Failed to get venv directory")?;
43+
let cfg_path = venv_dir.join("pyvenv.cfg");
44+
45+
if !cfg_path.exists() {
46+
return Err(format!("pyvenv.cfg not found: {}", cfg_path.display()).into());
47+
}
48+
49+
// 4. Parse home= from pyvenv.cfg
50+
let home = read_home(&cfg_path)?;
51+
52+
// 5. Locate python executable in home directory
53+
let python_path = PathBuf::from(&home).join(target_exe);
54+
if !python_path.exists() {
55+
return Err(format!("Python not found: {}", python_path.display()).into());
56+
}
57+
58+
// 6. Set __PYVENV_LAUNCHER__ environment variable
59+
// This tells Python it was launched from a venv
60+
// SAFETY: We are in a single-threaded context (program entry point)
61+
unsafe {
62+
env::set_var("__PYVENV_LAUNCHER__", &exe_path);
63+
}
64+
65+
// 7. Launch Python with same arguments
66+
let args: Vec<String> = env::args().skip(1).collect();
67+
launch_process(&python_path, &args)
68+
}
69+
70+
/// Parse the `home=` value from pyvenv.cfg
71+
fn read_home(cfg_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
72+
let content = fs::read_to_string(cfg_path)?;
73+
74+
for line in content.lines() {
75+
let line = line.trim();
76+
// Skip comments and empty lines
77+
if line.is_empty() || line.starts_with('#') {
78+
continue;
79+
}
80+
81+
// Look for "home = <path>" or "home=<path>"
82+
if let Some(rest) = line.strip_prefix("home") {
83+
let rest = rest.trim_start();
84+
if let Some(value) = rest.strip_prefix('=') {
85+
return Ok(value.trim().to_string());
86+
}
87+
}
88+
}
89+
90+
Err("'home' key not found in pyvenv.cfg".into())
91+
}
92+
93+
/// Launch the Python process and wait for it to complete
94+
fn launch_process(exe: &Path, args: &[String]) -> Result<u32, Box<dyn std::error::Error>> {
95+
use std::process::Command;
96+
97+
let status = Command::new(exe).args(args).status()?;
98+
99+
Ok(status.code().unwrap_or(1) as u32)
100+
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::*;
105+
use std::io::Write;
106+
107+
#[test]
108+
fn test_read_home() {
109+
let temp_dir = std::env::temp_dir();
110+
let cfg_path = temp_dir.join("test_pyvenv.cfg");
111+
112+
let mut file = fs::File::create(&cfg_path).unwrap();
113+
writeln!(file, "home = C:\\Python313").unwrap();
114+
writeln!(file, "include-system-site-packages = false").unwrap();
115+
writeln!(file, "version = 3.13.0").unwrap();
116+
117+
let home = read_home(&cfg_path).unwrap();
118+
assert_eq!(home, "C:\\Python313");
119+
120+
fs::remove_file(&cfg_path).unwrap();
121+
}
122+
123+
#[test]
124+
fn test_read_home_no_spaces() {
125+
let temp_dir = std::env::temp_dir();
126+
let cfg_path = temp_dir.join("test_pyvenv2.cfg");
127+
128+
let mut file = fs::File::create(&cfg_path).unwrap();
129+
writeln!(file, "home=C:\\Python313").unwrap();
130+
131+
let home = read_home(&cfg_path).unwrap();
132+
assert_eq!(home, "C:\\Python313");
133+
134+
fs::remove_file(&cfg_path).unwrap();
135+
}
136+
137+
#[test]
138+
fn test_read_home_with_comments() {
139+
let temp_dir = std::env::temp_dir();
140+
let cfg_path = temp_dir.join("test_pyvenv3.cfg");
141+
142+
let mut file = fs::File::create(&cfg_path).unwrap();
143+
writeln!(file, "# This is a comment").unwrap();
144+
writeln!(file, "home = D:\\RustPython").unwrap();
145+
146+
let home = read_home(&cfg_path).unwrap();
147+
assert_eq!(home, "D:\\RustPython");
148+
149+
fs::remove_file(&cfg_path).unwrap();
150+
}
151+
}

crates/vm/src/getpath.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,26 @@ pub fn init_path_config(settings: &Settings) -> Paths {
110110

111111
// Step 0: Get executable path
112112
let executable = get_executable_path();
113-
paths.executable = executable
113+
let real_executable = executable
114114
.as_ref()
115115
.map(|p| p.to_string_lossy().into_owned())
116116
.unwrap_or_default();
117117

118-
let exe_dir = executable
119-
.as_ref()
120-
.and_then(|p| p.parent().map(PathBuf::from));
121-
122118
// Step 1: Check for __PYVENV_LAUNCHER__ environment variable
123-
if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") {
124-
paths.base_executable = launcher;
125-
}
119+
// When launched from a venv launcher, __PYVENV_LAUNCHER__ contains the venv's python.exe path
120+
// In this case:
121+
// - sys.executable should be the launcher path (where user invoked Python)
122+
// - sys._base_executable should be the real Python executable
123+
let exe_dir = if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") {
124+
paths.executable = launcher.clone();
125+
paths.base_executable = real_executable;
126+
PathBuf::from(&launcher).parent().map(PathBuf::from)
127+
} else {
128+
paths.executable = real_executable;
129+
executable
130+
.as_ref()
131+
.and_then(|p| p.parent().map(PathBuf::from))
132+
};
126133

127134
// Step 2: Check for venv (pyvenv.cfg) and get 'home'
128135
let (venv_prefix, home_dir) = detect_venv(&exe_dir);

0 commit comments

Comments
 (0)