mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-26 20:19:08 +00:00
Show URLs and version together for installed, URL-based dependencies (#690)
The snapshot test changes will give you a sense for the impact of the change and the output formatting. Closes https://github.com/astral-sh/puffin/issues/686.
This commit is contained in:
parent
365c860e27
commit
31afb39a10
11 changed files with 228 additions and 93 deletions
|
@ -49,7 +49,7 @@ impl Metadata for CachedDirectUrlDist {
|
|||
}
|
||||
|
||||
fn version_or_url(&self) -> VersionOrUrl {
|
||||
VersionOrUrl::Url(&self.url)
|
||||
VersionOrUrl::VersionedUrl(self.url.raw(), &self.filename.version)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use url::Url;
|
|||
|
||||
use pep440_rs::Version;
|
||||
use puffin_normalize::PackageName;
|
||||
use pypi_types::{DirectUrl, Metadata21};
|
||||
|
||||
use crate::{Metadata, VersionOrUrl};
|
||||
|
||||
|
@ -31,7 +30,8 @@ pub struct InstalledRegistryDist {
|
|||
pub struct InstalledDirectUrlDist {
|
||||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
pub url: DirectUrl,
|
||||
pub url: Url,
|
||||
pub editable: bool,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
|
@ -51,8 +51,7 @@ impl Metadata for InstalledDirectUrlDist {
|
|||
}
|
||||
|
||||
fn version_or_url(&self) -> VersionOrUrl {
|
||||
// TODO(charlie): Convert a `DirectUrl` to `Url`.
|
||||
VersionOrUrl::Version(&self.version)
|
||||
VersionOrUrl::VersionedUrl(&self.url, &self.version)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +93,8 @@ impl InstalledDist {
|
|||
Ok(Some(Self::Url(InstalledDirectUrlDist {
|
||||
name,
|
||||
version,
|
||||
url: direct_url,
|
||||
editable: matches!(&direct_url, pypi_types::DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
||||
url: Url::from(direct_url),
|
||||
path: path.to_path_buf(),
|
||||
})))
|
||||
} else {
|
||||
|
@ -125,31 +125,28 @@ impl InstalledDist {
|
|||
}
|
||||
|
||||
/// Read the `direct_url.json` file from a `.dist-info` directory.
|
||||
fn direct_url(path: &Path) -> Result<Option<DirectUrl>> {
|
||||
fn direct_url(path: &Path) -> Result<Option<pypi_types::DirectUrl>> {
|
||||
let path = path.join("direct_url.json");
|
||||
let Ok(file) = fs_err::File::open(path) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let direct_url = serde_json::from_reader::<fs_err::File, DirectUrl>(file)?;
|
||||
let direct_url = serde_json::from_reader::<fs_err::File, pypi_types::DirectUrl>(file)?;
|
||||
Ok(Some(direct_url))
|
||||
}
|
||||
|
||||
/// Read the `METADATA` file from a `.dist-info` directory.
|
||||
pub fn metadata(&self) -> Result<Metadata21> {
|
||||
pub fn metadata(&self) -> Result<pypi_types::Metadata21> {
|
||||
let path = self.path().join("METADATA");
|
||||
let contents = fs::read(&path)?;
|
||||
Metadata21::parse(&contents)
|
||||
pypi_types::Metadata21::parse(&contents)
|
||||
.with_context(|| format!("Failed to parse METADATA file at: {}", path.display()))
|
||||
}
|
||||
|
||||
/// Return the [`Url`] of the distribution, if it is editable.
|
||||
pub fn editable(&self) -> Option<&Url> {
|
||||
pub fn as_editable(&self) -> Option<&Url> {
|
||||
match self {
|
||||
Self::Url(InstalledDirectUrlDist {
|
||||
url: DirectUrl::LocalDirectory { url, dir_info },
|
||||
..
|
||||
}) if dir_info.editable == Some(true) => Some(url),
|
||||
_ => None,
|
||||
Self::Registry(_) => None,
|
||||
Self::Url(dist) => dist.editable.then_some(&dist.url),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,13 @@ pub enum VersionOrUrl<'a> {
|
|||
Version(&'a Version),
|
||||
/// A URL, used to identify a distribution at an arbitrary location.
|
||||
Url(&'a VerbatimUrl),
|
||||
/// A URL, used to identify a distribution at an arbitrary location, along with the version
|
||||
/// specifier to which it resolved. This is typically derived from a distribution that's already
|
||||
/// been built and perhaps even installed on-disk, as the version specifier is not available
|
||||
/// from the URL itself. As such, the URL is not guaranteed to be verbatim, as it could've been
|
||||
/// serialized to disk and deserialized back from the virtual environment's `direct_url.json`.
|
||||
/// TODO(charlie): Separate into a distinct enum to avoid this confusion.
|
||||
VersionedUrl(&'a Url, &'a Version),
|
||||
}
|
||||
|
||||
impl Verbatim for VersionOrUrl<'_> {
|
||||
|
@ -82,6 +89,7 @@ impl Verbatim for VersionOrUrl<'_> {
|
|||
match self {
|
||||
VersionOrUrl::Version(version) => Cow::Owned(format!("=={version}")),
|
||||
VersionOrUrl::Url(url) => Cow::Owned(format!(" @ {}", url.verbatim())),
|
||||
VersionOrUrl::VersionedUrl(url, ..) => Cow::Owned(format!(" @ {url}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +99,7 @@ impl std::fmt::Display for VersionOrUrl<'_> {
|
|||
match self {
|
||||
VersionOrUrl::Version(version) => write!(f, "=={version}"),
|
||||
VersionOrUrl::Url(url) => write!(f, " @ {url}"),
|
||||
VersionOrUrl::VersionedUrl(url, version) => write!(f, "=={version} (from {url})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ pub trait Metadata {
|
|||
format!("{}-{}", self.name().as_dist_info_name(), version)
|
||||
}
|
||||
VersionOrUrl::Url(url) => puffin_cache::digest(&CanonicalUrl::new(url)),
|
||||
VersionOrUrl::VersionedUrl(url, ..) => puffin_cache::digest(&CanonicalUrl::new(url)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -234,6 +234,9 @@ impl puffin_resolver::ResolverReporter for ResolverReporter {
|
|||
VersionOrUrl::Url(url) => {
|
||||
self.progress.set_message(format!("{name} @ {url}"));
|
||||
}
|
||||
VersionOrUrl::VersionedUrl(url, ..) => {
|
||||
self.progress.set_message(format!("{name} @ {url}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -432,12 +432,11 @@ fn install_editable() -> Result<()> {
|
|||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
let current_dir = std::env::current_dir()?
|
||||
.join("..")
|
||||
.join("..")
|
||||
.canonicalize()?;
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = current_dir.join("..").join("..").canonicalize()?;
|
||||
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((current_dir.to_str().unwrap(), "[CURRENT_DIR]"));
|
||||
filters.push((workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"));
|
||||
|
||||
// Install the editable package.
|
||||
insta::with_settings!({
|
||||
|
@ -461,7 +460,7 @@ fn install_editable() -> Result<()> {
|
|||
Downloaded 1 package in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ numpy==1.26.2
|
||||
+ poetry-editable @ file://[CURRENT_DIR]/scripts/editable-installs/poetry_editable/
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -520,8 +519,8 @@ fn install_editable() -> Result<()> {
|
|||
+ packaging==23.2
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.1.0
|
||||
- poetry-editable==0.1.0
|
||||
+ poetry-editable @ file://[CURRENT_DIR]/scripts/editable-installs/poetry_editable/
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
+ yarl==1.9.4
|
||||
"###);
|
||||
});
|
||||
|
@ -549,9 +548,9 @@ fn install_editable() -> Result<()> {
|
|||
Built 2 editables in [TIME]
|
||||
Resolved 16 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ maturin-editable @ file://[CURRENT_DIR]/scripts/editable-installs/maturin_editable/
|
||||
- poetry-editable==0.1.0
|
||||
+ poetry-editable @ file://[CURRENT_DIR]/scripts/editable-installs/poetry_editable/
|
||||
+ maturin-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/maturin_editable/)
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -564,12 +563,11 @@ fn install_editable_and_registry() -> Result<()> {
|
|||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
let current_dir = std::env::current_dir()?
|
||||
.join("..")
|
||||
.join("..")
|
||||
.canonicalize()?;
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = current_dir.join("..").join("..").canonicalize()?;
|
||||
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((current_dir.to_str().unwrap(), "[CURRENT_DIR]"));
|
||||
filters.push((workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"));
|
||||
|
||||
// Install the registry-based version of Black.
|
||||
insta::with_settings!({
|
||||
|
@ -627,7 +625,7 @@ fn install_editable_and_registry() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- black==23.12.0
|
||||
+ black @ file://[CURRENT_DIR]/scripts/editable-installs/black_editable/
|
||||
+ black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -671,7 +669,7 @@ fn install_editable_and_registry() -> Result<()> {
|
|||
Resolved 6 packages in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- black==0.1.0
|
||||
- black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable/)
|
||||
+ black==23.10.0
|
||||
"###);
|
||||
});
|
||||
|
|
|
@ -542,7 +542,7 @@ fn install_url() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl
|
||||
+ werkzeug==2.0.0 (from https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -583,7 +583,7 @@ fn install_git_commit() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -624,7 +624,7 @@ fn install_git_tag() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ git+https://github.com/pallets/werkzeug.git@2.0.0
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@2.0.0)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -665,8 +665,8 @@ fn install_git_subdirectories() -> Result<()> {
|
|||
Resolved 2 packages in [TIME]
|
||||
Downloaded 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ example-pkg-a @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_a
|
||||
+ example-pkg-b @ git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_b
|
||||
+ example-pkg-a==1 (from git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_a)
|
||||
+ example-pkg-b==1 (from git+https://github.com/pypa/sample-namespace-packages.git@df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45#subdirectory=pkg_resources/pkg_b)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -748,7 +748,7 @@ fn install_sdist_url() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ https://files.pythonhosted.org/packages/63/69/5702e5eb897d1a144001e21d676676bcb87b88c0862f947509ea95ea54fc/Werkzeug-0.9.6.tar.gz
|
||||
+ werkzeug==0.9.6 (from https://files.pythonhosted.org/packages/63/69/5702e5eb897d1a144001e21d676676bcb87b88c0862f947509ea95ea54fc/Werkzeug-0.9.6.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -905,7 +905,7 @@ fn install_version_then_install_url() -> Result<()> {
|
|||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- werkzeug==2.0.0
|
||||
+ werkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl
|
||||
+ werkzeug==2.0.0 (from https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1042,7 +1042,7 @@ fn install_local_wheel() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli @ file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1089,7 +1089,7 @@ fn install_local_source_distribution() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ wheel @ file://[TEMP_DIR]/wheel-0.42.0.tar.gz
|
||||
+ wheel==0.42.0 (from file://[TEMP_DIR]/wheel-0.42.0.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1135,7 +1135,7 @@ fn install_ujson() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ ujson @ https://files.pythonhosted.org/packages/43/1a/b0a027144aa5c8f4ea654f4afdd634578b450807bb70b9f8bad00d6f6d3c/ujson-5.7.0.tar.gz
|
||||
+ ujson==5.7.0 (from https://files.pythonhosted.org/packages/43/1a/b0a027144aa5c8f4ea654f4afdd634578b450807bb70b9f8bad00d6f6d3c/ujson-5.7.0.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1181,7 +1181,7 @@ fn install_dtls_socket() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ dtlssocket @ https://files.pythonhosted.org/packages/58/42/0a0442118096eb9fbc9dc70b45aee2957f7546b80545e2a05bd839380519/DTLSSocket-0.1.16.tar.gz
|
||||
+ dtlssocket==0.1.16 (from https://files.pythonhosted.org/packages/58/42/0a0442118096eb9fbc9dc70b45aee2957f7546b80545e2a05bd839380519/DTLSSocket-0.1.16.tar.gz)
|
||||
warning: The package `dtlssocket` requires `cython <3`, but it's not installed.
|
||||
"###);
|
||||
});
|
||||
|
@ -1220,7 +1220,7 @@ fn install_url_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1246,7 +1246,7 @@ fn install_url_source_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1293,7 +1293,7 @@ fn install_url_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/62/06/d5604a70d160f6a6ca5fd2ba25597c24abd5c5ca5f437263d177ac242308/tqdm-4.66.1.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1332,7 +1332,7 @@ fn install_git_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1358,7 +1358,7 @@ fn install_git_source_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1405,7 +1405,7 @@ fn install_git_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1563,7 +1563,7 @@ fn install_path_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ wheel @ file://[TEMP_DIR]/wheel-0.42.0.tar.gz
|
||||
+ wheel==0.42.0 (from file://[TEMP_DIR]/wheel-0.42.0.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1589,7 +1589,7 @@ fn install_path_source_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ wheel @ file://[TEMP_DIR]/wheel-0.42.0.tar.gz
|
||||
+ wheel==0.42.0 (from file://[TEMP_DIR]/wheel-0.42.0.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1636,7 +1636,7 @@ fn install_path_source_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ wheel @ file://[TEMP_DIR]/wheel-0.42.0.tar.gz
|
||||
+ wheel==0.42.0 (from file://[TEMP_DIR]/wheel-0.42.0.tar.gz)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1683,7 +1683,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli @ file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1709,7 +1709,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli @ file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1756,7 +1756,7 @@ fn install_path_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tomli @ file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl
|
||||
+ tomli==3.0.1 (from file://[TEMP_DIR]/tomli-3.0.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1794,7 +1794,7 @@ fn install_url_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1820,7 +1820,7 @@ fn install_url_built_dist_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -1867,7 +1867,7 @@ fn install_url_built_dist_cached() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ tqdm @ https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl
|
||||
+ tqdm==4.66.1 (from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -2087,6 +2087,74 @@ fn reinstall_package() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that we can force reinstall of Git dependencies.
|
||||
#[test]
|
||||
#[cfg(feature = "git")]
|
||||
fn reinstall_git() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
let requirements_txt = temp_dir.child("requirements.txt");
|
||||
requirements_txt.touch()?;
|
||||
requirements_txt.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-sync")
|
||||
.arg("requirements.txt")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
check_command(&venv, "import werkzeug", &temp_dir);
|
||||
|
||||
// Re-run the installation with `--reinstall`.
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-sync")
|
||||
.arg("requirements.txt")
|
||||
.arg("--reinstall-package")
|
||||
.arg("WerkZeug")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###);
|
||||
});
|
||||
|
||||
check_command(&venv, "import werkzeug", &temp_dir);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_editable() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
|
@ -2116,7 +2184,7 @@ fn sync_editable() -> Result<()> {
|
|||
r"file://.*/../../scripts/editable-installs/poetry_editable",
|
||||
"file://[TEMP_DIR]/../../scripts/editable-installs/poetry_editable",
|
||||
),
|
||||
(workspace_dir.to_str().unwrap(), "[CURRENT_DIR]"),
|
||||
(workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"),
|
||||
])
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -2142,9 +2210,9 @@ fn sync_editable() -> Result<()> {
|
|||
Downloaded 2 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ boltons==23.1.1
|
||||
+ maturin-editable @ file://[CURRENT_DIR]/scripts/editable-installs/maturin_editable/
|
||||
+ maturin-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/maturin_editable/)
|
||||
+ numpy==1.26.2
|
||||
+ poetry-editable @ file://[CURRENT_DIR]/scripts/editable-installs/poetry_editable
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -2169,8 +2237,8 @@ fn sync_editable() -> Result<()> {
|
|||
Built 1 editable in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- poetry-editable==0.1.0
|
||||
+ poetry-editable @ file://[CURRENT_DIR]/scripts/editable-installs/poetry_editable
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -2253,7 +2321,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
.iter()
|
||||
.chain(&[
|
||||
(filter_path.as_str(), "requirements.txt"),
|
||||
(workspace_dir.to_str().unwrap(), "[CURRENT_DIR]"),
|
||||
(workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"),
|
||||
])
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -2297,7 +2365,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
.iter()
|
||||
.chain(&[
|
||||
(filter_path.as_str(), "requirements.txt"),
|
||||
(workspace_dir.to_str().unwrap(), "[CURRENT_DIR]"),
|
||||
(workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"),
|
||||
])
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -2320,7 +2388,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- black==24.1a1
|
||||
+ black @ file://[CURRENT_DIR]/scripts/editable-installs/black_editable/
|
||||
+ black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -2337,7 +2405,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
.iter()
|
||||
.chain(&[
|
||||
(filter_path.as_str(), "requirements.txt"),
|
||||
(workspace_dir.to_str().unwrap(), "[CURRENT_DIR]"),
|
||||
(workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"),
|
||||
])
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -2372,7 +2440,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
.iter()
|
||||
.chain(&[
|
||||
(filter_path.as_str(), "requirements.txt"),
|
||||
(workspace_dir.to_str().unwrap(), "[CURRENT_DIR]"),
|
||||
(workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"),
|
||||
])
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -2395,7 +2463,7 @@ fn sync_editable_and_registry() -> Result<()> {
|
|||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- black==0.1.0
|
||||
- black==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/black_editable/)
|
||||
+ black==23.10.0
|
||||
warning: The package `black` requires `click >=8.0.0`, but it's not installed.
|
||||
warning: The package `black` requires `mypy-extensions >=0.4.3`, but it's not installed.
|
||||
|
|
|
@ -289,6 +289,12 @@ fn uninstall_editable_by_name() -> Result<()> {
|
|||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = temp_dir.child(".venv");
|
||||
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = current_dir.join("..").join("..").canonicalize()?;
|
||||
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"));
|
||||
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("venv")
|
||||
.arg(venv.as_os_str())
|
||||
|
@ -320,7 +326,7 @@ fn uninstall_editable_by_name() -> Result<()> {
|
|||
|
||||
// Uninstall the editable by name.
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-uninstall")
|
||||
|
@ -335,7 +341,7 @@ fn uninstall_editable_by_name() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Uninstalled 1 package in [TIME]
|
||||
- poetry-editable==0.1.0
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -354,6 +360,12 @@ fn uninstall_editable_by_path() -> Result<()> {
|
|||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = temp_dir.child(".venv");
|
||||
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = current_dir.join("..").join("..").canonicalize()?;
|
||||
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"));
|
||||
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("venv")
|
||||
.arg(venv.as_os_str())
|
||||
|
@ -384,7 +396,7 @@ fn uninstall_editable_by_path() -> Result<()> {
|
|||
|
||||
// Uninstall the editable by path.
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-uninstall")
|
||||
|
@ -399,7 +411,7 @@ fn uninstall_editable_by_path() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Uninstalled 1 package in [TIME]
|
||||
- poetry-editable==0.1.0
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
@ -418,6 +430,12 @@ fn uninstall_duplicate_editable() -> Result<()> {
|
|||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = temp_dir.child(".venv");
|
||||
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = current_dir.join("..").join("..").canonicalize()?;
|
||||
|
||||
let mut filters = INSTA_FILTERS.to_vec();
|
||||
filters.push((workspace_dir.to_str().unwrap(), "[WORKSPACE_DIR]"));
|
||||
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("venv")
|
||||
.arg(venv.as_os_str())
|
||||
|
@ -448,7 +466,7 @@ fn uninstall_duplicate_editable() -> Result<()> {
|
|||
|
||||
// Uninstall the editable by both path and name.
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip-uninstall")
|
||||
|
@ -464,7 +482,7 @@ fn uninstall_duplicate_editable() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Uninstalled 1 package in [TIME]
|
||||
- poetry-editable==0.1.0
|
||||
- poetry-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/poetry_editable/)
|
||||
"###);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
|||
use rustc_hash::FxHashSet;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use distribution_types::direct_url::{git_reference, DirectUrl};
|
||||
use distribution_types::direct_url::git_reference;
|
||||
use distribution_types::{BuiltDist, Dist, SourceDist};
|
||||
use distribution_types::{CachedDirectUrlDist, CachedDist, InstalledDist, Metadata};
|
||||
use pep508_rs::{Requirement, VersionOrUrl};
|
||||
|
@ -75,7 +75,7 @@ impl InstallPlan {
|
|||
debug!("Treating editable requirement as immutable: {installed}");
|
||||
|
||||
// Remove from the site-packages index, to avoid marking as extraneous.
|
||||
let Some(editable) = installed.editable() else {
|
||||
let Some(editable) = installed.as_editable() else {
|
||||
warn!("Editable requirement is not editable: {installed}");
|
||||
continue;
|
||||
};
|
||||
|
@ -143,17 +143,9 @@ impl InstallPlan {
|
|||
// If the requirement comes from a direct URL, check by URL.
|
||||
Some(VersionOrUrl::Url(url)) => {
|
||||
if let InstalledDist::Url(distribution) = &distribution {
|
||||
if let Ok(direct_url) = DirectUrl::try_from(url.raw()) {
|
||||
if let Ok(direct_url) =
|
||||
pypi_types::DirectUrl::try_from(&direct_url)
|
||||
{
|
||||
// TODO(charlie): These don't need to be strictly equal. We only care
|
||||
// about a subset of the fields.
|
||||
if direct_url == distribution.url {
|
||||
debug!("Requirement already satisfied: {distribution}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if &distribution.url == url.raw() {
|
||||
debug!("Requirement already satisfied: {distribution}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use url::Url;
|
|||
|
||||
use distribution_types::{InstalledDist, Metadata, VersionOrUrl};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep508_rs::Requirement;
|
||||
use pep508_rs::{Requirement, VerbatimUrl};
|
||||
use puffin_interpreter::Virtualenv;
|
||||
use puffin_normalize::PackageName;
|
||||
use requirements_txt::EditableRequirement;
|
||||
|
@ -59,7 +59,7 @@ impl<'a> SitePackages<'a> {
|
|||
}
|
||||
|
||||
// Index the distribution by URL.
|
||||
if let Some(url) = dist_info.editable() {
|
||||
if let Some(url) = dist_info.as_editable() {
|
||||
if let Some(existing) = by_url.insert(url.clone(), idx) {
|
||||
let existing = &distributions[existing];
|
||||
anyhow::bail!(
|
||||
|
@ -101,6 +101,9 @@ impl<'a> SitePackages<'a> {
|
|||
))
|
||||
}
|
||||
VersionOrUrl::Url(url) => pep508_rs::VersionOrUrl::Url(url.clone()),
|
||||
VersionOrUrl::VersionedUrl(url, ..) => {
|
||||
pep508_rs::VersionOrUrl::Url(VerbatimUrl::unknown(url.clone()))
|
||||
}
|
||||
}),
|
||||
marker: None,
|
||||
})
|
||||
|
@ -139,7 +142,7 @@ impl<'a> SitePackages<'a> {
|
|||
if let Some(prev) = self.by_name.get_mut(moved.name()) {
|
||||
*prev = idx;
|
||||
}
|
||||
if let Some(url) = moved.editable() {
|
||||
if let Some(url) = moved.as_editable() {
|
||||
if let Some(prev) = self.by_url.get_mut(url) {
|
||||
*prev = idx;
|
||||
}
|
||||
|
|
|
@ -71,3 +71,49 @@ pub enum VcsKind {
|
|||
Bzr,
|
||||
Svn,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VcsKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VcsKind::Git => write!(f, "git"),
|
||||
VcsKind::Hg => write!(f, "hg"),
|
||||
VcsKind::Bzr => write!(f, "bzr"),
|
||||
VcsKind::Svn => write!(f, "svn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DirectUrl> for Url {
|
||||
fn from(value: DirectUrl) -> Self {
|
||||
match value {
|
||||
DirectUrl::LocalDirectory { url, .. } => url,
|
||||
DirectUrl::ArchiveUrl {
|
||||
mut url,
|
||||
subdirectory,
|
||||
archive_info: _,
|
||||
} => {
|
||||
if let Some(subdirectory) = subdirectory {
|
||||
url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
|
||||
}
|
||||
url
|
||||
}
|
||||
DirectUrl::VcsUrl {
|
||||
url,
|
||||
vcs_info,
|
||||
subdirectory,
|
||||
} => {
|
||||
let mut url =
|
||||
Url::parse(&format!("{}+{}", vcs_info.vcs, url)).expect("VCS URL is invalid");
|
||||
if let Some(commit_id) = vcs_info.commit_id {
|
||||
url.set_path(&format!("{}@{commit_id}", url.path()));
|
||||
} else if let Some(requested_revision) = vcs_info.requested_revision {
|
||||
url.set_path(&format!("{}@{requested_revision}", url.path()));
|
||||
}
|
||||
if let Some(subdirectory) = subdirectory {
|
||||
url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue