Allow pip-compile without a venv (#494)

The semantics are a bit unintuitive because `--python-version` is a
preference when looking for a python version without a venv, but if we
don't find that exact version we'll take `python3` and patch the
markers. This will make more sense once we start provisioning python
builds.

We can now resolve black with both python 3.8 and 3.12, with or without
that python version being in scope. In the example below,
`PATH=$HOME/.cargo/bin:/usr/bin` removes the pyenv builds and leaves
only `python3`, which is python 3.11.

```console
$ RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
    0.004108s DEBUG puffin::commands::pip_compile Using Python 3.8 at /home/konsti/.local/bin/python3.8
Resolved 8 packages in 44ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
tomli==2.0.1
    # via black
typing-extensions==4.8.0
    # via black
$ PATH=$HOME/.cargo/bin:/usr/bin RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
    0.004315s DEBUG puffin::commands::pip_compile Using Python 3.11 at /usr/bin/python3
Resolved 8 packages in 43ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
tomli==2.0.1
    # via black
typing-extensions==4.8.0
    # via black
```

```console
$ RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
    0.004216s DEBUG puffin::commands::pip_compile Using Python 3.12 at /home/konsti/.local/bin/python3.12
Resolved 6 packages in 37ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
$ PATH=$HOME/.cargo/bin:/usr/bin RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
    0.004190s DEBUG puffin::commands::pip_compile Using Python 3.11 at /usr/bin/python3
Resolved 6 packages in 39ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
```

Fixes #235.

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
konsti 2024-01-05 16:01:06 +01:00 committed by GitHub
parent 76064cdec2
commit 673bece595
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 21 deletions

View file

@ -20,7 +20,7 @@ use puffin_cache::Cache;
use puffin_client::RegistryClientBuilder;
use puffin_dispatch::BuildDispatch;
use puffin_installer::Downloader;
use puffin_interpreter::Virtualenv;
use puffin_interpreter::{Interpreter, PythonVersion};
use puffin_normalize::ExtraName;
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, ResolutionOptions, Resolver};
use requirements_txt::EditableRequirement;
@ -28,7 +28,6 @@ use requirements_txt::EditableRequirement;
use crate::commands::reporters::{DownloadReporter, ResolverReporter};
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
use crate::python_version::PythonVersion;
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -115,20 +114,19 @@ pub(crate) async fn pip_compile(
// Detect the current Python interpreter.
let platform = Platform::current()?;
let venv = Virtualenv::from_env(platform, &cache)?;
let interpreter = Interpreter::find(python_version.as_ref(), platform, &cache)?;
debug!(
"Using Python {} at {}",
venv.interpreter().markers().python_version,
venv.python_executable().display()
interpreter.markers().python_version,
interpreter.sys_executable().display()
);
// Determine the tags, markers, and interpreter to use for resolution.
let interpreter = venv.interpreter().clone();
let tags = venv.interpreter().tags()?;
let tags = interpreter.tags()?;
let markers = python_version.map_or_else(
|| Cow::Borrowed(venv.interpreter().markers()),
|python_version| Cow::Owned(python_version.markers(venv.interpreter().markers())),
|| Cow::Borrowed(interpreter.markers()),
|python_version| Cow::Owned(python_version.markers(interpreter.markers())),
);
// Instantiate a client.
@ -142,7 +140,7 @@ pub(crate) async fn pip_compile(
&cache,
&interpreter,
&index_urls,
venv.python_executable(),
interpreter.sys_executable().to_path_buf(),
no_build,
)
.with_options(options);
@ -170,7 +168,7 @@ pub(crate) async fn pip_compile(
let downloader = Downloader::new(&cache, tags, &client, &build_dispatch)
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
let editable_wheel_dir = tempdir_in(venv.root())?;
let editable_wheel_dir = tempdir_in(cache.root())?;
let editable_metadata: Vec<_> = downloader
.build_editables(editables, editable_wheel_dir.path())
.await

View file

@ -10,12 +10,12 @@ use colored::Colorize;
use distribution_types::{IndexUrl, IndexUrls};
use puffin_cache::{Cache, CacheArgs};
use puffin_installer::Reinstall;
use puffin_interpreter::PythonVersion;
use puffin_normalize::{ExtraName, PackageName};
use puffin_resolver::{PreReleaseMode, ResolutionMode};
use requirements::ExtrasSpecification;
use crate::commands::{extra_name_with_clap_error, ExitStatus};
use crate::python_version::PythonVersion;
use crate::requirements::RequirementsSource;
#[cfg(target_os = "windows")]
@ -37,7 +37,6 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
mod commands;
mod logging;
mod printer;
mod python_version;
mod requirements;
#[derive(Parser)]
@ -179,7 +178,7 @@ struct PipCompileArgs {
///
/// If a patch version is omitted, the most recent known patch version for that minor version
/// is assumed. For example, `3.7` is mapped to `3.7.17`.
#[arg(long, short, value_enum)]
#[arg(long, short)]
python_version: Option<PythonVersion>,
/// Try to resolve at a past time.

View file

@ -1,84 +0,0 @@
use std::str::FromStr;
use tracing::debug;
use pep440_rs::Version;
use pep508_rs::{MarkerEnvironment, StringVersion};
#[derive(Debug, Clone)]
pub(crate) struct PythonVersion(StringVersion);
impl FromStr for PythonVersion {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let version = StringVersion::from_str(s)?;
if version.is_dev() {
return Err(format!("Python version {s} is a development release"));
}
if version.is_local() {
return Err(format!("Python version {s} is a local version"));
}
if version.epoch() != 0 {
return Err(format!("Python version {s} has a non-zero epoch"));
}
if version.version < Version::new([3, 7]) {
return Err(format!("Python version {s} must be >= 3.7"));
}
if version.version >= Version::new([4, 0]) {
return Err(format!("Python version {s} must be < 4.0"));
}
// If the version lacks a patch, assume the most recent known patch for that minor version.
match version.release() {
[3, 7] => {
debug!("Assuming Python 3.7.17");
Ok(Self(StringVersion::from_str("3.7.17")?))
}
[3, 8] => {
debug!("Assuming Python 3.8.18");
Ok(Self(StringVersion::from_str("3.8.18")?))
}
[3, 9] => {
debug!("Assuming Python 3.9.18");
Ok(Self(StringVersion::from_str("3.9.18")?))
}
[3, 10] => {
debug!("Assuming Python 3.10.13");
Ok(Self(StringVersion::from_str("3.10.13")?))
}
[3, 11] => {
debug!("Assuming Python 3.11.6");
Ok(Self(StringVersion::from_str("3.11.6")?))
}
[3, 12] => {
debug!("Assuming Python 3.12.0");
Ok(Self(StringVersion::from_str("3.12.0")?))
}
_ => Ok(Self(version)),
}
}
}
impl PythonVersion {
/// 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) `implementation_version == "3.12.0"`
if markers.implementation_name == "cpython" {
markers.implementation_version = self.0.clone();
}
// Ex) `python_full_version == "3.12.0"`
markers.python_full_version = self.0.clone();
// Ex) `python_version == "3.12"`
markers.python_version = self.0;
markers
}
}