From 4ac4f5b6aaaf9ad7216c6273e87b9fcd33d0d8d5 Mon Sep 17 00:00:00 2001 From: konstin Date: Fri, 9 Apr 2021 16:32:01 +0200 Subject: [PATCH] Consider requires-python when searching for interpreters Fixes #195 --- Changelog.md | 3 +++ src/build_options.rs | 45 ++++++++++++++++++++++++++++--- src/main.rs | 2 +- src/python_interpreter.rs | 29 ++++++++++---------- test-crates/pyo3-mixed/Cargo.toml | 1 + 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/Changelog.md b/Changelog.md index 7914950de..8afc91a65 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased + * Interpreter search now uses python 3.6 to 3.12 + * Consider requires-python when searching for interpreters + ## 0.10.3 - 2021-04-13 * The `upload` command is now implemented, it is mostly similar to `twine upload`. [#484](https://github.com/PyO3/maturin/pull/484) diff --git a/src/build_options.rs b/src/build_options.rs index 2c73dc857..423ff61b0 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -8,6 +8,7 @@ use crate::PythonInterpreter; use crate::Target; use anyhow::{bail, format_err, Context, Result}; use cargo_metadata::{Metadata, MetadataCommand, Node}; +use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::env; @@ -176,9 +177,9 @@ impl BuildOptions { // Only build a source distribution Some(ref interpreter) if interpreter.is_empty() => vec![], // User given list of interpreters - Some(interpreter) => find_interpreter(&bridge, &interpreter, &target)?, + Some(interpreter) => find_interpreter(&bridge, &interpreter, &target, None)?, // Auto-detect interpreters - None => find_interpreter(&bridge, &[], &target)?, + None => find_interpreter(&bridge, &[], &target, get_min_python_minor(&metadata21))?, }; let rustc_extra_args = split_extra_args(&self.rustc_extra_args)?; @@ -225,6 +226,29 @@ impl BuildOptions { } } +/// Uses very simple PEP 440 subset parsing to determine the +/// minimum supported python minor version for interpreter search +fn get_min_python_minor(metadata21: &Metadata21) -> Option { + if let Some(requires_python) = &metadata21.requires_python { + let regex = Regex::new(r#"(?:\^|>=)3\.(\d+)(?:\.\d)?"#).unwrap(); + if let Some(captures) = regex.captures(&requires_python) { + let min_python_minor = captures[1] + .parse::() + .expect("Regex must only match usize"); + Some(min_python_minor) + } else { + println!( + "⚠ Couldn't parse the value of requires-python, \ + not taking it into account when searching for python interpreter.\ + Note: Only the forms `^3.x` and `>=3.x.y` are currently supported." + ); + None + } + } else { + None + } +} + /// pyo3 supports building abi3 wheels if the unstable-api feature is not selected fn has_abi3(cargo_metadata: &Metadata) -> Result> { let resolve = cargo_metadata @@ -384,6 +408,7 @@ pub fn find_interpreter( bridge: &BridgeModel, interpreter: &[PathBuf], target: &Target, + min_python_minor: Option, ) -> Result> { match bridge { BridgeModel::Bindings(_) => { @@ -391,7 +416,7 @@ pub fn find_interpreter( PythonInterpreter::check_executables(&interpreter, &target, &bridge) .context("The given list of python interpreters is invalid")? } else { - PythonInterpreter::find_all(&target, &bridge) + PythonInterpreter::find_all(&target, &bridge, min_python_minor) .context("Finding python interpreters failed")? }; @@ -660,4 +685,18 @@ mod test { assert_eq!(extract_cargo_metadata_args(&args).unwrap(), expected); } + + #[test] + fn test_get_min_python_minor() { + // Nothing specified + let cargo_toml = CargoToml::from_path("test-crates/pyo3-pure/Cargo.toml").unwrap(); + let metadata21 = + Metadata21::from_cargo_toml(&cargo_toml, &"test-crates/pyo3-pure").unwrap(); + assert_eq!(get_min_python_minor(&metadata21), None); + // ^3.7 + let cargo_toml = CargoToml::from_path("test-crates/pyo3-mixed/Cargo.toml").unwrap(); + let metadata21 = + Metadata21::from_cargo_toml(&cargo_toml, &"test-crates/pyo3-mixed").unwrap(); + assert_eq!(get_min_python_minor(&metadata21), Some(7)); + } } diff --git a/src/main.rs b/src/main.rs index a94e64bd7..9aaa454ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -529,7 +529,7 @@ fn run() -> Result<()> { Opt::ListPython => { let target = Target::from_target_triple(None)?; // We don't know the targeted bindings yet, so we use the most lenient - let found = PythonInterpreter::find_all(&target, &BridgeModel::Cffi)?; + let found = PythonInterpreter::find_all(&target, &BridgeModel::Cffi, None)?; println!("🐍 {} python interpreter found:", found.len()); for interpreter in found { println!(" - {}", interpreter); diff --git a/src/python_interpreter.rs b/src/python_interpreter.rs index deb454c5d..a9c2c86f2 100644 --- a/src/python_interpreter.rs +++ b/src/python_interpreter.rs @@ -23,6 +23,7 @@ fn windows_interpreter_no_build( minor: usize, target_width: usize, pointer_width: usize, + min_python_minor: usize, ) -> bool { // Python 2 support has been dropped if major == 2 { @@ -30,7 +31,7 @@ fn windows_interpreter_no_build( } // Ignore python 3.0 - 3.5 - if major == 3 && minor < MINIMUM_PYTHON_MINOR { + if major == 3 && minor < min_python_minor { return true; } @@ -83,7 +84,7 @@ fn windows_interpreter_no_build( /// As well as the version numbers, etc. of the interpreters we also have to find the /// pointer width to make sure that the pointer width (32-bit or 64-bit) matches across /// platforms. -fn find_all_windows(target: &Target) -> Result> { +fn find_all_windows(target: &Target, min_python_minor: usize) -> Result> { let code = "import sys; print(sys.executable or '')"; let mut interpreter = vec![]; let mut versions_found = HashSet::new(); @@ -123,6 +124,7 @@ fn find_all_windows(target: &Target) -> Result> { minor, target.pointer_width(), pointer_width, + min_python_minor, ) { continue; } @@ -201,6 +203,7 @@ fn find_all_windows(target: &Target) -> Result> { minor, target.pointer_width(), pointer_width, + min_python_minor, ) { continue; } @@ -219,15 +222,6 @@ fn find_all_windows(target: &Target) -> Result> { Ok(interpreter) } -/// Since there is no known way to list the installed python versions on unix -/// (or just generally to list all binaries in $PATH, which could then be -/// filtered down), we use this workaround. -fn find_all_unix() -> Vec { - (MINIMUM_PYTHON_MINOR..MAXIMUM_PYTHON_MINOR) - .map(|minor| format!("python3.{}", minor)) - .collect() -} - #[derive(Debug, Clone, Eq, PartialEq)] pub enum InterpreterKind { CPython, @@ -482,11 +476,18 @@ impl PythonInterpreter { /// Tries to find all installed python versions using the heuristic for the /// given platform - pub fn find_all(target: &Target, bridge: &BridgeModel) -> Result> { + pub fn find_all( + target: &Target, + bridge: &BridgeModel, + min_python_minor: Option, + ) -> Result> { + let min_python_minor = min_python_minor.unwrap_or(MINIMUM_PYTHON_MINOR); let executables = if target.is_windows() { - find_all_windows(&target)? + find_all_windows(&target, min_python_minor)? } else { - find_all_unix() + (min_python_minor..MAXIMUM_PYTHON_MINOR) + .map(|minor| format!("python3.{}", minor)) + .collect() }; let mut available_versions = Vec::new(); for executable in executables { diff --git a/test-crates/pyo3-mixed/Cargo.toml b/test-crates/pyo3-mixed/Cargo.toml index 9c3ce76d0..e08dc8284 100644 --- a/test-crates/pyo3-mixed/Cargo.toml +++ b/test-crates/pyo3-mixed/Cargo.toml @@ -15,6 +15,7 @@ classifier = [ "Programming Language :: Rust" ] requires-dist = ["boltons"] +requires-python = "^3.7" [dependencies] pyo3 = { version = "0.13.2", features = ["extension-module"] }