diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 70f83ee43..083455111 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -1545,6 +1545,7 @@ command's integration tests check its output. | `redirect_poetry_stale_install_risk` | `warnings[]` (warning) | scan `--mode hosted` (poetry): same writer test as above — a warm virtualenv keeps the upstream package after the redirect on Poetry < 1.4 (1.4+ re-installs from the new source); fresh installs pick up the patched wheel. Emitted once per rewritten lock, only on the run that rewrites it. | | `redirect_poetry_source_constraint` | `warnings[]` (warning) | scan `--mode hosted` (poetry): the pyproject pins the redirected package to a named source (`source = "pypi"`) and the lock is in format 2.1+ and was not written by Poetry 1.3 – 2.2 (those writers, and any `lock-version` 1.0 / 1.1 lock, which every Poetry 1.3+ re-solves and refuses (#1503), are refused with `redirect_poetry_lock_unsupported`, also when the lock is already redirected and before a vendored → hosted takeover reverts anything). The rewrite stands, and an already-redirected lock is still confirmed; the detail says a Poetry 1.3 – 2.2 installer would refuse the lock and to drop the `source` key if the project is installed with one. Exit code and `status` unchanged. | | `redirect_poetry_entry_not_found` / `redirect_poetry_missing_sha256` / `redirect_poetry_lock_unsupported` | `warnings[]` (warning) | scan `--mode hosted` (poetry): the lock has no `[[package]]` at the granted version (uv-parity twin of `redirect_uv_entry_not_found`); the grant carries no SHA-256 (gated once per dep, not per lock); the lock is refused — Poetry 0.12 layout (URL sources ignored), an unsupported `lock-version`, a forked package listed at several versions, a user-authored `[package.source]` on another origin (an earlier Socket URL for the same wheel is superseded in place), a package the pyproject pins to a named source (`source = "pypi"`) over a lock written by Poetry 1.3 – 2.2, whose installer refuses that constraint on a URL-sourced package, or over a `lock-version` 1.0 / 1.1 lock, which every Poetry 1.3+ re-solves and refuses the same way (#1503), a malformed `[metadata.files]`/`[metadata.hashes]`, or a wheel whose filename does not match the locked package. Exit code and `status` unchanged (hosted-refusal posture). A vendored → hosted takeover over a Poetry 0.x lock is retracted with this code (wet and `--dry-run`, see **Staged takeover**): the purl stays vendored and patched and is skipped with this code as `skipped` (hosted event). The detail names the remedy and its reach: run `socket-patch vendor --revert` (it reverts EVERY vendored package in the project, not just this one), upgrade to Poetry >= 1.0 and re-lock, then re-run `scan --mode hosted`. | +| `redirect_uv_override_requires_uv_0_5_6` / `redirect_uv_constraints_require_uv_0_5_6` | `warnings[]` (warning) | scan/get `--mode hosted` (uv project and script locks, #1569): the hosted twins of vendored's `pypi_uv_override_requires_uv_0_5_6` / `pypi_uv_constraints_require_uv_0_5_6`. The run wired a transitive package through a `[tool.uv] override-dependencies` entry, its own marked one or the user's (uv before 0.5.5 re-resolves it and a plain `uv sync` reinstalls the upstream wheel), or repointed a `[manifest]` `constraints` / `build-constraints` entry (uv before 0.5.5 rejects it under `uv sync --locked`). The CLI cannot tell those uv releases apart from the lock, so these advise and the redirect still counts. Emitted on a run that writes the package's wiring (a first pin or a re-pin to a superseding patch); a settled re-run is silent. Exit 0. | | `redirect_pdm_refused` / `redirect_pdm_legacy_sync_required` | `warnings[]` (warning) | scan `--mode hosted` (pdm): the `pdm.lock` rewrite was refused — an unsupported `[metadata] lock_version` (the identity-losing `3.1` / `4.0`–`4.2` formats or an untested future format), an unsupported `strategy`, a package listed at several versions (fork) or absent, a user-authored `url`/`path`/VCS/`editable` source, hash-less or malformed `files`, or a wheel whose filename does not match the locked package (`redirect_pdm_refused`); or the lock was written in format `2` (PDM 0.12–1.4), whose upstream freshness bug lets `pdm install` regenerate the lock — use `pdm sync` (`redirect_pdm_legacy_sync_required`). A refused uuid is withheld from every other PyPI rewriter when `pdm.lock` is the install driver, and its patch is not confirmed. Exit code and `status` unchanged (hosted-refusal posture). | | `redirect_bun_lock_unsupported` | `warnings[]` (warning) | scan/get `--mode hosted` (bun): the text lock's `lockfileVersion` is not 0, 1 or 2 (a newer version: update socket-patch, re-locking would reproduce it; no integer: re-lock with Bun ≥ 1.2 — the shared gate's text, identical to vendored's `vendor_lockfile_version_unsupported`), or its `packages` section is not bun's single-line grammar. Nothing rewritten; exit 0 (hosted-refusal posture). | | `redirect_bun_workspace_unsupported` | `warnings[]` (warning) | scan/get `--mode hosted` (bun): a lockfileVersion-0 lock (Bun 1.1.39–1.1.45 `--save-text-lockfile`) holds `workspace:` packages; frozen installs of that grammar cannot keep the hosted tuple. Detail: "Bun version-0 workspace locks cannot preserve hosted tarballs on frozen installs; delete bun.lock and re-run `bun install` with Bun >= 1.2 (which writes lockfileVersion 1, accepted by hosted mode) — a plain in-place `bun install` bumps the version only when a workspace depends on another workspace (e.g. root -> member); otherwise it keeps version 0 or fails to resolve" (measured: Bun 1.2.0 keeps 0, 1.2.23–1.4.2 exit 1 "failed to resolve" on a root that does not depend on its members). Version-1/2 workspace locks are rewritten. Exit 0. | diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index df1087ffb..5faed5a13 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -2338,13 +2338,16 @@ fn describe_skip_reason(reason: &str) -> String { /// package has no lock entry: the count `format_unredirected` turns into its /// "(see the warning below)" hint. `redirect_bun_default_trust_lost` rides in /// the same vector but is about a pin that *was* written (Bun's default trust -/// lost on the hosted URL, #371), so it never explains a missing entry. +/// lost on the hosted URL, #371), so it never explains a missing entry. The +/// uv < 0.5.6 advisories (#1569) likewise describe a written pin. fn lock_entry_warning_count( warnings: &[socket_patch_core::patch::redirect::RewriteWarning], ) -> usize { + use socket_patch_core::patch::redirect::UV_OLD_RELEASE_ADVISORIES; warnings .iter() .filter(|w| w.code != "redirect_bun_default_trust_lost") + .filter(|w| !UV_OLD_RELEASE_ADVISORIES.contains(&w.code.as_str())) .count() } @@ -4396,6 +4399,14 @@ mod tests { lock_entry_warning_count(&[w("redirect_bun_default_trust_lost")]), 0 ); + // Nor the uv < 0.5.6 advisories, which follow a written pin (#1569). + assert_eq!( + lock_entry_warning_count(&[ + w("redirect_uv_override_requires_uv_0_5_6"), + w("redirect_uv_constraints_require_uv_0_5_6"), + ]), + 0 + ); assert_eq!( lock_entry_warning_count(&[ w("redirect_bun_default_trust_lost"), diff --git a/crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs b/crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs index 26db94db1..2362182c3 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs @@ -437,7 +437,8 @@ const LANDED_PIN_ADVISORIES: &[&str] = &[ /// wired outside the root file #699, a uv entry pinned down from another /// locked version #723, a Poetry 0.x lock #945); a rewriter warning naming /// the package; the rewrite's first warning (a lock-level refusal names no -/// package), skipping [`LANDED_PIN_ADVISORIES`]; else [`NOT_PINNED`]. +/// package), skipping [`LANDED_PIN_ADVISORIES`] and the uv < 0.5.6 +/// advisories (#1569); else [`NOT_PINNED`]. async fn explain( root: &std::path::Path, entry: &VendorEntry, @@ -447,19 +448,22 @@ async fn explain( if let Err(w) = socket_patch_core::patch::redirect::preflight_pypi_takeover(root, entry).await { return (w.code.clone(), vec![w]); } - if let Some(w) = dep.and_then(|dep| { - warnings - .iter() - .find(|w| names_package(&w.detail, &dep.name)) - }) { + // The uv < 0.5.6 advisories ride on a pin that landed, and their + // wording ("wheel", "uv", "sync") can read as another package's name, + // so neither pass may pick one (#1569). + let candidates = || { + warnings.iter().filter(|w| { + !socket_patch_core::patch::redirect::UV_OLD_RELEASE_ADVISORIES + .contains(&w.code.as_str()) + }) + }; + if let Some(w) = dep.and_then(|dep| candidates().find(|w| names_package(&w.detail, &dep.name))) + { return (w.code.clone(), vec![w.clone()]); } // A pin that did land can carry a success advisory; it never explains // a pin that did not. - match warnings - .iter() - .find(|w| !LANDED_PIN_ADVISORIES.contains(&w.code.as_str())) - { + match candidates().find(|w| !LANDED_PIN_ADVISORIES.contains(&w.code.as_str())) { Some(w) => (w.code.clone(), vec![w.clone()]), None => (NOT_PINNED.to_string(), Vec::new()), } @@ -674,4 +678,43 @@ mod tests { assert_eq!(code, "redirect_lock_refused"); assert_eq!(explained, vec![warn("redirect_lock_refused")]); } + + /// #1569: another pin's uv advisory says "wheel" and "uv sync", whole + /// words that are also package names; it never explains a staged + /// package that was not pinned, by name or as the lock-wide fallback. + #[tokio::test] + async fn uv_old_release_advisories_never_explain_a_skip() { + let entry: VendorEntry = serde_json::from_value(serde_json::json!({ + "ecosystem": "npm", + "basePurl": "pkg:npm/wheel@1.0.0", + "uuid": "u1", + "artifact": { "path": ".socket/vendor/npm/u1/wheel.tgz" }, + "wiring": [], + })) + .expect("minimal vendor entry"); + let dep = socket_patch_core::patch::redirect::DepOverride { + ecosystem: "npm".into(), + name: "wheel".into(), + namespace: None, + version: "1.0.0".into(), + token: String::new(), + patch_uuid: "u1".into(), + artifact_url: String::new(), + registry_override: None, + integrity: Default::default(), + }; + let warnings: Vec = + socket_patch_core::patch::redirect::UV_OLD_RELEASE_ADVISORIES + .iter() + .map(|code| RewriteWarning { + code: code.to_string(), + detail: "uv.lock: repointed at the hosted wheel; uv sync".into(), + }) + .collect(); + let root = std::path::Path::new("."); + assert_eq!( + explain(root, &entry, Some(&dep), &warnings).await.0, + NOT_PINNED + ); + } } diff --git a/crates/socket-patch-cli/tests/vex_e2e_common/uv.rs b/crates/socket-patch-cli/tests/vex_e2e_common/uv.rs index 8f9fae0d9..35913958a 100644 --- a/crates/socket-patch-cli/tests/vex_e2e_common/uv.rs +++ b/crates/socket-patch-cli/tests/vex_e2e_common/uv.rs @@ -1345,6 +1345,24 @@ pub fn run_lane(suite: &str, uv: &Uv, mode: Mode, lane: Lane) { "{}: the stale pristine install is not reported: {env:#}", report.what("scan --mode hosted") ); + // #1569: the wiring shapes uv before 0.5.5 does not honour carry + // the same advisory vendored gives, in its hosted spelling. + let advisory = match lane { + Lane::Transitive => Some("redirect_uv_override_requires_uv_0_5_6"), + Lane::Constraints => Some("redirect_uv_constraints_require_uv_0_5_6"), + _ => None, + }; + for code in [ + "redirect_uv_override_requires_uv_0_5_6", + "redirect_uv_constraints_require_uv_0_5_6", + ] { + assert_eq!( + env.to_string().contains(code), + advisory == Some(code), + "{}: `{code}` advisory expected only on its own lane: {env:#}", + report.what("scan --mode hosted") + ); + } assert!( !embedded_doc.exists(), "a failed in-run --vex wrote a document" diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index 8e060bd9c..17bd47196 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -6033,6 +6033,51 @@ fn pipenv_pylock_refusal(path: &str, dep: &DepOverride) -> RewriteWarning { } } +/// The hosted uv advisories for the two wiring shapes uv before 0.5.5 does +/// not honour (#1569). They accompany a pin that landed, so a caller that +/// counts or explains refusals skips them. +pub const UV_OLD_RELEASE_ADVISORIES: [&str; 2] = [ + "redirect_uv_override_requires_uv_0_5_6", + "redirect_uv_constraints_require_uv_0_5_6", +]; + +/// #1569: the advisories vendored mode gives as `pypi_uv_*_0_5_6`, for a dep +/// this run wired into `path`. `metadata` is the paired pyproject.toml / +/// script as the run leaves it, and `constrained` whether the lock's +/// `[manifest]` constrains the dep. The CLI cannot tell those uv binaries +/// apart from the lock, so these advise rather than refuse; the redirect +/// still counts. +fn uv_old_release_advisories( + path: &str, + metadata: Option<&str>, + constrained: bool, + dep: &DepOverride, +) -> Vec { + use crate::crawlers::python_crawler::canonicalize_pypi_name; + use crate::utils::python_lock::is_script_lock_name; + use crate::utils::python_script::{ + uv_constraints_advisory, uv_override_advisory, wires_through_override, + }; + + let [override_code, constraints_code] = UV_OLD_RELEASE_ADVISORIES; + let name = canonicalize_pypi_name(&dep.name); + let script = is_script_lock_name(path); + let mut warnings = Vec::new(); + if metadata.is_some_and(|text| wires_through_override(text, script, &name)) { + warnings.push(RewriteWarning { + code: override_code.into(), + detail: format!("{path}: {}", uv_override_advisory(&name, &dep.version)), + }); + } + if constrained { + warnings.push(RewriteWarning { + code: constraints_code.into(), + detail: format!("{path}: {}", uv_constraints_advisory(&name, "hosted")), + }); + } + warnings +} + /// Each lock is parsed once ([`PythonLockSession`]) and every dep is /// planned, refused or applied against that one document. The lock is still /// rendered after every rewritten dep: each dep's FileEdit fragments are @@ -6046,7 +6091,8 @@ fn rewrite_uv_lock( result: &mut RewriteResult, ) { use crate::utils::python_lock::{ - is_python_lock_name, pipenv_reads_pylock, ArtifactSource, PythonLockSession, + is_python_lock_name, paired_metadata_rel, pipenv_reads_pylock, ArtifactSource, + PythonLockSession, }; let locks: Vec<(&String, &String)> = files @@ -6144,6 +6190,19 @@ fn rewrite_uv_lock( result .confirmed_python_lock_uuids .insert(dep.patch_uuid.clone()); + if metadata_edit.is_some() || rewritten != content { + let metadata = metadata_edit + .as_ref() + .map(|edit| edit.rewritten.as_str()) + .or_else(|| { + paired_metadata_rel(path) + .and_then(|rel| result.files.get(rel).or_else(|| files.get(rel))) + .map(String::as_str) + }); + let constrained = session.manifest_constrains(&dep.name); + let advisories = uv_old_release_advisories(path, metadata, constrained, dep); + result.warnings.extend(advisories); + } if let Some(edit) = metadata_edit { record_python_metadata_edit(edit, dep, result); } @@ -26995,3 +27054,258 @@ mod owned_pin_generation_matrix { assert!(again.files.is_empty(), "{:?}", again.files.keys()); } } + +/// #1569: the hosted uv rewrite wires a transitive package through a marked +/// `[tool.uv] override-dependencies` entry and repoints `[manifest] +/// constraints`, two shapes uv before 0.5.5 does not honour. Vendored mode +/// advises on both (`pypi_uv_*_0_5_6`); hosted must too, on the run that +/// wires them, without refusing the redirect. +#[cfg(test)] +mod uv_old_release_advisories { + use super::*; + + const URL: &str = "https://patch.test/six-1.16.0-py2.py3-none-any.whl"; + const HOSTED_OVERRIDE_MARK_TEXT: &str = "# socket-patch hosted:"; + const SIX: &str = "[[package]]\nname = \"six\"\nversion = \"1.16.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [{ url = \"https://files.pythonhosted.org/packages/six-1.16.0-py2.py3-none-any.whl\", hash = \"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254\" }]\n"; + const ISODATE: &str = "[[package]]\nname = \"isodate\"\nversion = \"0.6.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [{ name = \"six\" }]\nwheels = [{ url = \"https://files.pythonhosted.org/packages/isodate-0.6.1-py2.py3-none-any.whl\", hash = \"sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96\" }]\n"; + + fn six() -> DepOverride { + DepOverride { + ecosystem: "pypi".into(), + name: "six".into(), + namespace: None, + version: "1.16.0".into(), + token: String::new(), + patch_uuid: "11111111-1111-4111-8111-111111111111".into(), + artifact_url: URL.into(), + registry_override: None, + integrity: Integrity { + sha256: Some("c".repeat(64)), + ..Default::default() + }, + } + } + + fn scan(files: &BTreeMap) -> (BTreeMap, RewriteResult) { + let result = rewrite_registry_redirect(files, &[six()]); + let mut out = files.clone(); + out.extend(result.files.clone()); + (out, result) + } + + fn codes(result: &RewriteResult) -> Vec<&str> { + result.warnings.iter().map(|w| w.code.as_str()).collect() + } + + fn project(dependencies: &str, uv: &str) -> String { + format!("[project]\nname = \"app\"\nversion = \"0.1.0\"\nrequires-python = \">=3.9\"\ndependencies = [{dependencies}]\n{uv}") + } + + fn lock(app_dep: &str, requires: &str, manifest: &str, packages: &[&str]) -> String { + format!( + "version = 1\nrevision = 3\nrequires-python = \">=3.9\"\n{manifest}\n[[package]]\nname = \"app\"\nversion = \"0.1.0\"\nsource = {{ virtual = \".\" }}\ndependencies = [{{ name = \"{app_dep}\" }}]\n\n[package.metadata]\nrequires-dist = [{requires}]\n\n{}", + packages.join("\n") + ) + } + + fn files(pairs: &[(&str, String)]) -> BTreeMap { + pairs + .iter() + .map(|(path, text)| (path.to_string(), text.clone())) + .collect() + } + + #[test] + fn transitive_override_wiring_advises_uv_0_5_6() { + for newline in ["\n", "\r\n"] { + let input = files(&[ + ( + "pyproject.toml", + project("\"isodate==0.6.1\"", "").replace('\n', newline), + ), + ( + "uv.lock", + lock( + "isodate", + "{ name = \"isodate\", specifier = \"==0.6.1\" }", + "", + &[ISODATE, SIX], + ) + .replace('\n', newline), + ), + ]); + let (wired, result) = scan(&input); + assert!( + result + .confirmed_python_lock_uuids + .contains(&six().patch_uuid), + "{:?}", + result.warnings + ); + assert!(wired["pyproject.toml"].contains("override-dependencies")); + assert_eq!( + codes(&result), + vec!["redirect_uv_override_requires_uv_0_5_6"], + "{:?}", + result.warnings + ); + let detail = &result.warnings[0].detail; + assert!( + detail.contains("six") && detail.contains("uv sync"), + "{detail}" + ); + // A settled re-run wires nothing and repeats nothing. + let (_, again) = scan(&wired); + assert!( + again.files.is_empty() && again.warnings.is_empty(), + "{:?}", + again.warnings + ); + } + } + + #[test] + fn repointed_manifest_constraint_advises_uv_0_5_6() { + let input = files(&[ + ( + "pyproject.toml", + project( + "\"six==1.16.0\"", + "\n[tool.uv]\nconstraint-dependencies = [\"six==1.16.0\"]\n", + ), + ), + ( + "uv.lock", + lock( + "six", + "{ name = \"six\", specifier = \"==1.16.0\" }", + "\n[manifest]\nconstraints = [{ name = \"six\", specifier = \"==1.16.0\" }]\n", + &[SIX], + ), + ), + ]); + let (wired, result) = scan(&input); + assert!( + result + .confirmed_python_lock_uuids + .contains(&six().patch_uuid), + "{:?}", + result.warnings + ); + assert!( + wired["uv.lock"].contains(&format!( + "constraints = [{{ name = \"six\", url = \"{URL}\" }}]" + )), + "{}", + wired["uv.lock"] + ); + assert_eq!( + codes(&result), + vec!["redirect_uv_constraints_require_uv_0_5_6"], + "{:?}", + result.warnings + ); + let (_, again) = scan(&wired); + assert!(again.files.is_empty() && again.warnings.is_empty()); + } + + /// The user's own unmarked `six==1.16.0` override is reused for the + /// hosted source, and uv before 0.5.5 ignores that source just the + /// same: advise, under the canonical name even for a `Six` grant. + #[test] + fn user_written_override_advises_uv_0_5_6() { + let input = files(&[ + ( + "pyproject.toml", + project( + "\"isodate==0.6.1\"", + "\n[tool.uv]\noverride-dependencies = [\"six==1.16.0\"]\n", + ), + ), + ( + "uv.lock", + lock( + "isodate", + "{ name = \"isodate\", specifier = \"==0.6.1\" }", + "", + &[ISODATE, SIX], + ), + ), + ]); + let dep = DepOverride { + name: "Six".into(), + ..six() + }; + let result = rewrite_registry_redirect(&input, std::slice::from_ref(&dep)); + assert!( + result.confirmed_python_lock_uuids.contains(&dep.patch_uuid), + "{:?}", + result.warnings + ); + assert!(!result.files["pyproject.toml"].contains(HOSTED_OVERRIDE_MARK_TEXT)); + assert_eq!( + codes(&result), + vec!["redirect_uv_override_requires_uv_0_5_6"], + "{:?}", + result.warnings + ); + assert!( + result.warnings[0].detail.contains("six==1.16.0"), + "{}", + result.warnings[0].detail + ); + } + + /// A direct dependency with no constraint needs neither shape: no + /// advisory (the existing hosted lanes stay warning-free). + #[test] + fn direct_dependency_without_constraint_is_silent() { + let input = files(&[ + ("pyproject.toml", project("\"six==1.16.0\"", "")), + ( + "uv.lock", + lock( + "six", + "{ name = \"six\", specifier = \"==1.16.0\" }", + "", + &[SIX], + ), + ), + ]); + let (_, result) = scan(&input); + assert!(result + .confirmed_python_lock_uuids + .contains(&six().patch_uuid)); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + } + + /// A PEP 723 script lock wires a transitive package through the + /// script block's own `[tool.uv] override-dependencies`. + #[test] + fn transitive_script_override_advises_uv_0_5_6() { + let script = "# /// script\n# requires-python = \">=3.9\"\n# dependencies = [\"isodate==0.6.1\"]\n# ///\nimport isodate\n"; + let lock = format!( + "version = 1\nrevision = 3\nrequires-python = \">=3.9\"\n\n[manifest]\nrequirements = [{{ name = \"isodate\", specifier = \"==0.6.1\" }}]\n\n{ISODATE}\n{SIX}" + ); + let input = files(&[("tool.py", script.to_string()), ("tool.py.lock", lock)]); + let (wired, result) = scan(&input); + assert!( + result + .confirmed_python_lock_uuids + .contains(&six().patch_uuid), + "{:?}", + result.warnings + ); + assert!( + wired["tool.py"].contains("override-dependencies"), + "{}", + wired["tool.py"] + ); + assert_eq!( + codes(&result), + vec!["redirect_uv_override_requires_uv_0_5_6"], + "{:?}", + result.warnings + ); + } +} diff --git a/crates/socket-patch-core/src/patch/redirect/python_lock_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/python_lock_equivalence_tests.rs index b1ca98a0b..15a720d00 100644 --- a/crates/socket-patch-core/src/patch/redirect/python_lock_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/python_lock_equivalence_tests.rs @@ -457,6 +457,8 @@ fn single_parse_uv_rewrite_matches_golden() { "redirect_uv_project_unsupported", "redirect_uv_script_unsupported", "redirect_uv_script_missing", + "redirect_uv_override_requires_uv_0_5_6", + "redirect_uv_constraints_require_uv_0_5_6", ] { assert!(codes.contains(code), "no case reached {code}: {codes:?}"); } diff --git a/crates/socket-patch-core/src/utils/python_lock.rs b/crates/socket-patch-core/src/utils/python_lock.rs index 6b41dab16..72ffe1254 100644 --- a/crates/socket-patch-core/src/utils/python_lock.rs +++ b/crates/socket-patch-core/src/utils/python_lock.rs @@ -410,6 +410,30 @@ fn rewrite_references( } } +/// Whether the lock's `[manifest]` `constraints` / `build-constraints` name +/// `name`: the entries [`rewrite_manifest`] repoints at the artifact, a +/// shape uv before 0.5.5 rejects under `uv sync --locked` (#1569). +fn manifest_constrains(document: &DocumentMut, name: &str) -> bool { + let name = canonicalize_pypi_name(name); + let Some(manifest) = document.get("manifest").and_then(Item::as_table_like) else { + return false; + }; + ["constraints", "build-constraints"].iter().any(|key| { + manifest + .get(key) + .and_then(Item::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_inline_table) + .any(|constraint| { + constraint + .get("name") + .and_then(Value::as_str) + .is_some_and(|value| canonicalize_pypi_name(value) == name) + }) + }) +} + fn rewrite_manifest(document: &mut DocumentMut, name: &str, artifact: ArtifactSource<'_>) { let Some(manifest) = document .get_mut("manifest") @@ -1059,6 +1083,14 @@ impl PythonLockSession { } Ok(preserve_line_endings(text, document.to_string())) } + + /// [`manifest_constrains`] over the session's document as the + /// rewrites so far left it, without re-parsing the lock. + pub(crate) fn manifest_constrains(&self, name: &str) -> bool { + self.document + .as_ref() + .is_ok_and(|document| manifest_constrains(document, name)) + } } pub fn rewrite_python_lock( diff --git a/crates/socket-patch-core/src/utils/python_script.rs b/crates/socket-patch-core/src/utils/python_script.rs index 68ed68494..82ea51384 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -351,6 +351,74 @@ pub(crate) fn hosted_override_pins(doc: &DocumentMut, script: bool) -> Vec bool { + let toml = if script { + match script_metadata(metadata) { + Ok((_, toml)) => toml, + Err(_) => return false, + } + } else { + metadata.to_string() + }; + let Ok(doc) = toml.parse::() else { + return false; + }; + let name = canonicalize_pypi_name(name); + let uv = doc + .get("tool") + .and_then(Item::as_table_like) + .and_then(|tool| tool.get("uv")) + .and_then(Item::as_table_like); + if !contains_dependency(uv.and_then(|uv| uv.get("override-dependencies")), &name) { + return false; + } + // The same "direct" reading the rewrites use to decide on an override. + let direct = if script { + contains_dependency(doc.get("dependencies"), &name) + } else { + pyproject_dependency_specs(&doc) + .into_iter() + .any(|(_, spec)| dependency_name(spec) == name) + || uv.is_some_and(|uv| contains_dependency(uv.get("dev-dependencies"), &name)) + }; + !direct +} + +/// The advisory for [`wires_through_override`] wiring, shared by hosted +/// (`redirect_uv_override_requires_uv_0_5_6`) and vendored +/// (`pypi_uv_override_requires_uv_0_5_6`). Measured with the real binaries, +/// 0.5.5 is the first uv that keeps the override's source; the advice and +/// the codes keep the documented 0.5.6. +pub(crate) fn uv_override_advisory(name: &str, version: &str) -> String { + format!( + "transitive wiring of {name}=={version} via [tool.uv] override-dependencies is \ + not honored by uv before 0.5.5, which reinstalls the registry wheel on a plain \ + `uv sync` — pin uv >= 0.5.6 or make the package a direct dependency" + ) +} + +/// The advisory for a repointed `[manifest]` `constraints` / +/// `build-constraints` entry, shared by hosted +/// (`redirect_uv_constraints_require_uv_0_5_6`) and vendored +/// (`pypi_uv_constraints_require_uv_0_5_6`); `wheel` says whose wheel +/// (`hosted` / `vendored`). uv 0.5.4 still rejects the entry, 0.5.5 accepts +/// it. +pub(crate) fn uv_constraints_advisory(name: &str, wheel: &str) -> String { + format!( + "[manifest] constraints for {name} were repointed at the {wheel} wheel, the shape \ + uv 0.5.5+ writes; uv 0.2.37–0.5.4 reject it under `uv sync --locked` (`--frozen` \ + and a plain `uv sync` still install the patch) — pin uv >= 0.5.6 or drop the \ + constraint on the patched package" + ) +} + /// Append `specifier` to `overrides` on its own line, under /// [`HOSTED_OVERRIDE_MARK`]. An array the rewrite just created (empty) is /// laid out multi-line with a trailing comma; an existing array keeps its diff --git a/crates/socket-patch-core/src/vendor/pypi_uv.rs b/crates/socket-patch-core/src/vendor/pypi_uv.rs index babe183f9..c1a6be023 100644 --- a/crates/socket-patch-core/src/vendor/pypi_uv.rs +++ b/crates/socket-patch-core/src/vendor/pypi_uv.rs @@ -598,11 +598,7 @@ pub(super) async fn wire_uv( // 0.5.3 from 0.5.6, so this cannot be an offline refusal — advise. advisories.push(VendorWarning::new( "pypi_uv_override_requires_uv_0_5_6", - format!( - "transitive wiring of {canon_name} via [tool.uv] override-dependencies is \ - honored only by uv >= 0.5.6; older uv reinstalls the registry wheel on a \ - plain `uv sync` — pin uv or make the package a direct dependency" - ), + crate::utils::python_script::uv_override_advisory(canon_name, version), )); let spec = format!("{canon_name}=={version}"); let uv_table = ensure_table(&mut doc, &["tool", "uv"])?; @@ -790,12 +786,7 @@ pub(super) async fn wire_uv( // advise rather than refuse. advisories.push(VendorWarning::new( "pypi_uv_constraints_require_uv_0_5_6", - format!( - "[manifest] constraints for {canon_name} were repointed at the vendored wheel, \ - the shape uv >= 0.5.6 writes; uv 0.2.37–0.5.3 reject it under `uv sync \ - --locked` (`--frozen` and a plain `uv sync` still install the patch) — pin uv \ - >= 0.5.6 or drop the constraint on the patched package" - ), + crate::utils::python_script::uv_constraints_advisory(canon_name, "vendored"), )); } for edit in constraint_edits.iter().rev() { diff --git a/crates/socket-patch-core/tests/equivalence/python_lock_rewrite.golden b/crates/socket-patch-core/tests/equivalence/python_lock_rewrite.golden index 924aa0b70..553036d9e 100644 --- a/crates/socket-patch-core/tests/equivalence/python_lock_rewrite.golden +++ b/crates/socket-patch-core/tests/equivalence/python_lock_rewrite.golden @@ -2,301 +2,301 @@ # 0-4 cd5cd36a37fa8c9b 48a0e144464b8f8a 5-9 f58e23b53c07f972 2e6a9ab6c49b5af3 -10-14 63a0aa035a8a4e90 2941d973ad6169bd +10-14 63a0aa035a8a4e90 c7a0bc7ae987093b 15-19 7cf3d76e36752625 590f305643295efc 20-24 c7c648dc10c3c7cd 9fa1deaf96f07fe2 -25-29 831189dc733b8849 84549312d5b50708 +25-29 831189dc733b8849 c8d8bf1902aa1195 30-34 dcb109d643382adf 003703da5df0e30f 35-39 99a313947928f593 040d41e8e286d342 -40-44 cf5611d067871c91 bec1d86832e78be6 +40-44 cf5611d067871c91 a654da53f2bf68f3 45-49 04f0e41a9e302f59 703a8fae640714d8 -50-54 03d58da62810b2e4 e8dd94ea74dd1d17 -55-59 daf9f41b5decbf2c c8555affaeef1b02 +50-54 03d58da62810b2e4 b8a911ff40818fe5 +55-59 daf9f41b5decbf2c 4d5c0a6a8091972c 60-64 58413f6f2f266d0d e269d6e79181da6a 65-69 cd4f77526796ec70 ffec6efe319265a7 -70-74 3befe3b6747c7c5d ca9f27350d04be89 +70-74 3befe3b6747c7c5d e8bf2fc66a89b4c0 75-79 f704740390513318 42df63da4590a254 80-84 6bb2704eadd90bbb d31e655a72986955 -85-89 2b41ca85da12924d 5130902a838aec0f +85-89 2b41ca85da12924d 776f725f1d547ae5 90-94 b4f4d21d57b4e3e6 0f3435d61d7a36c1 95-99 1fcae5cb34b3557b 4eb4e16e7ffeb917 -100-104 22d4b081083ec3cf c96c490301f692ed -105-109 b33352ae7c78a21f 819b31e2f7334ee6 -110-114 459ff3efdff5f2cc 81712bc6d977c7c0 -115-119 291d724de0819b8d f5448022649e1124 -120-124 21a80a5bc6b9e421 1eedfb98cce3fef5 -125-129 8e678fbd38ff1c68 a47bfa725066c0f2 -130-134 a38ee386aaf54bc9 4c03a39f151a7e81 +100-104 22d4b081083ec3cf 59d0596979c806fd +105-109 b33352ae7c78a21f dd103383d74ec6c2 +110-114 459ff3efdff5f2cc 89ac036f574a3b03 +115-119 291d724de0819b8d 38569f9112f6bcf0 +120-124 21a80a5bc6b9e421 e1b74ff5361ca16b +125-129 8e678fbd38ff1c68 82c5b8414662e3a9 +130-134 a38ee386aaf54bc9 6aa8cd1363de91dd 135-139 f3c8324bccf53c32 ec9316e47aaf4eab 140-144 7d6c4b160a757e64 56ac168022c5a5f6 -145-149 e366e05fd9c0ef0b fb53672d3a8f3bd1 -150-154 7f552bc377ac439b d1b393713d916f80 +145-149 e366e05fd9c0ef0b 9d7f32c698f5a7b0 +150-154 7f552bc377ac439b 9c0bc71d15819ce0 155-159 5c987ad06f4c2cf3 853fcb9da6efea74 -160-164 2fd97b5cdeea8344 602b3155438aa1b5 +160-164 2fd97b5cdeea8344 b12d3fec3dcd896a 165-169 8f16946d73613e52 77dc4a7d5887d3c0 -170-174 72c1e1cb26500151 07da3560a0bf3737 -175-179 4333d230209fdfdd 5f85b72bba81cfc7 -180-184 70af4e6d0a9804eb a50449016f534bed +170-174 72c1e1cb26500151 57495ab2d9e9c573 +175-179 4333d230209fdfdd ad0aa98f28ad155a +180-184 70af4e6d0a9804eb 5ff214be13409158 185-189 6073f94564a728c8 42f8708863320edf 190-194 0976869c3d0f4aeb 03f687f9a1ef3754 -195-199 6e26da934df4169e 61889d549f7bf5b1 +195-199 6e26da934df4169e 1cfc9815b13e525a 200-204 fbb5bb450083c437 31aad032ae8b9c21 205-209 5f65204b08f141e8 82746d245db80449 -210-214 5325f0b8e4f86617 3875683118d50fb9 +210-214 5325f0b8e4f86617 a159c97d57695de6 215-219 93c1a1fd0318dd14 e1110991fbf29b6a 220-224 4b3c1e25ed139fda 6a359c12ce11bf75 225-229 01df8d39a686d542 57d31b8eda79aafd 230-234 d8671c2aa0925484 0bac935606117b80 235-239 c2ffbce64c565d4e 58b0fd772f4d8d6f 240-244 8840614f5d6ed107 8e780f81f0f963a3 -245-249 a3a20836097b17a3 e7d647ebe4acb5c1 -250-254 55507d905e78d536 51efab89af60246f -255-259 ddb6e7f50dc7b9a4 74573807d9a5ab22 -260-264 9e561d5848a362eb b8ca2b7ed478437b -265-269 d94a03864c991b66 a1e761f976f07a29 +245-249 a3a20836097b17a3 b493ad205a278801 +250-254 55507d905e78d536 62d3a163561cc5d2 +255-259 ddb6e7f50dc7b9a4 0dec44776ee628de +260-264 9e561d5848a362eb 6d8c43fb92450d14 +265-269 d94a03864c991b66 74fb268ed9578c04 270-274 d0100fe28bcc0b9f 420f0773032cf669 -275-279 c991021006094a2d 6bbae3ade35f43ac -280-284 9630c4201e5eb600 3875dd202ef094ae +275-279 c991021006094a2d 1804c68362d31a6d +280-284 9630c4201e5eb600 316f0182641471c7 285-289 fb53958c9f671828 532e901e273cb325 -290-294 c5bf6275b89317cf 7e3341f6a56d0554 +290-294 c5bf6275b89317cf 25a6ff6ba3fa9c2c 295-299 99fb36209f586bca e891c6658cee191a 300-304 5240085dd621df82 458bf378d060d912 305-309 616cfc3aae79162c 9773426cddea96c8 -310-314 a386fe7bc32fc73e ffcb8883f20fd068 -315-319 5d7084c26ae0f3f2 84663f27fddc4978 -320-324 e0a4ea3772d1038a 73f4cabd6f62f2b1 +310-314 a386fe7bc32fc73e 012e854d302744b3 +315-319 5d7084c26ae0f3f2 d1ce684178d4a81b +320-324 e0a4ea3772d1038a ade4bdb1bc7842ea 325-329 0cb388111d9a1b86 839131fd61828cf2 330-334 d1c41d95d297d164 35fdd17d28531ad6 335-339 2d6db0a042990332 61f544ab33b2cfd6 -340-344 acfe94db4b59c187 5569acdc9e22f787 -345-349 f443e9c02e4c493c c69bdb6548e13ca0 -350-354 6b8189fa7c7eb359 dfb1eb97be7092d2 -355-359 48640c9838b42488 279989c235a1b232 +340-344 acfe94db4b59c187 7daa8a861d019f20 +345-349 f443e9c02e4c493c 4770cd61363c20c9 +350-354 6b8189fa7c7eb359 f6a03545fbeec491 +355-359 48640c9838b42488 5aaab0260d513d16 360-364 86adc1124beaf310 71f8b11ce3847ea3 -365-369 c75c9390e1ec4dc0 29610066db8a37d4 +365-369 c75c9390e1ec4dc0 9ed2778ab5ff262d 370-374 4b47480d5c6430c8 eb537062c9b097a3 -375-379 97cadfe0779bf656 9ab1bbb442319fc5 +375-379 97cadfe0779bf656 7b2264c309fa86dc 380-384 bae2ef0777128159 394248e293242674 385-389 a72fe500fd93b54b a48cd849bf320b7b 390-394 1bda7f0386bcb4b2 8a4c74b37267fb35 395-399 662e2ce03cc70051 5fed6d12d2490ddc 400-404 27e91e5f8d9d0500 06b82bdf156dad07 405-409 d97cfed3b6019a84 4c43f9bf02b1c878 -410-414 9fc1135cea15545b be2a91c3665da64a +410-414 9fc1135cea15545b 931a5c267fbea391 415-419 ec0440f6f542cdbe 78e26f5a0c86f7bd -420-424 820d32ca97f38bf1 ba90066643d2da90 +420-424 820d32ca97f38bf1 6e8015b8a4a26459 425-429 a187a84aad4ddd82 f5d0b8ab277a2913 430-434 63cab5602f883c2b e5605a0a65a3449f 435-439 6b51186155d6b708 9f185e6f0051e7a7 -440-444 d3c75dad29457c53 30d025363bf2622b +440-444 d3c75dad29457c53 e274e66edc596cb2 445-449 5b312af32839186f 8fbf6337f2932e00 -450-454 867d9e5d19789b27 7d1aed6653d5101b +450-454 867d9e5d19789b27 6b144dbc0dd1e95a 455-459 845c435d60d254cd 0e0499e922caa21f 460-464 fe6ab0fb975606e6 ec3f3ab76d25c8de 465-469 cf94bc0890135d7d fd4b8e2927bebd03 -470-474 5e2dbff33d066336 29ac24fb4b12017a +470-474 5e2dbff33d066336 aca35a27b863300a 475-479 0479e5e66eeb0b35 b85602f8b33a3a40 -480-484 728477d2aff05013 2d2937d0ac2fdf23 +480-484 728477d2aff05013 0ab363fd26c2e144 485-489 71a0a6cd3a0f1dc4 e4d9c7504d28f0f3 490-494 778ed33daefe5147 f2ef44e740b04752 -495-499 3f3ef0e47bc79f65 f0233be4e062ccdb +495-499 3f3ef0e47bc79f65 87e3c9b2eb937276 500-504 aa9adf3185bd29fd 7c7d2a919fd338f9 505-509 45a1e1c9fbf16b34 8d22ee5d9c4a28c1 510-514 818d16cd8b3bec8a ade0d01f88cdff6f 515-519 c7422c418ed92126 4a93055b7f3ee09a -520-524 a3e57516f4ee56fe 06a47c0e77469fcd +520-524 a3e57516f4ee56fe 2025a941c447fa1e 525-529 554002fd18293950 43a791951252a9e9 530-534 70f5bc4e1811144d 2d5fece8c7f51bcc 535-539 3bc9d29e9245a772 772970ae662a3597 540-544 f2d746650f56eb6a a412eafd24000b57 -545-549 3ebc5e64e438a7ed 2e5e08f544df97c7 -550-554 b9d3351453b65997 68c9a15ecf308f27 -555-559 37bcc4d58a6d31ef a265f9a2caf4552c -560-564 e03fbe4bdb0ce235 f3eba7e85189cf67 -565-569 0b1e0cc286d8e2db 3b8dcc6421736fb2 -570-574 0eca795339181b14 4db62dea573c24d1 +545-549 3ebc5e64e438a7ed 95e9cd4425f6b3a5 +550-554 b9d3351453b65997 6dff678973f4d458 +555-559 37bcc4d58a6d31ef bb3b71f0bd49e80c +560-564 e03fbe4bdb0ce235 649ca4b830b83ea2 +565-569 0b1e0cc286d8e2db 9c09b80a57757558 +570-574 0eca795339181b14 adb66e3ab9f7f68c 575-579 5ca964a495b608c9 dccc25c013660626 -580-584 c526bfcdbdb19c13 d560ec858b3b2219 +580-584 c526bfcdbdb19c13 dd691349d322d087 585-589 c0f55f7b738667c8 f46682e9b36c7702 -590-594 9a57465f007322f6 3c64a5df77f9c4c6 -595-599 60d49df8b1a1ed60 e340ad56f3c2b7b2 -600-604 eb8f971fa2c853f1 11b66641ca6ae56a +590-594 9a57465f007322f6 51adc74f13726b01 +595-599 60d49df8b1a1ed60 ea2da15f06594617 +600-604 eb8f971fa2c853f1 77603a90b9c742bf 605-609 b9a0d832ab3703a4 f96c87efc5d481bc 610-614 b7cd2826e9ed4bad 1dae82316d44d372 615-619 dd6ea62b42964c5d efea9c54f9b7df69 -620-624 f8cafac82b3a2938 056ff72402397371 +620-624 f8cafac82b3a2938 51ead39c82193866 625-629 71dda2f6b4fcf3ad 35647b7151bbe854 630-634 c0625fb844d5bd9c c64a526fe5b72142 -635-639 89ce072ed93bd8b3 0578500fea7bcf61 +635-639 89ce072ed93bd8b3 b3f3596ff6d2e3ce 640-644 9a060a7b22a5ea63 3487fe4c2cec2c2a -645-649 4f18a0402cf96703 bf14b749f94b2db2 +645-649 4f18a0402cf96703 e6ac7a7562e4f5f0 650-654 799d0d7818b8ecdd 614c01474bb1e118 655-659 0f78bdef454a269d 8926ce36aa150d51 660-664 f1cd906360c54cfa e85fffa3833a1371 -665-669 a5564a8ea2270906 ba8d3f1d46b116bc +665-669 a5564a8ea2270906 916289a1b2b0cb01 670-674 c196359f0ba0e6c1 b7bf3747d5fbf62b 675-679 e0011b91b2f33aef 83b29f9e3c6941d3 -680-684 54159021ceb1a6a4 38d783f189586236 +680-684 54159021ceb1a6a4 091ec7dc35c953fc 685-689 8121cfcade405227 420a3a319d2d57be -690-694 841fe56913ede31a 1e3044225dd8d63c -695-699 968e63891e83acad 7c71d615d9bf67fb +690-694 841fe56913ede31a 85768477c9cf4b2b +695-699 968e63891e83acad d23a4030c85cfd61 700-704 b53560cda70aadb8 bbaa6aa02e40ab6a 705-709 83bece44cdd8d964 4f5ebb508596336a -710-714 0096b81bcf606d08 6439c26f360ca790 +710-714 0096b81bcf606d08 09c15a61fa9b7174 715-719 afbe3acb8a936112 df6e9deb669be132 720-724 e212d53b068c9367 aadfdb29a8bcc99e -725-729 b532d08326d3a1c1 2de2fbb2c43c7f8a +725-729 b532d08326d3a1c1 ab364c6f8a842555 730-734 fb89f0ab21e41184 8a330cf19706a811 735-739 7f21245b19048f6d 61a95bc86e9de20b 740-744 f5afc189901e25a7 e2e144556544337d 745-749 0d669a0c40d9ec06 b97784b37ffa6263 -750-754 a2da5259aa3dc6bb 012fdbd877db9c91 -755-759 d5fed3964610e80b f12a5d7653e27e6f +750-754 a2da5259aa3dc6bb 23d530751e7cdde1 +755-759 d5fed3964610e80b 659a2c1a04c6442e 760-764 eb74f4ed1d526a82 959dc5c757d52c8d -765-769 446fbe655c39474a 9b16fa067e375a47 -770-774 3ed2f6336ae1d8b4 93d42391b807598c +765-769 446fbe655c39474a 65dbfbc0e481e7a1 +770-774 3ed2f6336ae1d8b4 577ee0765be7700c 775-779 0c443aac3fea26b0 d014fd768c4bf4c7 780-784 fff5de443b6d9df3 aea7405cafc4c5b7 -785-789 95fa315efc5c1f38 fb9d734eaf54786c +785-789 95fa315efc5c1f38 06bc6fad8d1fb3b0 790-794 f7e4a7ca516970fa 87aa607e96342d45 795-799 c26cb82b9d6af0ae 7fe79ef93103d105 800-804 a278c8bb8be83819 515b49f0b6c50a17 -805-809 69d4f412ccd64cea 75694104c6c260c6 -810-814 76e6c0857a866513 a054910d270c4865 +805-809 69d4f412ccd64cea fd57694919819559 +810-814 76e6c0857a866513 df435d22fdd2ffec 815-819 4bf0e13795a7faff 73bb5b3877877d35 820-824 e661808004ced5e0 1c27f0c8126edd04 -825-829 d25f4793e042db64 c547d6d2f42aab92 +825-829 d25f4793e042db64 2ce8c2f1b24848ad 830-834 e3d03e8ef8b1295d ece90eb006469f2c -835-839 c00270de24a49e16 0356942dabe3c1a1 -840-844 865292082469a123 3a61750ab0484c76 -845-849 0a2f4f3e161db2bd c5d2f7f0c3a95ec8 -850-854 d5b245a2d4762809 7875eeafd9b0852c -855-859 d7a4147ec83ccdf3 77002587e0e6b904 -860-864 e0dfd4ee3b9b9115 fde9fa84bc8f9ec2 +835-839 c00270de24a49e16 d3a5b777c9e64629 +840-844 865292082469a123 1141ac788397e838 +845-849 0a2f4f3e161db2bd 1442af8e2f7934c1 +850-854 d5b245a2d4762809 3d5ec35bd59ffebb +855-859 d7a4147ec83ccdf3 4e39350195331812 +860-864 e0dfd4ee3b9b9115 9bccc43b5c137ed6 865-869 3e913b7411e46c27 4306522989316626 -870-874 fdd6c8ad6bcba04e 1882f07d3fed58b1 -875-879 daeef0f506171b5e ecf437a4a9aaa27b +870-874 fdd6c8ad6bcba04e 32ad3ee5e1c12d8e +875-879 daeef0f506171b5e f143eb2ccb94a6a1 880-884 a19a9111ca539727 47d9a8a193744011 -885-889 4964fd890aa0994b 9b7572f260470607 -890-894 50069923d0e0ff0c d4c627d208bbfe53 +885-889 4964fd890aa0994b 4af19ffda897291e +890-894 50069923d0e0ff0c ccd6d0e66f60528e 895-899 45a6202dc931bd35 ddc86e63ebde42c6 900-904 3e4b61b6dfc3922b 83e3365600063210 -905-909 c91a83be7091fe1c 24162906c0e7bfe9 -910-914 8418b5e59791bc82 867fa3281db7acf2 +905-909 c91a83be7091fe1c d13f31e52c02bcc7 +910-914 8418b5e59791bc82 14a923d5c8db097d 915-919 5a056b11f328365b e87365882f0beeba 920-924 34137d01c0b33616 25eb24321c54f780 925-929 2bfe9d227e06cc61 36a2a2804e7199c0 -930-934 44414e0793c1989b 81aeeee46cb523dd -935-939 2388bd7d5fdcaea3 ec1d5d5736d89bb4 +930-934 44414e0793c1989b 84f0d775f435081d +935-939 2388bd7d5fdcaea3 8064a2dba7de2b47 940-944 9526b9c0ef958cfb 0bb7947f46f77e87 945-949 c82059b88e2418d3 0b69206a8bdd92aa 950-954 2af969418fd97751 0d8028b27ca53895 -955-959 cb5af4f26e818f69 35a711c543af79eb +955-959 cb5af4f26e818f69 eb6b47ae6c909d66 960-964 703724ea36030a9a fcdcb792020ab11d -965-969 a5f349b716e2c17b 0991658fe758303a -970-974 2c51d78e4d21f34a 9daf5dd74a4769b4 -975-979 02fe70fe43798656 f88366d84fbc1a9b +965-969 a5f349b716e2c17b cd59fd57668c9579 +970-974 2c51d78e4d21f34a aedb583b721d03e0 +975-979 02fe70fe43798656 fadad95ad52605e6 980-984 2dfdeb1d730d437f a0d94833e09775ea -985-989 c0ce894b61174b60 a20389482ab6ee4b +985-989 c0ce894b61174b60 3aab2781acd07afb 990-994 a4c96d873de9cde3 eb3b3c6230974260 -995-999 3b004642590efb27 e3d1abbe834ad7fc -1000-1004 1e11b2228a87e2cd 87eb56ba44441baf +995-999 3b004642590efb27 de61743f7afd0468 +1000-1004 1e11b2228a87e2cd ddbbfeb2ef0cb9a6 1005-1009 863857d4840e1755 355fdf9286f05798 1010-1014 f32eb531a0227019 b9b5a3a04eecda96 -1015-1019 5df98b07a3f6fa22 ee20a674cbd0f164 +1015-1019 5df98b07a3f6fa22 0d2d4cda47b6f7bd 1020-1024 7788148d1188683a 186dad916be4a966 -1025-1029 2a42ee2c74e792bf aca5c17e940b4cb5 -1030-1034 8615096392ecaf53 eb809bbae88c2982 +1025-1029 2a42ee2c74e792bf 9cae632550c12f9a +1030-1034 8615096392ecaf53 cebb5c90b23dccac 1035-1039 c24e3d3e2b5b3a7c eacecb30a4ac9cb7 1040-1044 7940b56d09ea5bc3 2eb630023ef89bd6 -1045-1049 5be1c725b126b43d 2537be48e3fcd24a +1045-1049 5be1c725b126b43d 2f48a7079d9849c8 1050-1054 baf53597dd1ee0f7 bc2a9fe2197ec4b7 -1055-1059 89da0c30103093c2 42027a2a39ebde27 +1055-1059 89da0c30103093c2 b6ca00185709a5d6 1060-1064 088462cc1aad7bca dd6cd0776f80e22f 1065-1069 b3d2b22d55140c4d 5565fb9078c47532 1070-1074 744a72bd25aa8f8a 068bb08e149f4464 1075-1079 b865bc68064f3bf3 629a7afbccb7b938 -1080-1084 22a031b03ca02431 6c8443bf709e07a0 +1080-1084 22a031b03ca02431 1189d4a31e4d6e90 1085-1089 7fc382e8a1cfb392 9b87d1e77b5a7538 1090-1094 09636a2b50a36582 319c81aa9068c32e -1095-1099 848d4cb5147266ea 4aee1674aed627ed +1095-1099 848d4cb5147266ea 970bcbe02c09b5bf 1100-1104 717a47c102e234ea 5579c18523a5832c -1105-1109 b86d27ca80e7477d 034de03cd652a927 +1105-1109 b86d27ca80e7477d 4ad4e6c5b8a53c13 1110-1114 e75533c276ca26b6 d7616af8e73b78de -1115-1119 0967dca7a3b7f0db d8db41208638249f +1115-1119 0967dca7a3b7f0db 27372a1452b270f9 1120-1124 2b3d27a962eeecaa 6bbca72310c14bb7 -1125-1129 4c0f197f16fdc203 01b4ba3d9dfd2cb1 +1125-1129 4c0f197f16fdc203 73a47cdb889dde2d 1130-1134 b8c883083e2354b4 5f2e017c0be56cfc -1135-1139 052826a8ca213062 7cf591ec1d8fbd99 -1140-1144 643000c4068a73dc 3e49d60c36f682fd -1145-1149 a20dbdc962bf496d 480ad4ea25e080c5 +1135-1139 052826a8ca213062 0ce585e6253588ba +1140-1144 643000c4068a73dc 2e95ee9e1f3703cc +1145-1149 a20dbdc962bf496d 0546e3d1c9df5cd0 1150-1154 a8ff0a76fb490212 86381bb67dd20a29 -1155-1159 265ef1e7e4d619b0 39f15128482bd634 +1155-1159 265ef1e7e4d619b0 70f4ca757f3efda6 1160-1164 fd031848e1246ce5 be60639cbdf765b0 -1165-1169 46392bbe676ae180 4576a4072bbea30b -1170-1174 9ed9aa2666accf82 08224ec1b23ea5db +1165-1169 46392bbe676ae180 473a5c2505c04570 +1170-1174 9ed9aa2666accf82 812866a9c80517d4 1175-1179 42ba5427c32b6a50 f74c503679d95c63 -1180-1184 ec53e605cb4007b0 c99cfb38d4eb1f79 -1185-1189 a9bca1c3f2f28d14 380cee83a22a9e81 -1190-1194 99c3c937889a535d 9b9afb080dcb6d25 +1180-1184 ec53e605cb4007b0 c3e34158cb147df0 +1185-1189 a9bca1c3f2f28d14 f9858e5a8f8302ee +1190-1194 99c3c937889a535d 8acaa3ca71314677 1195-1199 516dc4d7731053d5 b805922b219f6337 -1200-1204 615780e5c51bd99d 3bad989b2e4a0c9d -1205-1209 2ba50ff0fb6738c8 927f591180c674c1 +1200-1204 615780e5c51bd99d 3f58dc0ca1eb2952 +1205-1209 2ba50ff0fb6738c8 8e99678812722afc 1210-1214 945fc79f04d19aaa f637ef58a7981b2f 1215-1219 0f1fd24ee8266b2f abc160e18a574734 1220-1224 d00cf5cc9e90ba28 f0ef15e8c3556e5f 1225-1229 d297953e40966981 b3b321c730c2a926 -1230-1234 2b75e34b10cb22be 6845360ccbcfbce3 -1235-1239 299fb21e399aef5d 74cf8eb7a9e159b2 +1230-1234 2b75e34b10cb22be f51e5fa9552e452a +1235-1239 299fb21e399aef5d d94589c943f7d8b7 1240-1244 c0ae0ef2eacb1d20 3edd06583588d801 1245-1249 8cece7e0363ae620 512797b949f264fd -1250-1254 124798b1ca2185da c5f06eb60c4ef186 +1250-1254 124798b1ca2185da 4907a2192ed01c3b 1255-1259 32bb20f47d09e0fd f88d92dd1bcada2c 1260-1264 a90cc7dbee7863ee a4f79bdb0ab09720 -1265-1269 9601afdc614c3378 bfe9f968dbed5e18 +1265-1269 9601afdc614c3378 07d5b302406eee8d 1270-1274 e4027cab6820ab9c f7909eaf9d4be339 -1275-1279 52f35f77c626c7a9 3abae8154491db24 +1275-1279 52f35f77c626c7a9 1777115e4bd07213 1280-1284 54db489225a8498f a23b0bca17624b1c -1285-1289 503688b417683963 78e995256686d747 -1290-1294 0ec750caf91a885a f1e7e9c4de111992 -1295-1299 57b68256c467948e 48c3046250deef23 +1285-1289 503688b417683963 e173db817b5bd10f +1290-1294 0ec750caf91a885a ba83a3f344246704 +1295-1299 57b68256c467948e c147e50f569c4f55 1300-1304 f69a72cf286243a6 bb86e358f7072cf6 -1305-1309 5427028ca0307488 b63f19108a0245c6 -1310-1314 687bd0b688c5245a b114a72652c69bf7 +1305-1309 5427028ca0307488 7f65675305e5b10a +1310-1314 687bd0b688c5245a 5b4e4e87a3576c60 1315-1319 a3c063f7319cc243 e4de2fccfcf488e4 1320-1324 f71d4ea9b4e443ea 48b0bff72e8cc16c -1325-1329 36b1c933b4769bef d3794598cba32e40 -1330-1334 12fec017822c9408 5e641f7a680d56c8 +1325-1329 36b1c933b4769bef b50cdd41385e4ebc +1330-1334 12fec017822c9408 de09767fdda5ab45 1335-1339 f2554c70f5b6c871 4f78edd9e58c2c92 1340-1344 eb3f9210aee55fa0 2421aa9dc78a887f -1345-1349 2227034cb8224e92 6e9c0ec28caf5ebb -1350-1354 d1c3cc3f3053abef 628e65bcd7739e72 +1345-1349 2227034cb8224e92 c880aabd679b4b6c +1350-1354 d1c3cc3f3053abef 4e4eb42bcf66f8bc 1355-1359 837b255beca431d9 ea6849feb7fcc751 1360-1364 5a0320d1b8d0c441 b02b6307ffd735ab 1365-1369 bc3c4bdab54f5895 8e01f8a9337c54eb -1370-1374 5b91bc73f002c848 752aba121679327c -1375-1379 b942d2d527e313bb 659eee69e96502c0 +1370-1374 5b91bc73f002c848 971ccbe0659fd777 +1375-1379 b942d2d527e313bb 30b4f2918f07a0da 1380-1384 ba26e589d1e33e42 c16656410a682f46 -1385-1389 814d598e06f65107 da581797f0d135d1 +1385-1389 814d598e06f65107 c55f8694d04338d8 1390-1394 b086f62b2050c60c 86d8cf73e159021f 1395-1399 b26420e6d29fdb12 4e4a263e2423589f -1400-1404 f9fe3b626f1686b5 cef2ca1c367efd3c +1400-1404 f9fe3b626f1686b5 96b84c42dc4a7351 1405-1409 4e57a3753006f235 bce4cb677d18c7a8 1410-1414 115e306882ded7e8 10af2c55531cb3e3 1415-1419 434caf3c3910b014 bf4169615aefe9c5 1420-1424 05902fd403f63f45 0944208a7b10e1f2 1425-1429 8ab91ea0295bfb52 80fefec043525894 1430-1434 3484e596f0cec9e4 4888e0644305f3e7 -1435-1439 13c16ded50903e22 c4ffe4dd5943efca +1435-1439 13c16ded50903e22 1d51e6402b9a96db 1440-1444 31e0740b70544201 2c7a5fe6c18d9400 1445-1449 8be55a7cd8438be9 247bbaaa4f55ab76 1450-1454 e9d1c354908dc7d5 858c296e8455d9e7 -1455-1459 c8229a6a88eec0ac 80d9c92c73325cb7 -1460-1464 ce73f815cae74496 bb6784e45e8f2626 +1455-1459 c8229a6a88eec0ac 509a315cb3142655 +1460-1464 ce73f815cae74496 4fb2814133a59555 1465-1469 1695babf780735fd cd1492eabdb7ef4a 1470-1474 7285c5cca58b9f85 9ebcb3c0e2ee91ad -1475-1479 826e8660179fe16a 66fd8f473ce0d8ea +1475-1479 826e8660179fe16a 9d157f6d6e39c7c5 1480-1484 8ecf1216c69ed810 e1a7dfcb53524ed9 -1485-1489 8a3905febae9ece0 352251fd114c8e13 +1485-1489 8a3905febae9ece0 97bc211c7cd77ba6 1490-1494 2f1c19a2427058be ccd75a4f323eb2a2 1495-1499 952ea603e646e84a 0a46e856da36a94c diff --git a/crates/socket-patch-core/tests/uv_hosted.rs b/crates/socket-patch-core/tests/uv_hosted.rs index 81696f5dc..9bfd07844 100644 --- a/crates/socket-patch-core/tests/uv_hosted.rs +++ b/crates/socket-patch-core/tests/uv_hosted.rs @@ -83,7 +83,15 @@ fn native_projects_keep_sources_and_metadata_in_sync() { std::slice::from_ref(&dep), &metadata, ); - assert!(result.warnings.is_empty(), "{:?}", result.warnings); + // A transitive pin goes through override-dependencies, which uv + // before 0.5.5 ignores: the run advises on it (#1569). + let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); + let expected: &[&str] = if direct { + &[] + } else { + &["redirect_uv_override_requires_uv_0_5_6"] + }; + assert_eq!(codes, expected, "{:?}", result.warnings); let project: toml_edit::DocumentMut = result.files["pyproject.toml"].parse().unwrap(); assert_eq!( project["tool"]["uv"]["sources"]["alpha"]["url"].as_str(), diff --git a/docs/testing/uv-compatibility.md b/docs/testing/uv-compatibility.md index 97b6985aa..33220e4dc 100644 --- a/docs/testing/uv-compatibility.md +++ b/docs/testing/uv-compatibility.md @@ -116,7 +116,8 @@ frozen, locked, and ordinary installation outcomes separately where supported. releases the repointed entry makes `uv sync --locked` fail (`--frozen` and a plain `uv sync` still install the patch; the plain sync rewrites the entry back). The CLI cannot tell those binaries apart from the lock, so the - repoint emits the advisory `pypi_uv_constraints_require_uv_0_5_6`. The + repoint emits the advisory `pypi_uv_constraints_require_uv_0_5_6` + (hosted: `redirect_uv_constraints_require_uv_0_5_6`). The project-variant lane below exercises both shapes. Measured with the real 0.5.4 and 0.5.5 binaries (`scripts/uv-vex-matrix.sh`): 0.5.4 still rejects the repointed entry under `--locked`, 0.5.5 accepts it — the effective @@ -130,7 +131,8 @@ frozen, locked, and ordinary installation outcomes separately where supported. but a plain `uv sync` re-resolves the override against the registry and reinstalls the pristine wheel (and rewrites the lock). The CLI cannot tell those binaries apart from the lock, so the override branch emits the - advisory warning `pypi_uv_override_requires_uv_0_5_6` instead of refusing. + advisory warning `pypi_uv_override_requires_uv_0_5_6` (hosted: + `redirect_uv_override_requires_uv_0_5_6`, #1569) instead of refusing. Measured with the real binaries: 0.5.4 still re-resolves the override, 0.5.5 already keeps it (effective boundary 0.5.5). When the environment already holds the patched install (a `--frozen` sync ran first), 0.2.37 –