mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Support resolving for an alternate Python distribution (#364)
## Summary Low-priority but fun thing to end the day. You can now pass `--target-version py37`, and we'll generate a resolution for Python 3.7. See: https://github.com/astral-sh/puffin/issues/183.
This commit is contained in:
parent
d407bbbee6
commit
cfd84d6365
6 changed files with 245 additions and 10 deletions
|
@ -1,13 +1,14 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::io::{stdout, BufWriter};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::{env, fs};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use fs_err::File;
|
||||
use itertools::Itertools;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use pep508_rs::Requirement;
|
||||
|
@ -18,12 +19,12 @@ use puffin_dispatch::BuildDispatch;
|
|||
use puffin_interpreter::Virtualenv;
|
||||
use puffin_normalize::ExtraName;
|
||||
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::commands::reporters::ResolverReporter;
|
||||
use crate::commands::{elapsed, ExitStatus};
|
||||
use crate::index_urls::IndexUrls;
|
||||
use crate::printer::Printer;
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
@ -40,6 +41,7 @@ pub(crate) async fn pip_compile(
|
|||
upgrade_mode: UpgradeMode,
|
||||
index_urls: Option<IndexUrls>,
|
||||
no_build: bool,
|
||||
python_version: Option<PythonVersion>,
|
||||
cache: &Path,
|
||||
mut printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
|
@ -118,6 +120,12 @@ pub(crate) async fn pip_compile(
|
|||
venv.interpreter_info().simple_version(),
|
||||
)?;
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = python_version.map_or_else(
|
||||
|| Cow::Borrowed(venv.interpreter_info().markers()),
|
||||
|python_version| Cow::Owned(python_version.markers(venv.interpreter_info().markers())),
|
||||
);
|
||||
|
||||
// Instantiate a client.
|
||||
let client = {
|
||||
let mut builder = RegistryClientBuilder::default();
|
||||
|
@ -142,14 +150,9 @@ pub(crate) async fn pip_compile(
|
|||
);
|
||||
|
||||
// Resolve the dependencies.
|
||||
let resolver = puffin_resolver::Resolver::new(
|
||||
manifest,
|
||||
venv.interpreter_info().markers(),
|
||||
&tags,
|
||||
&client,
|
||||
&build_dispatch,
|
||||
)
|
||||
.with_reporter(ResolverReporter::from(printer));
|
||||
let resolver =
|
||||
puffin_resolver::Resolver::new(manifest, &markers, &tags, &client, &build_dispatch)
|
||||
.with_reporter(ResolverReporter::from(printer));
|
||||
let resolution = match resolver.resolve().await {
|
||||
Err(puffin_resolver::ResolveError::PubGrub(err)) => {
|
||||
#[allow(clippy::print_stderr)]
|
||||
|
|
|
@ -15,12 +15,14 @@ use requirements::ExtrasSpecification;
|
|||
|
||||
use crate::commands::{extra_name_with_clap_error, ExitStatus};
|
||||
use crate::index_urls::IndexUrls;
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::requirements::RequirementsSource;
|
||||
|
||||
mod commands;
|
||||
mod index_urls;
|
||||
mod logging;
|
||||
mod printer;
|
||||
mod python_version;
|
||||
mod requirements;
|
||||
|
||||
#[derive(Parser)]
|
||||
|
@ -116,6 +118,10 @@ struct PipCompileArgs {
|
|||
/// cached wheels of already built source distributions will be reused.
|
||||
#[clap(long)]
|
||||
no_build: bool,
|
||||
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, short, value_enum)]
|
||||
python_version: Option<PythonVersion>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
@ -249,6 +255,7 @@ async fn inner() -> Result<ExitStatus> {
|
|||
args.upgrade.into(),
|
||||
index_urls,
|
||||
args.no_build,
|
||||
args.python_version,
|
||||
&cache_dir,
|
||||
printer,
|
||||
)
|
||||
|
|
70
crates/puffin-cli/src/python_version.rs
Normal file
70
crates/puffin-cli/src/python_version.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use pep508_rs::{MarkerEnvironment, StringVersion};
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub(crate) enum PythonVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
}
|
||||
|
||||
impl PythonVersion {
|
||||
/// Return the `python_version` marker for a [`PythonVersion`].
|
||||
fn python_version(self) -> &'static str {
|
||||
match self {
|
||||
Self::Py37 => "3.7",
|
||||
Self::Py38 => "3.8",
|
||||
Self::Py39 => "3.9",
|
||||
Self::Py310 => "3.10",
|
||||
Self::Py311 => "3.11",
|
||||
Self::Py312 => "3.12",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the `python_full_version` marker for a [`PythonVersion`].
|
||||
fn python_full_version(self) -> &'static str {
|
||||
match self {
|
||||
Self::Py37 => "3.7.0",
|
||||
Self::Py38 => "3.8.0",
|
||||
Self::Py39 => "3.9.0",
|
||||
Self::Py310 => "3.10.0",
|
||||
Self::Py311 => "3.11.0",
|
||||
Self::Py312 => "3.12.0",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the `implementation_version` marker for a [`PythonVersion`].
|
||||
fn implementation_version(self) -> &'static str {
|
||||
match self {
|
||||
Self::Py37 => "3.7.0",
|
||||
Self::Py38 => "3.8.0",
|
||||
Self::Py39 => "3.9.0",
|
||||
Self::Py310 => "3.10.0",
|
||||
Self::Py311 => "3.11.0",
|
||||
Self::Py312 => "3.12.0",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [`MarkerEnvironment`] compatible with the given [`PythonVersion`], based on
|
||||
/// a base [`MarkerEnvironment`].
|
||||
///
|
||||
/// The returned [`MarkerEnvironment`] will preserve the base environment's platform markers,
|
||||
/// but override its Python version markers.
|
||||
pub(crate) fn markers(self, base: &MarkerEnvironment) -> MarkerEnvironment {
|
||||
let mut markers = base.clone();
|
||||
// Ex) `python_version == "3.12"`
|
||||
markers.python_version = StringVersion::from_str(self.python_version()).unwrap();
|
||||
// Ex) `python_full_version == "3.12.0"`
|
||||
markers.python_full_version = StringVersion::from_str(self.python_full_version()).unwrap();
|
||||
// Ex) `implementation_version == "3.12.0"`
|
||||
if markers.implementation_name == "cpython" {
|
||||
markers.implementation_version =
|
||||
StringVersion::from_str(self.implementation_version()).unwrap();
|
||||
}
|
||||
markers
|
||||
}
|
||||
}
|
|
@ -554,6 +554,82 @@ optional-dependencies.foo = [
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a specific version of Black at Python 3.12.
|
||||
#[test]
|
||||
fn compile_python_312() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = temp_dir.child(".venv");
|
||||
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("venv")
|
||||
.arg(venv.as_os_str())
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.current_dir(&temp_dir)
|
||||
.assert()
|
||||
.success();
|
||||
venv.assert(predicates::path::is_dir());
|
||||
|
||||
let requirements_in = temp_dir.child("requirements.in");
|
||||
requirements_in.touch()?;
|
||||
requirements_in.write_str("black==23.10.1")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-compile")
|
||||
.arg("requirements.in")
|
||||
.arg("--python-version")
|
||||
.arg("py312")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a specific version of Black at Python 3.7.
|
||||
#[test]
|
||||
fn compile_python_37() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = temp_dir.child(".venv");
|
||||
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("venv")
|
||||
.arg(venv.as_os_str())
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.current_dir(&temp_dir)
|
||||
.assert()
|
||||
.success();
|
||||
venv.assert(predicates::path::is_dir());
|
||||
|
||||
let requirements_in = temp_dir.child("requirements.in");
|
||||
requirements_in.touch()?;
|
||||
requirements_in.write_str("black==23.10.1")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-compile")
|
||||
.arg("requirements.in")
|
||||
.arg("--python-version")
|
||||
.arg("py37")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a specific Flask wheel via a URL dependency.
|
||||
#[test]
|
||||
fn compile_wheel_url_dependency() -> Result<()> {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
source: crates/puffin-cli/tests/pip_compile.rs
|
||||
info:
|
||||
program: puffin
|
||||
args:
|
||||
- pip-compile
|
||||
- requirements.in
|
||||
- "--python-version"
|
||||
- py312
|
||||
- "--cache-dir"
|
||||
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpbKzceW
|
||||
env:
|
||||
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpZkKRNz/.venv
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by Puffin v0.0.1 via the following command:
|
||||
# [BIN_PATH] pip-compile requirements.in --python-version py312 --cache-dir [CACHE_DIR]
|
||||
black==23.10.1
|
||||
click==8.1.7
|
||||
# via black
|
||||
mypy-extensions==1.0.0
|
||||
# via black
|
||||
packaging==23.2
|
||||
# via black
|
||||
pathspec==0.11.2
|
||||
# via black
|
||||
platformdirs==3.11.0
|
||||
# via black
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
source: crates/puffin-cli/tests/pip_compile.rs
|
||||
info:
|
||||
program: puffin
|
||||
args:
|
||||
- pip-compile
|
||||
- requirements.in
|
||||
- "--python-version"
|
||||
- py37
|
||||
- "--cache-dir"
|
||||
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpQwHoBA
|
||||
env:
|
||||
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp1TmUIW/.venv
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by Puffin v0.0.1 via the following command:
|
||||
# [BIN_PATH] pip-compile requirements.in --python-version py37 --cache-dir [CACHE_DIR]
|
||||
black==23.10.1
|
||||
click==8.1.7
|
||||
# via black
|
||||
importlib-metadata==6.8.0
|
||||
# via click
|
||||
mypy-extensions==1.0.0
|
||||
# via black
|
||||
packaging==23.2
|
||||
# via black
|
||||
pathspec==0.11.2
|
||||
# via black
|
||||
platformdirs==3.11.0
|
||||
# via black
|
||||
tomli==2.0.1
|
||||
# via black
|
||||
typing-extensions==4.8.0
|
||||
# via
|
||||
# black
|
||||
# importlib-metadata
|
||||
# platformdirs
|
||||
zipp==3.17.0
|
||||
# via importlib-metadata
|
||||
|
||||
----- stderr -----
|
||||
Resolved 10 packages in [TIME]
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue