Skip to content

Commit

Permalink
Support resolving for an alternate Python distribution (#364)
Browse files Browse the repository at this point in the history
## 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: #183.
  • Loading branch information
charliermarsh committed Nov 8, 2023
1 parent d407bbb commit cfd84d6
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 10 deletions.
23 changes: 13 additions & 10 deletions crates/puffin-cli/src/commands/pip_compile.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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");
Expand All @@ -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> {
Expand Down Expand Up @@ -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();
Expand All @@ -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)]
Expand Down
7 changes: 7 additions & 0 deletions crates/puffin-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -249,6 +255,7 @@ async fn inner() -> Result<ExitStatus> {
args.upgrade.into(),
index_urls,
args.no_build,
args.python_version,
&cache_dir,
printer,
)
Expand Down
70 changes: 70 additions & 0 deletions crates/puffin-cli/src/python_version.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
76 changes: 76 additions & 0 deletions crates/puffin-cli/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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]

Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit cfd84d6

Please sign in to comment.