Skip to content

Commit 307e0a9

Browse files
committed
Merge branch 'main' into py314-docker
2 parents 7140a06 + df0a12d commit 307e0a9

File tree

15 files changed

+7525
-4586
lines changed

15 files changed

+7525
-4586
lines changed

crates/uv-python/build.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::fs;
2-
use std::path::Path;
1+
use std::path::PathBuf;
2+
use std::{env, fs};
33

44
fn process_json(data: &serde_json::Value) -> serde_json::Value {
55
let mut out_data = serde_json::Map::new();
@@ -14,9 +14,25 @@ fn process_json(data: &serde_json::Value) -> serde_json::Value {
1414
}
1515

1616
fn main() {
17-
let version_metadata = "download-metadata.json";
18-
println!("cargo::rerun-if-changed={version_metadata}");
19-
let target = Path::new("src/download-metadata-minified.json");
17+
let version_metadata = PathBuf::from_iter([
18+
env::var("CARGO_MANIFEST_DIR").unwrap(),
19+
"download-metadata.json".into(),
20+
]);
21+
22+
let version_metadata_minified = PathBuf::from_iter([
23+
env::var("OUT_DIR").unwrap(),
24+
"download-metadata-minified.json".into(),
25+
]);
26+
27+
println!(
28+
"cargo::rerun-if-changed={}",
29+
version_metadata.to_str().unwrap()
30+
);
31+
32+
println!(
33+
"cargo::rerun-if-changed={}",
34+
version_metadata_minified.to_str().unwrap()
35+
);
2036

2137
let json_data: serde_json::Value = serde_json::from_str(
2238
#[allow(clippy::disallowed_methods)]
@@ -28,7 +44,7 @@ fn main() {
2844

2945
#[allow(clippy::disallowed_methods)]
3046
fs::write(
31-
target,
47+
version_metadata_minified,
3248
serde_json::to_string(&filtered_data).expect("Failed to serialize JSON"),
3349
)
3450
.expect("Failed to write minified JSON");

crates/uv-python/download-metadata.json

Lines changed: 6834 additions & 4318 deletions
Large diffs are not rendered by default.

crates/uv-python/src/discovery.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,9 +1668,19 @@ fn is_windows_store_shim(_path: &Path) -> bool {
16681668
impl PythonVariant {
16691669
fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
16701670
match self {
1671-
// TODO(zanieb): Right now, we allow debug interpreters to be selected by default for
1672-
// backwards compatibility, but we may want to change this in the future.
1673-
Self::Default => !interpreter.gil_disabled(),
1671+
Self::Default => {
1672+
// TODO(zanieb): Right now, we allow debug interpreters to be selected by default for
1673+
// backwards compatibility, but we may want to change this in the future.
1674+
if (interpreter.python_major(), interpreter.python_minor()) >= (3, 14) {
1675+
// For Python 3.14+, the free-threaded build is not considered experimental
1676+
// and can satisfy the default variant without opt-in
1677+
true
1678+
} else {
1679+
// In Python 3.13 and earlier, the free-threaded build is considered
1680+
// experimental and requires explicit opt-in
1681+
!interpreter.gil_disabled()
1682+
}
1683+
}
16741684
Self::Debug => interpreter.debug_enabled(),
16751685
Self::Freethreaded => interpreter.gil_disabled(),
16761686
Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
@@ -1935,6 +1945,24 @@ impl PythonRequest {
19351945
}
19361946
}
19371947

1948+
/// Check if this request includes a specific prerelease version.
1949+
pub fn includes_prerelease(&self) -> bool {
1950+
match self {
1951+
Self::Default => false,
1952+
Self::Any => false,
1953+
Self::Version(version_request) => version_request.prerelease().is_some(),
1954+
Self::Directory(..) => false,
1955+
Self::File(..) => false,
1956+
Self::ExecutableName(..) => false,
1957+
Self::Implementation(..) => false,
1958+
Self::ImplementationVersion(_, version) => version.prerelease().is_some(),
1959+
Self::Key(request) => request
1960+
.version
1961+
.as_ref()
1962+
.is_some_and(|request| request.prerelease().is_some()),
1963+
}
1964+
}
1965+
19381966
/// Check if a given interpreter satisfies the interpreter request.
19391967
pub fn satisfied(&self, interpreter: &Interpreter, cache: &Cache) -> bool {
19401968
/// Returns `true` if the two paths refer to the same interpreter executable.
@@ -2555,6 +2583,17 @@ impl VersionRequest {
25552583
}
25562584
}
25572585

2586+
/// Return the pre-release segment of the request, if any.
2587+
pub(crate) fn prerelease(&self) -> Option<&Prerelease> {
2588+
match self {
2589+
Self::Any | Self::Default | Self::Range(_, _) => None,
2590+
Self::Major(_, _) => None,
2591+
Self::MajorMinor(_, _, _) => None,
2592+
Self::MajorMinorPatch(_, _, _, _) => None,
2593+
Self::MajorMinorPrerelease(_, _, prerelease, _) => Some(prerelease),
2594+
}
2595+
}
2596+
25582597
/// Check if the request is for a version supported by uv.
25592598
///
25602599
/// If not, an `Err` is returned with an explanatory message.
@@ -2760,8 +2799,8 @@ impl VersionRequest {
27602799
),
27612800
Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
27622801
// Pre-releases of Python versions are always for the zero patch version
2763-
(*self_major, *self_minor, 0) == (major, minor, patch)
2764-
&& prerelease.is_none_or(|pre| *self_prerelease == pre)
2802+
(*self_major, *self_minor, 0, Some(*self_prerelease))
2803+
== (major, minor, patch, prerelease)
27652804
}
27662805
}
27672806
}

crates/uv-python/src/downloads.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,8 @@ impl FromStr for PythonDownloadRequest {
792792
}
793793
}
794794

795-
const BUILTIN_PYTHON_DOWNLOADS_JSON: &str = include_str!("download-metadata-minified.json");
795+
const BUILTIN_PYTHON_DOWNLOADS_JSON: &str =
796+
include_str!(concat!(env!("OUT_DIR"), "/download-metadata-minified.json"));
796797
static PYTHON_DOWNLOADS: OnceCell<std::borrow::Cow<'static, [ManagedPythonDownload]>> =
797798
OnceCell::new();
798799

crates/uv-python/src/managed.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -681,19 +681,22 @@ impl ManagedPythonInstallation {
681681
if (self.key.major, self.key.minor) != (other.key.major, other.key.minor) {
682682
return false;
683683
}
684-
// Require a newer, or equal patch version (for pre-release upgrades)
685-
if self.key.patch < other.key.patch {
686-
return false;
684+
// If the patch versions are the same, we're handling a pre-release upgrade
685+
if self.key.patch == other.key.patch {
686+
return match (self.key.prerelease, other.key.prerelease) {
687+
// Require a newer pre-release, if present on both
688+
(Some(self_pre), Some(other_pre)) => self_pre > other_pre,
689+
// Allow upgrade from pre-release to stable
690+
(None, Some(_)) => true,
691+
// Do not upgrade from pre-release to stable, or for matching versions
692+
(_, None) => false,
693+
};
687694
}
688-
if let Some(other_pre) = other.key.prerelease {
689-
if let Some(self_pre) = self.key.prerelease {
690-
return self_pre > other_pre;
691-
}
692-
// Do not upgrade from non-prerelease to prerelease
695+
// Require a newer patch version
696+
if self.key.patch < other.key.patch {
693697
return false;
694698
}
695-
// Do not upgrade if the patch versions are the same
696-
self.key.patch != other.key.patch
699+
true
697700
}
698701

699702
pub fn url(&self) -> Option<&str> {
@@ -1136,9 +1139,10 @@ mod tests {
11361139
PythonVariant::Default,
11371140
);
11381141

1139-
// Stable version should not upgrade from prerelease
1140-
assert!(!stable.is_upgrade_of(&prerelease));
1141-
// Prerelease should not upgrade to stable (same patch version)
1142+
// A stable version is an upgrade from prerelease
1143+
assert!(stable.is_upgrade_of(&prerelease));
1144+
1145+
// Prerelease are not upgrades of stable versions
11421146
assert!(!prerelease.is_upgrade_of(&stable));
11431147
}
11441148

crates/uv-resolver/src/resolver/mod.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2938,13 +2938,31 @@ impl ForkState {
29382938
resolution_strategy,
29392939
ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
29402940
);
2941+
29412942
if !has_url && missing_lower_bound && strategy_lowest {
2942-
warn_user_once!(
2943-
"The direct dependency `{name}` is unpinned. \
2944-
Consider setting a lower bound when using `--resolution lowest` \
2945-
or `--resolution lowest-direct` to avoid using outdated versions.",
2946-
name = package.name_no_root().unwrap(),
2947-
);
2943+
let name = package.name_no_root().unwrap();
2944+
// Handle cases where a package is listed both without and with a lower bound.
2945+
// Example:
2946+
// ```
2947+
// "coverage[toml] ; python_version < '3.11'",
2948+
// "coverage >= 7.10.0",
2949+
// ```
2950+
let bound_on_other_package = dependencies.iter().any(|other| {
2951+
Some(name) == other.package.name()
2952+
&& !other
2953+
.version
2954+
.bounding_range()
2955+
.map(|(lowest, _highest)| lowest == Bound::Unbounded)
2956+
.unwrap_or(true)
2957+
});
2958+
2959+
if !bound_on_other_package {
2960+
warn_user_once!(
2961+
"The direct dependency `{name}` is unpinned. \
2962+
Consider setting a lower bound when using `--resolution lowest` \
2963+
or `--resolution lowest-direct` to avoid using outdated versions.",
2964+
);
2965+
}
29482966
}
29492967
}
29502968

crates/uv/src/commands/python/install.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ pub(crate) async fn install(
285285
.collect::<IndexSet<_>>();
286286

287287
if upgrade
288-
&& requests
289-
.iter()
290-
.any(|request| request.request.includes_patch())
288+
&& requests.iter().any(|request| {
289+
request.request.includes_patch() || request.request.includes_prerelease()
290+
})
291291
{
292292
writeln!(
293293
printer.stderr(),
@@ -551,19 +551,28 @@ pub(crate) async fn install(
551551
printer.stderr(),
552552
"There are no installed versions to upgrade"
553553
)?;
554-
} else if requests.len() > 1 {
554+
} else if upgrade && is_unspecified_upgrade {
555+
writeln!(
556+
printer.stderr(),
557+
"All versions already on latest supported patch release"
558+
)?;
559+
} else if let [request] = requests.as_slice() {
560+
// Convert to the inner request
561+
let request = &request.request;
555562
if upgrade {
556-
if is_unspecified_upgrade {
557-
writeln!(
558-
printer.stderr(),
559-
"All versions already on latest supported patch release"
560-
)?;
561-
} else {
562-
writeln!(
563-
printer.stderr(),
564-
"All requested versions already on latest supported patch release"
565-
)?;
566-
}
563+
writeln!(
564+
printer.stderr(),
565+
"{request} is already on the latest supported patch release"
566+
)?;
567+
} else {
568+
writeln!(printer.stderr(), "{request} is already installed")?;
569+
}
570+
} else {
571+
if upgrade {
572+
writeln!(
573+
printer.stderr(),
574+
"All requested versions already on latest supported patch release"
575+
)?;
567576
} else {
568577
writeln!(printer.stderr(), "All requested versions already installed")?;
569578
}

0 commit comments

Comments
 (0)