From 57546a07fc73e31d18517b4be8db5ff12d71b6af Mon Sep 17 00:00:00 2001 From: SteveLauC Date: Fri, 23 Jun 2023 17:02:58 +0800 Subject: [PATCH] fix(pip3): prefer python when available (#471) --- src/steps/generic.rs | 15 +++++++++++++-- src/utils.rs | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/steps/generic.rs b/src/steps/generic.rs index 9aae4fb8..afc8482d 100644 --- a/src/steps/generic.rs +++ b/src/steps/generic.rs @@ -15,7 +15,7 @@ use crate::command::{CommandExt, Utf8Output}; use crate::execution_context::ExecutionContext; use crate::executor::ExecutorOutput; use crate::terminal::{print_separator, shell}; -use crate::utils::{self, require, require_option, which, PathExt, REQUIRE_SUDO}; +use crate::utils::{self, check_is_python_2_or_shim, require, require_option, which, PathExt, REQUIRE_SUDO}; use crate::Step; use crate::HOME_DIR; use crate::{ @@ -368,7 +368,18 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> { } pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> { - let python3 = require("python3")?; + let py = require("python").and_then(check_is_python_2_or_shim); + let py3 = require("python3").and_then(check_is_python_2_or_shim); + + let python3 = match (py, py3) { + // prefer `python` if it is available and is a valid Python 3. + (Ok(py), _) => py, + (Err(_), Ok(py3)) => py3, + (Err(py_err), Err(py3_err)) => { + return Err(SkipStep(format!("Skip due to following reasons: {} {}", py_err, py3_err)).into()); + } + }; + Command::new(&python3) .args(["-m", "pip"]) .output_checked_utf8() diff --git a/src/utils.rs b/src/utils.rs index b88be69d..ee9cc0f8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,10 +2,12 @@ use std::env; use std::ffi::OsStr; use std::fmt::Debug; use std::path::{Path, PathBuf}; +use std::process::Command; use color_eyre::eyre::Result; use tracing::{debug, error}; +use crate::command::CommandExt; use crate::error::SkipStep; pub trait PathExt @@ -152,9 +154,6 @@ pub fn hostname() -> Result { #[cfg(target_family = "windows")] pub fn hostname() -> Result { - use crate::command::CommandExt; - use std::process::Command; - Command::new("hostname") .output_checked_utf8() .map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into()) @@ -217,3 +216,38 @@ pub mod merge_strategies { // Skip causes // TODO: Put them in a better place when we have more of them pub const REQUIRE_SUDO: &str = "Require sudo or counterpart but not found, skip"; + +/// Return `Err(SkipStep)` if `python` is a Python 2 or shim. +/// +/// # Shim +/// On Windows, if you install `python` through `winget`, an actual `python` +/// is installed as well as a `python3` shim. Shim is invokable, but when you +/// execute it, the Microsoft App Store will be launched instead of a Python +/// shell. +/// +/// We do this check through `python -V`, a shim will just give `Python` with +/// no version number. +pub fn check_is_python_2_or_shim(python: PathBuf) -> Result { + let output = Command::new(&python).arg("-V").output_checked_utf8()?; + // "Python x.x.x\n" + let stdout = output.stdout; + // ["Python"] or ["Python", "x.x.x"], the newline char is trimmed. + let mut split = stdout.split_whitespace(); + + if let Some(version) = split.nth(1) { + let major_version = version + .split('.') + .next() + .expect("Should have a major version number") + .parse::() + .expect("Major version should be a valid number"); + if major_version == 2 { + return Err(SkipStep(format!("{} is a Python 2, skip.", python.display())).into()); + } + } else { + // No version number, is a shim + return Err(SkipStep(format!("{} is a Python shim, skip.", python.display())).into()); + } + + Ok(python) +}