fix(tools): find Python on Windows without python3.exe

Installing debugpy on Windows failed with "cannot run python3.exe … (os error 3)": the tool
manager looked for `python3` first, and on Windows that only finds the App Execution Alias in
WindowsApps, which can't start without the Store's Python. Windows installs `python.exe` and the
`py` launcher instead. On Windows the interpreter is now looked up as `python`, `py`, then
`python3`, and a WindowsApps alias only counts when it runs. The debugpy adapter's default
interpreter is `python` on Windows too. macOS and Linux keep `python3` first.
Цей коміт міститься в:
l0ng-ai committed 2026-11-05 10:30:03 +08:00
1 джерело 54502e0a17
коміт ad1fdf9e0e
3 files changed
+48 -2

No files matched your search

+7 -1
Переглянути файл
@@ -72,7 +72,13 @@ fn builtin(adapter: &str) -> Option<DebuggerConfig> {
"stdio",
"Install the Kotlin Debug Adapter from Settings > Language Servers",
),
"debugpy" => cfg("python3", &["-m", "debugpy.adapter"], "stdio", "pip install debugpy"),
// Windows installs `python.exe`; `python3.exe` there is at most a Store alias.
"debugpy" => cfg(
if cfg!(windows) { "python" } else { "python3" },
&["-m", "debugpy.adapter"],
"stdio",
"pip install debugpy",
),
"js-debug" => cfg(
"js-debug-adapter",
// Without a host it listens on `localhost`, which may resolve to ::1 only.
+19 -1
Переглянути файл
@@ -395,6 +395,11 @@ pub fn parse_version(text: &str) -> Option<String> {
None
}
/// A Windows App Execution Alias (`%LOCALAPPDATA%\Microsoft\WindowsApps\python.exe`).
fn is_app_execution_alias(path: &Path) -> bool {
path.parent().and_then(Path::file_name).is_some_and(|d| d.eq_ignore_ascii_case("WindowsApps"))
}
/// A tool whose "command" is a plugin jar (e.g. a jdtls bundle) rather than an executable.
fn is_bundle(command: &str) -> bool {
command.ends_with(".jar")
@@ -491,8 +496,21 @@ impl Tools {
cmd
}
/// The interpreter that installs and probes Python tools. Windows installs `python.exe`
/// and the `py` launcher, not `python3.exe`; its `WindowsApps` entries are App Execution
/// Aliases that only open the Store unless the Store's Python is installed, so they count
/// only when they run.
fn python(&self) -> Option<PathBuf> {
self.which_any(&["python3", "python"])
if self.platform.os != Os::Windows {
return self.which_any(&["python3", "python"]);
}
["python", "py", "python3"].into_iter().filter_map(|c| self.which(c)).find(|p| {
!is_app_execution_alias(p) || {
let mut c = self.python_command(p);
c.arg("--version");
run_quiet(c, Duration::from_secs(5)).is_some_and(|(ok, _)| ok)
}
})
}
/// Executable `cmd` inside `dir`, honouring Windows suffixes.
+22
Переглянути файл
@@ -700,6 +700,28 @@ fn rust_analyzer_managed_copy_survives_rustup_becoming_available() {
assert!(!env.root.join("rust-analyzer").exists());
}
/// Windows has `python.exe` and `py`, not `python3.exe`; the `WindowsApps` aliases only count
/// when the Store's Python behind them runs.
#[cfg(unix)]
#[test]
fn windows_python_skips_store_aliases_that_do_not_run() {
let env = Env::new();
let aliases = env.sys.parent().unwrap().join("Microsoft").join("WindowsApps");
env.script(&aliases, "python", "exit 9009");
env.script(&aliases, "python3", "exit 9009");
env.script(&env.sys, "py", "echo Python 3.12.0");
let windows = Platform { os: Os::Windows, arch: platform::Arch::X64 };
let found = || {
let dirs = std::env::join_paths([aliases.clone(), env.sys.clone()]).unwrap();
let python = Tools::with_paths(env.root.clone(), dirs, windows).python().unwrap();
let dir = python.parent().unwrap().file_name().unwrap().to_string_lossy().into_owned();
format!("{dir}/{}", python.file_name().unwrap().to_string_lossy())
};
assert_eq!(found(), "sysbin/py");
env.script(&aliases, "python", "echo Python 3.13.0");
assert_eq!(found(), "WindowsApps/python", "the Store's Python runs");
}
#[cfg(unix)]
#[test]
fn python_probes_run_in_an_empty_private_directory() {