mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
parent
f86d9b1c31
commit
69c72b6fa1
5 changed files with 146 additions and 10 deletions
|
@ -51,16 +51,13 @@ pub enum Error {
|
|||
/// The wheel is broken
|
||||
#[error("The wheel is invalid: {0}")]
|
||||
InvalidWheel(String),
|
||||
/// pyproject.toml or poetry.lock are broken
|
||||
#[error("The poetry dependency specification (pyproject.toml or poetry.lock) is broken (try `poetry update`?): {0}")]
|
||||
InvalidPoetry(String),
|
||||
/// Doesn't follow file name schema
|
||||
#[error(transparent)]
|
||||
InvalidWheelFileName(#[from] distribution_filename::WheelFilenameError),
|
||||
/// The caller must add the name of the zip file (See note on type).
|
||||
#[error("Failed to read {0} from zip file")]
|
||||
Zip(String, #[source] ZipError),
|
||||
#[error("Failed to run python subcommand")]
|
||||
#[error("Failed to run Python subcommand")]
|
||||
PythonSubcommand(#[source] io::Error),
|
||||
#[error("Failed to move data files")]
|
||||
WalkDir(#[from] walkdir::Error),
|
||||
|
@ -86,6 +83,14 @@ pub enum Error {
|
|||
MultipleDistInfo(String),
|
||||
#[error("Invalid wheel size")]
|
||||
InvalidSize,
|
||||
#[error("Invalid package name")]
|
||||
InvalidName(#[from] puffin_normalize::InvalidNameError),
|
||||
#[error("Invalid package version")]
|
||||
InvalidVersion(#[from] pep440_rs::VersionParseError),
|
||||
#[error("Wheel package name does not match filename: {0} != {1}")]
|
||||
MismatchedName(PackageName, PackageName),
|
||||
#[error("Wheel version does not match filename: {0} != {1}")]
|
||||
MismatchedVersion(Version, Version),
|
||||
}
|
||||
|
||||
/// Find the `dist-info` directory from a list of files.
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
//! reading from a zip file.
|
||||
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use configparser::ini::Ini;
|
||||
use distribution_filename::WheelFilename;
|
||||
use fs_err as fs;
|
||||
use fs_err::File;
|
||||
use pep440_rs::Version;
|
||||
use puffin_normalize::PackageName;
|
||||
use tempfile::tempdir_in;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
|
@ -29,6 +33,7 @@ use crate::{read_record_file, Error, Script};
|
|||
pub fn install_wheel(
|
||||
location: &InstallLocation<impl AsRef<Path>>,
|
||||
wheel: impl AsRef<Path>,
|
||||
filename: &WheelFilename,
|
||||
direct_url: Option<&DirectUrl>,
|
||||
installer: Option<&str>,
|
||||
link_mode: LinkMode,
|
||||
|
@ -52,7 +57,20 @@ pub fn install_wheel(
|
|||
|
||||
let dist_info_prefix = find_dist_info(&wheel)?;
|
||||
let metadata = dist_info_metadata(&dist_info_prefix, &wheel)?;
|
||||
let (name, _version) = parse_metadata(&dist_info_prefix, &metadata)?;
|
||||
let (name, version) = parse_metadata(&dist_info_prefix, &metadata)?;
|
||||
|
||||
// Validate the wheel name and version.
|
||||
{
|
||||
let name = PackageName::from_str(&name)?;
|
||||
if name != filename.name {
|
||||
return Err(Error::MismatchedName(name, filename.name.clone()));
|
||||
}
|
||||
|
||||
let version = Version::from_str(&version)?;
|
||||
if version != filename.version {
|
||||
return Err(Error::MismatchedVersion(version, filename.version.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// We're going step by step though
|
||||
// https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||
use std::io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::{env, io, iter};
|
||||
|
||||
use configparser::ini::Ini;
|
||||
|
@ -20,6 +21,8 @@ use zip::write::FileOptions;
|
|||
use zip::{ZipArchive, ZipWriter};
|
||||
|
||||
use distribution_filename::WheelFilename;
|
||||
use pep440_rs::Version;
|
||||
use puffin_normalize::PackageName;
|
||||
use pypi_types::DirectUrl;
|
||||
|
||||
use crate::install_location::{InstallLocation, LockedDir};
|
||||
|
@ -955,7 +958,20 @@ pub fn install_wheel(
|
|||
.1
|
||||
.to_string();
|
||||
let metadata = dist_info_metadata(&dist_info_prefix, &mut archive)?;
|
||||
let (name, _version) = parse_metadata(&dist_info_prefix, &metadata)?;
|
||||
let (name, version) = parse_metadata(&dist_info_prefix, &metadata)?;
|
||||
|
||||
// Validate the wheel name and version.
|
||||
{
|
||||
let name = PackageName::from_str(&name)?;
|
||||
if name != filename.name {
|
||||
return Err(Error::MismatchedName(name, filename.name.clone()));
|
||||
}
|
||||
|
||||
let version = Version::from_str(&version)?;
|
||||
if version != filename.version {
|
||||
return Err(Error::MismatchedVersion(version, filename.version.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let record_path = format!("{dist_info_prefix}.dist-info/RECORD");
|
||||
let mut record = read_record_file(&mut archive.by_name(&record_path).map_err(|err| {
|
||||
|
|
|
@ -49,6 +49,7 @@ impl<'a> Installer<'a> {
|
|||
install_wheel_rs::linker::install_wheel(
|
||||
&location,
|
||||
wheel.path(),
|
||||
wheel.filename(),
|
||||
wheel
|
||||
.direct_url()?
|
||||
.as_ref()
|
||||
|
|
|
@ -1210,6 +1210,102 @@ fn install_local_wheel() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a wheel whose actual version doesn't match the version encoded in the filename.
|
||||
#[test]
|
||||
fn mismatched_version() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
// Download a wheel.
|
||||
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl")?;
|
||||
let archive = temp_dir.child("tomli-3.7.2-py3-none-any.whl");
|
||||
let mut archive_file = std::fs::File::create(&archive)?;
|
||||
std::io::copy(&mut response.bytes()?.as_ref(), &mut archive_file)?;
|
||||
|
||||
let requirements_txt = temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(&format!("tomli @ file://{}", archive.path().display()))?;
|
||||
|
||||
// In addition to the standard filters, remove the temporary directory from the snapshot.
|
||||
let filters: Vec<_> = iter::once((r"file://.*/", "file://[TEMP_DIR]/"))
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip")
|
||||
.arg("sync")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
error: Failed to install: tomli-3.7.2-py3-none-any.whl (tomli==3.7.2 (from file://[TEMP_DIR]/tomli-3.7.2-py3-none-any.whl))
|
||||
Caused by: Wheel version does not match filename: 2.0.1 != 3.7.2
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a wheel whose actual name doesn't match the name encoded in the filename.
|
||||
#[test]
|
||||
fn mismatched_name() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
// Download a wheel.
|
||||
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl")?;
|
||||
let archive = temp_dir.child("foo-2.0.1-py3-none-any.whl");
|
||||
let mut archive_file = std::fs::File::create(&archive)?;
|
||||
std::io::copy(&mut response.bytes()?.as_ref(), &mut archive_file)?;
|
||||
|
||||
let requirements_txt = temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(&format!("tomli @ file://{}", archive.path().display()))?;
|
||||
|
||||
// In addition to the standard filters, remove the temporary directory from the snapshot.
|
||||
let filters: Vec<_> = iter::once((r"file://.*/", "file://[TEMP_DIR]/"))
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip")
|
||||
.arg("sync")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
error: Failed to install: foo-2.0.1-py3-none-any.whl (foo==2.0.1 (from file://[TEMP_DIR]/foo-2.0.1-py3-none-any.whl))
|
||||
Caused by: Wheel package name does not match filename: tomli != foo
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a local source distribution.
|
||||
#[test]
|
||||
fn install_local_source_distribution() -> Result<()> {
|
||||
|
@ -1847,7 +1943,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
|
||||
// Download a wheel.
|
||||
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl")?;
|
||||
let archive = temp_dir.child("tomli-3.0.1-py3-none-any.whl");
|
||||
let archive = temp_dir.child("tomli-2.0.1-py3-none-any.whl");
|
||||
let mut archive_file = std::fs::File::create(&archive)?;
|
||||
std::io::copy(&mut response.bytes()?.as_ref(), &mut archive_file)?;
|
||||
|
||||
|
@ -1879,7 +1975,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1907,7 +2003,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1956,7 +2052,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
+ tomli==2.0.1 (from file://[TEMP_DIR]/tomli-2.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue