Allow round-trip via freeze command (#1936)

## Summary

We're printing the `Display` representation of `InstalledDist`, which
isn't guaranteed to be (and in fact isn't) a valid PEP 508 requirement,
making it impossible to use the `freeze` output as an input to an
install.

Closes https://github.com/astral-sh/uv/issues/1931.
This commit is contained in:
Charlie Marsh 2024-02-23 15:01:54 -05:00 committed by GitHub
parent 8ff6182815
commit ca489ac3bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 60 additions and 31 deletions

View file

@ -6,7 +6,7 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::debug;
use distribution_types::Name;
use distribution_types::{InstalledDist, Name};
use platform_host::Platform;
use uv_cache::Cache;
use uv_fs::Normalized;
@ -34,7 +34,18 @@ pub(crate) fn pip_freeze(cache: &Cache, strict: bool, mut printer: Printer) -> R
.iter()
.sorted_unstable_by(|a, b| a.name().cmp(b.name()).then(a.version().cmp(b.version())))
{
println!("{dist}");
match dist {
InstalledDist::Registry(dist) => {
println!("{}=={}", dist.name().bold(), dist.version);
}
InstalledDist::Url(dist) => {
if dist.editable {
println!("-e {}", dist.url);
} else {
println!("{} @ {}", dist.name().bold(), dist.url);
}
}
}
}
// Validate that the environment is consistent.

View file

@ -3,6 +3,7 @@
use std::process::Command;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use crate::common::{get_bin, uv_snapshot, TestContext};
@ -22,19 +23,6 @@ fn command(context: &TestContext) -> Command {
command
}
/// Create a `pip sync` command with options shared across scenarios.
fn sync_command(context: &TestContext) -> Command {
let mut command = Command::new(get_bin());
command
.arg("pip")
.arg("sync")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir);
command
}
/// List multiple installed packages in a virtual environment.
#[test]
fn freeze_many() -> Result<()> {
@ -44,20 +32,15 @@ fn freeze_many() -> Result<()> {
requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?;
// Run `pip sync`.
uv_snapshot!(sync_command(&context)
.arg("requirements.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Downloaded 2 packages in [TIME]
Installed 2 packages in [TIME]
+ markupsafe==2.1.3
+ tomli==2.0.1
"###
);
Command::new(get_bin())
.arg("pip")
.arg("sync")
.arg(requirements_txt.path())
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.assert()
.success();
// Run `pip freeze`.
uv_snapshot!(command(&context)
@ -79,8 +62,6 @@ fn freeze_many() -> Result<()> {
#[test]
#[cfg(unix)]
fn freeze_duplicate() -> Result<()> {
use assert_cmd::assert::OutputAssertExt;
use crate::common::{copy_dir_all, INSTA_FILTERS};
// Sync a version of `pip` into a virtual environment.
@ -157,3 +138,40 @@ fn freeze_duplicate() -> Result<()> {
Ok(())
}
/// List a direct URL package in a virtual environment.
#[test]
fn freeze_url() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio\niniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl")?;
// Run `pip sync`.
Command::new(get_bin())
.arg("pip")
.arg("sync")
.arg(requirements_txt.path())
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.assert()
.success();
// Run `pip freeze`.
uv_snapshot!(command(&context)
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
anyio==4.3.0
iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl
----- stderr -----
warning: The package `anyio` requires `idna >=2.8`, but it's not installed.
warning: The package `anyio` requires `sniffio >=1.1`, but it's not installed.
"###
);
Ok(())
}