mirror of https://github.com/astral-sh/uv
Merge 67ce1983c6 into b58f543e5e
This commit is contained in:
commit
16b124454e
|
|
@ -6038,6 +6038,12 @@ pub struct PythonListArgs {
|
|||
#[arg(long, alias = "all_architectures")]
|
||||
pub all_arches: bool,
|
||||
|
||||
/// Show all Python variants, including debug and freethreaded builds.
|
||||
///
|
||||
/// By default, debug and freethreaded builds are hidden from the list.
|
||||
#[arg(long)]
|
||||
pub all_variants: bool,
|
||||
|
||||
/// Only show installed Python versions.
|
||||
///
|
||||
/// By default, installed distributions and available downloads for the current platform are shown.
|
||||
|
|
|
|||
|
|
@ -1742,6 +1742,21 @@ impl PythonVariant {
|
|||
}
|
||||
}
|
||||
impl PythonRequest {
|
||||
/// Return the [`PythonVariant`] of the request, if any.
|
||||
pub fn variant(&self) -> Option<PythonVariant> {
|
||||
match self {
|
||||
Self::Version(version) => version.variant(),
|
||||
Self::ImplementationVersion(_, version) => version.variant(),
|
||||
Self::Default
|
||||
| Self::Any
|
||||
| Self::Directory(_)
|
||||
| Self::File(_)
|
||||
| Self::ExecutableName(_)
|
||||
| Self::Implementation(_)
|
||||
| Self::Key(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a request from a string.
|
||||
///
|
||||
/// This cannot fail, which means weird inputs will be parsed as [`PythonRequest::File`] or
|
||||
|
|
@ -3453,7 +3468,8 @@ mod tests {
|
|||
os: None,
|
||||
libc: None,
|
||||
build: None,
|
||||
prereleases: None
|
||||
prereleases: None,
|
||||
all_variants: false
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -3473,7 +3489,8 @@ mod tests {
|
|||
os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
|
||||
libc: Some(Libc::None),
|
||||
build: None,
|
||||
prereleases: None
|
||||
prereleases: None,
|
||||
all_variants: false
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -3490,7 +3507,8 @@ mod tests {
|
|||
os: None,
|
||||
libc: None,
|
||||
build: None,
|
||||
prereleases: None
|
||||
prereleases: None,
|
||||
all_variants: false
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -3510,7 +3528,8 @@ mod tests {
|
|||
os: None,
|
||||
libc: None,
|
||||
build: None,
|
||||
prereleases: None
|
||||
prereleases: None,
|
||||
all_variants: false
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -164,6 +164,13 @@ pub struct PythonDownloadRequest {
|
|||
/// Whether to allow pre-releases or not. If not set, defaults to true if [`Self::version`] is
|
||||
/// not None, and false otherwise.
|
||||
pub(crate) prereleases: Option<bool>,
|
||||
|
||||
/// Whether to include all Python variants (e.g., debug, freethreaded) in the results.
|
||||
///
|
||||
/// If `true`, all variants matching the version request are included.
|
||||
/// If `false`, only the variant specified in the [`Self::version`] request is included,
|
||||
/// defaulting to the default variant if no variant is specified.
|
||||
pub(crate) all_variants: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -255,6 +262,7 @@ impl PythonDownloadRequest {
|
|||
os: Option<Os>,
|
||||
libc: Option<Libc>,
|
||||
prereleases: Option<bool>,
|
||||
all_variants: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
version,
|
||||
|
|
@ -264,6 +272,7 @@ impl PythonDownloadRequest {
|
|||
libc,
|
||||
build: None,
|
||||
prereleases,
|
||||
all_variants,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +328,12 @@ impl PythonDownloadRequest {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_all_variants(mut self, all_variants: bool) -> Self {
|
||||
self.all_variants = all_variants;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_build(mut self, build: String) -> Self {
|
||||
self.build = Some(build);
|
||||
|
|
@ -526,10 +541,10 @@ impl PythonDownloadRequest {
|
|||
) {
|
||||
return false;
|
||||
}
|
||||
if let Some(variant) = version.variant() {
|
||||
if variant != key.variant {
|
||||
return false;
|
||||
}
|
||||
// When all_variants is false, only match the variant from the version request.
|
||||
// For example, `3.13+debug` will only match debug builds.
|
||||
if !self.all_variants && version.variant().is_some_and(|v| v != key.variant) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
|
|
@ -646,6 +661,7 @@ impl TryFrom<&PythonInstallationKey> for PythonDownloadRequest {
|
|||
Some(*key.os()),
|
||||
Some(*key.libc()),
|
||||
Some(key.prerelease().is_some()),
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -665,6 +681,7 @@ impl From<&ManagedPythonInstallation> for PythonDownloadRequest {
|
|||
Some(*key.os()),
|
||||
Some(*key.libc()),
|
||||
Some(key.prerelease.is_some()),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -888,7 +905,15 @@ impl FromStr for PythonDownloadRequest {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(Self::new(version, implementation, arch, os, libc, None))
|
||||
Ok(Self::new(
|
||||
version,
|
||||
implementation,
|
||||
arch,
|
||||
os,
|
||||
libc,
|
||||
None,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ pub(crate) async fn list(
|
|||
all_versions: bool,
|
||||
all_platforms: bool,
|
||||
all_arches: bool,
|
||||
all_variants: bool,
|
||||
show_urls: bool,
|
||||
output_format: PythonListFormat,
|
||||
python_downloads_json_url: Option<String>,
|
||||
|
|
@ -70,11 +71,27 @@ pub(crate) async fn list(
|
|||
preview: Preview,
|
||||
) -> Result<ExitStatus> {
|
||||
let request = request.as_deref().map(PythonRequest::parse);
|
||||
|
||||
// Reject --all-variants with explicit non-default variant requests
|
||||
if all_variants
|
||||
&& request
|
||||
.as_ref()
|
||||
.and_then(uv_python::PythonRequest::variant)
|
||||
.is_some_and(|v| v != uv_python::PythonVariant::Default)
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"`--all-variants` cannot be used with a request that specifies a variant\n\n{}{} Use `--all-variants` to show all variants for a Python version, or specify an exact variant like `3.13t` or `3.13+freethreaded`, but not both",
|
||||
"hint".bold().cyan(),
|
||||
":".bold()
|
||||
));
|
||||
}
|
||||
|
||||
let base_download_request = if python_preference == PythonPreference::OnlySystem {
|
||||
None
|
||||
} else {
|
||||
// If the user request cannot be mapped to a download request, we won't show any downloads
|
||||
PythonDownloadRequest::from_request(request.as_ref().unwrap_or(&PythonRequest::Any))
|
||||
.map(|request| request.with_all_variants(all_variants))
|
||||
};
|
||||
|
||||
let client = client_builder.build();
|
||||
|
|
@ -114,8 +131,17 @@ pub(crate) async fn list(
|
|||
.map(|request| download_list.iter_matching(request))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
// TODO(zanieb): Add a way to show debug downloads, we just hide them for now
|
||||
.filter(|download| !download.key().variant().is_debug());
|
||||
.filter(|download| {
|
||||
// Show all variants when --all-variants is set
|
||||
all_variants
|
||||
// Show all variants when a specific non-default variant was requested
|
||||
|| !matches!(
|
||||
request.as_ref().and_then(uv_python::PythonRequest::variant),
|
||||
Some(uv_python::PythonVariant::Default) | None
|
||||
)
|
||||
// Otherwise, hide debug builds by default
|
||||
|| !download.key().variant().is_debug()
|
||||
});
|
||||
|
||||
for download in downloads {
|
||||
output.insert((
|
||||
|
|
|
|||
|
|
@ -1582,6 +1582,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
args.all_versions,
|
||||
args.all_platforms,
|
||||
args.all_arches,
|
||||
args.all_variants,
|
||||
args.show_urls,
|
||||
args.output_format,
|
||||
args.python_downloads_json_url,
|
||||
|
|
|
|||
|
|
@ -992,6 +992,7 @@ pub(crate) struct PythonListSettings {
|
|||
pub(crate) all_platforms: bool,
|
||||
pub(crate) all_arches: bool,
|
||||
pub(crate) all_versions: bool,
|
||||
pub(crate) all_variants: bool,
|
||||
pub(crate) show_urls: bool,
|
||||
pub(crate) output_format: PythonListFormat,
|
||||
pub(crate) python_downloads_json_url: Option<String>,
|
||||
|
|
@ -1012,6 +1013,7 @@ impl PythonListSettings {
|
|||
all_arches,
|
||||
only_installed,
|
||||
only_downloads,
|
||||
all_variants,
|
||||
show_urls,
|
||||
output_format,
|
||||
python_downloads_json_url: python_downloads_json_url_arg,
|
||||
|
|
@ -1041,6 +1043,7 @@ impl PythonListSettings {
|
|||
all_platforms,
|
||||
all_arches,
|
||||
all_versions,
|
||||
all_variants,
|
||||
show_urls,
|
||||
output_format,
|
||||
python_downloads_json_url,
|
||||
|
|
|
|||
|
|
@ -483,6 +483,194 @@ fn python_list_downloads_installed() {
|
|||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Install a debug variant to test variant behavior when installed
|
||||
context
|
||||
.python_install()
|
||||
.arg("3.10+debug")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Now the debug variant should show as installed while others remain downloads
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.10").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.10.19-[PLATFORM] managed/cpython-3.10.19-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
cpython-3.10.19+debug-[PLATFORM] managed/cpython-3.10.19+debug-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
pypy-3.10.16-[PLATFORM] <download available>
|
||||
graalpy-3.10.0-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Test --only-installed now shows both installed variants
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.10").arg("--only-installed").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.10.19-[PLATFORM] managed/cpython-3.10.19-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
cpython-3.10.19+debug-[PLATFORM] managed/cpython-3.10.19+debug-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_list_variants() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_keys();
|
||||
|
||||
// Use cpython@3.9 for stable tests - Python 3.9 is EOL (Oct 2025) so 3.9.25 is the final version
|
||||
// Default behavior should only show default variants (no debug/freethreaded)
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.9.25-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// With --all-variants, should show all variants including debug
|
||||
#[cfg(unix)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9").arg("--all-variants").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.9.25-[PLATFORM] <download available>
|
||||
cpython-3.9.25+debug-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// On Windows, debug builds are not available from python-build-standalone
|
||||
#[cfg(windows)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9").arg("--all-variants").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.9.25-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Explicit debug variant request should work without --all-variants
|
||||
#[cfg(unix)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9+debug").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.9.25+debug-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// On Windows, explicit debug variant request returns empty since no debug builds available
|
||||
#[cfg(windows)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9+debug").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
// Explicit freethreaded variant request on 3.9 should fail with error
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.9+freethreaded").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Invalid version request: Python <3.13 does not support free-threading but 3.9+freethreaded was requested.
|
||||
");
|
||||
|
||||
// Explicit freethreaded+debug variant request on 3.9 should fail with error
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.9+freethreaded+debug").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Invalid version request: Python <3.13 does not support free-threading but 3.9+freethreaded+debug was requested.
|
||||
");
|
||||
|
||||
// Using --all-variants with a specific variant request should fail
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("cpython@3.9+debug").arg("--all-variants").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `--all-variants` cannot be used with a request that specifies a variant
|
||||
|
||||
hint: Use `--all-variants` to show all variants for a Python version, or specify an exact variant like `3.13t` or `3.13+freethreaded`, but not both
|
||||
");
|
||||
|
||||
// Note: --all-variants combined with --all-versions is tested implicitly through the
|
||||
// individual flag tests above. A dedicated combined test would be fragile due to
|
||||
// platform-specific version availability in python-build-standalone.
|
||||
|
||||
// Test freethreaded variants with stable pinned version 3.13.0
|
||||
// This ensures test stability since 3.13 is still under active development
|
||||
#[cfg(unix)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.13.0").arg("--all-variants").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.13.0-[PLATFORM] <download available>
|
||||
cpython-3.13.0+debug-[PLATFORM] <download available>
|
||||
cpython-3.13.0+freethreaded-[PLATFORM] <download available>
|
||||
cpython-3.13.0+freethreaded+debug-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
// On Windows, freethreaded variants without debug builds
|
||||
#[cfg(windows)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.13.0").arg("--all-variants").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.13.0-[PLATFORM] <download available>
|
||||
cpython-3.13.0+freethreaded-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
// Test explicit freethreaded variant request with pinned version
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.13.0+freethreaded").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.13.0+freethreaded-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
// Test explicit freethreaded+debug variant request with pinned version
|
||||
#[cfg(unix)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.13.0+freethreaded+debug").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
cpython-3.13.0+freethreaded+debug-[PLATFORM] <download available>
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
// On Windows, freethreaded+debug variant request returns empty since no debug builds available
|
||||
#[cfg(windows)]
|
||||
uv_snapshot!(context.filters(), context.python_list().arg("3.13.0+freethreaded+debug").arg("--only-downloads").env_remove(EnvVars::UV_PYTHON_DOWNLOADS), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
|
|
@ -259,6 +259,12 @@ To view Python versions for other platforms:
|
|||
$ uv python list --all-platforms
|
||||
```
|
||||
|
||||
To view all Python variants, including debug and freethreaded builds:
|
||||
|
||||
```console
|
||||
$ uv python list --all-variants
|
||||
```
|
||||
|
||||
To exclude downloads and only show installed Python versions:
|
||||
|
||||
```console
|
||||
|
|
|
|||
Loading…
Reference in New Issue