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:
Charlie Marsh 2023-11-08 15:19:16 -08:00 committed by GitHub
parent d407bbbee6
commit cfd84d6365
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 10 deletions

View file

@ -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)]

View file

@ -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,
)

View 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
}
}

View file

@ -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<()> {

View file

@ -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]

View file

@ -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]