Ignore origin when comparing installed tools (#16055)

## Summary

This field gets dropped when you serialize and deserialize, so we should
ignore it when comparing indexes.

Closes https://github.com/astral-sh/uv/issues/16051.
This commit is contained in:
Charlie Marsh 2025-09-29 13:23:18 -04:00 committed by GitHub
parent fd908aa439
commit 170ab1cd7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 197 additions and 2 deletions

View File

@ -48,7 +48,7 @@ impl IndexCacheControl {
} }
} }
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Index { pub struct Index {
@ -156,6 +156,92 @@ pub struct Index {
pub cache_control: Option<IndexCacheControl>, pub cache_control: Option<IndexCacheControl>,
} }
impl PartialEq for Index {
fn eq(&self, other: &Self) -> bool {
let Self {
name,
url,
explicit,
default,
origin: _,
format,
publish_url,
authenticate,
ignore_error_codes,
cache_control,
} = self;
*url == other.url
&& *name == other.name
&& *explicit == other.explicit
&& *default == other.default
&& *format == other.format
&& *publish_url == other.publish_url
&& *authenticate == other.authenticate
&& *ignore_error_codes == other.ignore_error_codes
&& *cache_control == other.cache_control
}
}
impl Eq for Index {}
impl PartialOrd for Index {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Index {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let Self {
name,
url,
explicit,
default,
origin: _,
format,
publish_url,
authenticate,
ignore_error_codes,
cache_control,
} = self;
url.cmp(&other.url)
.then_with(|| name.cmp(&other.name))
.then_with(|| explicit.cmp(&other.explicit))
.then_with(|| default.cmp(&other.default))
.then_with(|| format.cmp(&other.format))
.then_with(|| publish_url.cmp(&other.publish_url))
.then_with(|| authenticate.cmp(&other.authenticate))
.then_with(|| ignore_error_codes.cmp(&other.ignore_error_codes))
.then_with(|| cache_control.cmp(&other.cache_control))
}
}
impl std::hash::Hash for Index {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let Self {
name,
url,
explicit,
default,
origin: _,
format,
publish_url,
authenticate,
ignore_error_codes,
cache_control,
} = self;
url.hash(state);
name.hash(state);
explicit.hash(state);
default.hash(state);
format.hash(state);
publish_url.hash(state);
authenticate.hash(state);
ignore_error_codes.hash(state);
cache_control.hash(state);
}
}
#[derive( #[derive(
Default, Default,
Debug, Debug,

View File

@ -3906,7 +3906,6 @@ fn tool_install_default_credentials() -> Result<()> {
sys.argv[0] = sys.argv[0][:-4] sys.argv[0] = sys.argv[0][:-4]
sys.exit(main()) sys.exit(main())
"###); "###);
}); });
insta::with_settings!({ insta::with_settings!({
@ -4094,6 +4093,116 @@ fn tool_install_with_executables_from_no_entrypoints() {
"###); "###);
} }
#[test]
fn tool_install_find_links() {
let context = TestContext::new("3.13").with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// Run with `--find-links`.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--find-links")
.arg(context.workspace_root.join("scripts/links/"))
.arg("basic-app")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r"
success: true
exit_code: 0
----- stdout -----
Hello from basic-app!
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ basic-app==0.1.0
");
// Install with `--find-links`.
uv_snapshot!(context.filters(), context.tool_install()
.arg("--find-links")
.arg(context.workspace_root.join("scripts/links/"))
.arg("basic-app")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ basic-app==0.1.0
Installed 1 executable: basic-app
");
tool_dir
.child("basic-app")
.assert(predicate::path::is_dir());
tool_dir
.child("basic-app")
.child("uv-receipt.toml")
.assert(predicate::path::exists());
let executable = bin_dir.child(format!("basic-app{}", std::env::consts::EXE_SUFFIX));
assert!(executable.exists());
// On Windows, we can't snapshot an executable file.
#[cfg(not(windows))]
insta::with_settings!({
filters => context.filters(),
}, {
// Should run basic-app in the virtual environment
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r#"
#![TEMP_DIR]/tools/basic-app/bin/python
# -*- coding: utf-8 -*-
import sys
from basic_app import main
if __name__ == "__main__":
if sys.argv[0].endswith("-script.pyw"):
sys.argv[0] = sys.argv[0][:-11]
elif sys.argv[0].endswith(".exe"):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())
"#);
});
// Run the installed version with `--find-links` on the CLI again.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--offline")
.arg("--find-links")
.arg(context.workspace_root.join("scripts/links/"))
.arg("basic-app")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r"
success: true
exit_code: 0
----- stdout -----
Hello from basic-app!
----- stderr -----
");
// Run the installed version without `--find-links`.
uv_snapshot!(context.filters(), context.tool_run()
.arg("--offline")
.arg("basic-app")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving tool dependencies:
Because only basic-app==0.1 is available and basic-app==0.1 needs to be downloaded from a registry, we can conclude that all versions of basic-app cannot be used.
And because you require basic-app, we can conclude that your requirements are unsatisfiable.
hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.
");
}
#[test] #[test]
fn tool_install_python_platform() { fn tool_install_python_platform() {
let context = TestContext::new("3.12") let context = TestContext::new("3.12")

Binary file not shown.