mirror of https://github.com/astral-sh/uv
Merge branch 'main' into py314-docker
This commit is contained in:
commit
307e0a9b5e
|
|
@ -1,5 +1,5 @@
|
|||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
fn process_json(data: &serde_json::Value) -> serde_json::Value {
|
||||
let mut out_data = serde_json::Map::new();
|
||||
|
|
@ -14,9 +14,25 @@ fn process_json(data: &serde_json::Value) -> serde_json::Value {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let version_metadata = "download-metadata.json";
|
||||
println!("cargo::rerun-if-changed={version_metadata}");
|
||||
let target = Path::new("src/download-metadata-minified.json");
|
||||
let version_metadata = PathBuf::from_iter([
|
||||
env::var("CARGO_MANIFEST_DIR").unwrap(),
|
||||
"download-metadata.json".into(),
|
||||
]);
|
||||
|
||||
let version_metadata_minified = PathBuf::from_iter([
|
||||
env::var("OUT_DIR").unwrap(),
|
||||
"download-metadata-minified.json".into(),
|
||||
]);
|
||||
|
||||
println!(
|
||||
"cargo::rerun-if-changed={}",
|
||||
version_metadata.to_str().unwrap()
|
||||
);
|
||||
|
||||
println!(
|
||||
"cargo::rerun-if-changed={}",
|
||||
version_metadata_minified.to_str().unwrap()
|
||||
);
|
||||
|
||||
let json_data: serde_json::Value = serde_json::from_str(
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
|
|
@ -28,7 +44,7 @@ fn main() {
|
|||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
fs::write(
|
||||
target,
|
||||
version_metadata_minified,
|
||||
serde_json::to_string(&filtered_data).expect("Failed to serialize JSON"),
|
||||
)
|
||||
.expect("Failed to write minified JSON");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1668,9 +1668,19 @@ fn is_windows_store_shim(_path: &Path) -> bool {
|
|||
impl PythonVariant {
|
||||
fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
|
||||
match self {
|
||||
// TODO(zanieb): Right now, we allow debug interpreters to be selected by default for
|
||||
// backwards compatibility, but we may want to change this in the future.
|
||||
Self::Default => !interpreter.gil_disabled(),
|
||||
Self::Default => {
|
||||
// TODO(zanieb): Right now, we allow debug interpreters to be selected by default for
|
||||
// backwards compatibility, but we may want to change this in the future.
|
||||
if (interpreter.python_major(), interpreter.python_minor()) >= (3, 14) {
|
||||
// For Python 3.14+, the free-threaded build is not considered experimental
|
||||
// and can satisfy the default variant without opt-in
|
||||
true
|
||||
} else {
|
||||
// In Python 3.13 and earlier, the free-threaded build is considered
|
||||
// experimental and requires explicit opt-in
|
||||
!interpreter.gil_disabled()
|
||||
}
|
||||
}
|
||||
Self::Debug => interpreter.debug_enabled(),
|
||||
Self::Freethreaded => interpreter.gil_disabled(),
|
||||
Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
|
||||
|
|
@ -1935,6 +1945,24 @@ impl PythonRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if this request includes a specific prerelease version.
|
||||
pub fn includes_prerelease(&self) -> bool {
|
||||
match self {
|
||||
Self::Default => false,
|
||||
Self::Any => false,
|
||||
Self::Version(version_request) => version_request.prerelease().is_some(),
|
||||
Self::Directory(..) => false,
|
||||
Self::File(..) => false,
|
||||
Self::ExecutableName(..) => false,
|
||||
Self::Implementation(..) => false,
|
||||
Self::ImplementationVersion(_, version) => version.prerelease().is_some(),
|
||||
Self::Key(request) => request
|
||||
.version
|
||||
.as_ref()
|
||||
.is_some_and(|request| request.prerelease().is_some()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a given interpreter satisfies the interpreter request.
|
||||
pub fn satisfied(&self, interpreter: &Interpreter, cache: &Cache) -> bool {
|
||||
/// Returns `true` if the two paths refer to the same interpreter executable.
|
||||
|
|
@ -2555,6 +2583,17 @@ impl VersionRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the pre-release segment of the request, if any.
|
||||
pub(crate) fn prerelease(&self) -> Option<&Prerelease> {
|
||||
match self {
|
||||
Self::Any | Self::Default | Self::Range(_, _) => None,
|
||||
Self::Major(_, _) => None,
|
||||
Self::MajorMinor(_, _, _) => None,
|
||||
Self::MajorMinorPatch(_, _, _, _) => None,
|
||||
Self::MajorMinorPrerelease(_, _, prerelease, _) => Some(prerelease),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the request is for a version supported by uv.
|
||||
///
|
||||
/// If not, an `Err` is returned with an explanatory message.
|
||||
|
|
@ -2760,8 +2799,8 @@ impl VersionRequest {
|
|||
),
|
||||
Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
|
||||
// Pre-releases of Python versions are always for the zero patch version
|
||||
(*self_major, *self_minor, 0) == (major, minor, patch)
|
||||
&& prerelease.is_none_or(|pre| *self_prerelease == pre)
|
||||
(*self_major, *self_minor, 0, Some(*self_prerelease))
|
||||
== (major, minor, patch, prerelease)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -792,7 +792,8 @@ impl FromStr for PythonDownloadRequest {
|
|||
}
|
||||
}
|
||||
|
||||
const BUILTIN_PYTHON_DOWNLOADS_JSON: &str = include_str!("download-metadata-minified.json");
|
||||
const BUILTIN_PYTHON_DOWNLOADS_JSON: &str =
|
||||
include_str!(concat!(env!("OUT_DIR"), "/download-metadata-minified.json"));
|
||||
static PYTHON_DOWNLOADS: OnceCell<std::borrow::Cow<'static, [ManagedPythonDownload]>> =
|
||||
OnceCell::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -681,19 +681,22 @@ impl ManagedPythonInstallation {
|
|||
if (self.key.major, self.key.minor) != (other.key.major, other.key.minor) {
|
||||
return false;
|
||||
}
|
||||
// Require a newer, or equal patch version (for pre-release upgrades)
|
||||
// If the patch versions are the same, we're handling a pre-release upgrade
|
||||
if self.key.patch == other.key.patch {
|
||||
return match (self.key.prerelease, other.key.prerelease) {
|
||||
// Require a newer pre-release, if present on both
|
||||
(Some(self_pre), Some(other_pre)) => self_pre > other_pre,
|
||||
// Allow upgrade from pre-release to stable
|
||||
(None, Some(_)) => true,
|
||||
// Do not upgrade from pre-release to stable, or for matching versions
|
||||
(_, None) => false,
|
||||
};
|
||||
}
|
||||
// Require a newer patch version
|
||||
if self.key.patch < other.key.patch {
|
||||
return false;
|
||||
}
|
||||
if let Some(other_pre) = other.key.prerelease {
|
||||
if let Some(self_pre) = self.key.prerelease {
|
||||
return self_pre > other_pre;
|
||||
}
|
||||
// Do not upgrade from non-prerelease to prerelease
|
||||
return false;
|
||||
}
|
||||
// Do not upgrade if the patch versions are the same
|
||||
self.key.patch != other.key.patch
|
||||
true
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Option<&str> {
|
||||
|
|
@ -1136,9 +1139,10 @@ mod tests {
|
|||
PythonVariant::Default,
|
||||
);
|
||||
|
||||
// Stable version should not upgrade from prerelease
|
||||
assert!(!stable.is_upgrade_of(&prerelease));
|
||||
// Prerelease should not upgrade to stable (same patch version)
|
||||
// A stable version is an upgrade from prerelease
|
||||
assert!(stable.is_upgrade_of(&prerelease));
|
||||
|
||||
// Prerelease are not upgrades of stable versions
|
||||
assert!(!prerelease.is_upgrade_of(&stable));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2938,13 +2938,31 @@ impl ForkState {
|
|||
resolution_strategy,
|
||||
ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
|
||||
);
|
||||
|
||||
if !has_url && missing_lower_bound && strategy_lowest {
|
||||
warn_user_once!(
|
||||
"The direct dependency `{name}` is unpinned. \
|
||||
Consider setting a lower bound when using `--resolution lowest` \
|
||||
or `--resolution lowest-direct` to avoid using outdated versions.",
|
||||
name = package.name_no_root().unwrap(),
|
||||
);
|
||||
let name = package.name_no_root().unwrap();
|
||||
// Handle cases where a package is listed both without and with a lower bound.
|
||||
// Example:
|
||||
// ```
|
||||
// "coverage[toml] ; python_version < '3.11'",
|
||||
// "coverage >= 7.10.0",
|
||||
// ```
|
||||
let bound_on_other_package = dependencies.iter().any(|other| {
|
||||
Some(name) == other.package.name()
|
||||
&& !other
|
||||
.version
|
||||
.bounding_range()
|
||||
.map(|(lowest, _highest)| lowest == Bound::Unbounded)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
if !bound_on_other_package {
|
||||
warn_user_once!(
|
||||
"The direct dependency `{name}` is unpinned. \
|
||||
Consider setting a lower bound when using `--resolution lowest` \
|
||||
or `--resolution lowest-direct` to avoid using outdated versions.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -285,9 +285,9 @@ pub(crate) async fn install(
|
|||
.collect::<IndexSet<_>>();
|
||||
|
||||
if upgrade
|
||||
&& requests
|
||||
.iter()
|
||||
.any(|request| request.request.includes_patch())
|
||||
&& requests.iter().any(|request| {
|
||||
request.request.includes_patch() || request.request.includes_prerelease()
|
||||
})
|
||||
{
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
|
|
@ -551,19 +551,28 @@ pub(crate) async fn install(
|
|||
printer.stderr(),
|
||||
"There are no installed versions to upgrade"
|
||||
)?;
|
||||
} else if requests.len() > 1 {
|
||||
} else if upgrade && is_unspecified_upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All versions already on latest supported patch release"
|
||||
)?;
|
||||
} else if let [request] = requests.as_slice() {
|
||||
// Convert to the inner request
|
||||
let request = &request.request;
|
||||
if upgrade {
|
||||
if is_unspecified_upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All versions already on latest supported patch release"
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All requested versions already on latest supported patch release"
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{request} is already on the latest supported patch release"
|
||||
)?;
|
||||
} else {
|
||||
writeln!(printer.stderr(), "{request} is already installed")?;
|
||||
}
|
||||
} else {
|
||||
if upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All requested versions already on latest supported patch release"
|
||||
)?;
|
||||
} else {
|
||||
writeln!(printer.stderr(), "All requested versions already installed")?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ use tracing::{debug, trace};
|
|||
use uv_cache::Cache;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::{Concurrency, Constraints, DryRun, TargetTriple};
|
||||
use uv_distribution_types::{ExtraBuildRequires, Requirement};
|
||||
use uv_distribution_types::{ExtraBuildRequires, Requirement, RequirementSource};
|
||||
use uv_fs::CWD;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{Operator, Version};
|
||||
use uv_preview::Preview;
|
||||
use uv_python::{
|
||||
EnvironmentPreference, Interpreter, PythonDownloads, PythonInstallation, PythonPreference,
|
||||
|
|
@ -19,7 +20,7 @@ use uv_python::{
|
|||
};
|
||||
use uv_requirements::RequirementsSpecification;
|
||||
use uv_settings::{Combine, PythonInstallMirrors, ResolverInstallerOptions, ToolOptions};
|
||||
use uv_tool::InstalledTools;
|
||||
use uv_tool::{InstalledTools, Tool};
|
||||
use uv_warnings::write_error_chain;
|
||||
use uv_workspace::WorkspaceCache;
|
||||
|
||||
|
|
@ -114,6 +115,9 @@ pub(crate) async fn upgrade(
|
|||
// Determine whether we applied any upgrades.
|
||||
let mut did_upgrade_environment = vec![];
|
||||
|
||||
// Constraints that caused upgrades to be skipped or altered.
|
||||
let mut collected_constraints: Vec<(PackageName, UpgradeConstraint)> = Vec::new();
|
||||
|
||||
let mut errors = Vec::new();
|
||||
for (name, constraints) in &names {
|
||||
debug!("Upgrading tool: `{name}`");
|
||||
|
|
@ -135,14 +139,22 @@ pub(crate) async fn upgrade(
|
|||
.await;
|
||||
|
||||
match result {
|
||||
Ok(UpgradeOutcome::UpgradeEnvironment) => {
|
||||
did_upgrade_environment.push(name);
|
||||
}
|
||||
Ok(UpgradeOutcome::UpgradeDependencies | UpgradeOutcome::UpgradeTool) => {
|
||||
did_upgrade_tool.push(name);
|
||||
}
|
||||
Ok(UpgradeOutcome::NoOp) => {
|
||||
debug!("Upgrading `{name}` was a no-op");
|
||||
Ok(report) => {
|
||||
match report.outcome {
|
||||
UpgradeOutcome::UpgradeEnvironment => {
|
||||
did_upgrade_environment.push(name);
|
||||
}
|
||||
UpgradeOutcome::UpgradeTool | UpgradeOutcome::UpgradeDependencies => {
|
||||
did_upgrade_tool.push(name);
|
||||
}
|
||||
UpgradeOutcome::NoOp => {
|
||||
debug!("Upgrading `{name}` was a no-op");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(constraint) = report.constraint.clone() {
|
||||
collected_constraints.push((name.clone(), constraint));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push((name, err));
|
||||
|
|
@ -187,6 +199,14 @@ pub(crate) async fn upgrade(
|
|||
}
|
||||
}
|
||||
|
||||
if !collected_constraints.is_empty() {
|
||||
writeln!(printer.stderr())?;
|
||||
}
|
||||
|
||||
for (name, constraint) in collected_constraints {
|
||||
constraint.print(&name, printer)?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +222,39 @@ enum UpgradeOutcome {
|
|||
NoOp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum UpgradeConstraint {
|
||||
/// The tool remains pinned to an exact version, so an upgrade was skipped.
|
||||
PinnedVersion { version: Version },
|
||||
}
|
||||
|
||||
impl UpgradeConstraint {
|
||||
fn print(&self, name: &PackageName, printer: Printer) -> Result<()> {
|
||||
match self {
|
||||
Self::PinnedVersion { version } => {
|
||||
let name = name.to_string();
|
||||
let reinstall_command = format!("uv tool install {name}@latest");
|
||||
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"hint: `{}` is pinned to `{}` (installed with an exact version pin); reinstall with `{}` to upgrade to a new version.",
|
||||
name.cyan(),
|
||||
version.to_string().magenta(),
|
||||
reinstall_command.green(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct UpgradeReport {
|
||||
outcome: UpgradeOutcome,
|
||||
constraint: Option<UpgradeConstraint>,
|
||||
}
|
||||
|
||||
/// Upgrade a specific tool.
|
||||
async fn upgrade_tool(
|
||||
name: &PackageName,
|
||||
|
|
@ -217,7 +270,7 @@ async fn upgrade_tool(
|
|||
installer_metadata: bool,
|
||||
concurrency: Concurrency,
|
||||
preview: Preview,
|
||||
) -> Result<UpgradeOutcome> {
|
||||
) -> Result<UpgradeReport> {
|
||||
// Ensure the tool is installed.
|
||||
let existing_tool_receipt = match installed_tools.get_tool_receipt(name) {
|
||||
Ok(Some(receipt)) => receipt,
|
||||
|
|
@ -398,5 +451,38 @@ async fn upgrade_tool(
|
|||
)?;
|
||||
}
|
||||
|
||||
Ok(outcome)
|
||||
let constraint = match &outcome {
|
||||
UpgradeOutcome::UpgradeDependencies | UpgradeOutcome::NoOp => {
|
||||
pinned_requirement_version(&existing_tool_receipt, name)
|
||||
.map(|version| UpgradeConstraint::PinnedVersion { version })
|
||||
}
|
||||
UpgradeOutcome::UpgradeTool | UpgradeOutcome::UpgradeEnvironment => None,
|
||||
};
|
||||
|
||||
Ok(UpgradeReport {
|
||||
outcome,
|
||||
constraint,
|
||||
})
|
||||
}
|
||||
|
||||
fn pinned_requirement_version(tool: &Tool, name: &PackageName) -> Option<Version> {
|
||||
pinned_version_from(tool.requirements(), name)
|
||||
.or_else(|| pinned_version_from(tool.constraints(), name))
|
||||
}
|
||||
|
||||
fn pinned_version_from(requirements: &[Requirement], name: &PackageName) -> Option<Version> {
|
||||
requirements
|
||||
.iter()
|
||||
.filter(|requirement| requirement.name == *name)
|
||||
.find_map(|requirement| match &requirement.source {
|
||||
RequirementSource::Registry { specifier, .. } => {
|
||||
specifier
|
||||
.iter()
|
||||
.find_map(|specifier| match specifier.operator() {
|
||||
Operator::Equal | Operator::ExactEqual => Some(specifier.version().clone()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31969,3 +31969,34 @@ fn collapsed_error_with_marker_packages() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://github.com/astral-sh/uv/issues/16148>
|
||||
#[test]
|
||||
fn no_warning_without_and_with_lower_bound() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"anyio[trio]",
|
||||
"anyio>=4"
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--resolution").arg("lowest-direct"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 10 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::prelude::{FileTouch, PathChild};
|
||||
use assert_fs::{fixture::FileWriteStr, prelude::PathCreateDir};
|
||||
use indoc::indoc;
|
||||
|
|
@ -1155,7 +1156,7 @@ fn python_find_script_no_such_version() {
|
|||
script
|
||||
.write_str(indoc! {r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.14"
|
||||
# requires-python = ">=3.15"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"#})
|
||||
|
|
@ -1167,7 +1168,7 @@ fn python_find_script_no_such_version() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
No interpreter found for Python >=3.14 in [PYTHON SOURCES]
|
||||
No interpreter found for Python >=3.15 in [PYTHON SOURCES]
|
||||
");
|
||||
}
|
||||
|
||||
|
|
@ -1256,3 +1257,81 @@ fn python_find_path() {
|
|||
error: No interpreter found at path `foobar`
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_find_freethreaded_313() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_python_sources()
|
||||
.with_managed_python_dirs()
|
||||
.with_python_download_cache()
|
||||
.with_filtered_python_install_bin()
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_exe_suffix();
|
||||
|
||||
context
|
||||
.python_install()
|
||||
.arg("--preview")
|
||||
.arg("3.13t")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Request Python 3.13 (without opt-in)
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("3.13"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for Python 3.13 in [PYTHON SOURCES]
|
||||
");
|
||||
|
||||
// Request Python 3.13t (with explicit opt-in)
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("3.13t"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/cpython-3.13+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_find_freethreaded_314() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_python_sources()
|
||||
.with_managed_python_dirs()
|
||||
.with_python_download_cache()
|
||||
.with_filtered_python_install_bin()
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_exe_suffix();
|
||||
|
||||
context
|
||||
.python_install()
|
||||
.arg("--preview")
|
||||
.arg("3.14t")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Request Python 3.14 (without opt-in)
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/cpython-3.14+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Request Python 3.14t (with explicit opt-in)
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("3.14t"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/cpython-3.14+freethreaded-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -51,6 +51,7 @@ fn python_upgrade() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Python 3.10 is already on the latest supported patch release
|
||||
");
|
||||
|
||||
// Should reinstall on `--reinstall`
|
||||
|
|
@ -83,8 +84,8 @@ fn python_upgrade() {
|
|||
|
||||
----- stderr -----
|
||||
warning: `uv python upgrade` is experimental and may change without warning. Pass `--preview-features python-upgrade` to disable this warning
|
||||
Installed Python 3.14.0rc3 in [TIME]
|
||||
+ cpython-3.14.0rc3-[PLATFORM] (python3.14)
|
||||
Installed Python 3.14.0 in [TIME]
|
||||
+ cpython-3.14.0-[PLATFORM] (python3.14)
|
||||
");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5035,7 +5035,7 @@ fn run_groups_requires_python() -> Result<()> {
|
|||
dev = ["sniffio"]
|
||||
|
||||
[tool.uv.dependency-groups]
|
||||
foo = {requires-python=">=3.14"}
|
||||
foo = {requires-python=">=3.100"}
|
||||
bar = {requires-python=">=3.13"}
|
||||
dev = {requires-python=">=3.12"}
|
||||
"#,
|
||||
|
|
@ -5168,7 +5168,7 @@ fn run_groups_requires_python() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for Python >=3.14 in [PYTHON SOURCES]
|
||||
error: No interpreter found for Python >=3.100 in [PYTHON SOURCES]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -215,6 +215,109 @@ fn tool_upgrade_multiple_names() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_upgrade_pinned_hint() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install a specific version of `babel` so the receipt records an exact pin.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("babel==2.6.0")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple/")
|
||||
.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 [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ babel==2.6.0
|
||||
+ pytz==2018.5
|
||||
Installed 1 executable: pybabel
|
||||
"###);
|
||||
|
||||
// Attempt to upgrade `babel`; it should remain pinned and emit a hint explaining why.
|
||||
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||
.arg("babel")
|
||||
.arg("--index-url")
|
||||
.arg("https://pypi.org/simple/")
|
||||
.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 -----
|
||||
Modified babel environment
|
||||
- pytz==2018.5
|
||||
+ pytz==2024.1
|
||||
|
||||
hint: `babel` is pinned to `2.6.0` (installed with an exact version pin); reinstall with `uv tool install babel@latest` to upgrade to a new version.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_upgrade_pinned_hint_with_mixed_constraint() {
|
||||
let context = TestContext::new("3.12")
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install a specific version of `babel` with an additional constraint to ensure the requirement
|
||||
// contains multiple specifiers while still including an exact pin.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("babel>=2.0,==2.6.0")
|
||||
.arg("--index-url")
|
||||
.arg("https://test.pypi.org/simple/")
|
||||
.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 [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ babel==2.6.0
|
||||
+ pytz==2018.5
|
||||
Installed 1 executable: pybabel
|
||||
"###);
|
||||
|
||||
// Attempt to upgrade `babel`; it should remain pinned and emit a hint explaining why.
|
||||
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||
.arg("babel")
|
||||
.arg("--index-url")
|
||||
.arg("https://pypi.org/simple/")
|
||||
.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 -----
|
||||
Modified babel environment
|
||||
- pytz==2018.5
|
||||
+ pytz==2024.1
|
||||
|
||||
hint: `babel` is pinned to `2.6.0` (installed with an exact version pin); reinstall with `uv tool install babel@latest` to upgrade to a new version.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_upgrade_all() {
|
||||
let context = TestContext::new("3.12")
|
||||
|
|
@ -683,6 +786,8 @@ fn tool_upgrade_with() {
|
|||
Modified babel environment
|
||||
- pytz==2018.5
|
||||
+ pytz==2024.1
|
||||
|
||||
hint: `babel` is pinned to `2.6.0` (installed with an exact version pin); reinstall with `uv tool install babel@latest` to upgrade to a new version.
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,6 @@ However, uv supports `pylock.toml` as an export target and in the `uv pip` CLI.
|
|||
|
||||
- To export a `uv.lock` to the `pylock.toml` format, run: `uv export -o pylock.toml`
|
||||
- To generate a `pylock.toml` file from a set of requirements, run:
|
||||
`uv pip compile -o pylock.toml -r requirements.in`
|
||||
`uv pip compile requirements.in -o pylock.toml`
|
||||
- To install from a `pylock.toml` file, run: `uv pip sync pylock.toml` or
|
||||
`uv pip install -r pylock.toml`
|
||||
|
|
|
|||
Loading…
Reference in New Issue