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:
Charlie Marsh 2023-12-18 17:21:37 -05:00 committed by GitHub
parent 365c860e27
commit 31afb39a10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 228 additions and 93 deletions

View file

@ -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)
}
}

View file

@ -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),
}
}
}

View file

@ -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})"),
}
}
}

View file

@ -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)),
})
}
}

View file

@ -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}"));
}
}
}

View file

@ -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
"###);
});

View file

@ -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.

View file

@ -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/)
"###);
});

View file

@ -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,21 +143,13 @@ 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 {
if &distribution.url == url.raw() {
debug!("Requirement already satisfied: {distribution}");
continue;
}
}
}
}
}
}
reinstalls.push(distribution);
}

View file

@ -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;
}

View file

@ -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
}
}
}
}