diff --git a/Cargo.lock b/Cargo.lock index 2c000bfbe..df71eadbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4181,6 +4181,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ + "indexmap", "itoa", "memchr", "ryu", @@ -5492,6 +5493,7 @@ dependencies = [ "uv-torch", "uv-trampoline-builder", "uv-types", + "uv-variants", "uv-version", "uv-virtualenv", "uv-warnings", @@ -5831,6 +5833,7 @@ dependencies = [ "uv-small-str", "uv-static", "uv-torch", + "uv-variants", "uv-version", "uv-warnings", "wiremock", @@ -5961,6 +5964,8 @@ dependencies = [ "uv-python", "uv-resolver", "uv-types", + "uv-variant-frontend", + "uv-variants", "uv-version", "uv-workspace", ] @@ -5976,6 +5981,7 @@ dependencies = [ "futures", "indoc", "insta", + "itertools 0.14.0", "nanoid", "owo-colors", "reqwest", @@ -6002,12 +6008,14 @@ dependencies = [ "uv-git-types", "uv-metadata", "uv-normalize", + "uv-once-map", "uv-pep440", "uv-pep508", "uv-platform-tags", "uv-pypi-types", "uv-redacted", "uv-types", + "uv-variants", "uv-workspace", "walkdir", "zip", @@ -6067,6 +6075,7 @@ dependencies = [ "uv-pypi-types", "uv-redacted", "uv-small-str", + "uv-variants", "uv-warnings", ] @@ -6482,6 +6491,7 @@ dependencies = [ "anyhow", "hashbrown 0.16.1", "indexmap", + "indoc", "insta", "itertools 0.14.0", "jiff", @@ -6708,6 +6718,7 @@ dependencies = [ "uv-static", "uv-torch", "uv-types", + "uv-variants", "uv-version", "uv-warnings", "uv-workspace", @@ -6897,12 +6908,60 @@ dependencies = [ "uv-normalize", "uv-once-map", "uv-pep440", + "uv-pep508", "uv-pypi-types", "uv-python", "uv-redacted", + "uv-variants", "uv-workspace", ] +[[package]] +name = "uv-variant-frontend" +version = "0.0.3" +dependencies = [ + "anstream", + "anyhow", + "fs-err", + "indoc", + "owo-colors", + "rustc-hash", + "serde_json", + "tempfile", + "thiserror 2.0.17", + "tokio", + "tracing", + "uv-configuration", + "uv-distribution-types", + "uv-fs", + "uv-preview", + "uv-python", + "uv-static", + "uv-types", + "uv-variants", + "uv-virtualenv", +] + +[[package]] +name = "uv-variants" +version = "0.0.3" +dependencies = [ + "indexmap", + "insta", + "itertools 0.14.0", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", + "uv-distribution-filename", + "uv-normalize", + "uv-once-map", + "uv-pep440", + "uv-pep508", + "uv-pypi-types", +] + [[package]] name = "uv-version" version = "0.9.14" diff --git a/Cargo.toml b/Cargo.toml index 66afd27b3..97b8e66dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,8 @@ uv-tool = { version = "0.0.4", path = "crates/uv-tool" } uv-torch = { version = "0.0.4", path = "crates/uv-torch" } uv-trampoline-builder = { version = "0.0.4", path = "crates/uv-trampoline-builder" } uv-types = { version = "0.0.4", path = "crates/uv-types" } +uv-variant-frontend = { path = "crates/uv-variant-frontend" } +uv-variants = { path = "crates/uv-variants" } uv-version = { version = "0.9.14", path = "crates/uv-version" } uv-virtualenv = { version = "0.0.4", path = "crates/uv-virtualenv" } uv-warnings = { version = "0.0.4", path = "crates/uv-warnings" } @@ -166,7 +168,7 @@ security-framework = { version = "3" } self-replace = { version = "1.5.0" } serde = { version = "1.0.210", features = ["derive", "rc"] } serde-untagged = { version = "0.1.6" } -serde_json = { version = "1.0.128" } +serde_json = { version = "1.0.128", features = ["preserve_order"] } sha2 = { version = "0.10.8" } smallvec = { version = "1.13.2" } spdx = { version = "0.12.0" } diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 441213a90..fb9fcda7f 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -597,7 +597,7 @@ mod tests { // Check that the source dist is reproducible across platforms. assert_snapshot!( format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())), - @"871d1f859140721b67cbeaca074e7a2740c88c38028d0509eba87d1285f1da9e" + @"590388c63ef4379eef57bedafffc6522dd2e3b84e689fe55ba3b1e7f2de8cc13" ); // Check both the files we report and the actual files assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r" diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index c054c2e26..71769204e 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -34,6 +34,7 @@ uv-small-str = { workspace = true } uv-redacted = { workspace = true } uv-static = { workspace = true } uv-torch = { workspace = true } +uv-variants = { workspace = true } uv-version = { workspace = true } uv-warnings = { workspace = true } diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 7dc2b25a2..033631036 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use std::path::PathBuf; use uv_distribution_filename::{WheelFilename, WheelFilenameError}; +use uv_distribution_types::VariantsJsonFilename; use uv_normalize::PackageName; use uv_redacted::DisplaySafeUrl; @@ -278,6 +279,10 @@ pub enum ErrorKind { #[error("Package `{0}` was not found in the local index")] LocalPackageNotFound(PackageName), + /// The `variants.json` file was not found in the local (file-based) index. + #[error("Variants JSON file `{0}` was not found in the local index")] + VariantsJsonNotFile(VariantsJsonFilename), + /// The root was not found in the local (file-based) index. #[error("Local index not found at: `{}`", _0.display())] LocalIndexNotFound(PathBuf), @@ -368,6 +373,9 @@ pub enum ErrorKind { #[error("Invalid cache control header: `{0}`")] InvalidCacheControl(String), + + #[error("Invalid variants.json format: {0}")] + VariantsJsonFormat(DisplaySafeUrl, #[source] serde_json::Error), } impl ErrorKind { diff --git a/crates/uv-client/src/flat_index.rs b/crates/uv-client/src/flat_index.rs index 5e898f38c..3b2b0a98f 100644 --- a/crates/uv-client/src/flat_index.rs +++ b/crates/uv-client/src/flat_index.rs @@ -7,8 +7,7 @@ use url::Url; use uv_cache::{Cache, CacheBucket}; use uv_cache_key::cache_digest; -use uv_distribution_filename::DistFilename; -use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString}; +use uv_distribution_types::{File, FileLocation, IndexEntryFilename, IndexUrl, UrlString}; use uv_pypi_types::HashDigests; use uv_redacted::DisplaySafeUrl; use uv_small_str::SmallString; @@ -40,7 +39,7 @@ pub enum FindLinksDirectoryError { /// An entry in a `--find-links` index. #[derive(Debug, Clone)] pub struct FlatIndexEntry { - pub filename: DistFilename, + pub filename: IndexEntryFilename, pub file: File, pub index: IndexUrl, } @@ -238,7 +237,9 @@ impl<'a> FlatIndexClient<'a> { }) .filter_map(|file| { Some(FlatIndexEntry { - filename: DistFilename::try_from_normalized_filename(&file.filename)?, + filename: IndexEntryFilename::try_from_normalized_filename( + &file.filename, + )?, file, index: flat_index.clone(), }) @@ -308,9 +309,10 @@ impl<'a> FlatIndexClient<'a> { zstd: None, }; - let Some(filename) = DistFilename::try_from_normalized_filename(filename) else { + // Try to parse as a distribution filename first + let Some(filename) = IndexEntryFilename::try_from_normalized_filename(filename) else { debug!( - "Ignoring `--find-links` entry (expected a wheel or source distribution filename): {}", + "Ignoring `--find-links` entry (expected a wheel, source distribution, or variants.json filename): {}", entry.path().display() ); continue; @@ -338,6 +340,7 @@ mod tests { use fs_err::File; use std::io::Write; use tempfile::tempdir; + use uv_distribution_filename::DistFilename; #[test] fn read_from_directory_sorts_distributions() { diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index f94d63b70..d882e2252 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -272,6 +272,11 @@ impl SimpleIndexHtml { return None; } + // + if project_name.ends_with("-variants.json") { + return None; + } + PackageName::from_str(project_name).ok() } } @@ -1603,4 +1608,63 @@ mod tests { } "#); } + + #[test] + fn parse_variants_json() { + // A variants.json without wheels doesn't make much sense, but it's sufficient to test + // parsing. + let text = r#" + + + +

Links for jinja2

+jinja2-3.1.2-variants.json
+ + + + "#; + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let result = SimpleDetailHTML::parse(text, &base).unwrap(); + insta::assert_debug_snapshot!(result, @r#" + SimpleDetailHTML { + base: BaseUrl( + DisplaySafeUrl { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", + ), + ), + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, + }, + ), + files: [ + PypiFile { + core_metadata: None, + filename: "jinja2-3.1.2-variants.json", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + blake2b: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/jinja2-3.1.2-variants.json", + yanked: None, + }, + ], + } + "#); + } } diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 79b6025f2..a169d7333 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -21,8 +21,9 @@ use uv_configuration::IndexStrategy; use uv_configuration::KeyringProviderType; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ - BuiltDist, File, IndexCapabilities, IndexFormat, IndexLocations, IndexMetadataRef, - IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, IndexUrls, Name, + BuiltDist, File, IndexCapabilities, IndexEntryFilename, IndexFormat, IndexLocations, + IndexMetadataRef, IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, IndexUrls, Name, + RegistryVariantsJson, VariantsJsonFilename, }; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; use uv_normalize::PackageName; @@ -35,6 +36,7 @@ use uv_pypi_types::{ use uv_redacted::DisplaySafeUrl; use uv_small_str::SmallString; use uv_torch::TorchStrategy; +use uv_variants::variants_json::VariantsJsonContent; use crate::base_client::{BaseClientBuilder, ExtraMiddleware, RedirectPolicy}; use crate::cached_client::CacheControl; @@ -867,6 +869,85 @@ impl RegistryClient { OwnedArchive::from_unarchived(&metadata) } + /// Fetch the variants.json contents from a remote index (cached) a local index. + pub async fn fetch_variants_json( + &self, + variants_json: &RegistryVariantsJson, + ) -> Result { + let url = variants_json + .file + .url + .to_url() + .map_err(ErrorKind::InvalidUrl)?; + + // If the URL is a file URL, load the variants directly from the file system. + let variants_json = if url.scheme() == "file" { + let path = url + .to_file_path() + .map_err(|()| ErrorKind::NonFileUrl(url.clone()))?; + let bytes = match fs_err::tokio::read(&path).await { + Ok(text) => text, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + return Err(Error::from(ErrorKind::VariantsJsonNotFile( + variants_json.filename.clone(), + ))); + } + Err(err) => { + return Err(Error::from(ErrorKind::Io(err))); + } + }; + info_span!("parse_variants_json") + .in_scope(|| serde_json::from_slice::(&bytes)) + .map_err(|err| ErrorKind::VariantsJsonFormat(url, err))? + } else { + let cache_entry = self.cache.entry( + CacheBucket::Wheels, + WheelCache::Index(&variants_json.index) + .wheel_dir(variants_json.filename.name.as_ref()), + format!("variants-{}.msgpack", variants_json.filename.cache_key()), + ); + + let cache_control = match self.connectivity { + Connectivity::Online => { + if let Some(header) = self + .index_urls + .artifact_cache_control_for(&variants_json.index) + { + CacheControl::Override(header) + } else { + CacheControl::from( + self.cache + .freshness(&cache_entry, Some(&variants_json.filename.name), None) + .map_err(ErrorKind::Io)?, + ) + } + } + Connectivity::Offline => CacheControl::AllowStale, + }; + + let response_callback = async |response: Response| { + let bytes = response + .bytes() + .await + .map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?; + + info_span!("parse_variants_json") + .in_scope(|| serde_json::from_slice::(&bytes)) + .map_err(|err| Error::from(ErrorKind::VariantsJsonFormat(url.clone(), err))) + }; + + let req = self + .uncached_client(&url) + .get(Url::from(url.clone())) + .build() + .map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?; + self.cached_client() + .get_serde_with_retry(req, &cache_entry, cache_control, response_callback) + .await? + }; + Ok(variants_json) + } + /// Fetch the metadata for a remote wheel file. /// /// For a remote wheel, we try the following ways to fetch the metadata: @@ -1263,19 +1344,28 @@ impl FlatIndexCache { pub struct VersionFiles { pub wheels: Vec, pub source_dists: Vec, + pub variant_jsons: Vec, } impl VersionFiles { - fn push(&mut self, filename: DistFilename, file: File) { + fn push(&mut self, filename: IndexEntryFilename, file: File) { match filename { - DistFilename::WheelFilename(name) => self.wheels.push(VersionWheel { name, file }), - DistFilename::SourceDistFilename(name) => { + IndexEntryFilename::DistFilename(DistFilename::WheelFilename(name)) => { + self.wheels.push(VersionWheel { name, file }); + } + IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(name)) => { self.source_dists.push(VersionSourceDist { name, file }); } + IndexEntryFilename::VariantJson(variants_json) => { + self.variant_jsons.push(VersionVariantJson { + name: variants_json, + file, + }); + } } } - pub fn all(self) -> impl Iterator { + pub fn dists(self) -> impl Iterator { self.source_dists .into_iter() .map(|VersionSourceDist { name, file }| (DistFilename::SourceDistFilename(name), file)) @@ -1285,6 +1375,30 @@ impl VersionFiles { .map(|VersionWheel { name, file }| (DistFilename::WheelFilename(name), file)), ) } + + pub fn all(self) -> impl Iterator { + self.source_dists + .into_iter() + .map(|VersionSourceDist { name, file }| { + ( + IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(name)), + file, + ) + }) + .chain(self.wheels.into_iter().map(|VersionWheel { name, file }| { + ( + IndexEntryFilename::DistFilename(DistFilename::WheelFilename(name)), + file, + ) + })) + .chain( + self.variant_jsons + .into_iter() + .map(|VersionVariantJson { name, file }| { + (IndexEntryFilename::VariantJson(name), file) + }), + ) + } } #[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] @@ -1301,6 +1415,13 @@ pub struct VersionSourceDist { pub file: File, } +#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] +#[rkyv(derive(Debug))] +pub struct VersionVariantJson { + pub name: VariantsJsonFilename, + pub file: File, +} + /// The list of projects available in a Simple API index. #[derive(Default, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] #[rkyv(derive(Debug))] @@ -1372,7 +1493,8 @@ impl SimpleDetailMetadata { // Group the distributions by version and kind for file in files { - let Some(filename) = DistFilename::try_from_filename(&file.filename, package_name) + let Some(filename) = + IndexEntryFilename::try_from_filename(&file.filename, package_name) else { warn!("Skipping file for {package_name}: {}", file.filename); continue; @@ -1430,7 +1552,8 @@ impl SimpleDetailMetadata { continue; } }; - let Some(filename) = DistFilename::try_from_filename(&file.filename, package_name) + let Some(filename) = + IndexEntryFilename::try_from_filename(&file.filename, package_name) else { warn!("Skipping file for {package_name}: {}", file.filename); continue; diff --git a/crates/uv-configuration/src/required_version.rs b/crates/uv-configuration/src/required_version.rs index 70c69eaf3..93327a0ac 100644 --- a/crates/uv-configuration/src/required_version.rs +++ b/crates/uv-configuration/src/required_version.rs @@ -1,6 +1,7 @@ #[cfg(feature = "schemars")] use std::borrow::Cow; -use std::{fmt::Formatter, str::FromStr}; +use std::fmt::Formatter; +use std::str::FromStr; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError}; diff --git a/crates/uv-dispatch/Cargo.toml b/crates/uv-dispatch/Cargo.toml index 009b01adb..6ff42b538 100644 --- a/crates/uv-dispatch/Cargo.toml +++ b/crates/uv-dispatch/Cargo.toml @@ -33,6 +33,8 @@ uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-resolver = { workspace = true } uv-types = { workspace = true } +uv-variant-frontend = { workspace = true } +uv-variants = { workspace = true } uv-version = { workspace = true } uv-workspace = { workspace = true } diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 1b8570bab..2fc774a21 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -40,6 +40,9 @@ use uv_types::{ AnyErrorBuild, BuildArena, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, HashStrategy, InFlight, }; +use uv_variant_frontend::VariantBuild; +use uv_variants::cache::VariantProviderCache; +use uv_variants::variants_json::Provider; use uv_workspace::WorkspaceCache; #[derive(Debug, Error)] @@ -177,6 +180,7 @@ impl<'a> BuildDispatch<'a> { #[allow(refining_impl_trait)] impl BuildContext for BuildDispatch<'_> { type SourceDistBuilder = SourceBuild; + type VariantsBuilder = VariantBuild; async fn interpreter(&self) -> &Interpreter { self.interpreter @@ -190,6 +194,10 @@ impl BuildContext for BuildDispatch<'_> { &self.shared_state.git } + fn variants(&self) -> &VariantProviderCache { + self.shared_state.index.variant_providers() + } + fn build_arena(&self) -> &BuildArena { &self.shared_state.build_arena } @@ -559,6 +567,26 @@ impl BuildContext for BuildDispatch<'_> { Ok(Some(filename)) } + + async fn setup_variants<'data>( + &'data self, + backend_name: String, + backend: &'data Provider, + build_output: BuildOutput, + ) -> anyhow::Result { + let builder = VariantBuild::setup( + backend_name, + backend, + self.interpreter, + self, + self.build_extra_env_vars.clone(), + build_output, + self.concurrency.builds, + ) + .boxed_local() + .await?; + Ok(builder) + } } /// Shared state used during resolution and installation. diff --git a/crates/uv-distribution-filename/src/expanded_tags.rs b/crates/uv-distribution-filename/src/expanded_tags.rs index b04898089..938112320 100644 --- a/crates/uv-distribution-filename/src/expanded_tags.rs +++ b/crates/uv-distribution-filename/src/expanded_tags.rs @@ -10,6 +10,7 @@ use uv_platform_tags::{ use crate::splitter::MemchrSplitter; use crate::wheel_tag::{WheelTag, WheelTagLarge, WheelTagSmall}; +use crate::{InvalidVariantLabel, VariantLabel}; /// The expanded wheel tags as stored in a `WHEEL` file. /// @@ -81,6 +82,8 @@ pub enum ExpandedTagError { InvalidAbiTag(String, #[source] ParseAbiTagError), #[error("The wheel tag \"{0}\" contains an invalid platform tag")] InvalidPlatformTag(String, #[source] ParsePlatformTagError), + #[error("The wheel tag \"{0}\" contains an invalid variant label")] + InvalidVariantLabel(String, #[source] InvalidVariantLabel), } /// Parse an expanded (i.e., simplified) wheel tag, e.g. `py3-none-any`. @@ -100,13 +103,15 @@ fn parse_expanded_tag(tag: &str) -> Result { let Some(abi_tag_index) = splitter.next() else { return Err(ExpandedTagError::MissingPlatformTag(tag.to_string())); }; + let variant = splitter.next(); if splitter.next().is_some() { return Err(ExpandedTagError::ExtraSegment(tag.to_string())); } let python_tag = &tag[..python_tag_index]; let abi_tag = &tag[python_tag_index + 1..abi_tag_index]; - let platform_tag = &tag[abi_tag_index + 1..]; + let platform_tag = &tag[abi_tag_index + 1..variant.unwrap_or(tag.len())]; + let variant = variant.map(|variant| &tag[variant + 1..]); let is_small = memchr(b'.', tag.as_bytes()).is_none(); @@ -137,6 +142,10 @@ fn parse_expanded_tag(tag: &str) -> Result { .map(PlatformTag::from_str) .filter_map(Result::ok) .collect(), + variant: variant + .map(VariantLabel::from_str) + .transpose() + .map_err(|err| ExpandedTagError::InvalidVariantLabel(tag.to_string(), err))?, repr: tag.into(), }), }) @@ -267,6 +276,7 @@ mod tests { arch: X86_64, }, ], + variant: None, repr: "cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64", }, }, @@ -295,6 +305,7 @@ mod tests { platform_tag: [ Any, ], + variant: None, repr: "py3-foo-any", }, }, @@ -329,6 +340,7 @@ mod tests { platform_tag: [ Any, ], + variant: None, repr: "py2.py3-none-any", }, }, @@ -367,16 +379,6 @@ mod tests { "#); } - #[test] - fn test_error_extra_segment() { - let err = ExpandedTags::parse(vec!["py3-none-any-extra"]).unwrap_err(); - insta::assert_debug_snapshot!(err, @r#" - ExtraSegment( - "py3-none-any-extra", - ) - "#); - } - #[test] fn test_parse_expanded_tag_single_segment() { let result = parse_expanded_tag("py3-none-any"); @@ -445,6 +447,7 @@ mod tests { arch: X86, }, ], + variant: None, repr: "cp39.cp310-cp39.cp310-linux_x86_64.linux_i686", }, } @@ -487,18 +490,6 @@ mod tests { "#); } - #[test] - fn test_parse_expanded_tag_four_segments() { - let result = parse_expanded_tag("py3-none-any-extra"); - assert!(result.is_err()); - - insta::assert_debug_snapshot!(result.unwrap_err(), @r#" - ExtraSegment( - "py3-none-any-extra", - ) - "#); - } - #[test] fn test_expanded_tags_ordering() { let tags1 = ExpandedTags::parse(vec!["py3-none-any"]).unwrap(); diff --git a/crates/uv-distribution-filename/src/lib.rs b/crates/uv-distribution-filename/src/lib.rs index 41220d34c..c1d4ed027 100644 --- a/crates/uv-distribution-filename/src/lib.rs +++ b/crates/uv-distribution-filename/src/lib.rs @@ -9,6 +9,7 @@ pub use egg::{EggInfoFilename, EggInfoFilenameError}; pub use expanded_tags::{ExpandedTagError, ExpandedTags}; pub use extension::{DistExtension, ExtensionError, SourceDistExtension}; pub use source_dist::{SourceDistFilename, SourceDistFilenameError}; +pub use variant_label::{InvalidVariantLabel, VariantLabel}; pub use wheel::{WheelFilename, WheelFilenameError}; mod build_tag; @@ -17,6 +18,7 @@ mod expanded_tags; mod extension; mod source_dist; mod splitter; +mod variant_label; mod wheel; mod wheel_tag; @@ -100,10 +102,20 @@ impl Display for DistFilename { #[cfg(test)] mod tests { - use crate::WheelFilename; + use super::*; + use crate::wheel_tag::WheelTag; + use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; #[test] fn wheel_filename_size() { + // This value is performance critical assert_eq!(size_of::(), 48); + // Components of the above size + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 3); + assert_eq!(size_of::(), 5); + assert_eq!(size_of::(), 16); } } diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap index ce7dcc62a..950e69f12 100644 --- a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap @@ -28,6 +28,7 @@ Ok( platform_tag: [ Any, ], + variant: None, repr: "202206090410-py3-none-any", }, }, diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap index a474bd98e..9e0afcd9b 100644 --- a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap @@ -38,6 +38,7 @@ Ok( arch: X86_64, }, ], + variant: None, repr: "cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64", }, }, diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag-2.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag-2.snap new file mode 100644 index 000000000..af6356b78 --- /dev/null +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag-2.snap @@ -0,0 +1,41 @@ +--- +source: crates/uv-distribution-filename/src/wheel.rs +expression: "WheelFilename::from_str(\"dummy_project-0.0.1-1234-py3-none-any-36266d4d.whl\")" +snapshot_kind: text +--- +Ok( + WheelFilename { + name: PackageName( + "dummy-project", + ), + version: "0.0.1", + tags: Large { + large: WheelTagLarge { + build_tag: Some( + BuildTag( + 1234, + None, + ), + ), + python_tag: [ + Python { + major: 3, + minor: None, + }, + ], + abi_tag: [ + None, + ], + platform_tag: [ + Any, + ], + variant: Some( + VariantLabel( + "36266d4d", + ), + ), + repr: "1234-py3-none-any-36266d4d", + }, + }, + }, +) diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag.snap new file mode 100644 index 000000000..806869ea1 --- /dev/null +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_variant_tag.snap @@ -0,0 +1,36 @@ +--- +source: crates/uv-distribution-filename/src/wheel.rs +expression: "WheelFilename::from_str(\"dummy_project-0.0.1-py3-none-any-36266d4d.whl\")" +snapshot_kind: text +--- +Ok( + WheelFilename { + name: PackageName( + "dummy-project", + ), + version: "0.0.1", + tags: Large { + large: WheelTagLarge { + build_tag: None, + python_tag: [ + Python { + major: 3, + minor: None, + }, + ], + abi_tag: [ + None, + ], + platform_tag: [ + Any, + ], + variant: Some( + VariantLabel( + "36266d4d", + ), + ), + repr: "py3-none-any-36266d4d", + }, + }, + }, +) diff --git a/crates/uv-distribution-filename/src/variant_label.rs b/crates/uv-distribution-filename/src/variant_label.rs new file mode 100644 index 000000000..82a28a365 --- /dev/null +++ b/crates/uv-distribution-filename/src/variant_label.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Deserializer}; +use std::fmt::Display; +use std::str::FromStr; +use uv_small_str::SmallString; + +#[derive(Debug, thiserror::Error)] +pub enum InvalidVariantLabel { + #[error("Invalid character `{invalid}` in variant label, only [a-z0-9._] are allowed: {input}")] + InvalidCharacter { invalid: char, input: String }, + #[error("Variant label must be between 1 and 16 characters long, not {length}: {input}")] + InvalidLength { length: usize, input: String }, +} + +#[derive( + Debug, + Clone, + Eq, + PartialEq, + Hash, + Ord, + PartialOrd, + serde::Serialize, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[rkyv(derive(Debug))] +#[serde(transparent)] +pub struct VariantLabel(SmallString); + +impl VariantLabel { + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl FromStr for VariantLabel { + type Err = InvalidVariantLabel; + + fn from_str(label: &str) -> Result { + if let Some(invalid) = label + .chars() + .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || *c == '.')) + { + if !invalid.is_ascii_lowercase() + && !invalid.is_ascii_digit() + && !matches!(invalid, '.' | '_') + { + return Err(InvalidVariantLabel::InvalidCharacter { + invalid, + input: label.to_string(), + }); + } + } + + // We checked that the label is ASCII only above, so we can use `len()`. + if label.is_empty() || label.len() > 16 { + return Err(InvalidVariantLabel::InvalidLength { + length: label.len(), + input: label.to_string(), + }); + } + + Ok(Self(SmallString::from(label))) + } +} + +impl<'de> Deserialize<'de> for VariantLabel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Display for VariantLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index c3ba40bdf..e15019a21 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -16,7 +16,7 @@ use uv_platform_tags::{ use crate::splitter::MemchrSplitter; use crate::wheel_tag::{WheelTag, WheelTagLarge, WheelTagSmall}; -use crate::{BuildTag, BuildTagError}; +use crate::{BuildTag, BuildTagError, InvalidVariantLabel, VariantLabel}; #[derive( Debug, @@ -113,11 +113,12 @@ impl WheelFilename { const CACHE_KEY_MAX_LEN: usize = 64; let full = format!("{}-{}", self.version, self.tags); + if full.len() <= CACHE_KEY_MAX_LEN { return full; } - // Create a digest of the tag string (instead of its individual fields) to retain + // Create a digest of the tag string (and variant if it exists) to retain // compatibility across platforms, Rust versions, etc. let digest = cache_digest(&format!("{}", self.tags)); @@ -132,6 +133,14 @@ impl WheelFilename { format!("{version}-{digest}") } + /// Return the wheel's variant tag, if present. + pub fn variant(&self) -> Option<&VariantLabel> { + match &self.tags { + WheelTag::Small { .. } => None, + WheelTag::Large { large } => large.variant.as_ref(), + } + } + /// Return the wheel's Python tags. pub fn python_tags(&self) -> &[LanguageTag] { self.tags.python_tags() @@ -201,24 +210,62 @@ impl WheelFilename { )); }; - let (name, version, build_tag, python_tag, abi_tag, platform_tag, is_small) = + let (name, version, build_tag, python_tag, abi_tag, platform_tag, variant, is_small) = if let Some(platform_tag) = splitter.next() { - if splitter.next().is_some() { - return Err(WheelFilenameError::InvalidWheelFileName( - filename.to_string(), - "Must have 5 or 6 components, but has more".to_string(), - )); + // Extract variant from filenames with the format, e.g., `ml_project-0.0.1-py3-none-any-cu128.whl`. + // TODO(charlie): Integrate this into the filename parsing; it's just easier to do it upfront + // for now. + // 7 components: We have both a build tag and a variant tag. + if let Some(variant_tag) = splitter.next() { + if splitter.next().is_some() { + return Err(WheelFilenameError::InvalidWheelFileName( + filename.to_string(), + "Must have 5 to 7 components, but has more".to_string(), + )); + } + ( + &stem[..version], + &stem[version + 1..build_tag_or_python_tag], + Some(&stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag]), + &stem[python_tag_or_abi_tag + 1..abi_tag_or_platform_tag], + &stem[abi_tag_or_platform_tag + 1..platform_tag], + &stem[platform_tag + 1..variant_tag], + Some(&stem[variant_tag + 1..]), + // Always take the slow path if build or variant tag are present. + false, + ) + } else { + // 6 components: Determine whether we have a build tag or a variant tag. + if LanguageTag::from_str( + &stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag], + ) + .is_ok() + { + ( + &stem[..version], + &stem[version + 1..build_tag_or_python_tag], + None, + &stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag], + &stem[python_tag_or_abi_tag + 1..abi_tag_or_platform_tag], + &stem[abi_tag_or_platform_tag + 1..platform_tag], + Some(&stem[platform_tag + 1..]), + // Always take the slow path if a variant tag is present. + false, + ) + } else { + ( + &stem[..version], + &stem[version + 1..build_tag_or_python_tag], + Some(&stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag]), + &stem[python_tag_or_abi_tag + 1..abi_tag_or_platform_tag], + &stem[abi_tag_or_platform_tag + 1..platform_tag], + &stem[platform_tag + 1..], + None, + // Always take the slow path if a build tag is present. + false, + ) + } } - ( - &stem[..version], - &stem[version + 1..build_tag_or_python_tag], - Some(&stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag]), - &stem[python_tag_or_abi_tag + 1..abi_tag_or_platform_tag], - &stem[abi_tag_or_platform_tag + 1..platform_tag], - &stem[platform_tag + 1..], - // Always take the slow path if a build tag is present. - false, - ) } else { ( &stem[..version], @@ -227,6 +274,7 @@ impl WheelFilename { &stem[build_tag_or_python_tag + 1..python_tag_or_abi_tag], &stem[python_tag_or_abi_tag + 1..abi_tag_or_platform_tag], &stem[abi_tag_or_platform_tag + 1..], + None, // Determine whether any of the tag types contain a period, which would indicate // that at least one of the tag types includes multiple tags (which in turn // necessitates taking the slow path). @@ -244,6 +292,13 @@ impl WheelFilename { .map_err(|err| WheelFilenameError::InvalidBuildTag(filename.to_string(), err)) }) .transpose()?; + let variant = variant + .map(|variant| { + VariantLabel::from_str(variant).map_err(|err| { + WheelFilenameError::InvalidVariantLabel(filename.to_string(), err) + }) + }) + .transpose()?; let tags = if let Some(small) = is_small .then(|| { @@ -274,6 +329,7 @@ impl WheelFilename { .map(PlatformTag::from_str) .filter_map(Result::ok) .collect(), + variant, repr: repr.into(), }), } @@ -335,6 +391,8 @@ pub enum WheelFilenameError { InvalidAbiTag(String, ParseAbiTagError), #[error("The wheel filename \"{0}\" has an invalid platform tag: {1}")] InvalidPlatformTag(String, ParsePlatformTagError), + #[error("The wheel filename \"{0}\" has an invalid variant label: {1}")] + InvalidVariantLabel(String, InvalidVariantLabel), #[error("The wheel filename \"{0}\" is missing a language tag")] MissingLanguageTag(String), #[error("The wheel filename \"{0}\" is missing an ABI tag")] @@ -388,8 +446,9 @@ mod tests { #[test] fn err_too_many_parts() { let err = - WheelFilename::from_str("foo-1.2.3-202206090410-py3-none-any-whoops.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-202206090410-py3-none-any-whoops.whl" is invalid: Must have 5 or 6 components, but has more"###); + WheelFilename::from_str("foo-1.2.3-202206090410-py3-none-any-whoopsie-whoops.whl") + .unwrap_err(); + insta::assert_snapshot!(err, @r#"The wheel filename "foo-1.2.3-202206090410-py3-none-any-whoopsie-whoops.whl" is invalid: Must have 5 to 7 components, but has more"#); } #[test] @@ -429,12 +488,23 @@ mod tests { )); } + #[test] + fn ok_variant_tag() { + insta::assert_debug_snapshot!(WheelFilename::from_str( + "dummy_project-0.0.1-py3-none-any-36266d4d.whl" + )); + insta::assert_debug_snapshot!(WheelFilename::from_str( + "dummy_project-0.0.1-1234-py3-none-any-36266d4d.whl" + )); + } + #[test] fn from_and_to_string() { let wheel_names = &[ "django_allauth-0.51.0-py3-none-any.whl", "osm2geojson-0.2.4-py3-none-any.whl", "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "dummy_project-0.0.1-py3-none-any-36266d4d.whl", ]; for wheel_name in wheel_names { assert_eq!( @@ -469,5 +539,10 @@ mod tests { "example-1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9.0.1.2.1.2.3.4.5.6.7.8.9.0.1.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" ).unwrap(); insta::assert_snapshot!(filename.cache_key(), @"1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9.0.1.2.1.2-80bf8598e9647cf7"); + + // Variant tags should be included in the cache key. + let filename = + WheelFilename::from_str("dummy_project-0.0.1-py3-none-any-36266d4d.whl").unwrap(); + insta::assert_snapshot!(filename.cache_key(), @"0.0.1-py3-none-any-36266d4d"); } } diff --git a/crates/uv-distribution-filename/src/wheel_tag.rs b/crates/uv-distribution-filename/src/wheel_tag.rs index 7d93e42a4..6437bafde 100644 --- a/crates/uv-distribution-filename/src/wheel_tag.rs +++ b/crates/uv-distribution-filename/src/wheel_tag.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use crate::BuildTag; +use crate::{BuildTag, VariantLabel}; use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; use uv_small_str::SmallString; @@ -136,6 +136,8 @@ pub(crate) struct WheelTagLarge { pub(crate) abi_tag: TagSet, /// The platform tag(s), e.g., `none` in `1.2.3-73-py3-none-any`. pub(crate) platform_tag: TagSet, + /// The optional variant tag. + pub(crate) variant: Option, /// The string representation of the tag. /// /// Preserves any unsupported tags that were filtered out when parsing the wheel filename. diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index 8e29f2e17..0b16fddd7 100644 --- a/crates/uv-distribution-types/Cargo.toml +++ b/crates/uv-distribution-types/Cargo.toml @@ -30,6 +30,7 @@ uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-small-str = { workspace = true } +uv-variants = { workspace = true } uv-warnings = { workspace = true } arcstr = { workspace = true } diff --git a/crates/uv-distribution-types/src/id.rs b/crates/uv-distribution-types/src/id.rs index b68b4f24c..e63ebc1fd 100644 --- a/crates/uv-distribution-types/src/id.rs +++ b/crates/uv-distribution-types/src/id.rs @@ -2,12 +2,13 @@ use std::fmt::{Display, Formatter}; use std::path::PathBuf; use uv_cache_key::{CanonicalUrl, RepositoryUrl}; - use uv_normalize::PackageName; use uv_pep440::Version; use uv_pypi_types::HashDigest; use uv_redacted::DisplaySafeUrl; +use crate::IndexUrl; + /// A unique identifier for a package. A package can either be identified by a name (e.g., `black`) /// or a URL (e.g., `git+https://github.com/psf/black`). #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -69,6 +70,25 @@ impl Display for VersionId { } } +/// A unique identifier for a package version at a specific index (e.g., `black==23.10.0 @ https://pypi.org/simple`). +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct GlobalVersionId { + version: VersionId, + index: IndexUrl, +} + +impl GlobalVersionId { + pub fn new(version: VersionId, index: IndexUrl) -> Self { + Self { version, index } + } +} + +impl Display for GlobalVersionId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} @ {}", self.version, self.index) + } +} + /// A unique resource identifier for the distribution, like a SHA-256 hash of the distribution's /// contents. /// diff --git a/crates/uv-distribution-types/src/index_entry.rs b/crates/uv-distribution-types/src/index_entry.rs new file mode 100644 index 000000000..e5a5379e9 --- /dev/null +++ b/crates/uv-distribution-types/src/index_entry.rs @@ -0,0 +1,62 @@ +use std::fmt::Display; +use std::str::FromStr; + +use uv_distribution_filename::DistFilename; +use uv_normalize::PackageName; +use uv_pep440::Version; + +use crate::VariantsJsonFilename; + +/// On an index page, there can be wheels, source distributions and `variants.json` files. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum IndexEntryFilename { + DistFilename(DistFilename), + VariantJson(VariantsJsonFilename), +} + +impl IndexEntryFilename { + pub fn name(&self) -> &PackageName { + match self { + Self::DistFilename(filename) => filename.name(), + Self::VariantJson(variant_json) => &variant_json.name, + } + } + + pub fn version(&self) -> &Version { + match self { + Self::DistFilename(filename) => filename.version(), + Self::VariantJson(variant_json) => &variant_json.version, + } + } + + /// Parse a filename as either a distribution filename or a `variants.json` filename. + pub fn try_from_normalized_filename(filename: &str) -> Option { + if let Some(dist_filename) = DistFilename::try_from_normalized_filename(filename) { + Some(Self::DistFilename(dist_filename)) + } else if let Ok(variant_json) = VariantsJsonFilename::from_str(filename) { + Some(Self::VariantJson(variant_json)) + } else { + None + } + } + + /// Parse a filename as either a distribution filename or a `variants.json` filename. + pub fn try_from_filename(filename: &str, package_name: &PackageName) -> Option { + if let Some(dist_filename) = DistFilename::try_from_filename(filename, package_name) { + Some(Self::DistFilename(dist_filename)) + } else if let Ok(variant_json) = VariantsJsonFilename::from_str(filename) { + Some(Self::VariantJson(variant_json)) + } else { + None + } + } +} + +impl Display for IndexEntryFilename { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::DistFilename(d) => d.fmt(f), + Self::VariantJson(variant_json) => variant_json.fmt(f), + } + } +} diff --git a/crates/uv-distribution-types/src/installed.rs b/crates/uv-distribution-types/src/installed.rs index 1f6a0f9d0..0dc20e019 100644 --- a/crates/uv-distribution-types/src/installed.rs +++ b/crates/uv-distribution-types/src/installed.rs @@ -17,6 +17,7 @@ use uv_normalize::PackageName; use uv_pep440::Version; use uv_pypi_types::{DirectUrl, MetadataError}; use uv_redacted::DisplaySafeUrl; +use uv_variants::variants_json::DistInfoVariantsJson; use crate::{ BuildInfo, DistributionMetadata, InstalledMetadata, InstalledVersion, Name, VersionOrUrlRef, @@ -484,6 +485,20 @@ impl InstalledDist { } } + /// Read the `variant.json` file of the distribution, if it exists. + pub fn read_variant_json(&self) -> Result, InstalledDistError> { + let path = self.install_path().join("variant.json"); + let file = match fs_err::File::open(&path) { + Ok(file) => file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err.into()), + }; + let variants_json = serde_json::from_reader::, DistInfoVariantsJson>( + BufReader::new(file), + )?; + Ok(Some(variants_json)) + } + /// Return the supported wheel tags for the distribution from the `WHEEL` file, if available. pub fn read_tags(&self) -> Result, InstalledDistError> { if let Some(tags) = self.tags_cache.get() { diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 5b4178f05..f1ed44f69 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -67,6 +67,7 @@ pub use crate::file::*; pub use crate::hash::*; pub use crate::id::*; pub use crate::index::*; +pub use crate::index_entry::*; pub use crate::index_name::*; pub use crate::index_url::*; pub use crate::installed::*; @@ -82,6 +83,7 @@ pub use crate::resolved::*; pub use crate::specified_requirement::*; pub use crate::status_code_strategy::*; pub use crate::traits::*; +pub use crate::variant_json::*; mod annotation; mod any; @@ -98,6 +100,7 @@ mod file; mod hash; mod id; mod index; +mod index_entry; mod index_name; mod index_url; mod installed; @@ -113,6 +116,7 @@ mod resolved; mod specified_requirement; mod status_code_strategy; mod traits; +mod variant_json; #[derive(Debug, Clone)] pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> { @@ -631,6 +635,14 @@ impl BuiltDist { Self::Path(wheel) => &wheel.filename.version, } } + + pub fn wheel_filename(&self) -> &WheelFilename { + match self { + Self::Registry(wheels) => &wheels.best_wheel().filename, + Self::DirectUrl(wheel) => &wheel.filename, + Self::Path(wheel) => &wheel.filename, + } + } } impl SourceDist { @@ -1467,6 +1479,34 @@ impl Identifier for BuildableSource<'_> { } } +/// A built distribution (wheel) that exists in a registry, like `PyPI`. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct RegistryVariantsJson { + pub filename: VariantsJsonFilename, + pub file: Box, + pub index: IndexUrl, +} + +impl RegistryVariantsJson { + /// Return the [`GlobalVersionId`] for this registry variants JSON. + pub fn version_id(&self) -> GlobalVersionId { + GlobalVersionId::new( + VersionId::NameVersion(self.filename.name.clone(), self.filename.version.clone()), + self.index.clone(), + ) + } +} + +impl Identifier for RegistryVariantsJson { + fn distribution_id(&self) -> DistributionId { + self.file.distribution_id() + } + + fn resource_id(&self) -> ResourceId { + self.file.resource_id() + } +} + #[cfg(test)] mod test { use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString}; diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index c96ad9ade..341467690 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -9,10 +9,12 @@ use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers}; use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString}; use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, PlatformTag, TagPriority, Tags}; use uv_pypi_types::{HashDigest, Yanked}; +use uv_variants::VariantPriority; +use uv_variants::resolved_variants::ResolvedVariants; use crate::{ - File, InstalledDist, KnownPlatform, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist, - ResolvedDistRef, + File, IndexUrl, InstalledDist, KnownPlatform, RegistryBuiltDist, RegistryBuiltWheel, + RegistrySourceDist, RegistryVariantsJson, ResolvedDistRef, }; /// A collection of distributions that have been filtered by relevance. @@ -26,9 +28,13 @@ struct PrioritizedDistInner { source: Option<(RegistrySourceDist, SourceDistCompatibility)>, /// The highest-priority wheel index. When present, it is /// guaranteed to be a valid index into `wheels`. + /// + /// This wheel may still be incompatible. best_wheel_index: Option, /// The set of all wheels associated with this distribution. wheels: Vec<(RegistryBuiltWheel, WheelCompatibility)>, + /// The `variants.json` file associated with the package version. + variants_json: Option, /// The hashes for each distribution. hashes: Vec, /// The set of supported platforms for the distribution, described in terms of their markers. @@ -41,6 +47,7 @@ impl Default for PrioritizedDistInner { source: None, best_wheel_index: None, wheels: Vec::new(), + variants_json: None, hashes: Vec::new(), markers: MarkerTree::FALSE, } @@ -91,6 +98,16 @@ impl CompatibleDist<'_> { } } + /// Return the index URL for the distribution, if any. + pub fn index(&self) -> Option<&IndexUrl> { + match self { + CompatibleDist::InstalledDist(_) => None, + CompatibleDist::SourceDist { sdist, .. } => Some(&sdist.index), + CompatibleDist::CompatibleWheel { wheel, .. } => Some(&wheel.index), + CompatibleDist::IncompatibleWheel { sdist, .. } => Some(&sdist.index), + } + } + // For installable distributions, return the prioritized distribution it was derived from. pub fn prioritized(&self) -> Option<&PrioritizedDist> { match self { @@ -125,6 +142,7 @@ impl IncompatibleDist { match self { Self::Wheel(incompatibility) => match incompatibility { IncompatibleWheel::NoBinary => format!("has {self}"), + IncompatibleWheel::Variant => format!("has {self}"), IncompatibleWheel::Tag(_) => format!("has {self}"), IncompatibleWheel::Yanked(_) => format!("was {self}"), IncompatibleWheel::ExcludeNewer(ts) => match ts { @@ -153,6 +171,7 @@ impl IncompatibleDist { match self { Self::Wheel(incompatibility) => match incompatibility { IncompatibleWheel::NoBinary => format!("have {self}"), + IncompatibleWheel::Variant => format!("have {self}"), IncompatibleWheel::Tag(_) => format!("have {self}"), IncompatibleWheel::Yanked(_) => format!("were {self}"), IncompatibleWheel::ExcludeNewer(ts) => match ts { @@ -201,6 +220,7 @@ impl IncompatibleDist { Some(format!("(e.g., `{tag}`)", tag = tag.cyan())) } IncompatibleWheel::Tag(IncompatibleTag::Invalid) => None, + IncompatibleWheel::Variant => None, IncompatibleWheel::NoBinary => None, IncompatibleWheel::Yanked(..) => None, IncompatibleWheel::ExcludeNewer(..) => None, @@ -218,6 +238,9 @@ impl Display for IncompatibleDist { match self { Self::Wheel(incompatibility) => match incompatibility { IncompatibleWheel::NoBinary => f.write_str("no source distribution"), + IncompatibleWheel::Variant => { + f.write_str("no wheels with a variant supported on the current platform") + } IncompatibleWheel::Tag(tag) => match tag { IncompatibleTag::Invalid => f.write_str("no wheels with valid tags"), IncompatibleTag::Python => { @@ -292,13 +315,20 @@ pub enum PythonRequirementKind { #[derive(Debug, Clone, PartialEq, Eq)] pub enum WheelCompatibility { Incompatible(IncompatibleWheel), - Compatible(HashComparison, Option, Option), + Compatible { + hash: HashComparison, + variant_priority: VariantPriority, + tag_priority: Option, + build_tag: Option, + }, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum IncompatibleWheel { /// The wheel was published after the exclude newer time. ExcludeNewer(Option), + /// The wheel variant does not match the target platform. + Variant, /// The wheel tags do not match those of the target Python platform. Tag(IncompatibleTag), /// The required Python version is not a superset of the target Python version range. @@ -346,6 +376,7 @@ impl PrioritizedDist { markers: implied_markers(&dist.filename), best_wheel_index: Some(0), wheels: vec![(dist, compatibility)], + variants_json: None, source: None, hashes, })) @@ -362,10 +393,23 @@ impl PrioritizedDist { best_wheel_index: None, wheels: vec![], source: Some((dist, compatibility)), + variants_json: None, hashes, })) } + /// Create a new [`PrioritizedDist`] from the `variants.json`. + pub fn from_variant_json(variant_json: RegistryVariantsJson) -> Self { + Self(Box::new(PrioritizedDistInner { + markers: MarkerTree::TRUE, + best_wheel_index: Some(0), + wheels: vec![], + source: None, + variants_json: Some(variant_json), + hashes: vec![], + })) + } + /// Insert the given built distribution into the [`PrioritizedDist`]. pub fn insert_built( &mut self, @@ -419,17 +463,51 @@ impl PrioritizedDist { } } + pub fn insert_variant_json(&mut self, variant_json: RegistryVariantsJson) { + debug_assert!( + self.0.variants_json.is_none(), + "The variants.json filename is unique" + ); + self.0.variants_json = Some(variant_json); + } + + /// Return the variants JSON for the distribution, if any. + pub fn variants_json(&self) -> Option<&RegistryVariantsJson> { + self.0.variants_json.as_ref() + } + + /// Return the index URL for the distribution, if any. + pub fn index(&self) -> Option<&IndexUrl> { + self.0 + .source + .as_ref() + .map(|(sdist, _)| &sdist.index) + .or_else(|| self.0.wheels.first().map(|(wheel, _)| &wheel.index)) + } + /// Return the highest-priority distribution for the package version, if any. - pub fn get(&self) -> Option> { + pub fn get(&self, allow_all_variants: bool) -> Option> { let best_wheel = self.0.best_wheel_index.map(|i| &self.0.wheels[i]); match (&best_wheel, &self.0.source) { // If both are compatible, break ties based on the hash outcome. For example, prefer a // source distribution with a matching hash over a wheel with a mismatched hash. When // the outcomes are equivalent (e.g., both have a matching hash), prefer the wheel. ( - Some((wheel, WheelCompatibility::Compatible(wheel_hash, tag_priority, ..))), + Some(( + wheel, + WheelCompatibility::Compatible { + hash: wheel_hash, + tag_priority, + variant_priority, + .. + }, + )), Some((sdist, SourceDistCompatibility::Compatible(sdist_hash))), - ) => { + ) if matches!( + variant_priority, + VariantPriority::BestVariant | VariantPriority::NonVariant + ) || allow_all_variants => + { if sdist_hash > wheel_hash { Some(CompatibleDist::SourceDist { sdist, @@ -444,7 +522,22 @@ impl PrioritizedDist { } } // Prefer the highest-priority, platform-compatible wheel. - (Some((wheel, WheelCompatibility::Compatible(_, tag_priority, ..))), _) => { + ( + Some(( + wheel, + WheelCompatibility::Compatible { + hash: _, + tag_priority, + variant_priority, + .. + }, + )), + _, + ) if matches!( + variant_priority, + VariantPriority::BestVariant | VariantPriority::NonVariant + ) || allow_all_variants => + { Some(CompatibleDist::CompatibleWheel { wheel, priority: *tag_priority, @@ -477,6 +570,56 @@ impl PrioritizedDist { } } + /// Prioritize a matching variant wheel over a matching non-variant wheel. + /// + /// Returns `None` or there is no matching variant. + pub fn prioritize_best_variant_wheel( + &self, + resolved_variants: &ResolvedVariants, + ) -> Option { + let mut highest_priority_variant_wheel: Option<(usize, Vec)> = None; + for (wheel_index, (wheel, compatibility)) in self.wheels().enumerate() { + if !compatibility.is_compatible() { + continue; + } + + let Some(variant) = wheel.filename.variant() else { + // The non-variant wheel is already supported + continue; + }; + + let Some(scores) = resolved_variants.score_variant(variant) else { + continue; + }; + + if let Some((_, old_scores)) = &highest_priority_variant_wheel { + if &scores > old_scores { + highest_priority_variant_wheel = Some((wheel_index, scores)); + } + } else { + highest_priority_variant_wheel = Some((wheel_index, scores)); + } + } + + if let Some((wheel_index, _)) = highest_priority_variant_wheel { + use owo_colors::OwoColorize; + + let inner = PrioritizedDistInner { + best_wheel_index: Some(wheel_index), + ..(*self.0).clone() + }; + let compatible_wheel = &inner.wheels[wheel_index]; + debug!( + "{} {}", + "Using variant wheel".red(), + compatible_wheel.0.filename + ); + Some(Self(Box::new(inner))) + } else { + None + } + } + /// Return the incompatibility for the best source distribution, if any. pub fn incompatible_source(&self) -> Option<&IncompatibleSource> { self.0 @@ -489,16 +632,24 @@ impl PrioritizedDist { } /// Return the incompatibility for the best wheel, if any. - pub fn incompatible_wheel(&self) -> Option<&IncompatibleWheel> { + pub fn incompatible_wheel(&self, allow_variants: bool) -> Option<&IncompatibleWheel> { self.0 .best_wheel_index .map(|i| &self.0.wheels[i]) .and_then(|(_, compatibility)| match compatibility { - WheelCompatibility::Compatible(_, _, _) => None, + WheelCompatibility::Compatible { + variant_priority: VariantPriority::Unknown, + .. + } if !allow_variants => Some(&IncompatibleWheel::Variant), + WheelCompatibility::Compatible { .. } => None, WheelCompatibility::Incompatible(incompatibility) => Some(incompatibility), }) } + pub fn wheels(&self) -> impl Iterator { + self.0.wheels.iter() + } + /// Return the hashes for each distribution. pub fn hashes(&self) -> &[HashDigest] { &self.0.hashes @@ -665,7 +816,7 @@ impl<'a> CompatibleDist<'a> { impl WheelCompatibility { /// Return `true` if the distribution is compatible. pub fn is_compatible(&self) -> bool { - matches!(self, Self::Compatible(_, _, _)) + matches!(self, Self::Compatible { .. }) } /// Return `true` if the distribution is excluded. @@ -679,14 +830,30 @@ impl WheelCompatibility { /// Compatible wheel ordering is determined by tag priority. pub fn is_more_compatible(&self, other: &Self) -> bool { match (self, other) { - (Self::Compatible(_, _, _), Self::Incompatible(_)) => true, + (Self::Compatible { .. }, Self::Incompatible(..)) => true, ( - Self::Compatible(hash, tag_priority, build_tag), - Self::Compatible(other_hash, other_tag_priority, other_build_tag), + Self::Compatible { + hash, + variant_priority, + tag_priority, + build_tag, + }, + Self::Compatible { + hash: other_hash, + variant_priority: other_variant_priority, + tag_priority: other_tag_priority, + build_tag: other_build_tag, + }, ) => { - (hash, tag_priority, build_tag) > (other_hash, other_tag_priority, other_build_tag) + (hash, variant_priority, tag_priority, build_tag) + > ( + other_hash, + other_variant_priority, + other_tag_priority, + other_build_tag, + ) } - (Self::Incompatible(_), Self::Compatible(_, _, _)) => false, + (Self::Incompatible(..), Self::Compatible { .. }) => false, (Self::Incompatible(incompatibility), Self::Incompatible(other_incompatibility)) => { incompatibility.is_more_compatible(other_incompatibility) } @@ -768,34 +935,45 @@ impl IncompatibleWheel { Self::MissingPlatform(_) | Self::NoBinary | Self::RequiresPython(_, _) + | Self::Variant | Self::Tag(_) | Self::Yanked(_) => true, }, + Self::Variant => match other { + Self::ExcludeNewer(_) + | Self::Tag(_) + | Self::RequiresPython(_, _) + | Self::Yanked(_) => false, + Self::Variant => false, + Self::MissingPlatform(_) | Self::NoBinary => true, + }, Self::Tag(tag_self) => match other { Self::ExcludeNewer(_) => false, Self::Tag(tag_other) => tag_self > tag_other, Self::MissingPlatform(_) | Self::NoBinary | Self::RequiresPython(_, _) + | Self::Variant | Self::Yanked(_) => true, }, Self::RequiresPython(_, _) => match other { Self::ExcludeNewer(_) | Self::Tag(_) => false, // Version specifiers cannot be reasonably compared Self::RequiresPython(_, _) => false, - Self::MissingPlatform(_) | Self::NoBinary | Self::Yanked(_) => true, + Self::MissingPlatform(_) | Self::NoBinary | Self::Yanked(_) | Self::Variant => true, }, Self::Yanked(_) => match other { Self::ExcludeNewer(_) | Self::Tag(_) | Self::RequiresPython(_, _) => false, // Yanks with a reason are more helpful for errors Self::Yanked(yanked_other) => matches!(yanked_other, Yanked::Reason(_)), - Self::MissingPlatform(_) | Self::NoBinary => true, + Self::MissingPlatform(_) | Self::NoBinary | Self::Variant => true, }, Self::NoBinary => match other { Self::ExcludeNewer(_) | Self::Tag(_) | Self::RequiresPython(_, _) - | Self::Yanked(_) => false, + | Self::Yanked(_) + | Self::Variant => false, Self::NoBinary => false, Self::MissingPlatform(_) => true, }, diff --git a/crates/uv-distribution-types/src/requirement.rs b/crates/uv-distribution-types/src/requirement.rs index 19a761546..cefcfe389 100644 --- a/crates/uv-distribution-types/src/requirement.rs +++ b/crates/uv-distribution-types/src/requirement.rs @@ -11,7 +11,8 @@ use uv_git_types::{GitLfs, GitOid, GitReference, GitUrl, GitUrlParseError, OidPa use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::VersionSpecifiers; use uv_pep508::{ - MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl, marker, + MarkerEnvironment, MarkerTree, MarkerVariantsEnvironment, RequirementOrigin, VerbatimUrl, + VersionOrUrl, marker, }; use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError}; @@ -69,8 +70,14 @@ impl Requirement { /// When `env` is `None`, this specifically evaluates all marker /// expressions based on the environment to `true`. That is, this provides /// environment independent marker evaluation. - pub fn evaluate_markers(&self, env: Option<&MarkerEnvironment>, extras: &[ExtraName]) -> bool { - self.marker.evaluate_optional_environment(env, extras) + pub fn evaluate_markers( + &self, + env: Option<&MarkerEnvironment>, + variants: &impl MarkerVariantsEnvironment, + extras: &[ExtraName], + ) -> bool { + self.marker + .evaluate_optional_environment(env, variants, extras) } /// Returns `true` if the requirement is editable. diff --git a/crates/uv-distribution-types/src/resolution.rs b/crates/uv-distribution-types/src/resolution.rs index e11d194cf..63f78bc18 100644 --- a/crates/uv-distribution-types/src/resolution.rs +++ b/crates/uv-distribution-types/src/resolution.rs @@ -109,7 +109,7 @@ impl Resolution { } } -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub enum ResolutionDiagnostic { MissingExtra { /// The distribution that was requested with a non-existent extra. For example, diff --git a/crates/uv-distribution-types/src/resolved.rs b/crates/uv-distribution-types/src/resolved.rs index c212ee6dd..54de23524 100644 --- a/crates/uv-distribution-types/src/resolved.rs +++ b/crates/uv-distribution-types/src/resolved.rs @@ -2,14 +2,15 @@ use std::fmt::{Display, Formatter}; use std::path::Path; use std::sync::Arc; +use uv_distribution_filename::WheelFilename; use uv_normalize::PackageName; use uv_pep440::Version; use uv_pypi_types::Yanked; use crate::{ BuiltDist, Dist, DistributionId, DistributionMetadata, Identifier, IndexUrl, InstalledDist, - Name, PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, ResourceId, SourceDist, - VersionOrUrlRef, + Name, PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, RegistryVariantsJson, + ResourceId, SourceDist, VersionOrUrlRef, }; /// A distribution that can be used for resolution and installation. @@ -23,6 +24,7 @@ pub enum ResolvedDist { }, Installable { dist: Arc, + variants_json: Option>, version: Option, }, } @@ -89,7 +91,7 @@ impl ResolvedDist { /// Returns the version of the distribution, if available. pub fn version(&self) -> Option<&Version> { match self { - Self::Installable { version, dist } => dist.version().or(version.as_ref()), + Self::Installable { version, dist, .. } => dist.version().or(version.as_ref()), Self::Installed { dist } => Some(dist.version()), } } @@ -101,6 +103,20 @@ impl ResolvedDist { Self::Installed { .. } => None, } } + + // TODO(konsti): That's the wrong "abstraction" + pub fn wheel_filename(&self) -> Option<&WheelFilename> { + match self { + Self::Installed { .. } => { + // TODO(konsti): Support installed dists too + None + } + Self::Installable { dist, .. } => match &**dist { + Dist::Built(dist) => Some(dist.wheel_filename()), + Dist::Source(_) => None, + }, + } + } } impl ResolvedDistRef<'_> { @@ -117,6 +133,9 @@ impl ResolvedDistRef<'_> { ); ResolvedDist::Installable { dist: Arc::new(Dist::Source(SourceDist::Registry(source))), + variants_json: prioritized + .variants_json() + .map(|variants_json| Arc::new(variants_json.clone())), version: Some(sdist.version.clone()), } } @@ -133,6 +152,9 @@ impl ResolvedDistRef<'_> { let built = prioritized.built_dist().expect("at least one wheel"); ResolvedDist::Installable { dist: Arc::new(Dist::Built(BuiltDist::Registry(built))), + variants_json: prioritized + .variants_json() + .map(|variants_json| Arc::new(variants_json.clone())), version: Some(wheel.filename.version.clone()), } } diff --git a/crates/uv-distribution-types/src/specified_requirement.rs b/crates/uv-distribution-types/src/specified_requirement.rs index a35ec7575..05654cb35 100644 --- a/crates/uv-distribution-types/src/specified_requirement.rs +++ b/crates/uv-distribution-types/src/specified_requirement.rs @@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter}; use uv_git_types::{GitLfs, GitReference}; use uv_normalize::ExtraName; -use uv_pep508::{MarkerEnvironment, MarkerTree, UnnamedRequirement}; +use uv_pep508::{MarkerEnvironment, MarkerTree, MarkerVariantsEnvironment, UnnamedRequirement}; use uv_pypi_types::{Hashes, ParsedUrl}; use crate::{Requirement, RequirementSource, VerbatimParsedUrl}; @@ -60,10 +60,17 @@ impl UnresolvedRequirement { /// that reference the environment as true. In other words, it does /// environment independent expression evaluation. (Which in turn devolves /// to "only evaluate marker expressions that reference an extra name.") - pub fn evaluate_markers(&self, env: Option<&MarkerEnvironment>, extras: &[ExtraName]) -> bool { + pub fn evaluate_markers( + &self, + env: Option<&MarkerEnvironment>, + variants: &impl MarkerVariantsEnvironment, + extras: &[ExtraName], + ) -> bool { match self { - Self::Named(requirement) => requirement.evaluate_markers(env, extras), - Self::Unnamed(requirement) => requirement.evaluate_optional_environment(env, extras), + Self::Named(requirement) => requirement.evaluate_markers(env, variants, extras), + Self::Unnamed(requirement) => { + requirement.evaluate_optional_environment(env, variants, extras) + } } } diff --git a/crates/uv-distribution-types/src/variant_json.rs b/crates/uv-distribution-types/src/variant_json.rs new file mode 100644 index 000000000..fa799e21b --- /dev/null +++ b/crates/uv-distribution-types/src/variant_json.rs @@ -0,0 +1,92 @@ +use std::fmt::Display; +use std::str::FromStr; + +use uv_normalize::{InvalidNameError, PackageName}; +use uv_pep440::{Version, VersionParseError}; + +#[derive(Debug, thiserror::Error)] +pub enum VariantsJsonError { + #[error("Invalid `variants.json` filename")] + InvalidFilename, + #[error("Invalid `variants.json` package name: {0}")] + InvalidName(#[from] InvalidNameError), + #[error("Invalid `variants.json` version: {0}")] + InvalidVersion(#[from] VersionParseError), +} + +/// A `--variants.json` filename. +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[rkyv(derive(Debug))] +pub struct VariantsJsonFilename { + pub name: PackageName, + pub version: Version, +} + +impl VariantsJsonFilename { + /// Returns a consistent cache key with a maximum length of 64 characters. + pub fn cache_key(&self) -> String { + const CACHE_KEY_MAX_LEN: usize = 64; + + let mut cache_key = self.version.to_string(); + + if cache_key.len() <= CACHE_KEY_MAX_LEN { + return cache_key; + } + + // PANIC SAFETY: version strings can only contain ASCII characters. + cache_key.truncate(CACHE_KEY_MAX_LEN); + let cache_key = cache_key.trim_end_matches(['.', '+']); + + cache_key.to_string() + } +} + +impl FromStr for VariantsJsonFilename { + type Err = VariantsJsonError; + + /// Parse a `--variants.json` filename. + /// + /// name and version must be normalized, i.e., they don't contain dashes. + fn from_str(filename: &str) -> Result { + let stem = filename + .strip_suffix("-variants.json") + .ok_or(VariantsJsonError::InvalidFilename)?; + + let (name, version) = stem + .split_once('-') + .ok_or(VariantsJsonError::InvalidFilename)?; + let name = PackageName::from_str(name)?; + let version = Version::from_str(version)?; + + Ok(Self { name, version }) + } +} + +impl Display for VariantsJsonFilename { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}-variants.json", self.name, self.version) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn variants_json_parsing() { + let variant = VariantsJsonFilename::from_str("numpy-1.21.0-variants.json").unwrap(); + assert_eq!(variant.name.as_str(), "numpy"); + assert_eq!(variant.version.to_string(), "1.21.0"); + } +} diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index d4adbeef3..151ae1721 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -29,18 +29,21 @@ uv-git = { workspace = true } uv-git-types = { workspace = true } uv-metadata = { workspace = true } uv-normalize = { workspace = true } +uv-once-map = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-types = { workspace = true } +uv-variants = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } either = { workspace = true } fs-err = { workspace = true } futures = { workspace = true } +itertools = { workspace = true } nanoid = { workspace = true } owo-colors = { workspace = true } reqwest = { workspace = true } diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index ef2227df6..fa8adb280 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -1,16 +1,19 @@ +use futures::{FutureExt, StreamExt, TryStreamExt}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::ffi::OsString; use std::future::Future; -use std::io; use std::path::Path; +use std::path::PathBuf; use std::pin::Pin; +use std::str::FromStr; use std::sync::Arc; use std::task::{Context, Poll}; - -use futures::{FutureExt, TryStreamExt}; +use std::{env, io}; use tempfile::TempDir; use tokio::io::{AsyncRead, AsyncSeekExt, ReadBuf}; use tokio::sync::Semaphore; use tokio_util::compat::FuturesAsyncReadCompatExt; -use tracing::{Instrument, info_span, instrument, warn}; +use tracing::{Instrument, debug, info_span, instrument, trace, warn}; use url::Url; use uv_cache::{ArchiveId, CacheBucket, CacheEntry, WheelCache}; @@ -18,17 +21,24 @@ use uv_cache_info::{CacheInfo, Timestamp}; use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, }; +use uv_configuration::BuildOutput; use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ BuildInfo, BuildableSource, BuiltDist, Dist, File, HashPolicy, Hashed, IndexUrl, InstalledDist, - Name, SourceDist, ToUrlError, + Name, RegistryVariantsJson, SourceDist, ToUrlError, VariantsJsonFilename, }; use uv_extract::hash::Hasher; use uv_fs::write_atomic; +use uv_pep440::VersionSpecifiers; +use uv_pep508::{MarkerEnvironment, MarkerVariantsUniversal, VariantNamespace, VersionOrUrl}; use uv_platform_tags::Tags; use uv_pypi_types::{HashDigest, HashDigests, PyProjectToml}; use uv_redacted::DisplaySafeUrl; -use uv_types::{BuildContext, BuildStack}; +use uv_types::{BuildContext, BuildStack, VariantsTrait}; +use uv_variants::VariantProviderOutput; +use uv_variants::resolved_variants::ResolvedVariants; +use uv_variants::variant_lock::{VariantLock, VariantLockProvider, VariantLockResolved}; +use uv_variants::variants_json::{Provider, VariantsJsonContent}; use crate::archive::Archive; use crate::metadata::{ArchiveMetadata, Metadata}; @@ -558,6 +568,201 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .await } + #[instrument(skip_all, fields(variants_json = %registry_variants_json.filename))] + pub async fn fetch_and_query_variants( + &self, + registry_variants_json: &RegistryVariantsJson, + marker_env: &MarkerEnvironment, + ) -> Result { + let variants_json = self.fetch_variants_json(registry_variants_json).await?; + let resolved_variants = self + .query_variant_providers(variants_json, marker_env, ®istry_variants_json.filename) + .await?; + Ok(resolved_variants) + } + + /// Fetch the variants.json contents from a URL (cached) or from a path. + async fn fetch_variants_json( + &self, + variants_json: &RegistryVariantsJson, + ) -> Result { + Ok(self + .client + .managed(|client| client.fetch_variants_json(variants_json)) + .await?) + } + + async fn query_variant_providers( + &self, + variants_json: VariantsJsonContent, + marker_env: &MarkerEnvironment, + debug_filename: &VariantsJsonFilename, + ) -> Result { + // TODO: parse_boolish_environment_variable + let locked_and_inferred = + env::var_os("UV_VARIANT_LOCK_INCOMPLETE").is_some_and(|var| var == "1"); + // TODO(konsti): Integrate this properly, and add this to the CLI. + let variant_lock = if let Some(variant_lock_path) = env::var_os("UV_VARIANT_LOCK") { + let variant_lock: VariantLock = toml::from_slice( + &fs_err::read(&variant_lock_path).map_err(Error::VariantLockRead)?, + ) + .map_err(|err| Error::VariantLockParse(PathBuf::from(&variant_lock_path), err))?; + // TODO(konsti): If parsing fails, check the version + if !VersionSpecifiers::from_str(">=0.1,<0.2") + .unwrap() + .contains(&variant_lock.metadata.version) + { + return Err(Error::VariantLockVersion( + PathBuf::from(variant_lock_path), + variant_lock.metadata.version, + )); + } + + Some((variant_lock_path, variant_lock)) + } else { + None + }; + + // Query the install time provider. + // TODO(konsti): Don't use threads if we're fully static. + let mut disabled_namespaces = FxHashSet::default(); + let mut resolved_namespaces: FxHashMap> = + futures::stream::iter(variants_json.providers.iter().filter(|(_, provider)| { + provider.install_time.unwrap_or(true) + && !provider.optional + && provider + .enable_if + .evaluate(marker_env, &MarkerVariantsUniversal, &[]) + })) + .map(|(name, provider)| { + self.resolve_provider(locked_and_inferred, variant_lock.as_ref(), name, provider) + }) + // TODO(konsti): Buffer size + .buffered(8) + .try_collect() + .await?; + + // "Query" the static providers + for (namespace, provider) in &variants_json.providers { + // Track disabled namespaces for consistency checks. + if !provider + .enable_if + .evaluate(marker_env, &MarkerVariantsUniversal, &[]) + || provider.optional + { + disabled_namespaces.insert(namespace.clone()); + continue; + } + + if provider.install_time.unwrap_or(true) { + continue; + } + + let Some(features) = variants_json + .static_properties + .as_ref() + .and_then(|static_properties| static_properties.get(namespace)) + else { + warn!( + "Missing namespace {namespace} in default properties for {}=={}", + debug_filename.name, debug_filename.version + ); + continue; + }; + resolved_namespaces.insert( + namespace.clone(), + Arc::new(VariantProviderOutput { + namespace: namespace.clone(), + features: features.clone().into_iter().collect(), + }), + ); + } + + Ok(ResolvedVariants { + variants_json, + resolved_namespaces, + disabled_namespaces, + }) + } + + async fn resolve_provider( + &self, + locked_and_inferred: bool, + variant_lock: Option<&(OsString, VariantLock)>, + name: &VariantNamespace, + provider: &Provider, + ) -> Result<(VariantNamespace, Arc), Error> { + if let Some((variant_lock_path, variant_lock)) = &variant_lock { + if let Some(static_provider) = variant_lock + .provider + .iter() + .find(|static_provider| satisfies_provider_requires(provider, static_provider)) + { + Ok(( + static_provider.namespace.clone(), + Arc::new(VariantProviderOutput { + namespace: static_provider.namespace.clone(), + features: static_provider.properties.clone().into_iter().collect(), + }), + )) + } else if locked_and_inferred { + let config = self.query_variant_provider(name, provider).await?; + Ok((config.namespace.clone(), config)) + } else { + Err(Error::VariantLockMissing { + variant_lock: PathBuf::from(variant_lock_path), + requires: provider.requires.clone().unwrap_or_default(), + plugin_api: provider.plugin_api.clone().unwrap_or_default().clone(), + }) + } + } else { + let config = self.query_variant_provider(name, provider).await?; + Ok((config.namespace.clone(), config)) + } + } + + async fn query_variant_provider( + &self, + name: &VariantNamespace, + provider: &Provider, + ) -> Result, Error> { + let config = if self.build_context.variants().register(provider.clone()) { + debug!("Querying provider `{name}` for variants"); + + // TODO(konsti): That's not spec compliant + let backend_name = provider.plugin_api.clone().unwrap_or(name.to_string()); + let builder = self + .build_context + .setup_variants(backend_name, provider, BuildOutput::Debug) + .await?; + let config = builder.query().await?; + trace!( + "Found namespace {} with configs {:?}", + config.namespace, config + ); + + let config = Arc::new(config); + self.build_context + .variants() + .done(provider.clone(), config.clone()); + config + } else { + debug!("Reading provider `{name}` from in-memory cache"); + self.build_context + .variants() + .wait(provider) + .await + .expect("missing value for registered task") + }; + if &config.namespace != name { + return Err(Error::WheelVariantNamespaceMismatch { + declared: name.clone(), + actual: config.namespace.clone(), + }); + } + Ok(config) + } + /// Stream a wheel from a URL, unzipping it into the cache as it's downloaded. async fn stream_wheel( &self, @@ -1343,6 +1548,46 @@ fn add_tar_zst_extension(mut url: DisplaySafeUrl) -> DisplaySafeUrl { url } +fn satisfies_provider_requires( + requested_provider: &Provider, + static_provider: &VariantLockProvider, +) -> bool { + // TODO(konsti): Correct plugin_api inference. + if static_provider.plugin_api.clone().or(static_provider + .resolved + .first() + .map(|resolved| resolved.name().to_string())) + != requested_provider.plugin_api.clone().or(static_provider + .resolved + .first() + .map(|resolved| resolved.name().to_string())) + { + return false; + } + let Some(requires) = &requested_provider.requires else { + return true; + }; + requires.iter().all(|requested| { + static_provider.resolved.iter().any(|resolved| { + // We ignore extras for simplicity. + &requested.name == resolved.name() + && match (&requested.version_or_url, resolved) { + (None, _) => true, + ( + Some(VersionOrUrl::VersionSpecifier(requested)), + VariantLockResolved::Version(_name, resolved), + ) => requested.contains(resolved), + ( + Some(VersionOrUrl::Url(resolved_url)), + VariantLockResolved::Url(_name, url), + ) => resolved_url == &**url, + // We don't support URL<->version matching + _ => false, + } + }) + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/uv-distribution/src/error.rs b/crates/uv-distribution/src/error.rs index 40e7f3b0e..80b1a0aaf 100644 --- a/crates/uv-distribution/src/error.rs +++ b/crates/uv-distribution/src/error.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use itertools::Itertools; use owo_colors::OwoColorize; use tokio::task::JoinError; use zip::result::ZipError; @@ -12,7 +13,8 @@ use uv_fs::Simplified; use uv_git::GitError; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; -use uv_pypi_types::{HashAlgorithm, HashDigest}; +use uv_pep508::{Requirement, VariantNamespace}; +use uv_pypi_types::{HashAlgorithm, HashDigest, VerbatimParsedUrl}; use uv_redacted::DisplaySafeUrl; use uv_types::AnyErrorBuild; @@ -75,6 +77,32 @@ pub enum Error { filename: Version, metadata: Version, }, + #[error( + "Package {name} has no matching wheel for the current platform, but has the following variants: {variants}" + )] + WheelVariantMismatch { name: PackageName, variants: String }, + #[error("Provider plugin is declared to use namespace {declared} but uses namespace {actual}")] + WheelVariantNamespaceMismatch { + declared: VariantNamespace, + actual: VariantNamespace, + }, + #[error("Failed to read variant lock")] + VariantLockRead(#[source] std::io::Error), + #[error("Variant lock has an unsupported format: {}", _0.user_display())] + VariantLockParse(PathBuf, #[source] toml::de::Error), + #[error("Variant lock has an unsupported version {}, only version 0.1 is supported: {}", _1, _0.user_display())] + VariantLockVersion(PathBuf, Version), + #[error( + "Variant lock is missing a matching provider and `UV_VARIANT_LOCK_INCOMPLETE` is not set\n variant lock: {}\n requires: `{}`\n plugin-api: {}", + variant_lock.user_display(), + requires.iter().join("`, `"), + plugin_api + )] + VariantLockMissing { + variant_lock: PathBuf, + requires: Vec>, + plugin_api: String, + }, #[error("Failed to parse metadata from built wheel")] Metadata(#[from] uv_pypi_types::MetadataError), #[error("Failed to read metadata: `{}`", _0.user_display())] diff --git a/crates/uv-distribution/src/lib.rs b/crates/uv-distribution/src/lib.rs index 6ffb2d6d8..6c03141c1 100644 --- a/crates/uv-distribution/src/lib.rs +++ b/crates/uv-distribution/src/lib.rs @@ -9,6 +9,7 @@ pub use metadata::{ }; pub use reporter::Reporter; pub use source::prune; +pub use variants::{PackageVariantCache, resolve_variants}; mod archive; mod distribution_database; @@ -18,3 +19,4 @@ mod index; mod metadata; mod reporter; mod source; +mod variants; diff --git a/crates/uv-distribution/src/variants.rs b/crates/uv-distribution/src/variants.rs new file mode 100644 index 000000000..d900f265e --- /dev/null +++ b/crates/uv-distribution/src/variants.rs @@ -0,0 +1,205 @@ +use std::hash::BuildHasherDefault; +use std::sync::Arc; + +use futures::{StreamExt, TryStreamExt}; +use itertools::Itertools; +use owo_colors::OwoColorize; +use rustc_hash::{FxHashMap, FxHasher}; +use tracing::{debug, trace}; +use uv_distribution_filename::BuildTag; +use uv_distribution_types::{ + BuiltDist, Dist, DistributionId, GlobalVersionId, Identifier, Name, Node, RegistryBuiltDist, + RegistryVariantsJson, Resolution, ResolvedDist, +}; +use uv_once_map::OnceMap; +use uv_platform_tags::{TagCompatibility, Tags}; +use uv_pypi_types::ResolverMarkerEnvironment; +use uv_types::BuildContext; +use uv_variants::resolved_variants::ResolvedVariants; + +use crate::{DistributionDatabase, Error}; + +type FxOnceMap = OnceMap>; + +/// An in-memory cache from package to resolved variants. +#[derive(Default)] +pub struct PackageVariantCache(FxOnceMap>); + +impl std::ops::Deref for PackageVariantCache { + type Target = FxOnceMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Resolve all variants for the given resolution. +pub async fn resolve_variants( + resolution: Resolution, + marker_env: &ResolverMarkerEnvironment, + distribution_database: DistributionDatabase<'_, Context>, + cache: &PackageVariantCache, + tags: &Tags, +) -> Result { + // Fetch variants.json and then query providers, running in parallel for all distributions. + let dist_resolved_variants: FxHashMap> = + futures::stream::iter( + resolution + .graph() + .node_weights() + .filter_map(|node| extract_variants(node)), + ) + .map(async |(variants_json, ..)| { + let id = variants_json.version_id(); + + let resolved_variants = if cache.register(id.clone()) { + let resolved_variants = distribution_database + .fetch_and_query_variants(variants_json, marker_env) + .await?; + + let resolved_variants = Arc::new(resolved_variants); + cache.done(id.clone(), resolved_variants.clone()); + resolved_variants + } else { + cache + .wait(&id) + .await + .expect("missing value for registered task") + }; + + Ok::<_, Error>((id, resolved_variants)) + }) + // TODO(konsti): Buffer size + .buffered(8) + .try_collect() + .await?; + + // Determine modification to the resolutions to select variant wheels, or error if there + // is no matching variant wheel and no matching non-variant wheel. + let mut new_best_wheel_index: FxHashMap = FxHashMap::default(); + for node in resolution.graph().node_weights() { + let Some((json, dist)) = extract_variants(node) else { + continue; + }; + let resolved_variants = &dist_resolved_variants[&json.version_id()]; + + // Select best wheel + let mut highest_priority_variant_wheel: Option<( + usize, + Vec, + TagCompatibility, + Option<&BuildTag>, + )> = None; + for (wheel_index, wheel) in dist.wheels.iter().enumerate() { + let build_tag = wheel.filename.build_tag(); + let compatibility = wheel.filename.compatibility(tags); + if !compatibility.is_compatible() { + continue; + } + + let Some(variant) = wheel.filename.variant() else { + // The non-variant wheel is already supported + continue; + }; + + let Some(scores) = resolved_variants.score_variant(variant) else { + continue; + }; + + if let Some((_, old_scores, old_compatibility, old_build_tag)) = + &highest_priority_variant_wheel + { + if (&scores, &compatibility, &build_tag) + > (old_scores, old_compatibility, old_build_tag) + { + highest_priority_variant_wheel = + Some((wheel_index, scores, compatibility, build_tag)); + } + } else { + highest_priority_variant_wheel = + Some((wheel_index, scores, compatibility, build_tag)); + } + } + + // Determine if we need to modify the resolution + if let Some((wheel_index, ..)) = highest_priority_variant_wheel { + debug!( + "{} for {}: {}", + "Using variant wheel".red(), + dist.name(), + dist.wheels[wheel_index].filename, + ); + new_best_wheel_index.insert(dist.distribution_id(), wheel_index); + } else if dist.best_wheel().filename.variant().is_some() { + return Err(Error::WheelVariantMismatch { + name: dist.name().clone(), + variants: dist + .wheels + .iter() + .filter_map(|wheel| wheel.filename.variant()) + .join(", "), + }); + } else { + trace!( + "No matching variant wheel, but matching non-variant wheel for {}", + dist.name() + ); + } + } + let resolution = resolution.map(|dist| { + let ResolvedDist::Installable { + dist, + version, + variants_json, + } = dist + else { + return None; + }; + let Dist::Built(BuiltDist::Registry(dist)) = &**dist else { + return None; + }; + // Check whether there is a matching variant wheel we want to use instead of the default. + let best_wheel_index = new_best_wheel_index.get(&dist.distribution_id())?; + Some(ResolvedDist::Installable { + dist: Arc::new(Dist::Built(BuiltDist::Registry(RegistryBuiltDist { + wheels: dist.wheels.clone(), + best_wheel_index: *best_wheel_index, + sdist: dist.sdist.clone(), + }))), + variants_json: variants_json.clone(), + version: version.clone(), + }) + }); + + Ok(resolution) +} + +fn extract_variants(node: &Node) -> Option<(&RegistryVariantsJson, &RegistryBuiltDist)> { + let Node::Dist { dist, .. } = node else { + // The root node has no variants + return None; + }; + let ResolvedDist::Installable { + dist, + variants_json, + .. + } = dist + else { + // TODO(konsti): Installed dists? Or is that not a thing here? + return None; + }; + let Some(variants_json) = variants_json else { + return None; + }; + let Dist::Built(BuiltDist::Registry(dist)) = &**dist else { + return None; + }; + if !dist + .wheels + .iter() + .any(|wheel| wheel.filename.variant().is_some()) + { + return None; + } + Some((variants_json, dist)) +} diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index 1941a8371..7007df9d5 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -14,7 +14,7 @@ use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ BuiltDist, CachedDirectUrlDist, CachedDist, ConfigSettings, Dist, Error, ExtraBuildRequires, ExtraBuildVariables, Hashed, IndexLocations, InstalledDist, Name, PackageConfigSettings, - RequirementSource, Resolution, ResolvedDist, SourceDist, + RemoteSource, RequirementSource, Resolution, ResolvedDist, SourceDist, }; use uv_fs::Simplified; use uv_normalize::PackageName; @@ -208,7 +208,10 @@ impl<'a> Planner<'a> { } Some(&entry.dist) }) { - debug!("Registry requirement already cached: {distribution}"); + debug!( + "Registry requirement already cached: {distribution} ({})", + wheel.best_wheel().filename + ); cached.push(CachedDist::Registry(distribution.clone())); continue; } @@ -494,7 +497,11 @@ impl<'a> Planner<'a> { } } - debug!("Identified uncached distribution: {dist}"); + if let Ok(filename) = dist.filename() { + debug!("Identified uncached distribution: {dist} ({filename})"); + } else { + debug!("Identified uncached distribution: {dist}"); + } remote.push(dist.clone()); } diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index 84dc7f66b..28f99fa80 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -15,7 +15,7 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; -use uv_pep508::VersionOrUrl; +use uv_pep508::{MarkerVariantsUniversal, VersionOrUrl}; use uv_platform_tags::Tags; use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl}; use uv_python::{Interpreter, PythonEnvironment}; @@ -265,7 +265,7 @@ impl SitePackages { // Verify that the dependencies are installed. for dependency in &metadata.requires_dist { - if !dependency.evaluate_markers(markers, &[]) { + if !dependency.evaluate_markers(markers, &MarkerVariantsUniversal, &[]) { continue; } @@ -454,14 +454,15 @@ impl SitePackages { for requirement in requirements { if let Some(r#overrides) = overrides.get(&requirement.name) { for dependency in r#overrides { - if dependency.evaluate_markers(Some(markers), &[]) { + if dependency.evaluate_markers(Some(markers), &MarkerVariantsUniversal, &[]) { if seen.insert((*dependency).clone()) { stack.push(Cow::Borrowed(*dependency)); } } } } else { - if requirement.evaluate_markers(Some(markers), &[]) { + // TODO(konsti): Evaluate variants + if requirement.evaluate_markers(Some(markers), &MarkerVariantsUniversal, &[]) { if seen.insert(requirement.clone()) { stack.push(Cow::Borrowed(requirement)); } @@ -480,7 +481,7 @@ impl SitePackages { } [distribution] => { // Validate that the requirement is satisfied. - if requirement.evaluate_markers(Some(markers), &[]) { + if requirement.evaluate_markers(Some(markers), &MarkerVariantsUniversal, &[]) { match RequirementSatisfaction::check( name, distribution, @@ -503,7 +504,8 @@ impl SitePackages { // Validate that the installed version satisfies the constraints. for constraint in constraints.get(name).into_iter().flatten() { - if constraint.evaluate_markers(Some(markers), &[]) { + if constraint.evaluate_markers(Some(markers), &MarkerVariantsUniversal, &[]) + { match RequirementSatisfaction::check( name, distribution, @@ -537,14 +539,22 @@ impl SitePackages { let dependency = Requirement::from(dependency.clone()); if let Some(r#overrides) = overrides.get(&dependency.name) { for dependency in r#overrides { - if dependency.evaluate_markers(Some(markers), &requirement.extras) { + if dependency.evaluate_markers( + Some(markers), + &MarkerVariantsUniversal, + &requirement.extras, + ) { if seen.insert((*dependency).clone()) { stack.push(Cow::Borrowed(*dependency)); } } } } else { - if dependency.evaluate_markers(Some(markers), &requirement.extras) { + if dependency.evaluate_markers( + Some(markers), + &MarkerVariantsUniversal, + &requirement.extras, + ) { if seen.insert(dependency.clone()) { stack.push(Cow::Owned(dependency)); } diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 30c7b9362..a66a758d5 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -36,7 +36,9 @@ pub use crate::marker::{ ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, - MarkerValueVersion, MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerValueVersion, MarkerVariantsEnvironment, MarkerVariantsUniversal, MarkerWarningKind, + StringMarkerTree, StringVersion, VariantFeature, VariantNamespace, VariantValue, + VersionMarkerTree, }; pub use crate::origin::RequirementOrigin; #[cfg(feature = "non-pep508-extensions")] @@ -50,6 +52,8 @@ pub use crate::verbatim_url::{ pub use uv_pep440; use uv_pep440::{VersionSpecifier, VersionSpecifiers}; +use crate::marker::VariantParseError; + mod cursor; pub mod marker; mod origin; @@ -82,6 +86,20 @@ pub enum Pep508ErrorSource { /// The version requirement is not supported. #[error("{0}")] UnsupportedRequirement(String), + /// The operator is not supported with the variant marker. + #[error( + "The operator {0} is not supported with the marker {1}, only the `in` and `not in` operators are supported" + )] + ListOperator(MarkerOperator, MarkerValueList), + /// The value is not a quoted string. + #[error("Only quoted strings are supported with the variant marker {1}, not {0}")] + ListValue(MarkerValue, MarkerValueList), + /// The variant marker is on the left hand side of the expression. + #[error("The marker {0} must be on the right hand side of the expression")] + ListLValue(MarkerValueList), + /// A variant segment uses invalid characters. + #[error(transparent)] + InvalidVariantSegment(VariantParseError), } impl Display for Pep508Error { @@ -298,8 +316,13 @@ impl CacheKey for Requirement { impl Requirement { /// Returns whether the markers apply for the given environment - pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { - self.marker.evaluate(env, extras) + pub fn evaluate_markers( + &self, + env: &MarkerEnvironment, + variants: &impl MarkerVariantsEnvironment, + extras: &[ExtraName], + ) -> bool { + self.marker.evaluate(env, variants, extras) } /// Return the requirement with an additional marker added, to require the given extra. diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index 4051fe88b..ac4fdfdb1 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -46,6 +46,7 @@ //! merged to be applied globally. use std::cmp::Ordering; +use std::collections::BTreeSet; use std::fmt; use std::ops::Bound; use std::sync::{LazyLock, Mutex, MutexGuard}; @@ -581,6 +582,43 @@ impl InternerGuard<'_> { } } + /// Contract: The type of variable remains the same. + pub(crate) fn edit_variable( + &mut self, + i: NodeId, + f: &impl Fn(&Variable) -> Option, + ) -> NodeId { + if matches!(i, NodeId::TRUE | NodeId::FALSE) { + return i; + } + + // Restrict all nodes recursively. + let node = self.shared.node(i); + let children = node.children.map(i, |node| self.edit_variable(node, f)); + + if let Some(var) = f(&node.var) { + self.create_node(var, children) + } else { + self.create_node(node.var.clone(), children) + } + } + + pub(crate) fn collect_variant_bases(&mut self, i: NodeId, bases: &mut BTreeSet) { + if matches!(i, NodeId::TRUE | NodeId::FALSE) { + return; + } + + // Restrict all nodes recursively. + let node = self.shared.node(i); + if let Some(base) = node.var.variant_base() { + bases.insert(base.to_string()); + } + + for child in node.children.nodes() { + self.collect_variant_bases(child, bases); + } + } + /// Returns a new tree where the only nodes remaining are `extra` nodes. /// /// If there are no extra nodes, then this returns a tree that is always @@ -1072,6 +1110,17 @@ impl Variable { }; marker.is_conflicting() } + + fn variant_base(&self) -> Option<&str> { + match self { + Self::List( + CanonicalMarkerListPair::VariantNamespaces { base, .. } + | CanonicalMarkerListPair::VariantFeatures { base, .. } + | CanonicalMarkerListPair::VariantProperties { base, .. }, + ) => base.as_deref(), + _ => None, + } + } } /// A decision node in an Algebraic Decision Diagram. diff --git a/crates/uv-pep508/src/marker/environment.rs b/crates/uv-pep508/src/marker/environment.rs index ce36fa862..5eaa646be 100644 --- a/crates/uv-pep508/src/marker/environment.rs +++ b/crates/uv-pep508/src/marker/environment.rs @@ -41,8 +41,8 @@ impl MarkerEnvironment { } /// Returns of the stringly typed value of the key in the current environment - pub fn get_string(&self, key: CanonicalMarkerValueString) -> &str { - match key { + pub fn get_string(&self, key: CanonicalMarkerValueString) -> Option<&str> { + Some(match key { CanonicalMarkerValueString::ImplementationName => self.implementation_name(), CanonicalMarkerValueString::OsName => self.os_name(), CanonicalMarkerValueString::PlatformMachine => self.platform_machine(), @@ -53,7 +53,8 @@ impl MarkerEnvironment { CanonicalMarkerValueString::PlatformSystem => self.platform_system(), CanonicalMarkerValueString::PlatformVersion => self.platform_version(), CanonicalMarkerValueString::SysPlatform => self.sys_platform(), - } + CanonicalMarkerValueString::VariantLabel => return None, + }) } } diff --git a/crates/uv-pep508/src/marker/lowering.rs b/crates/uv-pep508/src/marker/lowering.rs index e52669840..5cfb4be2f 100644 --- a/crates/uv-pep508/src/marker/lowering.rs +++ b/crates/uv-pep508/src/marker/lowering.rs @@ -1,8 +1,8 @@ use std::fmt::{Display, Formatter}; - use uv_normalize::{ExtraName, GroupName}; use crate::marker::tree::MarkerValueList; +use crate::marker::{VariantFeature, VariantNamespace, VariantValue}; use crate::{MarkerValueExtra, MarkerValueString, MarkerValueVersion}; /// Those environment markers with a PEP 440 version as value such as `python_version` @@ -60,6 +60,8 @@ pub enum CanonicalMarkerValueString { PlatformVersion, /// `implementation_name` ImplementationName, + /// `variant_label` + VariantLabel, } impl CanonicalMarkerValueString { @@ -92,6 +94,7 @@ impl From for CanonicalMarkerValueString { MarkerValueString::PlatformVersionDeprecated => Self::PlatformVersion, MarkerValueString::SysPlatform => Self::SysPlatform, MarkerValueString::SysPlatformDeprecated => Self::SysPlatform, + MarkerValueString::VariantLabel => Self::VariantLabel, } } } @@ -109,6 +112,7 @@ impl From for MarkerValueString { CanonicalMarkerValueString::PlatformSystem => Self::PlatformSystem, CanonicalMarkerValueString::PlatformVersion => Self::PlatformVersion, CanonicalMarkerValueString::SysPlatform => Self::SysPlatform, + CanonicalMarkerValueString::VariantLabel => Self::VariantLabel, } } } @@ -125,6 +129,7 @@ impl Display for CanonicalMarkerValueString { Self::PlatformSystem => f.write_str("platform_system"), Self::PlatformVersion => f.write_str("platform_version"), Self::SysPlatform => f.write_str("sys_platform"), + Self::VariantLabel => f.write_str("variant_label"), } } } @@ -163,13 +168,34 @@ impl Display for CanonicalMarkerValueExtra { /// A key-value pair for ` in ` or ` not in `, where the key is a list. /// -/// Used for PEP 751 markers. +/// Used for PEP 751 and variant markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum CanonicalMarkerListPair { /// A valid [`ExtraName`]. Extras(ExtraName), /// A valid [`GroupName`]. DependencyGroup(GroupName), + /// A valid `variant_namespaces`. + VariantNamespaces { + /// If set, the variant marker is evaluated as a variant of the base package. + base: Option, + namespace: VariantNamespace, + }, + /// A valid `variant_features`. + VariantFeatures { + /// If set, the variant marker is evaluated as a variant of the base package. + base: Option, + namespace: VariantNamespace, + feature: VariantFeature, + }, + /// A valid `variant_properties`. + VariantProperties { + /// If set, the variant marker is evaluated as a variant of the base package. + base: Option, + namespace: VariantNamespace, + feature: VariantFeature, + value: VariantValue, + }, /// For leniency, preserve invalid values. Arbitrary { key: MarkerValueList, value: String }, } @@ -180,6 +206,9 @@ impl CanonicalMarkerListPair { match self { Self::Extras(_) => MarkerValueList::Extras, Self::DependencyGroup(_) => MarkerValueList::DependencyGroups, + Self::VariantNamespaces { .. } => MarkerValueList::VariantNamespaces, + Self::VariantFeatures { .. } => MarkerValueList::VariantFeatures, + Self::VariantProperties { .. } => MarkerValueList::VariantProperties, Self::Arbitrary { key, .. } => *key, } } @@ -189,6 +218,39 @@ impl CanonicalMarkerListPair { match self { Self::Extras(extra) => extra.to_string(), Self::DependencyGroup(group) => group.to_string(), + Self::VariantNamespaces { + base: prefix, + namespace, + } => { + if let Some(prefix) = prefix { + format!("{prefix} | {namespace}") + } else { + namespace.to_string() + } + } + Self::VariantFeatures { + base: prefix, + namespace, + feature, + } => { + if let Some(prefix) = prefix { + format!("{prefix} | {namespace} :: {feature}") + } else { + format!("{namespace} :: {feature}") + } + } + Self::VariantProperties { + base: prefix, + namespace, + feature, + value, + } => { + if let Some(prefix) = prefix { + format!("{prefix} | {namespace} :: {feature} :: {value}") + } else { + format!("{namespace} :: {feature} :: {value}") + } + } Self::Arbitrary { value, .. } => value.clone(), } } diff --git a/crates/uv-pep508/src/marker/mod.rs b/crates/uv-pep508/src/marker/mod.rs index 55d21c69f..8407f2763 100644 --- a/crates/uv-pep508/src/marker/mod.rs +++ b/crates/uv-pep508/src/marker/mod.rs @@ -15,6 +15,7 @@ mod lowering; pub(crate) mod parse; mod simplify; mod tree; +mod variants; pub use environment::{MarkerEnvironment, MarkerEnvironmentBuilder}; pub use lowering::{ @@ -24,8 +25,10 @@ pub use tree::{ ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion, - MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerVariantsEnvironment, MarkerVariantsUniversal, MarkerWarningKind, StringMarkerTree, + StringVersion, VersionMarkerTree, }; +pub use variants::{VariantFeature, VariantNamespace, VariantParseError, VariantValue}; /// `serde` helpers for [`MarkerTree`]. pub mod ser { diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index 8e4a39078..2ac5df5ab 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -4,9 +4,9 @@ use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; -use crate::marker::MarkerValueExtra; use crate::marker::lowering::CanonicalMarkerListPair; use crate::marker::tree::{ContainerOperator, MarkerValueList}; +use crate::marker::{MarkerValueExtra, VariantFeature, VariantNamespace, VariantValue}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -181,6 +181,9 @@ pub(crate) fn parse_marker_key_op_value( let r_value = parse_marker_value(cursor, reporter)?; let len = cursor.pos() - start; + // TODO(konsti): Catch incorrect variant markers in all places, now that we have the + // opportunity to check from the beginning. + // Convert a ` ` expression into its // typed equivalent. let expr = match l_value { @@ -307,7 +310,7 @@ pub(crate) fn parse_marker_key_op_value( Ok(name) => CanonicalMarkerListPair::Extras(name), Err(err) => { reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, + MarkerWarningKind::ListInvalidComparison, format!("Expected extra name (found `{l_string}`): {err}"), ); CanonicalMarkerListPair::Arbitrary { @@ -322,7 +325,7 @@ pub(crate) fn parse_marker_key_op_value( Ok(name) => CanonicalMarkerListPair::DependencyGroup(name), Err(err) => { reporter.report( - MarkerWarningKind::ExtrasInvalidComparison, + MarkerWarningKind::ListInvalidComparison, format!("Expected dependency group name (found `{l_string}`): {err}"), ); CanonicalMarkerListPair::Arbitrary { @@ -332,6 +335,118 @@ pub(crate) fn parse_marker_key_op_value( } } } + MarkerValueList::VariantNamespaces => { + let (base, value) = + if let Some((base, value)) = l_string.split_once(" | ") { + (Some(base.trim().to_string()), value) + } else { + (None, l_string.as_str()) + }; + + CanonicalMarkerListPair::VariantNamespaces { + base, + namespace: VariantNamespace::from_str(value).map_err(|err| { + Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + } + })?, + } + } + MarkerValueList::VariantFeatures => { + let (base, value) = + if let Some((base, value)) = l_string.split_once(" | ") { + (Some(base.trim().to_string()), value) + } else { + (None, l_string.as_str()) + }; + + if let Some((namespace, feature)) = value.split_once("::") { + CanonicalMarkerListPair::VariantFeatures { + base, + namespace: VariantNamespace::from_str(namespace).map_err( + |err| Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + }, + )?, + feature: VariantFeature::from_str(feature).map_err(|err| { + Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + } + })?, + } + } else { + reporter.report( + MarkerWarningKind::ListInvalidComparison, + format!("Expected variant feature with two components separated by `::`, found `{value}`"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: value.to_string(), + } + } + } + MarkerValueList::VariantProperties => { + let (base, value) = + if let Some((base, value)) = l_string.trim().split_once(" | ") { + (Some(base.trim().to_string()), value) + } else { + (None, l_string.as_str()) + }; + + let mut components = value.split("::"); + if let (Some(namespace), Some(feature), Some(property), None) = ( + components.next(), + components.next(), + components.next(), + components.next(), + ) { + CanonicalMarkerListPair::VariantProperties { + base, + namespace: VariantNamespace::from_str(namespace).map_err( + |err| Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + }, + )?, + feature: VariantFeature::from_str(feature).map_err(|err| { + Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + } + })?, + value: VariantValue::from_str(property).map_err(|err| { + Pep508Error { + message: Pep508ErrorSource::InvalidVariantSegment(err), + start, + len, + input: cursor.to_string(), + } + })?, + } + } else { + reporter.report( + MarkerWarningKind::ListInvalidComparison, + format!("Expected variant property with three components separated by `::`, found `{value}`"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: value.to_string(), + } + } + } }; Some(MarkerExpression::List { pair, operator }) diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index ea175a309..0ef53b070 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::cmp::Ordering; +use std::collections::BTreeSet; use std::fmt::{self, Display, Formatter}; use std::ops::{Bound, Deref}; use std::str::FromStr; @@ -18,7 +19,7 @@ use crate::cursor::Cursor; use crate::marker::lowering::{ CanonicalMarkerListPair, CanonicalMarkerValueString, CanonicalMarkerValueVersion, }; -use crate::marker::parse; +use crate::marker::{VariantFeature, VariantNamespace, VariantValue, parse}; use crate::{ CanonicalMarkerValueExtra, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, TracingReporter, @@ -33,12 +34,9 @@ pub enum MarkerWarningKind { /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as /// `extra > "perf"` or `extra == os_name` ExtraInvalidComparison, - /// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as - /// `extras > "perf"` or `extras == os_name` - ExtrasInvalidComparison, - /// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`, - /// such as `dependency_groups > "perf"` or `dependency_groups == os_name` - DependencyGroupsInvalidComparison, + /// Doing an operation other than `in` and `not in` on a list marker, such as + /// `extras > "perf"` or `dependency_groups == os_name` + ListInvalidComparison, /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"` LexicographicComparison, /// Comparing two markers, such as `os_name != sys_implementation` @@ -102,6 +100,8 @@ pub enum MarkerValueString { SysPlatform, /// Deprecated `sys.platform` from SysPlatformDeprecated, + /// `variant_label`, the label of the selected variant wheel + VariantLabel, } impl Display for MarkerValueString { @@ -122,15 +122,22 @@ impl Display for MarkerValueString { f.write_str("platform_version") } Self::SysPlatform | Self::SysPlatformDeprecated => f.write_str("sys_platform"), + Self::VariantLabel => f.write_str("variant_label"), } } } /// Those markers with exclusively `in` and `not in` operators. /// -/// Contains PEP 751 lockfile markers. +/// Contains the PEP 751 lockfile marker and the variant markers. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum MarkerValueList { + /// `variant_namespaces` + VariantNamespaces, + /// `variant_features` + VariantFeatures, + /// `variant_properties` + VariantProperties, /// `extras`. This one is special because it's a list, and user-provided Extras, /// `dependency_groups`. This one is special because it's a list, and user-provided @@ -142,6 +149,9 @@ impl Display for MarkerValueList { match self { Self::Extras => f.write_str("extras"), Self::DependencyGroups => f.write_str("dependency_groups"), + Self::VariantNamespaces => f.write_str("variant_namespaces"), + Self::VariantFeatures => f.write_str("variant_features"), + Self::VariantProperties => f.write_str("variant_properties"), } } } @@ -200,6 +210,10 @@ impl FromStr for MarkerValue { "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), "extras" => Self::MarkerEnvList(MarkerValueList::Extras), "dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups), + "variant_namespaces" => Self::MarkerEnvList(MarkerValueList::VariantNamespaces), + "variant_features" => Self::MarkerEnvList(MarkerValueList::VariantFeatures), + "variant_properties" => Self::MarkerEnvList(MarkerValueList::VariantProperties), + "variant_label" => Self::MarkerEnvString(MarkerValueString::VariantLabel), "extra" => Self::Extra, _ => return Err(format!("Invalid key: {s}")), }; @@ -531,7 +545,7 @@ pub enum MarkerExpression { operator: MarkerOperator, value: ArcStr, }, - /// `'...' in `, a PEP 751 expression. + /// `'...' in `, either PEP 751 or a variant expression. List { pair: CanonicalMarkerListPair, operator: ContainerOperator, @@ -552,7 +566,8 @@ pub(crate) enum MarkerExpressionKind { VersionIn(MarkerValueVersion), /// A string marker comparison, e.g. `sys_platform == '...'`. String(MarkerValueString), - /// A list `in` or `not in` expression, e.g. `'...' in dependency_groups`. + /// A list `in` or `not in` expression, e.g. `'...' in dependency_groups` or + /// `'gpu :: cuda :: cu128' in variant_properties`. List(MarkerValueList), /// An extra expression, e.g. `extra == '...'`. Extra, @@ -760,6 +775,196 @@ impl<'a> ExtrasEnvironment<'a> { } } +/// The environment to use for evaluating `variant_namespaces`, `variant_features` and +/// `variant_properties` +// TODO(konsti): Ask for a better way with traits for this. +pub trait MarkerVariantsEnvironment { + /// Whether there is a property with the given namespace, for evaluating + /// `... in variant_namespaces`. + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool; + + /// Whether there is a property with the given feature, for evaluating + /// `... in variant_features`. + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool; + + /// Whether there is the given property, for evaluating `... in variant_properties`. + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool; + + /// Whether the base package has a property with the given namespace, for evaluating + /// `... in variant_namespaces`. + /// + /// This is an extension to the standard use internally by uv when variant markers occur + /// outside their base package. + fn contains_base_namespace(&self, _base: &str, _namespace: &VariantNamespace) -> bool { + false + } + + /// Whether the base package has a property with the given feature, for evaluating + /// `... in variant_features`. + /// + /// This is an extension to the standard use internally by uv when variant markers occur + /// outside their base package. + fn contains_base_feature( + &self, + _base: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + ) -> bool { + false + } + + /// Whether the base package has the given property, for evaluating `... in variant_properties`. + /// + /// This is an extension to the standard use internally by uv when variant markers occur + /// outside their base package. + fn contains_base_property( + &self, + _base: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + _value: &VariantValue, + ) -> bool { + false + } + + /// The variant label of the selected variant wheel, if any. + fn label(&self) -> Option<&str> { + None + } + + /// Whether variant markers always evaluate to `true`. + // TODO(konsti): This should be encoded in the type system. + fn is_universal(&self) -> bool { + false + } +} + +/// Evaluate lists of properties. +impl MarkerVariantsEnvironment for &[(VariantNamespace, VariantFeature, VariantValue)] { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + self.iter() + .any(|(namespace_, _feature, _value)| namespace == namespace_) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + self.iter() + .any(|(namespace_, feature_, _value)| namespace == namespace_ && feature == feature_) + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + self.iter().any(|(namespace_, feature_, value_)| { + namespace == namespace_ && feature == feature_ && value == value_ + }) + } +} + +// TODO(konsti): Use `AsRef` instead? +impl MarkerVariantsEnvironment for &T { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + T::contains_namespace(self, namespace) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + T::contains_feature(self, namespace, feature) + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + T::contains_property(self, namespace, feature, value) + } + + fn contains_base_namespace(&self, prefix: &str, namespace: &VariantNamespace) -> bool { + T::contains_base_namespace(self, prefix, namespace) + } + + fn contains_base_feature( + &self, + prefix: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + ) -> bool { + T::contains_base_feature(self, prefix, namespace, feature) + } + + fn contains_base_property( + &self, + prefix: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + T::contains_base_property(self, prefix, namespace, feature, value) + } +} + +/// A marker variants environment that always evaluates to `true`. +#[derive(Copy, Clone, Debug)] +pub struct MarkerVariantsUniversal; + +impl MarkerVariantsEnvironment for MarkerVariantsUniversal { + fn contains_namespace(&self, _namespace: &VariantNamespace) -> bool { + true + } + + fn contains_feature(&self, _namespace: &VariantNamespace, _feature: &VariantFeature) -> bool { + true + } + + fn contains_property( + &self, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + _value: &VariantValue, + ) -> bool { + true + } + + fn contains_base_namespace(&self, _prefix: &str, _namespace: &VariantNamespace) -> bool { + true + } + + fn contains_base_feature( + &self, + _prefix: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + ) -> bool { + true + } + + fn contains_base_property( + &self, + _prefix: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + _value: &VariantValue, + ) -> bool { + true + } + + fn label(&self) -> Option<&str> { + None + } + + fn is_universal(&self) -> bool { + true + } +} + /// Represents one or more nested marker expressions with and/or/parentheses. /// /// Marker trees are canonical, meaning any two functionally equivalent markers @@ -996,10 +1201,16 @@ impl MarkerTree { } /// Does this marker apply in the given environment? - pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { + pub fn evaluate( + self, + env: &MarkerEnvironment, + variants: &impl MarkerVariantsEnvironment, + extras: &[ExtraName], + ) -> bool { self.evaluate_reporter_impl( env, ExtrasEnvironment::from_extras(extras), + variants, &mut TracingReporter, ) } @@ -1010,12 +1221,14 @@ impl MarkerTree { pub fn evaluate_pep751( self, env: &MarkerEnvironment, + variants: &impl MarkerVariantsEnvironment, extras: &[ExtraName], groups: &[GroupName], ) -> bool { self.evaluate_reporter_impl( env, ExtrasEnvironment::from_pep751(extras, groups), + variants, &mut TracingReporter, ) } @@ -1030,6 +1243,7 @@ impl MarkerTree { pub fn evaluate_optional_environment( self, env: Option<&MarkerEnvironment>, + variants: &impl MarkerVariantsEnvironment, extras: &[ExtraName], ) -> bool { match env { @@ -1037,6 +1251,7 @@ impl MarkerTree { Some(env) => self.evaluate_reporter_impl( env, ExtrasEnvironment::from_extras(extras), + variants, &mut TracingReporter, ), } @@ -1048,15 +1263,22 @@ impl MarkerTree { self, env: &MarkerEnvironment, extras: &[ExtraName], + variants: &impl MarkerVariantsEnvironment, reporter: &mut impl Reporter, ) -> bool { - self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter) + self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_extras(extras), + variants, + reporter, + ) } fn evaluate_reporter_impl( self, env: &MarkerEnvironment, extras: ExtrasEnvironment, + variants: &impl MarkerVariantsEnvironment, reporter: &mut impl Reporter, ) -> bool { match self.kind() { @@ -1065,54 +1287,160 @@ impl MarkerTree { MarkerTreeKind::Version(marker) => { for (range, tree) in marker.edges() { if range.contains(env.get_version(marker.key())) { - return tree.evaluate_reporter_impl(env, extras, reporter); + return tree.evaluate_reporter_impl(env, extras, variants, reporter); } } } MarkerTreeKind::String(marker) => { - for (range, tree) in marker.children() { - let l_string = env.get_string(marker.key()); + if let Some(l_string) = env.get_string(marker.key()) { + for (range, tree) in marker.children() { + if range.as_singleton().is_none() { + if let Some((start, end)) = range.bounding_range() { + if let Bound::Included(value) | Bound::Excluded(value) = start { + reporter.report( + MarkerWarningKind::LexicographicComparison, + format!( + "Comparing {l_string} and {value} lexicographically" + ), + ); + } - if range.as_singleton().is_none() { - if let Some((start, end)) = range.bounding_range() { - if let Bound::Included(value) | Bound::Excluded(value) = start { - reporter.report( - MarkerWarningKind::LexicographicComparison, - format!("Comparing {l_string} and {value} lexicographically"), - ); - } - - if let Bound::Included(value) | Bound::Excluded(value) = end { - reporter.report( - MarkerWarningKind::LexicographicComparison, - format!("Comparing {l_string} and {value} lexicographically"), - ); + if let Bound::Included(value) | Bound::Excluded(value) = end { + reporter.report( + MarkerWarningKind::LexicographicComparison, + format!( + "Comparing {l_string} and {value} lexicographically" + ), + ); + } } } - } - if range.contains(l_string) { - return tree.evaluate_reporter_impl(env, extras, reporter); + if range.contains(l_string) { + return tree.evaluate_reporter_impl(env, extras, variants, reporter); + } + } + } else { + if variants.is_universal() { + return marker.children().any(|(_range, tree)| { + tree.evaluate_reporter_impl(env, extras, variants, reporter) + }); + } + // TODO(konsti): If we selected a non-variant wheel, what's the evaluation? + // "" is a placeholder for now. + let label = variants.label(); + let label = label.unwrap_or(""); + for (range, tree) in marker.children() { + if range.contains(label) { + return tree.evaluate_reporter_impl(env, extras, variants, reporter); + } } } } MarkerTreeKind::In(marker) => { - return marker - .edge(marker.value().contains(env.get_string(marker.key()))) - .evaluate_reporter_impl(env, extras, reporter); + return if let Some(l_string) = env.get_string(marker.key()) { + marker + .edge(marker.value().contains(l_string)) + .evaluate_reporter_impl(env, extras, variants, reporter) + } else { + if variants.is_universal() { + return marker.children().any(|(_range, tree)| { + tree.evaluate_reporter_impl(env, extras, variants, reporter) + }); + } + // TODO(konsti): If we selected a non-variant wheel, what's the evaluation? + if let Some(label) = variants.label() { + marker + .edge(marker.value().contains(label)) + .evaluate_reporter_impl(env, extras, variants, reporter) + } else { + marker + .edge(false) + .evaluate_reporter_impl(env, extras, variants, reporter) + } + }; } MarkerTreeKind::Contains(marker) => { - return marker - .edge(env.get_string(marker.key()).contains(marker.value())) - .evaluate_reporter_impl(env, extras, reporter); - } - MarkerTreeKind::Extra(marker) => { - return marker - .edge(extras.extra().contains(marker.name().extra())) - .evaluate_reporter_impl(env, extras, reporter); + return if let Some(l_string) = env.get_string(marker.key()) { + marker + .edge(l_string.contains(marker.value())) + .evaluate_reporter_impl(env, extras, variants, reporter) + } else { + if variants.is_universal() { + return marker.children().any(|(_range, tree)| { + tree.evaluate_reporter_impl(env, extras, variants, reporter) + }); + } + // TODO(konsti): If we selected a non-variant wheel, what's the evaluation? + if let Some(label) = variants.label() { + marker + .edge(label.contains(marker.value())) + .evaluate_reporter_impl(env, extras, variants, reporter) + } else { + marker + .edge(false) + .evaluate_reporter_impl(env, extras, variants, reporter) + } + }; } MarkerTreeKind::List(marker) => { let edge = match marker.pair() { + CanonicalMarkerListPair::VariantNamespaces { base, namespace } => { + if variants.is_universal() { + return marker + .edge(true) + .evaluate_reporter_impl(env, extras, variants, reporter) + || marker + .edge(false) + .evaluate_reporter_impl(env, extras, variants, reporter); + } + if let Some(base) = base { + variants.contains_base_namespace(base, namespace) + } else { + variants.contains_namespace(namespace) + } + } + CanonicalMarkerListPair::VariantFeatures { + base, + namespace, + feature, + } => { + if variants.is_universal() { + return marker + .edge(true) + .evaluate_reporter_impl(env, extras, variants, reporter) + || marker + .edge(false) + .evaluate_reporter_impl(env, extras, variants, reporter); + } + + if let Some(base) = base { + variants.contains_base_feature(base, namespace, feature) + } else { + variants.contains_feature(namespace, feature) + } + } + CanonicalMarkerListPair::VariantProperties { + base, + namespace, + feature, + value, + } => { + if variants.is_universal() { + return marker + .edge(true) + .evaluate_reporter_impl(env, extras, variants, reporter) + || marker + .edge(false) + .evaluate_reporter_impl(env, extras, variants, reporter); + } + + if let Some(base) = base { + variants.contains_base_property(base, namespace, feature, value) + } else { + variants.contains_property(namespace, feature, value) + } + } CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra), CanonicalMarkerListPair::DependencyGroup(dependency_group) => { extras.dependency_groups().contains(dependency_group) @@ -1123,7 +1451,12 @@ impl MarkerTree { return marker .edge(edge) - .evaluate_reporter_impl(env, extras, reporter); + .evaluate_reporter_impl(env, extras, variants, reporter); + } + MarkerTreeKind::Extra(marker) => { + return marker + .edge(extras.extra().contains(marker.name().extra())) + .evaluate_reporter_impl(env, extras, variants, reporter); } } @@ -1184,6 +1517,11 @@ impl MarkerTree { } } + /// Whether the marker requires variant information to be accurately evaluated + pub fn has_variant_expression(&self) -> bool { + self.kind().has_variant_expression() + } + /// Find a top level `extra == "..."` expression. /// /// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the @@ -1424,6 +1762,58 @@ impl MarkerTree { imp(self, &mut f); } + /// Ensure variant markers are globally identifiable by adding a prefix to all un-prefixed + /// variant markers. + /// + /// When using variant markers outside their original package, for example in the resolver forks + /// or in the lockfile, we need to attach the package of origin they need to be evaluated from. + #[must_use] + pub fn with_variant_base(self, base: &str) -> Self { + if !self.has_variant_expression() { + return self; + } + + Self(INTERNER.lock().edit_variable(self.0, &|var| match var { + Variable::List(CanonicalMarkerListPair::VariantNamespaces { + base: None, + namespace, + }) => Some(Variable::List(CanonicalMarkerListPair::VariantNamespaces { + base: Some(base.to_string()), + namespace: namespace.clone(), + })), + Variable::List(CanonicalMarkerListPair::VariantFeatures { + base: None, + namespace, + feature, + }) => Some(Variable::List(CanonicalMarkerListPair::VariantFeatures { + base: Some(base.to_string()), + namespace: namespace.clone(), + feature: feature.clone(), + })), + Variable::List(CanonicalMarkerListPair::VariantProperties { + base: None, + namespace, + feature, + value, + }) => Some(Variable::List(CanonicalMarkerListPair::VariantProperties { + base: Some(base.to_string()), + namespace: namespace.clone(), + feature: feature.clone(), + value: value.clone(), + })), + _ => None, + })) + } + + /// The base packages for variant markers, if any. + /// + /// Variant markers without a base package are ignored. + pub fn collect_variant_bases(&self) -> BTreeSet { + let mut bases = BTreeSet::new(); + INTERNER.lock().collect_variant_bases(self.0, &mut bases); + bases + } + fn simplify_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> Self { Self(INTERNER.lock().restrict(self.0, &|var| match var { Variable::Extra(name) => is_extra(name.extra()).then_some(true), @@ -1437,21 +1827,7 @@ impl MarkerTree { _ => None, })) } -} -impl fmt::Debug for MarkerTree { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.is_true() { - return write!(f, "true"); - } - if self.is_false() { - return write!(f, "false"); - } - write!(f, "{}", self.contents().unwrap()) - } -} - -impl MarkerTree { /// Formats a [`MarkerTree`] as a graph. /// /// This is useful for debugging when one wants to look at a @@ -1562,6 +1938,18 @@ impl MarkerTree { } } +impl fmt::Debug for MarkerTree { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.is_true() { + return write!(f, "true"); + } + if self.is_false() { + return write!(f, "false"); + } + write!(f, "{}", self.contents().unwrap()) + } +} + impl PartialOrd for MarkerTree { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -1631,6 +2019,23 @@ pub enum MarkerTreeKind<'a> { Extra(ExtraMarkerTree<'a>), } +impl MarkerTreeKind<'_> { + /// TODO(konsti) + pub fn has_variant_expression(&self) -> bool { + match self { + MarkerTreeKind::True | MarkerTreeKind::False => false, + Self::List(ListMarkerTree { + pair: + CanonicalMarkerListPair::VariantNamespaces { .. } + | CanonicalMarkerListPair::VariantFeatures { .. } + | CanonicalMarkerListPair::VariantProperties { .. }, + .. + }) => true, + _ => true, + } + } +} + /// A version marker node, such as `python_version < '3.7'`. #[derive(PartialEq, Eq, Clone, Debug)] pub struct VersionMarkerTree<'a> { @@ -1993,7 +2398,10 @@ mod test { use uv_pep440::Version; use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder}; - use crate::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString}; + use crate::{ + MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString, MarkerVariantsEnvironment, + MarkerVariantsUniversal, VariantFeature, VariantNamespace, VariantValue, + }; fn parse_err(input: &str) -> String { MarkerTree::from_str(input).unwrap_err().to_string() @@ -2020,6 +2428,57 @@ mod test { .unwrap() } + struct VariantEnv( + Vec<(VariantNamespace, VariantFeature, VariantValue)>, + String, + ); + + impl VariantEnv { + fn new(properties: &[(&str, &str, &str)], label: String) -> Self { + let properties = properties + .iter() + .map(|(namespace, feature, value)| { + ( + VariantNamespace::from_str(namespace).unwrap(), + VariantFeature::from_str(feature).unwrap(), + VariantValue::from_str(value).unwrap(), + ) + }) + .collect(); + Self(properties, label) + } + } + + #[cfg(test)] + impl MarkerVariantsEnvironment for VariantEnv { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + self.0 + .iter() + .any(|(namespace_, _feature, _value)| namespace == namespace_) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + self.0.iter().any(|(namespace_, feature_, _value)| { + namespace == namespace_ && feature == feature_ + }) + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + self.0.iter().any(|(namespace_, feature_, value_)| { + namespace == namespace_ && feature == feature_ && value == value_ + }) + } + + fn label(&self) -> Option<&str> { + Some(&self.1) + } + } + /// Copied from #[test] fn test_marker_equivalence() { @@ -2150,12 +2609,12 @@ mod test { let marker3 = MarkerTree::from_str( "python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")", ).unwrap(); - assert!(marker1.evaluate(&env27, &[])); - assert!(!marker1.evaluate(&env37, &[])); - assert!(marker2.evaluate(&env27, &[])); - assert!(marker2.evaluate(&env37, &[])); - assert!(marker3.evaluate(&env27, &[])); - assert!(!marker3.evaluate(&env37, &[])); + assert!(marker1.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker1.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker2.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker2.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker3.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker3.evaluate(&env37, &MarkerVariantsUniversal, &[])); } #[test] @@ -2177,48 +2636,48 @@ mod test { let env37 = env37(); let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap(); - assert!(!marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(!marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap(); - assert!(!marker.evaluate(&env27, &[])); - assert!(marker.evaluate(&env37, &[])); + assert!(!marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap(); - assert!(!marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(!marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap(); - assert!(!marker.evaluate(&env27, &[])); - assert!(!marker.evaluate(&env37, &[])); + assert!(!marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap(); - assert!(marker.evaluate(&env27, &[])); - assert!(marker.evaluate(&env37, &[])); + assert!(marker.evaluate(&env27, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); } #[test] @@ -2227,7 +2686,7 @@ mod test { fn warnings1() { let env37 = env37(); let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap(); - compare_keys.evaluate(&env37, &[]); + compare_keys.evaluate(&env37, &MarkerVariantsUniversal, &[]); logs_contain( "Comparing two markers with each other doesn't make any sense, will evaluate to false", ); @@ -2239,7 +2698,7 @@ mod test { fn warnings2() { let env37 = env37(); let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap(); - non_pep440.evaluate(&env37, &[]); + non_pep440.evaluate(&env37, &MarkerVariantsUniversal, &[]); logs_contain( "Expected PEP 440 version to compare with python_version, found `3.9.`, \ will evaluate to false: after parsing `3.9`, found `.`, which is \ @@ -2253,7 +2712,7 @@ mod test { fn warnings3() { let env37 = env37(); let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap(); - string_string.evaluate(&env37, &[]); + string_string.evaluate(&env37, &MarkerVariantsUniversal, &[]); logs_contain( "Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false", ); @@ -2265,7 +2724,7 @@ mod test { fn warnings4() { let env37 = env37(); let string_string = MarkerTree::from_str(r"os.name == 'posix' and platform.machine == 'x86_64' and platform.python_implementation == 'CPython' and 'Ubuntu' in platform.version and sys.platform == 'linux'").unwrap(); - string_string.evaluate(&env37, &[]); + string_string.evaluate(&env37, &MarkerVariantsUniversal, &[]); logs_assert(|lines: &[&str]| { let lines: Vec<_> = lines .iter() @@ -2297,18 +2756,18 @@ mod test { let env37 = env37(); let result = MarkerTree::from_str("python_version > '3.6'") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(result); let result = MarkerTree::from_str("'3.6' > python_version") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(!result); // Meaningless expressions are ignored, so this is always true. let result = MarkerTree::from_str("'3.*' == python_version") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(result); } @@ -2317,12 +2776,12 @@ mod test { let env37 = env37(); let result = MarkerTree::from_str("'nux' in sys_platform") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(result); let result = MarkerTree::from_str("sys_platform in 'nux'") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(!result); } @@ -2331,7 +2790,7 @@ mod test { let env37 = env37(); let result = MarkerTree::from_str("python_version == '3.7.*'") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(result); } @@ -2340,7 +2799,7 @@ mod test { let env37 = env37(); let result = MarkerTree::from_str("python_version ~= '3.7'") .unwrap() - .evaluate(&env37, &[]); + .evaluate(&env37, &MarkerVariantsUniversal, &[]); assert!(result); } @@ -3709,4 +4168,189 @@ mod test { assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b))); assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()])); } + + #[test] + fn marker_evaluation_variants() { + let env37 = env37(); + let gpu_namespaces = VariantEnv::new(&[("gpu", "cuda", "12.4")], String::new()); + let cpu_namespaces = VariantEnv::new(&[("cpu", "", "")], String::new()); + + // namespace variant markers + let marker1 = m("'gpu' in variant_namespaces"); + let marker2 = m("'gpu' not in variant_namespaces"); + + // If no variants are provided, we solve universally. + assert!(marker1.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker2.evaluate(&env37, &MarkerVariantsUniversal, &[])); + + assert!(marker1.evaluate(&env37, &gpu_namespaces, &[])); + assert!(!marker1.evaluate(&env37, &cpu_namespaces, &[])); + assert!(!marker2.evaluate(&env37, &gpu_namespaces, &[])); + assert!(marker2.evaluate(&env37, &cpu_namespaces, &[])); + + // property variant markers + let marker3 = m("'gpu :: cuda' in variant_features"); + let marker4 = m("'gpu :: rocm' in variant_features"); + + assert!(marker3.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker4.evaluate(&env37, &MarkerVariantsUniversal, &[])); + + assert!(marker3.evaluate(&env37, &gpu_namespaces, &[])); + assert!(!marker3.evaluate(&env37, &cpu_namespaces, &[])); + assert!(!marker4.evaluate(&env37, &gpu_namespaces, &[])); + assert!(!marker4.evaluate(&env37, &cpu_namespaces, &[])); + + // feature variant markers + let marker5 = m("'gpu :: cuda :: 12.4' in variant_properties"); + let marker6 = m("'gpu :: cuda :: 12.8' in variant_properties"); + + assert!(marker5.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker6.evaluate(&env37, &MarkerVariantsUniversal, &[])); + + assert!(marker5.evaluate(&env37, &gpu_namespaces, &[])); + assert!(!marker5.evaluate(&env37, &cpu_namespaces, &[])); + assert!(!marker6.evaluate(&env37, &gpu_namespaces, &[])); + assert!(!marker6.evaluate(&env37, &cpu_namespaces, &[])); + } + + #[test] + fn marker_evaluation_variants_combined() { + let env37 = env37(); + let namespaces = VariantEnv::new( + &[ + ("gpu", "cuda", "12.4"), + ("gpu", "cuda", "12.6"), + ("cpu", "x86_64", "v1"), + ("cpu", "x86_64", "v2"), + ("cpu", "x86_64", "v3"), + ], + String::new(), + ); + + let marker1 = m("'gpu' in variant_namespaces \ + and 'cpu:: x86_64 :: v3' in variant_properties \ + and python_version >= '3.7' \ + and 'gpu :: rocm' not in variant_features"); + assert!(marker1.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker1.evaluate(&env37, &namespaces, &[])); + + let marker2 = m("python_version >= '3.7' and 'gpu' not in variant_namespaces"); + assert!(marker2.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(!marker2.evaluate(&env37, &namespaces, &[])); + } + + #[test] + fn variant_to_string() { + let assert_roundtrips = |marker| { + assert_eq!(m(marker).try_to_string().unwrap(), marker); + }; + assert_roundtrips("'gpu' in variant_namespaces"); + assert_roundtrips("'gpu' not in variant_namespaces"); + assert_roundtrips("'gpu :: cuda' in variant_features"); + assert_roundtrips("'gpu :: cuda' not in variant_features"); + assert_roundtrips("'gpu :: cuda :: 12.4' in variant_properties"); + assert_roundtrips("'gpu :: cuda :: 12.8' not in variant_properties"); + + // TODO(konsti): Implement normalization and test it. + } + + #[test] + fn variant_errors() { + let err = MarkerExpression::from_str(r"variant_namespaces in 'gpu'") + .unwrap_err() + .to_string(); + assert_snapshot!( + err, + @r" + The marker variant_namespaces must be on the right hand side of the expression + variant_namespaces in 'gpu' + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + " + ); + let err = MarkerExpression::from_str(r"'gpu :: cuda' == variant_properties") + .unwrap_err() + .to_string(); + assert_snapshot!( + err, + @r" + The operator == is not supported with the marker variant_properties, only the `in` and `not in` operators are supported + 'gpu :: cuda' == variant_properties + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + " + ); + // TODO(konsti): Test all cases systematically + } + + #[test] + fn variant_invalid() { + let env37 = env37(); + let namespaces = VariantEnv::new(&[("gpu", "cuda", "cuda126")], String::new()); + + let marker = m(r"'gpu :: cuda :: cuda126 :: gtx1080' in variant_properties"); + assert!(!marker.evaluate(&env37, &namespaces, &[])); + } + + #[test] + fn torch_variant_marker() { + let env37 = env37(); + let cu126 = VariantEnv::new(&[("nvidia", "ctk", "12.6")], String::new()); + let cu126_2 = VariantEnv::new( + &[ + ("nvidia", "ctk", "12.6"), + ("nvidia", "cuda_version", ">=12.6,<13"), + ], + String::new(), + ); + let cu128 = VariantEnv::new(&[("nvidia", "ctk", "12.8")], String::new()); + + let marker = m( + "platform_machine == 'x86_64' and sys_platform == 'linux' and 'nvidia :: ctk :: 12.6' in variant_properties", + ); + + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); + assert!(marker.evaluate(&env37, &cu126, &[])); + assert!(marker.evaluate(&env37, &cu126_2, &[])); + assert!(!marker.evaluate(&env37, &cu128, &[])); + } + + #[test] + fn base_variant_marker() { + let env37 = env37(); + let cu128 = VariantEnv::new(&[("nvidia", "ctk", "12.8")], String::new()); + + let marker = m( + "platform_machine == 'x86_64' and sys_platform == 'linux' and 'nvidia :: ctk :: 12.8' in variant_properties", + ); + let marker_base = marker.with_variant_base("torch 2.8.0"); + + assert!(marker.evaluate(&env37, &cu128, &[])); + assert!(!marker_base.evaluate(&env37, &cu128, &[])); + + let marker = m( + "platform_machine == 'x86_64' and sys_platform == 'linux' and 'torch 2.8.0 | nvidia :: ctk :: 12.8' in variant_properties", + ); + + assert!(!marker.evaluate(&env37, &cu128, &[])); + } + + #[test] + fn variant_label() { + let env37 = env37(); + let foo = VariantEnv::new(&[], "foo".to_string()); + + let marker = m("sys_platform == 'linux' and 'foo' == variant_label"); + assert!(marker.evaluate(&env37, &foo, &[])); + let marker = m("sys_platform == 'linux' and 'foo' != variant_label"); + assert!(!marker.evaluate(&env37, &foo, &[])); + + let marker = m("sys_platform == 'linux' and 'f' in variant_label"); + assert!(marker.evaluate(&env37, &foo, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); + let marker = m("sys_platform == 'linux' and 'f' not in variant_label"); + assert!(!marker.evaluate(&env37, &foo, &[])); + assert!(marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); + let marker = m("sys_platform != 'linux' and 'f' in variant_label"); + assert!(!marker.evaluate(&env37, &foo, &[])); + assert!(!marker.evaluate(&env37, &MarkerVariantsUniversal, &[])); + } } diff --git a/crates/uv-pep508/src/marker/variants.rs b/crates/uv-pep508/src/marker/variants.rs new file mode 100644 index 000000000..b31b29056 --- /dev/null +++ b/crates/uv-pep508/src/marker/variants.rs @@ -0,0 +1,178 @@ +use std::fmt::Display; +use std::str::FromStr; + +use serde::{Deserialize, Deserializer}; +use thiserror::Error; + +// The parser and errors are also used for parsing JSON files, so we're keeping the error message +// generic without references to markers. +/// A segment of a variant uses invalid characters. +#[derive(Error, Debug)] +pub enum VariantParseError { + /// The namespace segment of a variant failed to parse. + #[error( + "Invalid character `{invalid}` in variant namespace, only [a-z0-9_] are allowed: {input}" + )] + Namespace { + /// The character outside the allowed character range. + invalid: char, + /// The invalid input string. + input: String, + }, + #[error( + "Invalid character `{invalid}` in variant feature, only [a-z0-9_] are allowed: {input}" + )] + /// The feature segment of a variant failed to parse. + Feature { + /// The character outside the allowed character range. + invalid: char, + /// The invalid input string. + input: String, + }, + #[error( + "Invalid character `{invalid}` in variant value, only [a-z0-9_.,!>~<=] are allowed: {input}" + )] + /// The value segment of a variant failed to parse. + Value { + /// The character outside the allowed character range. + invalid: char, + /// The invalid input string. + input: String, + }, +} + +/// The namespace segment in a variant. +/// +/// Variant properties have the structure `namespace :: feature ::value`. +/// +/// The segment is canonicalized by trimming it. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct VariantNamespace(String); + +impl FromStr for VariantNamespace { + type Err = VariantParseError; + + fn from_str(input: &str) -> Result { + let input = input.trim(); + if let Some(invalid) = input + .chars() + .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || *c == '_')) + { + return Err(VariantParseError::Namespace { + invalid, + input: input.to_string(), + }); + } + + Ok(Self(input.to_string())) + } +} + +impl<'de> Deserialize<'de> for VariantNamespace { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Display for VariantNamespace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +/// The feature segment in a variant. +/// +/// Variant properties have the structure `namespace :: feature ::value`. +/// +/// The segment is canonicalized by trimming it. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct VariantFeature(String); + +impl FromStr for VariantFeature { + type Err = VariantParseError; + + fn from_str(input: &str) -> Result { + let input = input.trim(); + if let Some(invalid) = input + .chars() + .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || *c == '_')) + { + return Err(VariantParseError::Feature { + invalid, + input: input.to_string(), + }); + } + + Ok(Self(input.to_string())) + } +} + +impl<'de> Deserialize<'de> for VariantFeature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Display for VariantFeature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +/// The value segment in a variant. +/// +/// Variant properties have the structure `namespace :: feature ::value`. +/// +/// The segment is canonicalized by trimming it. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct VariantValue(String); + +impl FromStr for VariantValue { + type Err = VariantParseError; + + fn from_str(input: &str) -> Result { + let input = input.trim(); + if let Some(invalid) = input.chars().find(|c| { + !(c.is_ascii_lowercase() + || c.is_ascii_digit() + || matches!(*c, '_' | '.' | ',' | '!' | '>' | '~' | '<' | '=')) + }) { + return Err(VariantParseError::Value { + invalid, + input: input.to_string(), + }); + } + + Ok(Self(input.to_string())) + } +} + +impl<'de> Deserialize<'de> for VariantValue { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Display for VariantValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} diff --git a/crates/uv-pep508/src/unnamed.rs b/crates/uv-pep508/src/unnamed.rs index 9dbd854de..763c522d6 100644 --- a/crates/uv-pep508/src/unnamed.rs +++ b/crates/uv-pep508/src/unnamed.rs @@ -8,9 +8,10 @@ use uv_normalize::ExtraName; use crate::marker::parse; use crate::{ - Cursor, MarkerEnvironment, MarkerTree, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, - RequirementOrigin, Scheme, TracingReporter, VerbatimUrl, VerbatimUrlError, expand_env_vars, - parse_extras_cursor, split_extras, split_scheme, strip_host, + Cursor, MarkerEnvironment, MarkerTree, MarkerVariantsEnvironment, Pep508Error, + Pep508ErrorSource, Pep508Url, Reporter, RequirementOrigin, Scheme, TracingReporter, + VerbatimUrl, VerbatimUrlError, expand_env_vars, parse_extras_cursor, split_extras, + split_scheme, strip_host, }; /// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths. @@ -82,17 +83,24 @@ pub struct UnnamedRequirement { impl UnnamedRequirement { /// Returns whether the markers apply for the given environment - pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { - self.evaluate_optional_environment(Some(env), extras) + pub fn evaluate_markers( + &self, + env: &MarkerEnvironment, + variants: &impl MarkerVariantsEnvironment, + extras: &[ExtraName], + ) -> bool { + self.evaluate_optional_environment(Some(env), variants, extras) } /// Returns whether the markers apply for the given environment pub fn evaluate_optional_environment( &self, env: Option<&MarkerEnvironment>, + variants: &impl MarkerVariantsEnvironment, extras: &[ExtraName], ) -> bool { - self.marker.evaluate_optional_environment(env, extras) + self.marker + .evaluate_optional_environment(env, variants, extras) } /// Set the source file containing the requirement. diff --git a/crates/uv-pypi-types/Cargo.toml b/crates/uv-pypi-types/Cargo.toml index 439d7a344..0ab892c5f 100644 --- a/crates/uv-pypi-types/Cargo.toml +++ b/crates/uv-pypi-types/Cargo.toml @@ -27,6 +27,7 @@ uv-small-str = { workspace = true } hashbrown = { workspace = true } indexmap = { workspace = true, features = ["serde"] } +indoc = { workspace = true } itertools = { workspace = true } jiff = { workspace = true, features = ["serde"] } mailparse = { workspace = true } diff --git a/crates/uv-pypi-types/src/lib.rs b/crates/uv-pypi-types/src/lib.rs index 06a9b50db..c00a66b80 100644 --- a/crates/uv-pypi-types/src/lib.rs +++ b/crates/uv-pypi-types/src/lib.rs @@ -10,6 +10,7 @@ pub use parsed_url::*; pub use scheme::*; pub use simple_json::*; pub use supported_environments::*; +pub use variants::*; mod base_url; mod conflicts; @@ -23,3 +24,4 @@ mod parsed_url; mod scheme; mod simple_json; mod supported_environments; +mod variants; diff --git a/crates/uv-pypi-types/src/variants.rs b/crates/uv-pypi-types/src/variants.rs new file mode 100644 index 000000000..a806c3340 --- /dev/null +++ b/crates/uv-pypi-types/src/variants.rs @@ -0,0 +1,31 @@ +use indoc::formatdoc; + +use crate::VerbatimParsedUrl; + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct VariantProviderBackend { + /// The provider backend string such as `fictional_tech.provider`. + pub backend: String, + /// The requirements that the backend requires (e.g., `["fictional_tech>=1.0"]`). + pub requires: Vec>, +} + +impl VariantProviderBackend { + pub fn import(&self) -> String { + let import = if let Some((path, object)) = self.backend.split_once(':') { + format!("from {path} import {object} as backend") + } else { + format!("import {} as backend", self.backend) + }; + + formatdoc! {r#" + import sys + + if sys.path[0] == "": + sys.path.pop(0) + + {import} + "#} + } +} diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index 44136bdcd..96beded67 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -8,6 +8,7 @@ use tracing::trace; use uv_configuration::{Constraints, Overrides}; use uv_distribution::{DistributionDatabase, Reporter}; use uv_distribution_types::{Dist, DistributionMetadata, Requirement, RequirementSource}; +use uv_pep508::MarkerVariantsUniversal; use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverEnvironment}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; @@ -91,7 +92,13 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { let mut queue: VecDeque<_> = self .constraints .apply(self.overrides.apply(self.requirements)) - .filter(|requirement| requirement.evaluate_markers(env.marker_environment(), &[])) + .filter(|requirement| { + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) + }) .map(|requirement| (*requirement).clone()) .collect(); @@ -110,9 +117,11 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { .constraints .apply(self.overrides.apply(lookahead.requirements())) { - if requirement - .evaluate_markers(env.marker_environment(), lookahead.extras()) - { + if requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + lookahead.extras(), + ) { queue.push_back((*requirement).clone()); } } diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index 3ec46f43d..8ac6b9e40 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -42,6 +42,7 @@ uv-small-str = { workspace = true } uv-static = { workspace = true } uv-torch = { workspace = true } uv-types = { workspace = true } +uv-variants = { workspace = true } uv-version = { workspace = true } uv-warnings = { workspace = true } uv-workspace = { workspace = true } diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index 6f92a3f6b..608b6af5f 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -1,9 +1,8 @@ -use std::fmt::{Display, Formatter}; - use either::Either; use itertools::Itertools; use pubgrub::Range; use smallvec::SmallVec; +use std::fmt::{Display, Formatter}; use tracing::{debug, trace}; use uv_configuration::IndexStrategy; @@ -653,9 +652,9 @@ impl CandidateDist<'_> { } } -impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> { - fn from(value: &'a PrioritizedDist) -> Self { - if let Some(dist) = value.get() { +impl<'a> CandidateDist<'a> { + fn from_prioritized_dist(value: &'a PrioritizedDist, allow_all_variants: bool) -> Self { + if let Some(dist) = value.get(allow_all_variants) { CandidateDist::Compatible(dist) } else { // TODO(zanieb) @@ -664,7 +663,7 @@ impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> { // why neither distribution kind can be used. let dist = if let Some(incompatibility) = value.incompatible_source() { IncompatibleDist::Source(incompatibility.clone()) - } else if let Some(incompatibility) = value.incompatible_wheel() { + } else if let Some(incompatibility) = value.incompatible_wheel(allow_all_variants) { IncompatibleDist::Wheel(incompatibility.clone()) } else { IncompatibleDist::Unavailable @@ -721,11 +720,42 @@ impl<'a> Candidate<'a> { Self { name, version, - dist: CandidateDist::from(dist), + dist: CandidateDist::from_prioritized_dist(dist, false), choice_kind, } } + /// By default, variant wheels are considered incompatible. During universal resolutions, + /// variant wheels should be allowed, similar to any other wheel that is only tag-incompatible + /// to the current platform. + pub(crate) fn allow_variant_wheels(self) -> Self { + // Optimization: Only if the current candidate is incompatible for being a variant, it can + // change if we allow variants. + let CandidateDist::Incompatible { + incompatible_dist: IncompatibleDist::Wheel(IncompatibleWheel::Variant), + prioritized_dist, + } = self.dist + else { + return self; + }; + + Self { + dist: CandidateDist::from_prioritized_dist(prioritized_dist, true), + ..self + } + } + + // TODO(konsti): Stop breaking isolation? + pub(crate) fn prioritize_best_variant_wheel( + self, + prioritized_dist: &'a PrioritizedDist, + ) -> Self { + Self { + dist: CandidateDist::from_prioritized_dist(prioritized_dist, true), + ..self + } + } + /// Return the name of the package. pub(crate) fn name(&self) -> &PackageName { self.name diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 8a348eafb..5571fbce2 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -16,7 +16,9 @@ use uv_distribution_types::{ }; use uv_normalize::{ExtraName, InvalidNameError, PackageName}; use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier}; -use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion}; +use uv_pep508::{ + MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion, MarkerVariantsUniversal, +}; use uv_platform_tags::Tags; use uv_pypi_types::ParsedUrl; use uv_redacted::DisplaySafeUrl; @@ -45,6 +47,9 @@ pub enum ResolveError { DerivationChain, ), + #[error(transparent)] + VariantFrontend(uv_distribution::Error), + #[error(transparent)] Client(#[from] uv_client::Error), @@ -409,7 +414,7 @@ impl NoSolutionError { ":".bold(), current_python_version, )?; - } else if !markers.evaluate(&self.current_environment, &[]) { + } else if !markers.evaluate(&self.current_environment, &MarkerVariantsUniversal, &[]) { write!( f, "\n\n{}{} The resolution failed for an environment that is not the current one, \ diff --git a/crates/uv-resolver/src/flat_index.rs b/crates/uv-resolver/src/flat_index.rs index 46da6ddbf..b5b104a94 100644 --- a/crates/uv-resolver/src/flat_index.rs +++ b/crates/uv-resolver/src/flat_index.rs @@ -1,22 +1,23 @@ -use std::collections::BTreeMap; -use std::collections::btree_map::Entry; - use rustc_hash::FxHashMap; use tracing::instrument; +use std::collections::BTreeMap; +use std::collections::btree_map::Entry; + use uv_client::{FlatIndexEntries, FlatIndexEntry}; use uv_configuration::BuildOptions; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ - File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexUrl, - PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility, - WheelCompatibility, + File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexEntryFilename, + IndexUrl, PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, RegistryVariantsJson, + SourceDistCompatibility, WheelCompatibility, }; use uv_normalize::PackageName; use uv_pep440::Version; use uv_platform_tags::{TagCompatibility, Tags}; use uv_pypi_types::HashDigest; use uv_types::HashStrategy; +use uv_variants::VariantPriority; /// A set of [`PrioritizedDist`] from a `--find-links` entry, indexed by [`PackageName`] /// and [`Version`]. @@ -112,7 +113,7 @@ impl FlatDistributions { fn add_file( &mut self, file: File, - filename: DistFilename, + filename: IndexEntryFilename, tags: Option<&Tags>, hasher: &HashStrategy, build_options: &BuildOptions, @@ -121,7 +122,7 @@ impl FlatDistributions { // No `requires-python` here: for source distributions, we don't have that information; // for wheels, we read it lazily only when selected. match filename { - DistFilename::WheelFilename(filename) => { + IndexEntryFilename::DistFilename(DistFilename::WheelFilename(filename)) => { let version = filename.version.clone(); let compatibility = Self::wheel_compatibility( @@ -145,7 +146,7 @@ impl FlatDistributions { } } } - DistFilename::SourceDistFilename(filename) => { + IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(filename)) => { let compatibility = Self::source_dist_compatibility( &filename, file.hashes.as_slice(), @@ -169,6 +170,22 @@ impl FlatDistributions { } } } + IndexEntryFilename::VariantJson(variants_json) => { + let version = variants_json.version.clone(); + let registry_variants_json = RegistryVariantsJson { + filename: variants_json, + file: Box::new(file), + index, + }; + match self.0.entry(version) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert_variant_json(registry_variants_json); + } + Entry::Vacant(entry) => { + entry.insert(PrioritizedDist::from_variant_json(registry_variants_json)); + } + } + } } } @@ -214,7 +231,7 @@ impl FlatDistributions { } // Determine a compatibility for the wheel based on tags. - let priority = match tags { + let tag_priority = match tags { Some(tags) => match filename.compatibility(tags) { TagCompatibility::Incompatible(tag) => { return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)); @@ -224,6 +241,13 @@ impl FlatDistributions { None => None, }; + // TODO(konsti): Currently we ignore variants here on only determine them later + let variant_priority = if filename.variant().is_none() { + VariantPriority::NonVariant + } else { + VariantPriority::Unknown + }; + // Check if hashes line up. let hash = if let HashPolicy::Validate(required) = hasher.get_package(&filename.name, &filename.version) @@ -242,7 +266,12 @@ impl FlatDistributions { // Break ties with the build tag. let build_tag = filename.build_tag().cloned(); - WheelCompatibility::Compatible(hash, priority, build_tag) + WheelCompatibility::Compatible { + hash, + variant_priority, + tag_priority, + build_tag, + } } } diff --git a/crates/uv-resolver/src/graph_ops.rs b/crates/uv-resolver/src/graph_ops.rs index 81640148b..72d1fa290 100644 --- a/crates/uv-resolver/src/graph_ops.rs +++ b/crates/uv-resolver/src/graph_ops.rs @@ -254,7 +254,6 @@ pub(crate) fn simplify_conflict_markers( // For example, when `a` and `b` conflict, this marker does not simplify: // ``` // (platform_machine == 'x86_64' and extra == 'extra-5-foo-b') or extra == 'extra-5-foo-a' - // ```` graph[edge_index].evaluate_only_extras(&extras, &groups) }); if all_paths_satisfied { diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 901f8366a..0bc52abcb 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -23,7 +23,7 @@ pub use resolution_mode::ResolutionMode; pub use resolver::{ BuildId, DefaultResolverProvider, DerivationChainBuilder, InMemoryIndex, MetadataResponse, PackageVersionsResult, Reporter as ResolverReporter, Resolver, ResolverEnvironment, - ResolverProvider, VersionsResponse, WheelMetadataResult, + ResolverProvider, VariantProviderResult, VersionsResponse, WheelMetadataResult, }; pub use universal_marker::{ConflictMarker, UniversalMarker}; pub use version_map::VersionMap; diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 394261587..3287e4b08 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -31,7 +31,7 @@ use uv_git::{RepositoryReference, ResolvedRepositoryReference}; use uv_git_types::{GitLfs, GitOid, GitReference, GitUrl, GitUrlParseError}; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::Version; -use uv_pep508::{MarkerEnvironment, MarkerTree, VerbatimUrl}; +use uv_pep508::{MarkerEnvironment, MarkerTree, MarkerVariantsUniversal, VerbatimUrl}; use uv_platform_tags::{TagCompatibility, TagPriority, Tags}; use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind}; use uv_redacted::DisplaySafeUrl; @@ -365,7 +365,7 @@ impl<'lock> PylockToml { if !node.is_base() { continue; } - let ResolvedDist::Installable { dist, version } = &node.dist else { + let ResolvedDist::Installable { dist, version, .. } = &node.dist else { continue; }; if omit.contains(dist.name()) { @@ -981,7 +981,10 @@ impl<'lock> PylockToml { for package in self.packages { // Omit packages that aren't relevant to the current environment. - if !package.marker.evaluate_pep751(markers, extras, groups) { + if !package + .marker + .evaluate_pep751(markers, &MarkerVariantsUniversal, extras, groups) + { continue; } @@ -1060,6 +1063,7 @@ impl<'lock> PylockToml { })); let dist = ResolvedDist::Installable { dist: Arc::new(built_dist), + variants_json: None, version: package.version, }; Node::Dist { @@ -1077,6 +1081,7 @@ impl<'lock> PylockToml { )?)); let dist = ResolvedDist::Installable { dist: Arc::new(sdist), + variants_json: None, version: package.version, }; Node::Dist { @@ -1091,6 +1096,7 @@ impl<'lock> PylockToml { )); let dist = ResolvedDist::Installable { dist: Arc::new(sdist), + variants_json: None, version: package.version, }; Node::Dist { @@ -1105,6 +1111,7 @@ impl<'lock> PylockToml { )); let dist = ResolvedDist::Installable { dist: Arc::new(sdist), + variants_json: None, version: package.version, }; Node::Dist { @@ -1121,6 +1128,7 @@ impl<'lock> PylockToml { let dist = dist.to_dist(install_path, &package.name, package.version.as_ref())?; let dist = ResolvedDist::Installable { dist: Arc::new(dist), + variants_json: None, version: package.version, }; Node::Dist { diff --git a/crates/uv-resolver/src/lock/installable.rs b/crates/uv-resolver/src/lock/installable.rs index b6d93904f..3195e7d71 100644 --- a/crates/uv-resolver/src/lock/installable.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -5,16 +5,24 @@ use std::path::Path; use std::sync::Arc; use either::Either; +use hashbrown::HashMap; use itertools::Itertools; use petgraph::Graph; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use uv_configuration::ExtrasSpecificationWithDefaults; use uv_configuration::{BuildOptions, DependencyGroupsWithDefaults, InstallOptions}; +use uv_distribution::{DistributionDatabase, PackageVariantCache}; use uv_distribution_types::{Edge, Node, Resolution, ResolvedDist}; use uv_normalize::{ExtraName, GroupName, PackageName}; +use uv_pep508::{ + MarkerVariantsEnvironment, MarkerVariantsUniversal, VariantFeature, VariantNamespace, + VariantValue, +}; use uv_platform_tags::Tags; use uv_pypi_types::ResolverMarkerEnvironment; +use uv_types::BuildContext; +use uv_variants::variant_with_label::VariantWithLabel; use crate::lock::{LockErrorKind, Package, TagPolicy}; use crate::{Lock, LockError}; @@ -33,7 +41,8 @@ pub trait Installable<'lock> { fn project_name(&self) -> Option<&PackageName>; /// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root. - fn to_resolution( + #[allow(async_fn_in_trait)] + async fn to_resolution( &self, marker_env: &ResolverMarkerEnvironment, tags: &Tags, @@ -41,6 +50,8 @@ pub trait Installable<'lock> { groups: &DependencyGroupsWithDefaults, build_options: &BuildOptions, install_options: &InstallOptions, + distribution_database: DistributionDatabase<'_, Context>, + variants_cache: &PackageVariantCache, ) -> Result { let size_guess = self.lock().packages.len(); let mut petgraph = Graph::with_capacity(size_guess, size_guess); @@ -52,6 +63,8 @@ pub trait Installable<'lock> { let mut activated_extras: Vec<(&PackageName, &ExtraName)> = vec![]; let mut activated_groups: Vec<(&PackageName, &GroupName)> = vec![]; + let mut resolved_variants = QueriedVariants::default(); + let root = petgraph.add_node(Node::Root); // Determine the set of activated extras and groups, from the root. @@ -143,8 +156,10 @@ pub trait Installable<'lock> { }) .flatten() { + // TODO(konsti): Evaluate variant declarations on workspace/path dependencies. if !dep.complexified_marker.evaluate( marker_env, + &MarkerVariantsUniversal, activated_projects.iter().copied(), activated_extras.iter().copied(), activated_groups.iter().copied(), @@ -211,7 +226,11 @@ pub trait Installable<'lock> { // Add any requirements that are exclusive to the workspace root (e.g., dependencies in // PEP 723 scripts). for dependency in self.lock().requirements() { - if !dependency.marker.evaluate(marker_env, &[]) { + if !dependency + .marker + // No package, evaluate markers to false. + .evaluate(marker_env, &Vec::new().as_slice(), &[]) + { continue; } @@ -263,11 +282,16 @@ pub trait Installable<'lock> { }) .flatten() { - if !dependency.marker.evaluate(marker_env, &[]) { + // TODO(konsti): Evaluate markers for the current package + if !dependency + .marker + .evaluate(marker_env, &MarkerVariantsUniversal, &[]) + { continue; } let root_name = &dependency.name; + // TODO(konsti): Evaluate variant declarations on workspace/path dependencies. let dist = self .lock() .find_by_markers(root_name, marker_env) @@ -377,8 +401,10 @@ pub trait Installable<'lock> { additional_activated_extras.push(key); } } + // TODO(konsti): Evaluate variants if !dep.complexified_marker.evaluate( marker_env, + &MarkerVariantsUniversal, activated_projects.iter().copied(), activated_extras .iter() @@ -464,9 +490,40 @@ pub trait Installable<'lock> { } else { Either::Right(package.dependencies.iter()) }; + + let variant_base = format!( + "{}=={}", + package.id.name, + package + .version() + .map(ToString::to_string) + .unwrap_or("TODO(konsti)".to_string()) + ); + if !resolved_variants + .0 + .contains_key(&package.name().to_string()) + { + let variant_properties = determine_properties( + package, + self.install_path(), + marker_env, + &distribution_database, + variants_cache, + ) + .await?; + + resolved_variants + .0 + .insert(variant_base.clone(), variant_properties); + } + for dep in deps { if !dep.complexified_marker.evaluate( marker_env, + &CurrentQueriedVariants { + global: &resolved_variants, + current: resolved_variants.0.get(&variant_base).unwrap(), + }, activated_projects.iter().copied(), activated_extras.iter().copied(), activated_groups.iter().copied(), @@ -534,8 +591,10 @@ pub trait Installable<'lock> { marker_env, )?; let version = package.version().cloned(); + let variants_json = package.to_registry_variants_json(self.install_path())?; let dist = ResolvedDist::Installable { dist: Arc::new(dist), + variants_json: variants_json.map(Arc::new), version, }; let hashes = package.hashes(); @@ -562,6 +621,8 @@ pub trait Installable<'lock> { let version = package.version().cloned(); let dist = ResolvedDist::Installable { dist: Arc::new(dist), + // No need to determine variants for something we don't install. + variants_json: None, version, }; let hashes = package.hashes(); @@ -592,3 +653,140 @@ pub trait Installable<'lock> { } } } + +/// Map for the package identifier to the package's variants for marker evaluation. +#[derive(Default, Debug)] +struct QueriedVariants(HashMap); + +/// Variants for markers evaluation both for the current package (without base) and globally (with +/// base). +#[derive(Copy, Clone, Debug)] +struct CurrentQueriedVariants<'a> { + current: &'a VariantWithLabel, + global: &'a QueriedVariants, +} + +impl MarkerVariantsEnvironment for CurrentQueriedVariants<'_> { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + self.current.contains_namespace(namespace) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + self.current.contains_feature(namespace, feature) + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + self.current.contains_property(namespace, feature, value) + } + + fn contains_base_namespace(&self, prefix: &str, namespace: &VariantNamespace) -> bool { + let Some(variant) = self.global.0.get(prefix) else { + return false; + }; + + variant.contains_namespace(namespace) + } + + fn contains_base_feature( + &self, + prefix: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + ) -> bool { + let Some(variant) = self.global.0.get(prefix) else { + return false; + }; + + variant.contains_feature(namespace, feature) + } + + fn contains_base_property( + &self, + prefix: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + let Some(variant) = self.global.0.get(prefix) else { + return false; + }; + + variant.contains_property(namespace, feature, value) + } + + fn label(&self) -> Option<&str> { + self.current.label() + } +} + +async fn determine_properties( + package: &Package, + workspace_root: &Path, + marker_env: &ResolverMarkerEnvironment, + distribution_database: &DistributionDatabase<'_, Context>, + variants_cache: &PackageVariantCache, +) -> Result { + let Some(variants_json) = package.to_registry_variants_json(workspace_root)? else { + // When selecting a non-variant wheel, all variant markers evaluate to false. + return Ok(VariantWithLabel::default()); + }; + let resolved_variants = if variants_cache.register(variants_json.version_id()) { + let resolved_variants = distribution_database + .fetch_and_query_variants(&variants_json, marker_env) + .await + .map_err(|err| LockErrorKind::VariantError { + package_id: package.id.clone(), + err, + })?; + + let resolved_variants = Arc::new(resolved_variants); + variants_cache.done(variants_json.version_id(), resolved_variants.clone()); + resolved_variants + } else { + variants_cache + .wait(&variants_json.version_id()) + .await + .expect("missing value for registered task") + }; + + // Select best wheel + let mut highest_priority_variant_wheel: Option<(_, Vec)> = None; + for wheel in &package.wheels { + let Some(variant) = wheel.filename.variant() else { + // The non-variant wheel is already supported + continue; + }; + + let Some(scores) = resolved_variants.score_variant(variant) else { + continue; + }; + + if let Some((_, old_scores)) = &highest_priority_variant_wheel { + if &scores > old_scores { + highest_priority_variant_wheel = Some((variant, scores)); + } + } else { + highest_priority_variant_wheel = Some((variant, scores)); + } + } + + if let Some((best_variant, _)) = highest_priority_variant_wheel { + // TODO(konsti): We shouldn't need to clone + + // TODO(konsti): The variant exists because we used it for scoring, but we should + // be able to write this without unwrap. + let known_properties = resolved_variants.variants_json.variants[best_variant].clone(); + Ok(VariantWithLabel { + variant: known_properties, + label: Some(best_variant.clone()), + }) + } else { + // When selecting the non-variant wheel, all variant markers evaluate to false. + Ok(VariantWithLabel::default()) + } +} diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index f4cdbb16c..6ca1ece14 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -26,10 +26,11 @@ use uv_distribution_filename::{ }; use uv_distribution_types::{ BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, - Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, IndexMetadata, + Dist, DistributionMetadata, File, FileLocation, GitSourceDist, IndexLocations, IndexMetadata, IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel, - RegistrySourceDist, RemoteSource, Requirement, RequirementSource, RequiresPython, ResolvedDist, - SimplifiedMarkerTree, StaticMetadata, ToUrlError, UrlString, + RegistrySourceDist, RegistryVariantsJson, RemoteSource, Requirement, RequirementSource, + RequiresPython, ResolvedDist, SimplifiedMarkerTree, StaticMetadata, ToUrlError, UrlString, + VariantsJsonFilename, }; use uv_fs::{PortablePath, PortablePathBuf, relative_to}; use uv_git::{RepositoryReference, ResolvedRepositoryReference}; @@ -832,7 +833,10 @@ impl Lock { &self.manifest.members } - /// Returns the dependency groups that were used to generate this lock. + /// Returns requirements provided to the resolver, exclusive of the workspace members. + /// + /// These are requirements that are attached to the project, but not to any of its + /// workspace members. For example, the requirements in a PEP 723 script would be included here. pub fn requirements(&self) -> &BTreeSet { &self.manifest.requirements } @@ -2365,6 +2369,10 @@ pub struct Package { pub(crate) id: PackageId, sdist: Option, wheels: Vec, + /// The variants JSON file for the package version, if available. + /// + /// Named `variants-json` in `uv.lock`. + variants_json: Option, /// If there are multiple versions or sources for the same package name, we add the markers of /// the fork(s) that contained this version or source, so we can set the correct preferences in /// the next resolution. @@ -2390,6 +2398,7 @@ impl Package { let id = PackageId::from_annotated_dist(annotated_dist, root)?; let sdist = SourceDist::from_annotated_dist(&id, annotated_dist)?; let wheels = Wheel::from_annotated_dist(annotated_dist)?; + let variants_json = VariantsJsonEntry::from_annotated_dist(annotated_dist)?; let requires_dist = if id.source.is_immutable() { BTreeSet::default() } else { @@ -2438,6 +2447,7 @@ impl Package { id, sdist, wheels, + variants_json, fork_markers, dependencies: vec![], optional_dependencies: BTreeMap::default(), @@ -2942,6 +2952,82 @@ impl Package { Ok(Some(sdist)) } + /// Convert to a [`RegistryVariantsJson`] for installation. + pub(crate) fn to_registry_variants_json( + &self, + workspace_root: &Path, + ) -> Result, LockError> { + let Some(variants_json) = &self.variants_json else { + return Ok(None); + }; + + let name = &self.id.name; + let version = self + .id + .version + .as_ref() + .expect("version for registry source"); + let (file_url, index) = match &self.id.source { + Source::Registry(RegistrySource::Url(url)) => { + let file_url = + variants_json + .url + .url() + .ok_or_else(|| LockErrorKind::MissingUrl { + name: name.clone(), + version: version.clone(), + })?; + let index = IndexUrl::from(VerbatimUrl::from_url( + url.to_url().map_err(LockErrorKind::InvalidUrl)?, + )); + (FileLocation::AbsoluteUrl(file_url.clone()), index) + } + Source::Registry(RegistrySource::Path(path)) => { + let index = IndexUrl::from( + VerbatimUrl::from_absolute_path(workspace_root.join(path)) + .map_err(LockErrorKind::RegistryVerbatimUrl)?, + ); + match &variants_json.url { + VariantsJsonSource::Url { url: file_url } => { + (FileLocation::AbsoluteUrl(file_url.clone()), index) + } + VariantsJsonSource::Path { path: file_path } => { + let file_path = workspace_root.join(path).join(file_path); + let file_url = + DisplaySafeUrl::from_file_path(&file_path).map_err(|()| { + LockErrorKind::PathToUrl { + path: file_path.into_boxed_path(), + } + })?; + (FileLocation::AbsoluteUrl(UrlString::from(file_url)), index) + } + } + } + _ => todo!("Handle error: variants.json can only be used on a registry source"), + }; + + let filename = format!("{name}-{version}-variants.json"); + let file = File { + dist_info_metadata: false, + filename: SmallString::from(filename), + hashes: variants_json.hash.iter().map(|h| h.0.clone()).collect(), + requires_python: None, + size: variants_json.size, + upload_time_utc_ms: variants_json.upload_time.map(Timestamp::as_millisecond), + url: file_url, + yanked: None, + zstd: None, + }; + Ok(Some(RegistryVariantsJson { + filename: VariantsJsonFilename { + name: self.name().clone(), + version: version.clone(), + }, + file: Box::new(file), + index, + })) + } + fn to_toml( &self, requires_python: &RequiresPython, @@ -3015,6 +3101,10 @@ impl Package { table.insert("wheels", value(wheels)); } + if let Some(ref variants_json) = self.variants_json { + table.insert("variants-json", value(variants_json.to_toml()?)); + } + // Write the package metadata, if non-empty. { let mut metadata_table = Table::new(); @@ -3086,7 +3176,7 @@ impl Package { } fn find_best_wheel(&self, tag_policy: TagPolicy<'_>) -> Option { - type WheelPriority<'lock> = (TagPriority, Option<&'lock BuildTag>); + type WheelPriority<'lock> = (bool, TagPriority, Option<&'lock BuildTag>); let mut best: Option<(WheelPriority, usize)> = None; for (i, wheel) in self.wheels.iter().enumerate() { @@ -3096,7 +3186,8 @@ impl Package { continue; }; let build_tag = wheel.filename.build_tag(); - let wheel_priority = (tag_priority, build_tag); + // Non-variant wheels before variant wheels. + let wheel_priority = (wheel.filename.variant().is_none(), tag_priority, build_tag); match best { None => { best = Some((wheel_priority, i)); @@ -3264,6 +3355,8 @@ struct PackageWire { sdist: Option, #[serde(default)] wheels: Vec, + #[serde(default, rename = "variants-json")] + variants_json: Option, #[serde(default, rename = "resolution-markers")] fork_markers: Vec, #[serde(default)] @@ -3321,6 +3414,7 @@ impl PackageWire { metadata: self.metadata, sdist: self.sdist, wheels: self.wheels, + variants_json: self.variants_json, fork_markers: self .fork_markers .into_iter() @@ -4405,6 +4499,152 @@ struct ZstdWheel { size: Option, } +#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[serde(from = "VariantsJsonWire")] +struct VariantsJsonEntry { + /// A URL or file path (via `file://`) where the variants JSON file that was locked + /// against was found. The location does not need to exist in the future, + /// so this should be treated as only a hint to where to look and/or + /// recording where the variants JSON file originally came from. + #[serde(flatten)] + url: VariantsJsonSource, + /// A hash of the variants JSON file. + /// + /// This is only present for variants JSON files that come from registries and direct + /// URLs. Files from git or path dependencies do not have hashes + /// associated with them. + hash: Option, + /// The size of the variants JSON file in bytes. + /// + /// This is only present for variants JSON files that come from registries. + size: Option, + /// The upload time of the variants JSON file. + /// + /// This is only present for variants JSON files that come from registries. + upload_time: Option, +} + +#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +struct VariantsJsonWire { + /// A URL or file path (via `file://`) where the variants JSON file that was locked + /// against was found. + #[serde(flatten)] + url: VariantsJsonSource, + /// A hash of the variants JSON file. + hash: Option, + /// The size of the variants JSON file in bytes. + size: Option, + /// The upload time of the variants JSON file. + #[serde(alias = "upload_time")] + upload_time: Option, +} + +impl VariantsJsonEntry { + fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> Result, LockError> { + match &annotated_dist.dist { + // We pass empty installed packages for locking. + ResolvedDist::Installed { .. } => unreachable!(), + ResolvedDist::Installable { variants_json, .. } => { + if let Some(variants_json) = variants_json { + let url = match &variants_json.index { + IndexUrl::Pypi(_) | IndexUrl::Url(_) => { + let url = normalize_file_location(&variants_json.file.url) + .map_err(LockErrorKind::InvalidUrl) + .map_err(LockError::from)?; + VariantsJsonSource::Url { url } + } + IndexUrl::Path(path) => { + let index_path = path + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: path.to_url() })?; + let variants_url = variants_json + .file + .url + .to_url() + .map_err(LockErrorKind::InvalidUrl)?; + + if variants_url.scheme() == "file" { + let variants_path = variants_url + .to_file_path() + .map_err(|()| LockErrorKind::UrlToPath { url: variants_url })?; + let path = relative_to(&variants_path, index_path) + .or_else(|_| std::path::absolute(&variants_path)) + .map_err(LockErrorKind::DistributionRelativePath)? + .into_boxed_path(); + VariantsJsonSource::Path { path } + } else { + let url = normalize_file_location(&variants_json.file.url) + .map_err(LockErrorKind::InvalidUrl) + .map_err(LockError::from)?; + VariantsJsonSource::Url { url } + } + } + }; + + Ok(Some(Self { + url, + hash: variants_json + .file + .hashes + .iter() + .max() + .cloned() + .map(Hash::from), + size: variants_json.file.size, + upload_time: variants_json + .file + .upload_time_utc_ms + .map(Timestamp::from_millisecond) + .transpose() + .map_err(LockErrorKind::InvalidTimestamp)?, + })) + } else { + Ok(None) + } + } + } + } + + /// Returns the TOML representation of this variants JSON file. + fn to_toml(&self) -> Result { + let mut table = InlineTable::new(); + match &self.url { + VariantsJsonSource::Url { url } => { + table.insert("url", Value::from(url.as_ref())); + } + VariantsJsonSource::Path { path } => { + table.insert("path", Value::from(PortablePath::from(path).to_string())); + } + } + if let Some(hash) = &self.hash { + table.insert("hash", Value::from(hash.to_string())); + } + if let Some(size) = self.size { + table.insert( + "size", + toml_edit::ser::ValueSerializer::new().serialize_u64(size)?, + ); + } + if let Some(upload_time) = self.upload_time { + table.insert("upload-time", Value::from(upload_time.to_string())); + } + Ok(table) + } +} + +impl From for VariantsJsonEntry { + fn from(wire: VariantsJsonWire) -> Self { + // TODO(konsti): Do we still need the wire type? + Self { + url: wire.url, + hash: wire.hash, + size: wire.size, + upload_time: wire.upload_time, + } + } +} + /// Inspired by: #[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] #[serde(try_from = "WheelWire")] @@ -4739,6 +4979,33 @@ enum WheelWireSource { }, } +#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[serde(untagged, rename_all = "kebab-case")] +enum VariantsJsonSource { + /// Used for all variants JSON files that come from remote sources. + Url { + /// A URL where the variants JSON file that was locked against was found. The location + /// does not need to exist in the future, so this should be treated as + /// only a hint to where to look and/or recording where the variants JSON file + /// originally came from. + url: UrlString, + }, + /// Used for variants JSON files that come from local registries (like `--find-links`). + Path { + /// The path to the variants JSON file, relative to the index. + path: Box, + }, +} + +impl VariantsJsonSource { + fn url(&self) -> Option<&UrlString> { + match &self { + Self::Path { .. } => None, + Self::Url { url, .. } => Some(url), + } + } +} + impl Wheel { /// Returns the TOML representation of this wheel. fn to_toml(&self) -> Result { @@ -5989,6 +6256,12 @@ enum LockErrorKind { /// The ID of the workspace member with an invalid source. id: PackageId, }, + #[error("Failed to fetch and query variants for `{package_id}`")] + VariantError { + package_id: PackageId, + #[source] + err: uv_distribution::Error, + }, } /// An error that occurs when a source string could not be parsed. diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap index 46298339f..16c9e106f 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -90,6 +91,7 @@ Ok( zstd: None, }, ], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap index c5ef76e9a..f87472c10 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -97,6 +98,7 @@ Ok( zstd: None, }, ], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap index eb24b2b94..0c47bfa31 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -93,6 +94,7 @@ Ok( zstd: None, }, ], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap index 4414974a0..7cd2833f0 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -82,6 +83,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, @@ -130,6 +132,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [ Dependency { diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap index 4414974a0..7cd2833f0 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -82,6 +83,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, @@ -130,6 +132,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [ Dependency { diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap index 50a5965e3..e4fec721d 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -56,6 +57,7 @@ Ok( }, sdist: None, wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, @@ -104,6 +106,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, @@ -152,6 +155,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [ Dependency { diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap index 4414974a0..7cd2833f0 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -82,6 +83,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, @@ -130,6 +132,7 @@ Ok( }, ), wheels: [], + variants_json: None, fork_markers: [], dependencies: [ Dependency { diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap index 454db2287..15662995f 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -65,6 +66,7 @@ Ok( }, sdist: None, wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap index 70d7b0e5c..844c91c97 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -63,6 +64,7 @@ Ok( }, sdist: None, wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap index 614e25dab..956ebc5c0 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -58,6 +59,7 @@ Ok( }, sdist: None, wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap index a54bf70f8..2f1914451 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap @@ -1,6 +1,7 @@ --- source: crates/uv-resolver/src/lock/mod.rs expression: result +snapshot_kind: text --- Ok( Lock { @@ -58,6 +59,7 @@ Ok( }, sdist: None, wheels: [], + variants_json: None, fork_markers: [], dependencies: [], optional_dependencies: {}, diff --git a/crates/uv-resolver/src/lock/tree.rs b/crates/uv-resolver/src/lock/tree.rs index d850c2f4c..8ced8af7c 100644 --- a/crates/uv-resolver/src/lock/tree.rs +++ b/crates/uv-resolver/src/lock/tree.rs @@ -13,7 +13,7 @@ use uv_configuration::DependencyGroupsWithDefaults; use uv_console::human_readable_bytes; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::Version; -use uv_pep508::MarkerTree; +use uv_pep508::{MarkerTree, MarkerVariantsUniversal}; use uv_pypi_types::ResolverMarkerEnvironment; use crate::lock::PackageId; @@ -196,7 +196,9 @@ impl<'env> TreeDisplay<'env> { if marker.is_false() { continue; } - if markers.is_some_and(|markers| !marker.evaluate(markers, &[])) { + if markers.is_some_and(|markers| { + !marker.evaluate(markers, &MarkerVariantsUniversal, &[]) + }) { continue; } // Add the package to the graph. @@ -233,7 +235,9 @@ impl<'env> TreeDisplay<'env> { if marker.is_false() { continue; } - if markers.is_some_and(|markers| !marker.evaluate(markers, &[])) { + if markers.is_some_and(|markers| { + !marker.evaluate(markers, &MarkerVariantsUniversal, &[]) + }) { continue; } // Add the package to the graph. diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index fd39470de..2efad4a1f 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -6,6 +6,7 @@ use either::Either; use uv_configuration::{Constraints, Excludes, Overrides}; use uv_distribution_types::Requirement; use uv_normalize::PackageName; +use uv_pep508::MarkerVariantsUniversal; use uv_types::RequestedRequirements; use crate::preferences::Preferences; @@ -130,8 +131,11 @@ impl Manifest { .apply(lookahead.requirements()) .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement - .evaluate_markers(env.marker_environment(), lookahead.extras()) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + lookahead.extras(), + ) }) }) .chain( @@ -139,7 +143,11 @@ impl Manifest { .apply(&self.requirements) .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }), ) .chain( @@ -147,7 +155,11 @@ impl Manifest { .requirements() .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) .map(Cow::Borrowed), ), @@ -159,7 +171,11 @@ impl Manifest { .chain(self.constraints.requirements().map(Cow::Borrowed)) .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }), ), } @@ -178,7 +194,11 @@ impl Manifest { .requirements() .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) .map(Cow::Borrowed), ), @@ -188,7 +208,11 @@ impl Manifest { .requirements() .filter(|requirement| !self.excludes.contains(&requirement.name)) .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) .map(Cow::Borrowed), ), @@ -213,31 +237,44 @@ impl Manifest { match mode { // Include direct requirements, dependencies of editables, and transitive dependencies // of local packages. - DependencyMode::Transitive => Either::Left( - self.lookaheads - .iter() - .filter(|lookahead| lookahead.direct()) - .flat_map(move |lookahead| { - self.overrides - .apply(lookahead.requirements()) - .filter(move |requirement| { - requirement - .evaluate_markers(env.marker_environment(), lookahead.extras()) - }) - }) - .chain( - self.overrides - .apply(&self.requirements) - .filter(move |requirement| { - requirement.evaluate_markers(env.marker_environment(), &[]) - }), - ), - ), + DependencyMode::Transitive => { + Either::Left( + self.lookaheads + .iter() + .filter(|lookahead| lookahead.direct()) + .flat_map(move |lookahead| { + self.overrides.apply(lookahead.requirements()).filter( + move |requirement| { + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + lookahead.extras(), + ) + }, + ) + }) + .chain(self.overrides.apply(&self.requirements).filter( + move |requirement| { + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) + }, + )), + ) + } // Restrict to the direct requirements. DependencyMode::Direct => { Either::Right(self.overrides.apply(self.requirements.iter()).filter( - move |requirement| requirement.evaluate_markers(env.marker_environment(), &[]), + move |requirement| { + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) + }, )) } } @@ -254,7 +291,13 @@ impl Manifest { ) -> impl Iterator> + 'a { self.overrides .apply(self.requirements.iter()) - .filter(move |requirement| requirement.evaluate_markers(env.marker_environment(), &[])) + .filter(move |requirement| { + requirement.evaluate_markers( + env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) + }) } /// Apply the overrides and constraints to a set of requirements. diff --git a/crates/uv-resolver/src/marker.rs b/crates/uv-resolver/src/marker.rs index 02ea1d6df..6dfba034d 100644 --- a/crates/uv-resolver/src/marker.rs +++ b/crates/uv-resolver/src/marker.rs @@ -49,12 +49,12 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option { collect_python_markers(tree, markers, range); } } - MarkerTreeKind::Extra(marker) => { + MarkerTreeKind::List(marker) => { for (_, tree) in marker.children() { collect_python_markers(tree, markers, range); } } - MarkerTreeKind::List(marker) => { + MarkerTreeKind::Extra(marker) => { for (_, tree) in marker.children() { collect_python_markers(tree, markers, range); } diff --git a/crates/uv-resolver/src/preferences.rs b/crates/uv-resolver/src/preferences.rs index 5683e8a17..86440336c 100644 --- a/crates/uv-resolver/src/preferences.rs +++ b/crates/uv-resolver/src/preferences.rs @@ -7,7 +7,7 @@ use tracing::trace; use uv_distribution_types::{IndexUrl, InstalledDist, InstalledDistKind}; use uv_normalize::PackageName; use uv_pep440::{Operator, Version}; -use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl}; +use uv_pep508::{MarkerTree, MarkerVariantsUniversal, VerbatimUrl, VersionOrUrl}; use uv_pypi_types::{HashDigest, HashDigests, HashError}; use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement}; @@ -241,7 +241,10 @@ impl Preferences { for preference in preferences { // Filter non-matching preferences when resolving for an environment. if let Some(markers) = env.marker_environment() { - if !preference.marker.evaluate(markers, &[]) { + if !preference + .marker + .evaluate(markers, &MarkerVariantsUniversal, &[]) + { trace!("Excluding {preference} from preferences due to unmatched markers"); continue; } diff --git a/crates/uv-resolver/src/resolution/display.rs b/crates/uv-resolver/src/resolution/display.rs index e5b5dfef5..549cc7eca 100644 --- a/crates/uv-resolver/src/resolution/display.rs +++ b/crates/uv-resolver/src/resolution/display.rs @@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap}; use uv_distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations}; use uv_normalize::PackageName; -use uv_pep508::MarkerTree; +use uv_pep508::{MarkerTree, MarkerVariantsUniversal}; use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode}; use crate::{ResolverEnvironment, ResolverOutput}; @@ -91,7 +91,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { let mut sources = SourceAnnotations::default(); for requirement in self.resolution.requirements.iter().filter(|requirement| { - requirement.evaluate_markers(self.env.marker_environment(), &[]) + requirement.evaluate_markers( + self.env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) { if let Some(origin) = &requirement.origin { sources.add( @@ -106,7 +110,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { .constraints .requirements() .filter(|requirement| { - requirement.evaluate_markers(self.env.marker_environment(), &[]) + requirement.evaluate_markers( + self.env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) { if let Some(origin) = &requirement.origin { @@ -122,7 +130,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { .overrides .requirements() .filter(|requirement| { - requirement.evaluate_markers(self.env.marker_environment(), &[]) + requirement.evaluate_markers( + self.env.marker_environment(), + &MarkerVariantsUniversal, + &[], + ) }) { if let Some(origin) = &requirement.origin { diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index edea7911e..6fead3019 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -449,6 +449,8 @@ impl ResolverOutput { ( ResolvedDist::Installable { dist: Arc::new(dist), + // Only registry distributions have a variants JSON file. + variants_json: None, version: Some(version.clone()), }, hashes, @@ -645,7 +647,7 @@ impl ResolverOutput { ) -> Result> { use uv_pep508::{ CanonicalMarkerValueString, CanonicalMarkerValueVersion, MarkerExpression, - MarkerOperator, MarkerTree, + MarkerOperator, MarkerTree, MarkerValueList, }; /// A subset of the possible marker values. @@ -657,6 +659,7 @@ impl ResolverOutput { enum MarkerParam { Version(CanonicalMarkerValueVersion), String(CanonicalMarkerValueString), + List(MarkerValueList), } /// Add all marker parameters from the given tree to the given set. @@ -688,6 +691,13 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } + MarkerTreeKind::List(marker) => { + // TODO(konsti): Do we care about this set here? + set.insert(MarkerParam::List(marker.key())); + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } // We specifically don't care about these for the // purposes of generating a marker string for a lock // file. Quoted strings are marker values given by the @@ -698,11 +708,6 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } - MarkerTreeKind::List(marker) => { - for (_, tree) in marker.children() { - add_marker_params_from_tree(tree, set); - } - } } } @@ -756,13 +761,29 @@ impl ResolverOutput { } } MarkerParam::String(value_string) => { - let from_env = marker_env.get_string(value_string); + // TODO(konsti): What's the correct handling for `variant_label`? + let from_env = marker_env.get_string(value_string).unwrap_or(""); MarkerExpression::String { key: value_string.into(), operator: MarkerOperator::Equal, value: from_env.into(), } } + MarkerParam::List(value_variant) => { + match value_variant { + MarkerValueList::VariantNamespaces + | MarkerValueList::VariantFeatures + | MarkerValueList::VariantProperties => { + // We ignore variants for the resolution marker tree since they are package + // specific. + continue; + } + MarkerValueList::Extras | MarkerValueList::DependencyGroups => { + // TODO(konsti): Do we need to track them? + continue; + } + } + } }; conjunction.and(MarkerTree::expression(expr)); } diff --git a/crates/uv-resolver/src/resolver/environment.rs b/crates/uv-resolver/src/resolver/environment.rs index 29581f72e..98ae46428 100644 --- a/crates/uv-resolver/src/resolver/environment.rs +++ b/crates/uv-resolver/src/resolver/environment.rs @@ -505,8 +505,16 @@ pub(crate) enum ForkingPossibility<'d> { } impl<'d> ForkingPossibility<'d> { - pub(crate) fn new(env: &ResolverEnvironment, dep: &'d PubGrubDependency) -> Self { - let marker = dep.package.marker(); + pub(crate) fn new( + env: &ResolverEnvironment, + dep: &'d PubGrubDependency, + variant_base: Option<&str>, + ) -> Self { + let marker = if let Some(variant_base) = variant_base { + dep.package.marker().with_variant_base(variant_base) + } else { + dep.package.marker() + }; if !env.included_by_marker(marker) { ForkingPossibility::DependencyAlwaysExcluded } else if marker.is_true() { @@ -576,8 +584,12 @@ impl Forker<'_> { /// Returns true if the dependency represented by this forker may be /// included in the given resolver environment. - pub(crate) fn included(&self, env: &ResolverEnvironment) -> bool { - let marker = self.package.marker(); + pub(crate) fn included(&self, env: &ResolverEnvironment, variant_base: Option<&str>) -> bool { + let marker = if let Some(variant_base) = variant_base { + self.package.marker().with_variant_base(variant_base) + } else { + self.package.marker() + }; env.included_by_marker(marker) } } diff --git a/crates/uv-resolver/src/resolver/index.rs b/crates/uv-resolver/src/resolver/index.rs index 222a93708..9e16a3911 100644 --- a/crates/uv-resolver/src/resolver/index.rs +++ b/crates/uv-resolver/src/resolver/index.rs @@ -2,9 +2,12 @@ use std::hash::BuildHasherDefault; use std::sync::Arc; use rustc_hash::FxHasher; + +use uv_distribution::PackageVariantCache; use uv_distribution_types::{IndexUrl, VersionId}; use uv_normalize::PackageName; use uv_once_map::OnceMap; +use uv_variants::cache::VariantProviderCache; use crate::resolver::provider::{MetadataResponse, VersionsResponse}; @@ -22,6 +25,12 @@ struct SharedInMemoryIndex { /// A map from package ID to metadata for that distribution. distributions: FxOnceMap>, + + /// The resolved variants, indexed by provider. + variant_providers: VariantProviderCache, + + /// The resolved variant priorities, indexed by package version. + variant_priorities: PackageVariantCache, } pub(crate) type FxOnceMap = OnceMap>; @@ -41,4 +50,14 @@ impl InMemoryIndex { pub fn distributions(&self) -> &FxOnceMap> { &self.0.distributions } + + /// Returns a reference to the variant providers map. + pub fn variant_providers(&self) -> &VariantProviderCache { + &self.0.variant_providers + } + + /// Returns a reference to the variant priorities map. + pub fn variant_priorities(&self) -> &PackageVariantCache { + &self.0.variant_priorities + } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index bb75f5b00..867643f81 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -24,15 +24,17 @@ use uv_configuration::{Constraints, Excludes, Overrides}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_distribution_types::{ BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, - IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, - IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, - ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, implied_markers, + GlobalVersionId, IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, + IndexLocations, IndexMetadata, IndexUrl, InstalledDist, Name, PackageId, PrioritizedDist, + PythonRequirementKind, RegistryVariantsJson, RemoteSource, Requirement, ResolvedDist, + ResolvedDistRef, SourceDist, VersionId, VersionOrUrlRef, implied_markers, }; use uv_git::GitResolver; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges}; use uv_pep508::{ MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString, + MarkerVariantsEnvironment, MarkerVariantsUniversal, }; use uv_platform_tags::{IncompatibleTag, Tags}; use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl}; @@ -71,7 +73,7 @@ pub use crate::resolver::index::InMemoryIndex; use crate::resolver::indexes::Indexes; pub use crate::resolver::provider::{ DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider, - VersionsResponse, WheelMetadataResult, + VariantProviderResult, VersionsResponse, WheelMetadataResult, }; pub use crate::resolver::reporter::{BuildId, Reporter}; use crate::resolver::system::SystemDependency; @@ -83,6 +85,8 @@ use crate::{ marker, }; pub(crate) use provider::MetadataUnavailable; +use uv_variants::resolved_variants::ResolvedVariants; +use uv_variants::variant_with_label::VariantWithLabel; mod availability; mod batch_prefetch; @@ -617,6 +621,7 @@ impl ResolverState ResolverState ResolverState dist, CandidateDist::Incompatible { @@ -1429,6 +1445,63 @@ impl ResolverState( + &self, + candidate: Candidate<'prioritized>, + env: &ResolverEnvironment, + request_sink: &Sender, + variant_prioritized_dist_binding: &'prioritized mut PrioritizedDist, + ) -> Result, ResolveError> { + let candidate = if env.marker_environment().is_some() { + // When solving for a specific environment, check if there is a matching variant wheel + // for the current environment. + // TODO(konsti): When solving for an environment that is not the current host, don't + // consider variants unless a static variant is given. + let Some(prioritized_dist) = candidate.prioritized() else { + return Ok(candidate); + }; + + // No `variants.json`, no variants. + // TODO(konsti): Be more lenient, e.g. parse the wheel itself? + let Some(variants_json) = prioritized_dist.variants_json() else { + return Ok(candidate); + }; + + // If the distribution is not indexed, we can't resolve variants. + let Some(index) = prioritized_dist.index() else { + return Ok(candidate); + }; + + // Query the host for the applicable features and properties. + let version_id = GlobalVersionId::new( + VersionId::NameVersion(candidate.name().clone(), candidate.version().clone()), + index.clone(), + ); + if self.index.variant_priorities().register(version_id.clone()) { + request_sink + .blocking_send(Request::Variants(version_id.clone(), variants_json.clone()))?; + } + + let resolved_variants = self.index.variant_priorities().wait_blocking(&version_id); + let Some(resolved_variants) = &resolved_variants else { + panic!("We have variants, why didn't they resolve?"); + }; + + let Some(variant_prioritized_dist) = + prioritized_dist.prioritize_best_variant_wheel(resolved_variants) + else { + return Ok(candidate); + }; + + *variant_prioritized_dist_binding = variant_prioritized_dist; + candidate.prioritize_best_variant_wheel(variant_prioritized_dist_binding) + } else { + // In universal mode, a variant wheel with an otherwise compatible tag is acceptable. + candidate.allow_variant_wheels() + }; + Ok(candidate) + } + /// Determine whether a candidate covers all supported platforms; and, if not, generate a fork. /// /// This only ever applies to versions that lack source distributions And, for now, we only @@ -1468,13 +1541,15 @@ impl ResolverState ResolverState ResolverState, ) -> Result { @@ -1758,6 +1835,7 @@ impl ResolverState ResolverState ForkedDependencies::Unavailable(err), }) } else { - Ok(result?.fork(env, python_requirement, &self.conflicts)) + // Grab the pinned distribution for the given name and version. + let variant_base = package.name().map(|name| format!("{name}=={version}")); + Ok(result?.fork( + env, + python_requirement, + &self.conflicts, + variant_base.as_deref(), + )) } } @@ -1783,6 +1868,7 @@ impl ResolverState, ) -> Result { @@ -1797,6 +1883,7 @@ impl ResolverState ResolverState ResolverState ResolverState VariantWithLabel { + // TODO(konsti): Perf/Caching with version selection: This is in the hot path! + + if env.marker_environment().is_none() { + return VariantWithLabel::default(); + } + + // Grab the pinned distribution for the given name and version. + let Some(dist) = pins.get(name, version) else { + return VariantWithLabel::default(); + }; + + let Some(filename) = dist.wheel_filename() else { + // TODO(konsti): Handle installed dists too + return VariantWithLabel::default(); + }; + + let Some(variant_label) = filename.variant() else { + return VariantWithLabel::default(); + }; + + let Some(index) = dist.index() else { + warn!("Wheel variant has no index: {filename}"); + return VariantWithLabel::default(); + }; + + let version_id = GlobalVersionId::new( + VersionId::NameVersion(name.clone(), version.clone()), + index.clone(), + ); + + let Some(resolved_variants) = in_memory_index.variant_priorities().get(&version_id) else { + return VariantWithLabel::default(); + }; + + // Collect the host properties for marker filtering. + // TODO(konsti): We shouldn't need to clone + let variant = resolved_variants + .variants_json + .variants + .get(variant_label) + .expect("Missing previously select variant label"); + + VariantWithLabel { + variant: variant.clone(), + label: Some(variant_label.clone()), + } + } + /// The regular and dev dependencies filtered by Python version and the markers of this fork, /// plus the extras dependencies of the current package (e.g., `black` depending on /// `black[colorama]`). @@ -2023,6 +2170,7 @@ impl ResolverState, name: Option<&PackageName>, env: &'a ResolverEnvironment, + variants: &'a impl MarkerVariantsEnvironment, python_requirement: &'a PythonRequirement, ) -> impl Iterator> { let python_marker = python_requirement.to_marker_tree(); @@ -2034,6 +2182,7 @@ impl ResolverState ResolverState ResolverState ResolverState ResolverState + 'parameters, extra: Option<&'parameters ExtraName>, env: &'parameters ResolverEnvironment, + variants: &'parameters impl MarkerVariantsEnvironment, python_marker: MarkerTree, python_requirement: &'parameters PythonRequirement, ) -> impl Iterator> + 'parameters @@ -2158,6 +2311,7 @@ impl ResolverState ResolverState, env: &ResolverEnvironment, + variants: &impl MarkerVariantsEnvironment, python_marker: MarkerTree, python_requirement: &PythonRequirement, ) -> bool { @@ -2186,12 +2342,14 @@ impl ResolverState { // Only include requirements that are relevant for the current extra. - if requirement.evaluate_markers(env.marker_environment(), &[]) { + if requirement.evaluate_markers(env.marker_environment(), &variants, &[]) { return false; } - if !requirement - .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra)) - { + if !requirement.evaluate_markers( + env.marker_environment(), + &variants, + slice::from_ref(source_extra), + ) { return false; } if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra))) @@ -2200,7 +2358,7 @@ impl ResolverState { - if !requirement.evaluate_markers(env.marker_environment(), &[]) { + if !requirement.evaluate_markers(env.marker_environment(), variants, &[]) { return false; } } @@ -2233,6 +2391,7 @@ impl ResolverState, extra: Option<&'parameters ExtraName>, env: &'parameters ResolverEnvironment, + variants: &'parameters (impl MarkerVariantsEnvironment + 'parameters), python_marker: MarkerTree, python_requirement: &'parameters PythonRequirement, ) -> impl Iterator> + 'parameters @@ -2294,7 +2453,8 @@ impl ResolverState ResolverState { - if !constraint - .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra)) - { + if !constraint.evaluate_markers( + env.marker_environment(), + &variants, + slice::from_ref(source_extra), + ) { return None; } - if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra))) - { + if !env.included_by_group(ConflictItemRef::from(( + &requirement.name, + source_extra, + ))) { return None; } } None => { - if !constraint.evaluate_markers(env.marker_environment(), &[]) { + if !constraint.evaluate_markers(env.marker_environment(), &variants, &[]) { return None; } } @@ -2394,6 +2558,15 @@ impl ResolverState { + trace!("Received variant metadata for: {version_id}"); + self.index + .variant_priorities() + .done(version_id, Arc::new(resolved_variants)); + } None => {} } } @@ -2560,6 +2733,7 @@ impl ResolverState ResolverState self + .fetch_and_query_variants(variants_json, provider) + .await + .map(|resolved_variants| { + Some(Response::Variants { + version_id, + resolved_variants, + }) + }), } } + async fn fetch_and_query_variants( + &self, + variants_json: RegistryVariantsJson, + provider: &Provider, + ) -> Result { + let Some(marker_env) = self.env.marker_environment() else { + unreachable!("Variants should only be queried in non-universal resolution") + }; + provider + .fetch_and_query_variants(&variants_json, marker_env) + .await + .map_err(ResolveError::VariantFrontend) + } + fn convert_no_solution_err( &self, mut err: pubgrub::NoSolutionError, @@ -3524,6 +3721,8 @@ pub(crate) enum Request { Installed(InstalledDist), /// A request to pre-fetch the metadata for a package and the best-guess distribution. Prefetch(PackageName, Range, PythonRequirement), + /// Resolve the variants for a package + Variants(GlobalVersionId, RegistryVariantsJson), } impl<'a> From> for Request { @@ -3578,6 +3777,9 @@ impl Display for Request { Self::Prefetch(package_name, range, _) => { write!(f, "Prefetch {package_name} {range}") } + Self::Variants(version_id, _) => { + write!(f, "Variants {version_id}") + } } } } @@ -3597,6 +3799,11 @@ enum Response { dist: InstalledDist, metadata: MetadataResponse, }, + /// The returned variant compatibility. + Variants { + version_id: GlobalVersionId, + resolved_variants: ResolvedVariants, + }, } /// Information about the dependencies for a particular package. @@ -3633,6 +3840,7 @@ impl Dependencies { env: &ResolverEnvironment, python_requirement: &PythonRequirement, conflicts: &Conflicts, + variant_base: Option<&str>, ) -> ForkedDependencies { let deps = match self { Self::Available(deps) => deps, @@ -3651,7 +3859,13 @@ impl Dependencies { let Forks { mut forks, diverging_packages, - } = Forks::new(name_to_deps, env, python_requirement, conflicts); + } = Forks::new( + name_to_deps, + env, + python_requirement, + conflicts, + variant_base, + ); if forks.is_empty() { ForkedDependencies::Unforked(vec![]) } else if forks.len() == 1 { @@ -3709,6 +3923,7 @@ impl Forks { env: &ResolverEnvironment, python_requirement: &PythonRequirement, conflicts: &Conflicts, + variant_base: Option<&str>, ) -> Self { let python_marker = python_requirement.to_marker_tree(); @@ -3738,7 +3953,11 @@ impl Forks { .is_none_or(|bound| !python_requirement.raises(&bound)) { let dep = deps.pop().unwrap(); - let marker = dep.package.marker(); + let marker = if let Some(variant_base) = variant_base { + dep.package.marker().with_variant_base(variant_base) + } else { + dep.package.marker() + }; for fork in &mut forks { if fork.env.included_by_marker(marker) { fork.add_dependency(dep.clone()); @@ -3770,7 +3989,7 @@ impl Forks { } } for dep in deps { - let mut forker = match ForkingPossibility::new(env, &dep) { + let mut forker = match ForkingPossibility::new(env, &dep, variant_base) { ForkingPossibility::Possible(forker) => forker, ForkingPossibility::DependencyAlwaysExcluded => { // If the markers can never be satisfied by the parent @@ -3799,12 +4018,12 @@ impl Forks { for fork_env in envs { let mut new_fork = fork.clone(); - new_fork.set_env(fork_env); + new_fork.set_env(fork_env, variant_base); // We only add the dependency to this fork if it // satisfies the fork's markers. Some forks are // specifically created to exclude this dependency, // so this isn't always true! - if forker.included(&new_fork.env) { + if forker.included(&new_fork.env, variant_base) { new_fork.add_dependency(dep.clone()); } // Filter out any forks we created that are disjoint with our @@ -3943,10 +4162,14 @@ impl Fork { /// /// Any dependency in this fork that does not satisfy the given environment /// is removed. - fn set_env(&mut self, env: ResolverEnvironment) { + fn set_env(&mut self, env: ResolverEnvironment, variant_base: Option<&str>) { self.env = env; self.dependencies.retain(|dep| { - let marker = dep.package.marker(); + let marker = if let Some(variant_base) = variant_base { + dep.package.marker().with_variant_base(variant_base) + } else { + dep.package.marker() + }; if self.env.included_by_marker(marker) { return true; } @@ -4076,7 +4299,11 @@ fn enrich_dependency_error( } /// Compute the set of markers for which a package is known to be relevant. -fn find_environments(id: Id, state: &State) -> MarkerTree { +fn find_environments( + id: Id, + state: &State, + variant_base: &str, +) -> MarkerTree { let package = &state.package_store[id]; if package.is_root() { return MarkerTree::TRUE; @@ -4094,8 +4321,8 @@ fn find_environments(id: Id, state: &State if let Kind::FromDependencyOf(id1, _, id2, _) = &incompat.kind { if id == *id2 { marker.or({ - let mut marker = package.marker(); - marker.and(find_environments(*id1, state)); + let mut marker = package.marker().with_variant_base(variant_base); + marker.and(find_environments(*id1, state, variant_base)); marker }); } diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index c2a0e0abd..3b2aaf338 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -4,13 +4,14 @@ use uv_client::MetadataFormat; use uv_configuration::BuildOptions; use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter}; use uv_distribution_types::{ - Dist, IndexCapabilities, IndexMetadata, IndexMetadataRef, InstalledDist, RequestedDist, - RequiresPython, + Dist, IndexCapabilities, IndexMetadata, IndexMetadataRef, InstalledDist, RegistryVariantsJson, + RequestedDist, RequiresPython, }; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; use uv_platform_tags::Tags; use uv_types::{BuildContext, HashStrategy}; +use uv_variants::resolved_variants::ResolvedVariants; use crate::ExcludeNewer; use crate::flat_index::FlatIndex; @@ -19,6 +20,7 @@ use crate::yanks::AllowedYanks; pub type PackageVersionsResult = Result; pub type WheelMetadataResult = Result; +pub type VariantProviderResult = Result; /// The response when requesting versions for a package #[derive(Debug)] @@ -100,6 +102,13 @@ pub trait ResolverProvider { dist: &'io InstalledDist, ) -> impl Future + 'io; + /// Fetch the variants for a distribution given the marker environment. + fn fetch_and_query_variants<'io>( + &'io self, + variants_json: &'io RegistryVariantsJson, + marker_env: &'io uv_pep508::MarkerEnvironment, + ) -> impl Future + 'io; + /// Set the [`Reporter`] to use for this installer. #[must_use] fn with_reporter(self, reporter: Arc) -> Self; @@ -308,6 +317,17 @@ impl ResolverProvider for DefaultResolverProvider<'_, Con } } + /// Fetch the variants for a distribution given the marker environment. + async fn fetch_and_query_variants<'io>( + &'io self, + variants_json: &'io RegistryVariantsJson, + marker_env: &'io uv_pep508::MarkerEnvironment, + ) -> VariantProviderResult { + self.fetcher + .fetch_and_query_variants(variants_json, marker_env) + .await + } + /// Set the [`Reporter`] to use for this installer. fn with_reporter(self, reporter: Arc) -> Self { Self { diff --git a/crates/uv-resolver/src/universal_marker.rs b/crates/uv-resolver/src/universal_marker.rs index 093253843..dcfce50d8 100644 --- a/crates/uv-resolver/src/universal_marker.rs +++ b/crates/uv-resolver/src/universal_marker.rs @@ -6,7 +6,10 @@ use itertools::Itertools; use rustc_hash::FxHashMap; use uv_normalize::{ExtraName, GroupName, PackageName}; -use uv_pep508::{ExtraOperator, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree}; +use uv_pep508::{ + ExtraOperator, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, + MarkerVariantsEnvironment, MarkerVariantsUniversal, +}; use uv_pypi_types::{ConflictItem, ConflictKind, Conflicts, Inference}; use crate::ResolveError; @@ -293,7 +296,7 @@ impl UniversalMarker { /// This should only be used when evaluating a marker that is known not to /// have any extras. For example, the PEP 508 markers on a fork. pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool { - self.marker.evaluate(env, &[]) + self.marker.evaluate(env, &MarkerVariantsUniversal, &[]) } /// Returns true if this universal marker is satisfied by the given marker @@ -305,6 +308,7 @@ impl UniversalMarker { pub(crate) fn evaluate( self, env: &MarkerEnvironment, + variants: &impl MarkerVariantsEnvironment, projects: impl Iterator, extras: impl Iterator, groups: impl Iterator, @@ -321,6 +325,7 @@ impl UniversalMarker { groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow())); self.marker.evaluate( env, + variants, &projects .chain(extras) .chain(groups) @@ -829,7 +834,6 @@ pub(crate) fn resolve_conflicts( mod tests { use super::*; use std::str::FromStr; - use uv_pypi_types::ConflictSet; /// Creates a collection of declared conflicts from the sets @@ -959,7 +963,7 @@ mod tests { .collect::>(); let groups = Vec::<(PackageName, GroupName)>::new(); assert!( - !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups), + !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups,), "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`" ); } @@ -982,7 +986,7 @@ mod tests { .collect::>(); let groups = Vec::<(PackageName, GroupName)>::new(); assert!( - UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups), + UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups,), "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`" ); } diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index 77415022d..aa2ba392d 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -11,15 +11,16 @@ use uv_client::{FlatIndexEntry, OwnedArchive, SimpleDetailMetadata, VersionFiles use uv_configuration::BuildOptions; use uv_distribution_filename::{DistFilename, WheelFilename}; use uv_distribution_types::{ - HashComparison, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist, - RegistryBuiltWheel, RegistrySourceDist, RequiresPython, SourceDistCompatibility, - WheelCompatibility, + HashComparison, IncompatibleSource, IncompatibleWheel, IndexEntryFilename, IndexUrl, + PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, RegistryVariantsJson, RequiresPython, + SourceDistCompatibility, WheelCompatibility, }; use uv_normalize::PackageName; use uv_pep440::Version; use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags}; use uv_pypi_types::{HashDigest, ResolutionMetadata, Yanked}; use uv_types::HashStrategy; +use uv_variants::VariantPriority; use uv_warnings::warn_user_once; use crate::flat_index::FlatDistributions; @@ -467,7 +468,7 @@ impl VersionMapLazy { let yanked = file.yanked.as_deref(); let hashes = file.hashes.clone(); match filename { - DistFilename::WheelFilename(filename) => { + IndexEntryFilename::DistFilename(DistFilename::WheelFilename(filename)) => { let compatibility = self.wheel_compatibility( &filename, &filename.name, @@ -484,7 +485,9 @@ impl VersionMapLazy { }; priority_dist.insert_built(dist, hashes, compatibility); } - DistFilename::SourceDistFilename(filename) => { + IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename( + filename, + )) => { let compatibility = self.source_dist_compatibility( &filename.name, &filename.version, @@ -503,6 +506,14 @@ impl VersionMapLazy { }; priority_dist.insert_source(dist, hashes, compatibility); } + IndexEntryFilename::VariantJson(filename) => { + let variant_json = RegistryVariantsJson { + filename, + file: Box::new(file), + index: self.index.clone(), + }; + priority_dist.insert_variant_json(variant_json); + } } } if priority_dist.is_empty() { @@ -589,8 +600,8 @@ impl VersionMapLazy { } } - // Determine a compatibility for the wheel based on tags. - let priority = if let Some(tags) = &self.tags { + // Determine a priority for the wheel based on tags. + let tag_priority = if let Some(tags) = &self.tags { match filename.compatibility(tags) { TagCompatibility::Incompatible(tag) => { return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag)); @@ -608,6 +619,13 @@ impl VersionMapLazy { None }; + // TODO(konsti): Currently we ignore variants here on only determine them later + let variant_priority = if filename.variant().is_none() { + VariantPriority::NonVariant + } else { + VariantPriority::Unknown + }; + // Check if hashes line up. If hashes aren't required, they're considered matching. let hash_policy = self.hasher.get_package(name, version); let required_hashes = hash_policy.digests(); @@ -626,7 +644,12 @@ impl VersionMapLazy { // Break ties with the build tag. let build_tag = filename.build_tag().cloned(); - WheelCompatibility::Compatible(hash, priority, build_tag) + WheelCompatibility::Compatible { + hash, + tag_priority, + variant_priority, + build_tag, + } } } diff --git a/crates/uv-small-str/src/lib.rs b/crates/uv-small-str/src/lib.rs index 1524f1b99..0d751186d 100644 --- a/crates/uv-small-str/src/lib.rs +++ b/crates/uv-small-str/src/lib.rs @@ -3,6 +3,8 @@ use std::cmp::PartialEq; use std::ops::Deref; /// An optimized type for immutable identifiers. Represented as an [`arcstr::ArcStr`] internally. +/// +/// This type is one pointer wide. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SmallString(arcstr::ArcStr); @@ -159,3 +161,13 @@ impl schemars::JsonSchema for SmallString { String::json_schema(generator) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn small_str_size() { + assert_eq!(size_of::(), size_of::()); + } +} diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 020d2e72b..ebc942bd7 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -1236,4 +1236,15 @@ impl EnvVars { /// around invalid artifacts in rare cases. #[attr_added_in("0.8.23")] pub const UV_SKIP_WHEEL_FILENAME_CHECK: &'static str = "UV_SKIP_WHEEL_FILENAME_CHECK"; + + /// A comma separated list of variant provider backends that use the current environment instead + /// of an isolated environment. + /// + /// The requirements need to be installed in the current environment, no provider `requires` + /// will be installed. This option is intended for development and as an escape hatch where + /// isolation fails. + /// + /// Example: `UV_NO_PROVIDER_ISOLATION=gpu.provider:api,cpu,blas.backend` + #[attr_added_in("0.9.2")] + pub const UV_NO_PROVIDER_ISOLATION: &'static str = "UV_NO_PROVIDER_ISOLATION"; } diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index c295adf13..9383d7e8b 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -24,9 +24,11 @@ uv-git = { workspace = true } uv-normalize = { workspace = true } uv-once-map = { workspace = true } uv-pep440 = { workspace = true } +uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-redacted = { workspace = true } +uv-variants = { workspace = true } uv-workspace = { workspace = true } anyhow = { workspace = true } diff --git a/crates/uv-types/src/hash.rs b/crates/uv-types/src/hash.rs index 9d48c8221..039af2a01 100644 --- a/crates/uv-types/src/hash.rs +++ b/crates/uv-types/src/hash.rs @@ -10,6 +10,7 @@ use uv_distribution_types::{ }; use uv_normalize::PackageName; use uv_pep440::Version; +use uv_pep508::MarkerVariantsUniversal; use uv_pypi_types::{HashDigest, HashDigests, HashError, ResolverMarkerEnvironment}; use uv_redacted::DisplaySafeUrl; @@ -134,9 +135,11 @@ impl HashStrategy { // First, index the constraints by name. for (requirement, digests) in constraints { - if !requirement - .evaluate_markers(marker_env.map(ResolverMarkerEnvironment::markers), &[]) - { + if !requirement.evaluate_markers( + marker_env.map(ResolverMarkerEnvironment::markers), + &MarkerVariantsUniversal, + &[], + ) { continue; } @@ -178,9 +181,11 @@ impl HashStrategy { // package. let mut requirement_hashes = FxHashMap::>::default(); for (requirement, digests) in requirements { - if !requirement - .evaluate_markers(marker_env.map(ResolverMarkerEnvironment::markers), &[]) - { + if !requirement.evaluate_markers( + marker_env.map(ResolverMarkerEnvironment::markers), + &MarkerVariantsUniversal, + &[], + ) { continue; } diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 6054ba302..9f7eac724 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -5,7 +5,6 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use rustc_hash::FxHashSet; - use uv_cache::Cache; use uv_configuration::{BuildKind, BuildOptions, BuildOutput, SourceStrategy}; use uv_distribution_filename::DistFilename; @@ -17,6 +16,8 @@ use uv_distribution_types::{ use uv_git::GitResolver; use uv_normalize::PackageName; use uv_python::{Interpreter, PythonEnvironment}; +use uv_variants::VariantProviderOutput; +use uv_variants::cache::VariantProviderCache; use uv_workspace::WorkspaceCache; use crate::{BuildArena, BuildIsolation}; @@ -60,6 +61,7 @@ use crate::{BuildArena, BuildIsolation}; /// them. pub trait BuildContext { type SourceDistBuilder: SourceBuildTrait; + type VariantsBuilder: VariantsTrait; // Note: this function is async deliberately, because downstream code may need to // run async code to get the interpreter, to resolve the Python version. @@ -72,6 +74,9 @@ pub trait BuildContext { /// Return a reference to the Git resolver. fn git(&self) -> &GitResolver; + /// Return a reference to the variant cache. + fn variants(&self) -> &VariantProviderCache; + /// Return a reference to the build arena. fn build_arena(&self) -> &BuildArena; @@ -161,6 +166,14 @@ pub trait BuildContext { build_kind: BuildKind, version_id: Option<&'a str>, ) -> impl Future, impl IsBuildBackendError>> + 'a; + + /// Set up the variants for the given provider. + fn setup_variants<'a>( + &'a self, + backend_name: String, + provider: &'a uv_variants::variants_json::Provider, + build_output: BuildOutput, + ) -> impl Future> + 'a; } /// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies. @@ -189,6 +202,10 @@ pub trait SourceBuildTrait { ) -> impl Future> + 'a; } +pub trait VariantsTrait { + fn query(&self) -> impl Future>; +} + /// A wrapper for [`uv_installer::SitePackages`] pub trait InstalledPackagesProvider: Clone + Send + Sync + 'static { fn iter(&self) -> impl Iterator; diff --git a/crates/uv-variant-frontend/Cargo.toml b/crates/uv-variant-frontend/Cargo.toml new file mode 100644 index 000000000..32da51b1c --- /dev/null +++ b/crates/uv-variant-frontend/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "uv-variant-frontend" +version = "0.0.3" +description = "This is an internal component crate of uv" +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +uv-configuration = { workspace = true } +uv-distribution-types = { workspace = true } +uv-fs = { workspace = true } +uv-preview = { workspace = true } +uv-python = { workspace = true } +uv-static = { workspace = true } +uv-types = { workspace = true } +uv-virtualenv = { workspace = true } +uv-variants = { workspace = true } + +anstream = { workspace = true } +anyhow = { workspace = true } +fs-err = { workspace = true } +indoc = { workspace = true } +owo-colors = { workspace = true } +rustc-hash = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } diff --git a/crates/uv-variant-frontend/src/error.rs b/crates/uv-variant-frontend/src/error.rs new file mode 100644 index 000000000..b104e78c6 --- /dev/null +++ b/crates/uv-variant-frontend/src/error.rs @@ -0,0 +1,109 @@ +use std::env; +use std::fmt::{Display, Formatter}; +use std::io; +use std::path::PathBuf; +use std::process::ExitStatus; + +use owo_colors::OwoColorize; +use thiserror::Error; +use tracing::error; + +use uv_configuration::BuildOutput; +use uv_types::AnyErrorBuild; + +use crate::PythonRunnerOutput; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + Io(#[from] io::Error), + #[error("Failed to resolve requirements from {0}")] + RequirementsResolve(&'static str, #[source] AnyErrorBuild), + #[error("Failed to install requirements from {0}")] + RequirementsInstall(&'static str, #[source] AnyErrorBuild), + #[error("Failed to create temporary virtualenv")] + Virtualenv(#[from] uv_virtualenv::Error), + #[error("Provider plugin `{0}` is missing `requires`")] + MissingRequires(String), + #[error("Failed to run `{0}`")] + CommandFailed(PathBuf, #[source] io::Error), + #[error("The build backend returned an error")] + ProviderBackend(#[from] ProviderBackendError), + #[error("Failed to build PATH for build script")] + BuildScriptPath(#[source] env::JoinPathsError), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + #[error( + "There must be exactly one `requires` value matching the current environment for \ + {backend_name}, but there are {matching}" + )] + InvalidRequires { + backend_name: String, + matching: usize, + }, +} + +#[derive(Debug, Error)] +pub struct ProviderBackendError { + message: String, + exit_code: ExitStatus, + stdout: Vec, + stderr: Vec, +} + +impl Display for ProviderBackendError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.message, self.exit_code)?; + + let mut non_empty = false; + + if self.stdout.iter().any(|line| !line.trim().is_empty()) { + write!(f, "\n\n{}\n{}", "[stdout]".red(), self.stdout.join("\n"))?; + non_empty = true; + } + + if self.stderr.iter().any(|line| !line.trim().is_empty()) { + write!(f, "\n\n{}\n{}", "[stderr]".red(), self.stderr.join("\n"))?; + non_empty = true; + } + + if non_empty { + writeln!(f)?; + } + + write!( + f, + "\n{}{} This usually indicates a problem with the package or the build environment.", + "hint".bold().cyan(), + ":".bold() + )?; + + Ok(()) + } +} + +impl Error { + /// Construct an [`Error`] from the output of a failed command. + pub(crate) fn from_command_output( + message: String, + output: &PythonRunnerOutput, + level: BuildOutput, + ) -> Self { + match level { + BuildOutput::Stderr | BuildOutput::Quiet => { + Self::ProviderBackend(ProviderBackendError { + message, + exit_code: output.status, + stdout: vec![], + stderr: vec![], + }) + } + BuildOutput::Debug => Self::ProviderBackend(ProviderBackendError { + message, + exit_code: output.status, + stdout: output.stdout.clone(), + stderr: output.stderr.clone(), + }), + } + } +} diff --git a/crates/uv-variant-frontend/src/lib.rs b/crates/uv-variant-frontend/src/lib.rs new file mode 100644 index 000000000..309010908 --- /dev/null +++ b/crates/uv-variant-frontend/src/lib.rs @@ -0,0 +1,441 @@ +//! Detect compatible variants from a variant provider. + +mod error; + +use std::borrow::Cow; +use std::ffi::OsString; +use std::fmt::Write; +use std::io; +use std::path::{Path, PathBuf}; +use std::process::ExitStatus; +use std::{env, iter}; + +use fs_err as fs; +use indoc::formatdoc; +use rustc_hash::FxHashMap; +use tempfile::TempDir; +use tokio::io::AsyncBufReadExt; +use tokio::process::Command; +use tokio::sync::Semaphore; +use tracing::{Instrument, debug, info_span}; + +pub use crate::error::Error; +use uv_configuration::BuildOutput; +use uv_distribution_types::Requirement; +use uv_fs::{PythonExt, Simplified}; +use uv_preview::Preview; +use uv_python::{Interpreter, PythonEnvironment}; +use uv_static::EnvVars; +use uv_types::{BuildContext, BuildStack, VariantsTrait}; +use uv_variants::VariantProviderOutput; +use uv_variants::variants_json::Provider; +use uv_virtualenv::OnExisting; + +pub struct VariantBuild { + temp_dir: TempDir, + /// The backend to use. + backend_name: String, + /// The backend to use. + backend: Provider, + /// The virtual environment in which to build the source distribution. + venv: PythonEnvironment, + /// Whether to send build output to `stderr` or `tracing`, etc. + level: BuildOutput, + /// Modified PATH that contains the `venv_bin`, `user_path` and `system_path` variables in that + /// order. + modified_path: OsString, + /// Environment variables to be passed in. + environment_variables: FxHashMap, + /// Runner for Python scripts. + runner: PythonRunner, +} + +impl VariantsTrait for VariantBuild { + async fn query(&self) -> anyhow::Result { + Ok(self.build().await?) + } +} + +impl VariantBuild { + /// Create a virtual environment in which to run a variant provider. + pub async fn setup( + backend_name: String, + backend: &Provider, + interpreter: &Interpreter, + build_context: &impl BuildContext, + mut environment_variables: FxHashMap, + level: BuildOutput, + concurrent_builds: usize, + ) -> Result { + let temp_dir = build_context.cache().venv_dir()?; + + // TODO(konsti): This is not the right location to parse the env var. + let plugin_api = Self::plugin_api(&backend_name, backend, interpreter)?; + let no_isolation = + env::var(EnvVars::UV_NO_PROVIDER_ISOLATION).is_ok_and(|no_provider_isolation| { + no_provider_isolation + .split(',') + .any(|api| (api) == plugin_api) + }); + + // TODO(konsti): Integrate this properly with the configuration system. + if no_isolation { + debug!("Querying provider plugin without isolation: {backend_name}"); + + let runner = PythonRunner::new(concurrent_builds, level); + let env = PythonEnvironment::from_interpreter(interpreter.clone()); + // The unmodified path + let modified_path = OsString::from(env.scripts()); + + return Ok(Self { + temp_dir, + backend_name, + backend: backend.clone(), + venv: env, + level, + modified_path, + environment_variables, + runner, + }); + } + + // Create a virtual environment. + let venv = uv_virtualenv::create_venv( + temp_dir.path(), + interpreter.clone(), + uv_virtualenv::Prompt::None, + false, + // This is a fresh temp dir + OnExisting::Fail, + false, + false, + false, + Preview::default(), // TODO(konsti) + )?; + + // Resolve and install the provider requirements. + let requirements = backend + .requires + .as_ref() + .ok_or_else(|| Error::MissingRequires(backend_name.clone()))? + .iter() + .cloned() + .map(Requirement::from) + .collect::>(); + let resolved_requirements = build_context + .resolve(&requirements, &BuildStack::empty()) + .await + .map_err(|err| { + Error::RequirementsResolve("`variant.providers.requires`", err.into()) + })?; + build_context + .install(&resolved_requirements, &venv, &BuildStack::empty()) + .await + .map_err(|err| { + Error::RequirementsInstall("`variant.providers.requires`", err.into()) + })?; + + // Figure out what the modified path should be, and remove the PATH variable from the + // environment variables if it's there. + let user_path = environment_variables.remove(&OsString::from(EnvVars::PATH)); + + // See if there is an OS PATH variable. + let os_path = env::var_os(EnvVars::PATH); + + // Prepend the user supplied PATH to the existing OS PATH. + let modified_path = if let Some(user_path) = user_path { + match os_path { + // Prepend the user supplied PATH to the existing PATH. + Some(env_path) => { + let user_path = PathBuf::from(user_path); + let new_path = env::split_paths(&user_path).chain(env::split_paths(&env_path)); + Some(env::join_paths(new_path).map_err(Error::BuildScriptPath)?) + } + // Use the user supplied PATH. + None => Some(user_path), + } + } else { + os_path + }; + + // Prepend the venv bin directory to the modified path. + let modified_path = if let Some(path) = modified_path { + let venv_path = iter::once(venv.scripts().to_path_buf()).chain(env::split_paths(&path)); + env::join_paths(venv_path).map_err(Error::BuildScriptPath)? + } else { + OsString::from(venv.scripts()) + }; + + let runner = PythonRunner::new(concurrent_builds, level); + + Ok(Self { + temp_dir, + backend_name, + backend: backend.clone(), + venv, + level, + modified_path, + environment_variables, + runner, + }) + } + + // Not a method to be callable in the constructor. + pub fn plugin_api<'a>( + backend_name: &str, + backend: &'a Provider, + interpreter: &Interpreter, + ) -> Result, Error> { + if let Some(plugin_api) = &backend.plugin_api { + Ok(Cow::Borrowed(plugin_api)) + } else { + let requires = backend + .requires + .as_ref() + .ok_or_else(|| Error::MissingRequires(backend_name.to_string()))? + .iter() + .filter(|requirement| { + requirement.evaluate_markers(interpreter.markers(), &Vec::new().as_slice(), &[]) + }) + .collect::>(); + if let [requires] = requires.as_slice() { + Ok(requires.name.as_dist_info_name()) + } else { + Err(Error::InvalidRequires { + backend_name: backend_name.to_string(), + matching: requires.len(), + }) + } + } + } + + pub fn import(&self) -> Result { + let plugin_api = + Self::plugin_api(&self.backend_name, &self.backend, self.venv.interpreter())?; + let import = if let Some((path, object)) = plugin_api.split_once(':') { + format!("from {path} import {object} as backend") + } else { + format!("import {plugin_api} as backend") + }; + + Ok(formatdoc! {r#" + import sys + + if sys.path[0] == "": + sys.path.pop(0) + + {import} + + if callable(backend): + backend = backend() + "#}) + } + + /// Run a variant provider to infer compatible variants. + pub async fn build(&self) -> Result { + // Write the hook output to a file so that we can read it back reliably. + let out_file = self.temp_dir.path().join("output.json"); + + // Construct the appropriate build script based on the build kind. + let script = formatdoc! { + r#" + {backend} + + import json + + configs = backend.get_supported_configs() + features = {{config.name: config.values for config in configs}} + output = {{"namespace": backend.namespace, "features": features}} + + with open("{out_file}", "w") as fp: + fp.write(json.dumps(output)) + "#, + backend = self.import()?, + out_file = out_file.escape_for_python() + }; + + let span = info_span!( + "run_variant_provider_script", + backend_name = self.backend_name + ); + let output = self + .runner + .run_script( + &self.venv, + &script, + self.temp_dir.path(), + &self.environment_variables, + &self.modified_path, + ) + .instrument(span) + .await?; + if !output.status.success() { + return Err(Error::from_command_output( + format!( + "Call to variant backend failed in `{}`", + self.backend + .plugin_api + .as_deref() + .unwrap_or(&self.backend_name) + ), + &output, + self.level, + )); + } + + // Read as JSON. + let json = fs::read(&out_file).map_err(|err| { + Error::CommandFailed(self.venv.python_executable().to_path_buf(), err) + })?; + let output = serde_json::from_slice::(&json).map_err(|err| { + Error::CommandFailed(self.venv.python_executable().to_path_buf(), err.into()) + })?; + + Ok(output) + } +} + +/// A runner that manages the execution of external python processes with a +/// concurrency limit. +#[derive(Debug)] +struct PythonRunner { + control: Semaphore, + level: BuildOutput, +} + +#[derive(Debug)] +struct PythonRunnerOutput { + stdout: Vec, + stderr: Vec, + status: ExitStatus, +} + +impl PythonRunner { + /// Create a `PythonRunner` with the provided concurrency limit and output level. + fn new(concurrency: usize, level: BuildOutput) -> Self { + Self { + control: Semaphore::new(concurrency), + level, + } + } + + /// Spawn a process that runs a python script in the provided environment. + /// + /// If the concurrency limit has been reached this method will wait until a pending + /// script completes before spawning this one. + /// + /// Note: It is the caller's responsibility to create an informative span. + async fn run_script( + &self, + venv: &PythonEnvironment, + script: &str, + source_tree: &Path, + environment_variables: &FxHashMap, + modified_path: &OsString, + ) -> Result { + /// Read lines from a reader and store them in a buffer. + async fn read_from( + mut reader: tokio::io::Split>, + mut printer: Printer, + buffer: &mut Vec, + ) -> io::Result<()> { + loop { + match reader.next_segment().await? { + Some(line_buf) => { + let line_buf = line_buf.strip_suffix(b"\r").unwrap_or(&line_buf); + let line = String::from_utf8_lossy(line_buf).into(); + let _ = write!(printer, "{line}"); + buffer.push(line); + } + None => return Ok(()), + } + } + } + + let _permit = self.control.acquire().await.unwrap(); + + let mut child = Command::new(venv.python_executable()) + .args(["-c", script]) + .current_dir(source_tree.simplified()) + .envs(environment_variables) + .env(EnvVars::PATH, modified_path) + .env(EnvVars::VIRTUAL_ENV, venv.root()) + .env(EnvVars::CLICOLOR_FORCE, "1") + .env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?; + + // Create buffers to capture `stdout` and `stderr`. + let mut stdout_buf = Vec::with_capacity(1024); + let mut stderr_buf = Vec::with_capacity(1024); + + // Create separate readers for `stdout` and `stderr`. + let stdout_reader = tokio::io::BufReader::new(child.stdout.take().unwrap()).split(b'\n'); + let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).split(b'\n'); + + // Asynchronously read from the in-memory pipes. + let printer = Printer::from(self.level); + let result = tokio::join!( + read_from(stdout_reader, printer, &mut stdout_buf), + read_from(stderr_reader, printer, &mut stderr_buf), + ); + match result { + (Ok(()), Ok(())) => {} + (Err(err), _) | (_, Err(err)) => { + return Err(Error::CommandFailed( + venv.python_executable().to_path_buf(), + err, + )); + } + } + + // Wait for the child process to finish. + let status = child + .wait() + .await + .map_err(|err| Error::CommandFailed(venv.python_executable().to_path_buf(), err))?; + + Ok(PythonRunnerOutput { + stdout: stdout_buf, + stderr: stderr_buf, + status, + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Printer { + /// Send the provider output to `stderr`. + Stderr, + /// Send the provider output to `tracing`. + Debug, + /// Hide the provider output. + Quiet, +} + +impl From for Printer { + fn from(output: BuildOutput) -> Self { + match output { + BuildOutput::Stderr => Self::Stderr, + BuildOutput::Debug => Self::Debug, + BuildOutput::Quiet => Self::Quiet, + } + } +} + +impl Write for Printer { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + match self { + Self::Stderr => { + anstream::eprintln!("{s}"); + } + Self::Debug => { + debug!("{s}"); + } + Self::Quiet => {} + } + Ok(()) + } +} diff --git a/crates/uv-variants/Cargo.toml b/crates/uv-variants/Cargo.toml new file mode 100644 index 000000000..e58af9db4 --- /dev/null +++ b/crates/uv-variants/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "uv-variants" +version = "0.0.3" +description = "This is an internal component crate of uv" +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[dependencies] +uv-distribution-filename = { workspace = true } +uv-normalize = { workspace = true } +uv-once-map = { workspace = true } +uv-pep440 = { workspace = true, features = ["serde"] } +uv-pep508 = { workspace = true, features = ["serde"] } +uv-pypi-types = { workspace = true } + +indexmap = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +insta = "1.43.1" +itertools = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } + +[lints] +workspace = true diff --git a/crates/uv-variants/src/cache.rs b/crates/uv-variants/src/cache.rs new file mode 100644 index 000000000..02934b905 --- /dev/null +++ b/crates/uv-variants/src/cache.rs @@ -0,0 +1,23 @@ +use std::hash::BuildHasherDefault; +use std::sync::Arc; + +use rustc_hash::FxHasher; + +use uv_once_map::OnceMap; + +use crate::VariantProviderOutput; +use crate::variants_json::Provider; + +type FxOnceMap = OnceMap>; + +/// An in-memory cache for variant provider outputs. +#[derive(Default)] +pub struct VariantProviderCache(FxOnceMap>); + +impl std::ops::Deref for VariantProviderCache { + type Target = FxOnceMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/uv-variants/src/lib.rs b/crates/uv-variants/src/lib.rs new file mode 100644 index 000000000..3e1b0bb88 --- /dev/null +++ b/crates/uv-variants/src/lib.rs @@ -0,0 +1,36 @@ +use indexmap::IndexMap; + +use uv_pep508::{VariantFeature, VariantNamespace, VariantValue}; + +pub mod cache; +pub mod resolved_variants; +pub mod variant_lock; +pub mod variant_with_label; +pub mod variants_json; + +/// Wire format between with the Python shim for provider plugins. +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize)] +pub struct VariantProviderOutput { + /// The namespace of the provider. + pub namespace: VariantNamespace, + /// Features (in order) mapped to their properties (in order). + pub features: IndexMap>, +} + +/// The priority of a variant. +/// +/// When we first create a `PrioritizedDist`, there is no information about which variants are +/// supported yet. In universal resolution, we don't need to query this information. In platform +/// specific resolution, we determine a best variant for the current platform - if any - after +/// selecting a version. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum VariantPriority { + /// A variant wheel, it's unclear whether it's compatible. + /// + /// Variants only become supported after we ran the provider plugins. + Unknown, + /// A non-variant wheel. + NonVariant, + /// The supported variant wheel in this prioritized dist with the highest score. + BestVariant, +} diff --git a/crates/uv-variants/src/resolved_variants.rs b/crates/uv-variants/src/resolved_variants.rs new file mode 100644 index 000000000..213579aef --- /dev/null +++ b/crates/uv-variants/src/resolved_variants.rs @@ -0,0 +1,295 @@ +use std::sync::Arc; + +use rustc_hash::{FxHashMap, FxHashSet}; +use tracing::{debug, trace, warn}; + +use uv_distribution_filename::VariantLabel; +use uv_pep508::{VariantNamespace, VariantValue}; + +use crate::VariantProviderOutput; +use crate::variants_json::{DefaultPriorities, Variant, VariantsJsonContent}; + +#[derive(Debug, Clone)] +pub struct ResolvedVariants { + pub variants_json: VariantsJsonContent, + pub resolved_namespaces: FxHashMap>, + /// Namespaces where `enable-if` didn't match. + pub disabled_namespaces: FxHashSet, +} + +impl ResolvedVariants { + pub fn score_variant(&self, variant: &VariantLabel) -> Option> { + let Some(variants_properties) = self.variants_json.variants.get(variant) else { + warn!("Variant {variant} is missing in variants.json"); + return None; + }; + + score_variant( + &self.variants_json.default_priorities, + &self.resolved_namespaces, + &self.disabled_namespaces, + variants_properties, + ) + } +} + +/// Return a priority score for the variant (higher is better) or `None` if it isn't compatible. +pub fn score_variant( + default_priorities: &DefaultPriorities, + target_namespaces: &FxHashMap>, + disabled_namespaces: &FxHashSet, + variants_properties: &Variant, +) -> Option> { + for (namespace, features) in &**variants_properties { + for (feature, properties) in features { + let resolved_properties = target_namespaces + .get(namespace) + .and_then(|namespace| namespace.features.get(feature))?; + if !properties + .iter() + .any(|property| resolved_properties.contains(property)) + { + return None; + } + } + } + + // TODO(konsti): This is performance sensitive, prepare priorities and use a pairwise wheel + // comparison function instead. + let mut scores = Vec::new(); + for namespace in &default_priorities.namespace { + if disabled_namespaces.contains(namespace) { + trace!("Skipping disabled namespace: {}", namespace); + continue; + } + // Explicit priorities are optional, but take priority over the provider + let explicit_feature_priorities = default_priorities.feature.get(namespace); + let Some(target_variants) = target_namespaces.get(namespace) else { + // TODO(konsti): Can this even happen? + debug!("Missing namespace priority: {namespace}"); + continue; + }; + let feature_priorities = explicit_feature_priorities.into_iter().flatten().chain( + target_variants.features.keys().filter(|priority| { + explicit_feature_priorities.is_none_or(|explicit| !explicit.contains(priority)) + }), + ); + + for feature in feature_priorities { + let value_priorities: Vec = default_priorities + .property + .get(namespace) + .and_then(|namespace_features| namespace_features.get(feature)) + .into_iter() + .flatten() + .cloned() + .chain( + target_namespaces + .get(namespace) + .and_then(|namespace| namespace.features.get(feature).cloned()) + .into_iter() + .flatten(), + ) + .collect(); + let Some(wheel_properties) = variants_properties + .get(namespace) + .and_then(|namespace| namespace.get(feature)) + else { + scores.push(0); + continue; + }; + + // Determine the highest scoring property + // Reversed to give a higher score to earlier entries + let score = value_priorities.len() + - value_priorities + .iter() + .position(|feature| wheel_properties.contains(feature)) + .unwrap_or(value_priorities.len()); + scores.push(score); + } + } + Some(scores) +} + +#[cfg(test)] +mod tests { + use insta::assert_snapshot; + use itertools::Itertools; + use rustc_hash::{FxHashMap, FxHashSet}; + use serde_json::json; + + use std::sync::Arc; + + use uv_pep508::VariantNamespace; + + use crate::VariantProviderOutput; + use crate::resolved_variants::score_variant; + use crate::variants_json::{DefaultPriorities, Variant}; + + fn host() -> FxHashMap> { + serde_json::from_value(json!({ + "gpu": { + "namespace": "gpu", + "features": { + // Even though they are ahead of CUDA here, they are sorted below it due to the + // default priorities + "rocm": ["rocm68"], + "xpu": ["xpu1"], + "cuda": ["cu128", "cu126"] + } + }, + "cpu": { + "namespace": "cpu", + "features": { + "level": ["x86_64_v2", "x86_64_v1"] + } + }, + })) + .unwrap() + } + + // Default priorities in `variants.json` + fn default_priorities() -> DefaultPriorities { + serde_json::from_value(json!({ + "namespace": ["gpu", "cpu", "blas", "not_used_namespace"], + "feature": { + "gpu": ["cuda", "not_used_feature"], + "cpu": ["level"], + }, + "property": { + "cpu": { + "level": ["x86_64_v4", "x86_64_v3", "x86_64_v2", "x86_64_v1", "not_used_value"], + }, + }, + })) + .unwrap() + } + + fn score(variant: &Variant) -> Option { + let score = score_variant( + &default_priorities(), + &host(), + &FxHashSet::default(), + variant, + )?; + Some(score.iter().map(ToString::to_string).join(", ")) + } + + #[test] + fn incompatible_variants() { + let incompatible_namespace: Variant = serde_json::from_value(json!({ + "serial": { + "usb": ["usb3"], + }, + })) + .unwrap(); + assert_eq!(score(&incompatible_namespace), None); + + let incompatible_feature: Variant = serde_json::from_value(json!({ + "gpu": { + "rocm": ["rocm69"], + }, + })) + .unwrap(); + assert_eq!(score(&incompatible_feature), None); + + let incompatible_value: Variant = serde_json::from_value(json!({ + "gpu": { + "cuda": ["cu130"], + }, + })) + .unwrap(); + assert_eq!(score(&incompatible_value), None); + } + + #[test] + fn variant_sorting() { + let cu128_v2: Variant = serde_json::from_value(json!({ + "gpu": { + "cuda": ["cu128"], + }, + "cpu": { + "level": ["x86_64_v2"], + }, + })) + .unwrap(); + let cu128_v1: Variant = serde_json::from_value(json!({ + "gpu": { + "cuda": ["cu128"], + }, + "cpu": { + "level": ["x86_64_v1"], + }, + })) + .unwrap(); + let cu126_v2: Variant = serde_json::from_value(json!({ + "gpu": { + "cuda": ["cu126"], + }, + "cpu": { + "level": ["x86_64_v2"], + }, + })) + .unwrap(); + let cu126_v1: Variant = serde_json::from_value(json!({ + "gpu": { + "cuda": ["cu126"], + }, + "cpu": { + "level": ["x86_64_v1"], + }, + })) + .unwrap(); + let rocm: Variant = serde_json::from_value(json!({ + "gpu": { + "rocm": ["rocm68"], + }, + })) + .unwrap(); + let xpu: Variant = serde_json::from_value(json!({ + "gpu": { + "xpu": ["xpu1"], + }, + })) + .unwrap(); + // If the namespace is missing, the variant is compatible but below the higher ranking + // namespace + let v1: Variant = serde_json::from_value(json!({ + "cpu": { + "level": ["x86_64_v1"], + }, + })) + .unwrap(); + // The null variant is last. + let null: Variant = serde_json::from_value(json!({})).unwrap(); + + assert_snapshot!(score(&cu128_v2).unwrap(), @"2, 0, 0, 0, 5"); + assert_snapshot!(score(&cu128_v1).unwrap(), @"2, 0, 0, 0, 4"); + assert_snapshot!(score(&cu126_v2).unwrap(), @"1, 0, 0, 0, 5"); + assert_snapshot!(score(&cu126_v1).unwrap(), @"1, 0, 0, 0, 4"); + assert_snapshot!(score(&rocm).unwrap(), @"0, 0, 1, 0, 0"); + assert_snapshot!(score(&xpu).unwrap(), @"0, 0, 0, 1, 0"); + assert_snapshot!(score(&v1).unwrap(), @"0, 0, 0, 0, 4"); + assert_snapshot!(score(&null).unwrap(), @"0, 0, 0, 0, 0"); + + let wheels = vec![ + &cu128_v2, &cu128_v1, &cu126_v2, &cu126_v1, &rocm, &xpu, &v1, &null, + ]; + let mut wheels2 = wheels.clone(); + // "shuffle" + wheels2.reverse(); + wheels2.sort_by(|a, b| { + score_variant(&default_priorities(), &host(), &FxHashSet::default(), a) + .cmp(&score_variant( + &default_priorities(), + &host(), + &FxHashSet::default(), + b, + )) + // higher is better + .reverse() + }); + assert_eq!(wheels2, wheels); + } +} diff --git a/crates/uv-variants/src/variant_lock.rs b/crates/uv-variants/src/variant_lock.rs new file mode 100644 index 000000000..2c74b4539 --- /dev/null +++ b/crates/uv-variants/src/variant_lock.rs @@ -0,0 +1,94 @@ +use std::str::FromStr; + +use indexmap::IndexMap; +use serde::{Deserialize, Deserializer}; +use thiserror::Error; + +use uv_normalize::PackageName; +use uv_pep440::Version; +use uv_pep508::{UnnamedRequirementUrl, VariantFeature, VariantNamespace, VariantValue}; +use uv_pypi_types::VerbatimParsedUrl; + +#[derive(Debug, Error)] +pub enum VariantLockError { + #[error("Invalid resolved requirement format: {0}")] + InvalidResolvedFormat(String), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct VariantLock { + pub metadata: VariantLockMetadata, + pub provider: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct VariantLockMetadata { + pub created_by: String, + pub version: Version, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct VariantLockProvider { + pub resolved: Vec, + pub plugin_api: Option, + pub namespace: VariantNamespace, + pub properties: IndexMap>, +} + +/// A resolved requirement in the form `==` or ` @ ` +#[derive(Debug, Clone)] +pub enum VariantLockResolved { + Version(PackageName, Version), + Url(PackageName, Box), +} + +impl VariantLockResolved { + pub fn name(&self) -> &PackageName { + match self { + Self::Version(name, _) => name, + Self::Url(name, _) => name, + } + } +} + +impl FromStr for VariantLockResolved { + type Err = VariantLockError; + + fn from_str(resolved: &str) -> Result { + if let Some((name, version)) = resolved.split_once("==") { + Ok(Self::Version( + PackageName::from_str(name.trim()) + .map_err(|_| VariantLockError::InvalidResolvedFormat(resolved.to_string()))?, + Version::from_str(version.trim()) + .map_err(|_| VariantLockError::InvalidResolvedFormat(resolved.to_string()))?, + )) + } else if let Some((name, url)) = resolved.split_once(" @ ") { + Ok(Self::Url( + PackageName::from_str(name.trim()) + .map_err(|_| VariantLockError::InvalidResolvedFormat(resolved.to_string()))?, + Box::new( + VerbatimParsedUrl::parse_unnamed_url(url.trim()).map_err(|_| { + VariantLockError::InvalidResolvedFormat(resolved.to_string()) + })?, + ), + )) + } else { + Err(VariantLockError::InvalidResolvedFormat( + resolved.to_string(), + )) + } + } +} + +impl<'de> Deserialize<'de> for VariantLockResolved { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/crates/uv-variants/src/variant_with_label.rs b/crates/uv-variants/src/variant_with_label.rs new file mode 100644 index 000000000..e03784b8c --- /dev/null +++ b/crates/uv-variants/src/variant_with_label.rs @@ -0,0 +1,62 @@ +use crate::variants_json::Variant; +use uv_distribution_filename::VariantLabel; +use uv_pep508::{MarkerVariantsEnvironment, VariantFeature, VariantNamespace, VariantValue}; + +#[derive(Debug, Default)] +pub struct VariantWithLabel { + pub variant: Variant, + pub label: Option, +} + +impl MarkerVariantsEnvironment for VariantWithLabel { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + self.variant.contains_namespace(namespace) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + self.variant.contains_feature(namespace, feature) + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + self.variant.contains_property(namespace, feature, value) + } + + fn contains_base_namespace(&self, base: &str, namespace: &VariantNamespace) -> bool { + self.variant.contains_base_namespace(base, namespace) + } + + fn contains_base_feature( + &self, + base: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + ) -> bool { + self.variant.contains_base_feature(base, namespace, feature) + } + + fn contains_base_property( + &self, + base: &str, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + self.variant + .contains_base_property(base, namespace, feature, value) + } + + fn label(&self) -> Option<&str> { + self.label + .as_ref() + .map(uv_distribution_filename::VariantLabel::as_str) + } + + fn is_universal(&self) -> bool { + false + } +} diff --git a/crates/uv-variants/src/variants_json.rs b/crates/uv-variants/src/variants_json.rs new file mode 100644 index 000000000..d6e0c1820 --- /dev/null +++ b/crates/uv-variants/src/variants_json.rs @@ -0,0 +1,170 @@ +use std::collections::BTreeMap; +use std::ops::Deref; + +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; + +use uv_distribution_filename::VariantLabel; +use uv_pep508::{ + MarkerTree, MarkerVariantsEnvironment, Requirement, VariantFeature, VariantNamespace, + VariantValue, +}; +use uv_pypi_types::VerbatimParsedUrl; + +/// Mapping of namespaces in a variant +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Variant(BTreeMap>>); + +impl MarkerVariantsEnvironment for Variant { + fn contains_namespace(&self, namespace: &VariantNamespace) -> bool { + self.0.contains_key(namespace) + } + + fn contains_feature(&self, namespace: &VariantNamespace, feature: &VariantFeature) -> bool { + let Some(features) = self.0.get(namespace) else { + return false; + }; + + let Some(properties) = features.get(feature) else { + return false; + }; + + !properties.is_empty() + } + + fn contains_property( + &self, + namespace: &VariantNamespace, + feature: &VariantFeature, + value: &VariantValue, + ) -> bool { + let Some(features) = self.0.get(namespace) else { + return false; + }; + + let Some(values) = features.get(feature) else { + return false; + }; + + values.iter().any(|values| values == value) + } + + fn contains_base_namespace(&self, _prefix: &str, _namespace: &VariantNamespace) -> bool { + false + } + + fn contains_base_feature( + &self, + _prefix: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + ) -> bool { + false + } + + fn contains_base_property( + &self, + _prefix: &str, + _namespace: &VariantNamespace, + _feature: &VariantFeature, + _value: &VariantValue, + ) -> bool { + false + } +} + +impl Deref for Variant { + type Target = BTreeMap>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Combined index metadata for wheel variants. +/// +/// See +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct VariantsJsonContent { + /// Default provider priorities. + pub default_priorities: DefaultPriorities, + /// Mapping of namespaces to provider information. + pub providers: FxHashMap, + /// The supported, ordered properties for `AoT` providers. + pub static_properties: Option, + /// Mapping of variant labels to properties. + pub variants: FxHashMap, +} + +/// A `{name}-{version}.dist-info/variant.json` file. +#[derive(Debug, Clone, serde::Deserialize)] +#[allow(clippy::zero_sized_map_values)] +pub struct DistInfoVariantsJson { + pub variants: FxHashMap, +} + +impl DistInfoVariantsJson { + /// Returns the label for the current variant. + pub fn label(&self) -> Option<&VariantLabel> { + let mut keys = self.variants.keys(); + let label = keys.next()?; + if keys.next().is_some() { + None + } else { + Some(label) + } + } +} + +/// Default provider priorities +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct DefaultPriorities { + /// Default namespace priorities + pub namespace: Vec, + /// Default feature priorities + #[serde(default)] + pub feature: BTreeMap>, + /// Default property priorities + #[serde(default)] + pub property: BTreeMap>>, +} + +/// A `namespace :: feature :: property` entry. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct VariantPropertyType { + pub namespace: VariantNamespace, + pub feature: VariantFeature, + pub value: VariantValue, +} + +/// Provider information +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Provider { + /// Environment marker specifying when to enable the plugin. + #[serde( + skip_serializing_if = "uv_pep508::marker::ser::is_empty", + serialize_with = "uv_pep508::marker::ser::serialize", + default + )] + pub enable_if: MarkerTree, + /// Whether this is an install-time provider. `false` means that it is an `AoT` provider instead. + /// + /// Defaults to `true` + pub install_time: Option, + /// Whether this is an optional provider. + /// + /// If it is `true`, the provider is not used unless the user opts in to it. + /// + /// Defaults to `false` + #[serde(default)] + pub optional: bool, + /// Object reference to plugin class + pub plugin_api: Option, + /// Dependency specifiers for how to install the plugin + pub requires: Option>>, +} diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 14e7733fd..ec5daf74f 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -60,6 +60,7 @@ uv-tool = { workspace = true } uv-torch = { workspace = true } uv-trampoline-builder = { workspace = true } uv-types = { workspace = true } +uv-variants = { workspace = true } uv-version = { workspace = true } uv-virtualenv = { workspace = true } uv-warnings = { workspace = true } diff --git a/crates/uv/src/commands/pip/latest.rs b/crates/uv/src/commands/pip/latest.rs index 486d034d0..e82bd828f 100644 --- a/crates/uv/src/commands/pip/latest.rs +++ b/crates/uv/src/commands/pip/latest.rs @@ -68,7 +68,7 @@ impl LatestClient<'_> { // Determine whether there's a compatible wheel and/or source distribution. let mut best = None; - for (filename, file) in files.all() { + for (filename, file) in files.dists() { // Skip distributions uploaded after the cutoff. if let Some(exclude_newer) = self.exclude_newer.exclude_newer_package(package) { match file.upload_time_utc_ms.as_ref() { diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 908cacbf0..7fba5be51 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -15,7 +15,7 @@ use uv_cache_info::Timestamp; use uv_cli::ListFormat; use uv_client::{BaseClientBuilder, RegistryClientBuilder}; use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType}; -use uv_distribution_filename::DistFilename; +use uv_distribution_filename::{DistFilename, VariantLabel}; use uv_distribution_types::{ Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, RequiresPython, }; @@ -27,6 +27,7 @@ use uv_preview::Preview; use uv_python::PythonRequest; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference}; use uv_resolver::{ExcludeNewer, PrereleaseMode}; +use uv_variants::variants_json::DistInfoVariantsJson; use crate::commands::ExitStatus; use crate::commands::pip::latest::LatestClient; @@ -165,6 +166,13 @@ pub(crate) async fn pip_list( .map(|dist| Entry { name: dist.name().clone(), version: dist.version().clone(), + variant: dist + .read_variant_json() + .ok() + .flatten() + .as_ref() + .and_then(DistInfoVariantsJson::label) + .cloned(), latest_version: latest .get(dist.name()) .and_then(|filename| filename.as_ref()) @@ -203,6 +211,28 @@ pub(crate) async fn pip_list( }, ]; + // Variant column is only displayed if at least one package has a variant. + let variants = results + .iter() + .map(|dist| { + dist.read_variant_json() + .ok() + .flatten() + .as_ref() + .and_then(DistInfoVariantsJson::label) + .map(ToString::to_string) + }) + .collect_vec(); + if variants.iter().any(Option::is_some) { + columns.push(Column { + header: String::from("Variant"), + rows: variants + .into_iter() + .map(std::option::Option::unwrap_or_default) + .collect_vec(), + }); + } + // The latest version and type are only displayed if outdated. if outdated { columns.push(Column { @@ -334,6 +364,8 @@ struct Entry { name: PackageName, version: Version, #[serde(skip_serializing_if = "Option::is_none")] + variant: Option, + #[serde(skip_serializing_if = "Option::is_none")] latest_version: Option, #[serde(skip_serializing_if = "Option::is_none")] latest_filetype: Option, diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 5a906547f..3dd003ca5 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -12,6 +12,7 @@ use uv_fs::Simplified; use uv_install_wheel::read_record_file; use uv_installer::SitePackages; use uv_normalize::PackageName; +use uv_pep508::MarkerVariantsUniversal; use uv_preview::Preview; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest}; @@ -105,7 +106,7 @@ pub(crate) fn pip_show( metadata .requires_dist .iter() - .filter(|req| req.evaluate_markers(&markers, &[])) + .filter(|req| req.evaluate_markers(&markers, &MarkerVariantsUniversal, &[])) .map(|req| &req.name) .sorted_unstable() .dedup() @@ -123,7 +124,7 @@ pub(crate) fn pip_show( let requires = metadata .requires_dist .iter() - .filter(|req| req.evaluate_markers(&markers, &[])) + .filter(|req| req.evaluate_markers(&markers, &MarkerVariantsUniversal, &[])) .map(|req| &req.name) .collect_vec(); if !requires.is_empty() { diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index a0b172fba..27eb0ad0d 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -19,7 +19,7 @@ use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name, use uv_installer::SitePackages; use uv_normalize::PackageName; use uv_pep440::{Operator, Version, VersionSpecifier, VersionSpecifiers}; -use uv_pep508::{Requirement, VersionOrUrl}; +use uv_pep508::{MarkerVariantsUniversal, Requirement, VersionOrUrl}; use uv_preview::Preview; use uv_pypi_types::{ResolutionMetadata, ResolverMarkerEnvironment, VerbatimParsedUrl}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest}; @@ -254,7 +254,10 @@ impl<'env> DisplayDependencyGraph<'env> { if prune.contains(&requirement.name) { continue; } - if !requirement.marker.evaluate(markers, &[]) { + if !requirement + .marker + .evaluate(markers, &MarkerVariantsUniversal, &[]) + { continue; } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 9f61445de..9f69bbfa0 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -261,6 +261,9 @@ pub(crate) enum ProjectError { #[error(transparent)] Workspace(#[from] uv_workspace::WorkspaceError), + #[error(transparent)] + Distribution(#[from] uv_distribution::Error), + #[error(transparent)] PyprojectMut(#[from] uv_workspace::pyproject_mut::Error), diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 6ee926daf..1a8de7c25 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use serde::Serialize; use tracing::warn; + use uv_cache::Cache; use uv_cli::SyncFormat; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; @@ -17,14 +18,14 @@ use uv_configuration::{ TargetTriple, Upgrade, }; use uv_dispatch::BuildDispatch; -use uv_distribution::LoweredExtraBuildDependencies; +use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies, resolve_variants}; use uv_distribution_types::{ DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist, }; use uv_fs::{PortablePathBuf, Simplified}; use uv_installer::{InstallationStrategy, SitePackages}; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; -use uv_pep508::{MarkerTree, VersionOrUrl}; +use uv_pep508::{MarkerTree, MarkerVariantsUniversal, VersionOrUrl}; use uv_preview::{Preview, PreviewFeatures}; use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; @@ -702,7 +703,7 @@ pub(super) async fn do_sync( if !environments.is_empty() { if !environments .iter() - .any(|env| env.evaluate(&marker_env, &[])) + .any(|env| env.evaluate(&marker_env, &MarkerVariantsUniversal, &[])) { return Err(ProjectError::LockedPlatformIncompatibility( // For error reporting, we use the "simplified" @@ -724,24 +725,7 @@ pub(super) async fn do_sync( // Determine the tags to use for the resolution. let tags = resolution_tags(None, python_platform, venv.interpreter())?; - // Read the lockfile. - let resolution = target.to_resolution( - &marker_env, - &tags, - extras, - groups, - build_options, - &install_options, - )?; - - // Always skip virtual projects, which shouldn't be built or installed. - let resolution = apply_no_virtual_project(resolution); - - // If necessary, convert editable to non-editable distributions. - let resolution = apply_editable_mode(resolution, editable); - - // Constrain any build requirements marked as `match-runtime = true`. - let extra_build_requires = extra_build_requires.match_runtime(&resolution)?; + index_locations.cache_index_credentials(); // Populate credentials from the target. store_credentials_from_target(target); @@ -766,6 +750,71 @@ pub(super) async fn do_sync( // Read the build constraints from the lockfile. let build_constraints = target.build_constraints(); + // TODO(konsti): Don't do this twice, find a better way to resolve the current build_dispatch + // -> resolution -> hasher -> flat_index -> resolution loop. + let build_hasher = HashStrategy::default(); + let hasher = HashStrategy::default(); + let flat_index = { + let client = FlatIndexClient::new(client.cached_client(), client.connectivity(), cache); + let entries = client + .fetch_all(index_locations.flat_indexes().map(Index::url)) + .await?; + FlatIndex::from_entries(entries, Some(&tags), &hasher, build_options) + }; + + // Create a build dispatch. + let build_dispatch = BuildDispatch::new( + &client, + cache, + &build_constraints, + venv.interpreter(), + index_locations, + &flat_index, + dependency_metadata, + state.clone().into_inner(), + index_strategy, + config_setting, + config_settings_package, + build_isolation, + &extra_build_requires, + extra_build_variables, + link_mode, + build_options, + &build_hasher, + exclude_newer.clone(), + sources, + workspace_cache.clone(), + concurrency, + preview, + ); + + // TODO(konsti): Pass this into operations::install + let distribution_database = + DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads); + + // Read the lockfile. + let resolution = target + .to_resolution( + &marker_env, + &tags, + extras, + groups, + build_options, + &install_options, + distribution_database, + state.index().variant_priorities(), + ) + .await?; + + // Always skip virtual projects, which shouldn't be built or installed. + let resolution = apply_no_virtual_project(resolution); + + // If necessary, convert editable to non-editable distributions. + let resolution = apply_editable_mode(resolution, editable); + + // Constrain any build requirements marked as `match-runtime = true`. + let extra_build_requires = extra_build_requires.match_runtime(&resolution)?; + // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. let build_hasher = HashStrategy::default(); @@ -808,6 +857,18 @@ pub(super) async fn do_sync( preview, ); + // TODO(konsti): Pass this into operations::install + let distribution_database = + DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads); + let resolution = resolve_variants( + resolution, + &marker_env, + distribution_database, + state.index().variant_priorities(), + &tags, + ) + .await?; + let site_packages = SitePackages::from_environment(venv)?; // Sync the environment. @@ -866,7 +927,12 @@ fn apply_editable_mode(resolution: Resolution, editable: Option) - // Filter out any non-editable distributions. Some(EditableMode::Editable) => resolution.map(|dist| { - let ResolvedDist::Installable { dist, version } = dist else { + let ResolvedDist::Installable { + dist, + version, + variants_json, + } = dist + else { return None; }; let Dist::Source(SourceDist::Directory(DirectorySourceDist { @@ -888,13 +954,19 @@ fn apply_editable_mode(resolution: Resolution, editable: Option) - r#virtual: *r#virtual, url: url.clone(), }))), + variants_json: variants_json.clone(), version: version.clone(), }) }), // Filter out any editable distributions. Some(EditableMode::NonEditable) => resolution.map(|dist| { - let ResolvedDist::Installable { dist, version } = dist else { + let ResolvedDist::Installable { + dist, + variants_json, + version, + } = dist + else { return None; }; let Dist::Source(SourceDist::Directory(DirectorySourceDist { @@ -916,6 +988,7 @@ fn apply_editable_mode(resolution: Resolution, editable: Option) - r#virtual: *r#virtual, url: url.clone(), }))), + variants_json: variants_json.clone(), version: version.clone(), }) }), diff --git a/docs/reference/environment.md b/docs/reference/environment.md index ccc3826d1..734176d03 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -392,6 +392,18 @@ alias, for backwards compatibility. Equivalent to the `--no-progress` command-line argument. Disables all progress output. For example, spinners and progress bars. +### `UV_NO_PROVIDER_ISOLATION` +added in `0.9.2` + +A comma separated list of variant provider backends that use the current environment instead +of an isolated environment. + +The requirements need to be installed in the current environment, no provider `requires` +will be installed. This option is intended for development and as an escape hatch where +isolation fails. + +Example: `UV_NO_PROVIDER_ISOLATION=gpu.provider:api,cpu,blas.backend` + ### `UV_NO_SOURCES` added in `0.9.8` diff --git a/files/anyio-4.9.0-py3-none-any.whl b/files/anyio-4.9.0-py3-none-any.whl new file mode 100644 index 000000000..35d82af00 Binary files /dev/null and b/files/anyio-4.9.0-py3-none-any.whl differ diff --git a/files/built_by_uv-0.1.0-py3-none-any-cpu1.whl b/files/built_by_uv-0.1.0-py3-none-any-cpu1.whl new file mode 100644 index 000000000..995f390ce Binary files /dev/null and b/files/built_by_uv-0.1.0-py3-none-any-cpu1.whl differ diff --git a/files/built_by_uv-0.1.0-py3-none-any-cpu2.whl b/files/built_by_uv-0.1.0-py3-none-any-cpu2.whl new file mode 100644 index 000000000..995f390ce Binary files /dev/null and b/files/built_by_uv-0.1.0-py3-none-any-cpu2.whl differ diff --git a/files/built_by_uv-0.1.0-py3-none-any-cpu3.whl b/files/built_by_uv-0.1.0-py3-none-any-cpu3.whl new file mode 100644 index 000000000..995f390ce Binary files /dev/null and b/files/built_by_uv-0.1.0-py3-none-any-cpu3.whl differ diff --git a/files/built_by_uv-0.1.0-py3-none-any-cpu4.whl b/files/built_by_uv-0.1.0-py3-none-any-cpu4.whl new file mode 100644 index 000000000..995f390ce Binary files /dev/null and b/files/built_by_uv-0.1.0-py3-none-any-cpu4.whl differ diff --git a/files/built_by_uv-0.1.0-variants.json b/files/built_by_uv-0.1.0-variants.json new file mode 100644 index 000000000..1efd65a76 --- /dev/null +++ b/files/built_by_uv-0.1.0-variants.json @@ -0,0 +1,91 @@ +{ + "default-priorities": { + "feature": { + "cpu_level": [ + "x86_64_level" + ] + }, + "namespace": [ + "cpu_level" + ], + "property": { + "cpu_level": { + "x86_64_level": [ + "v4", + "v3", + "v2", + "v1" + ] + } + } + }, + "static-properties": { + "mathlib": { + "kind": [ + "very_fast_lib" + ] + } + }, + "providers": { + "cpu_level": { + "enable-if": "platform_machine == 'x86_64' or platform_machine == 'AMD64'", + "plugin-api": "cpu_level_provider", + "requires": [ + "cpu_level_provider >= 0.1, <0.2" + ] + }, + "mathlib": { + "install-time": false + } + }, + "variants": { + "cpu1": { + "cpu_level": { + "x86_64_level": [ + "v1" + ] + }, + "mathlib": { + "kind": [ + "very_fast_lib" + ] + } + }, + "cpu2": { + "cpu_level": { + "x86_64_level": [ + "v2" + ] + }, + "mathlib": { + "kind": [ + "very_fast_lib" + ] + } + }, + "cpu3": { + "cpu_level": { + "x86_64_level": [ + "v3" + ] + }, + "mathlib": { + "kind": [ + "very_fast_lib" + ] + } + }, + "cpu4": { + "cpu_level": { + "x86_64_level": [ + "v4" + ] + }, + "mathlib": { + "kind": [ + "very_fast_lib" + ] + } + } + } +} diff --git a/files/cpu_level_provider-0.1.0-py3-none-any.whl b/files/cpu_level_provider-0.1.0-py3-none-any.whl new file mode 100644 index 000000000..6d1908b15 Binary files /dev/null and b/files/cpu_level_provider-0.1.0-py3-none-any.whl differ diff --git a/files/cpu_level_provider-0.1.0.tar.gz b/files/cpu_level_provider-0.1.0.tar.gz new file mode 100644 index 000000000..933ff802f Binary files /dev/null and b/files/cpu_level_provider-0.1.0.tar.gz differ diff --git a/files/idna-3.10-py3-none-any.whl b/files/idna-3.10-py3-none-any.whl new file mode 100644 index 000000000..52759bdd2 Binary files /dev/null and b/files/idna-3.10-py3-none-any.whl differ diff --git a/files/sniffio-1.3.1-py3-none-any.whl b/files/sniffio-1.3.1-py3-none-any.whl new file mode 100644 index 000000000..04f44e47d Binary files /dev/null and b/files/sniffio-1.3.1-py3-none-any.whl differ diff --git a/files/uv_build-0.7.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/files/uv_build-0.7.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 000000000..ae451709c Binary files /dev/null and b/files/uv_build-0.7.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/files/uv_build-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/files/uv_build-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 000000000..fda1369d8 Binary files /dev/null and b/files/uv_build-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/files/variant-lock-incomplete.toml b/files/variant-lock-incomplete.toml new file mode 100644 index 000000000..c08e8d89a --- /dev/null +++ b/files/variant-lock-incomplete.toml @@ -0,0 +1,12 @@ +[metadata] +created-by = "tool-that-created-the-file" # Freeform string +version = "0.1" # PEP 440 version + +# We don't have static information for the requested provider + +[[provider]] +resolved = ["foo==0.0.1"] +plugin-api = "foo" +namespace = "foo" + +[provider.properties] diff --git a/files/variant-lock-none.toml b/files/variant-lock-none.toml new file mode 100644 index 000000000..8ceade36d --- /dev/null +++ b/files/variant-lock-none.toml @@ -0,0 +1,11 @@ +[metadata] +created-by = "tool-that-created-the-file" # Freeform string +version = "0.1" # PEP 440 version + +[[provider]] +resolved = ["cpu_level_provider==0.1"] +plugin-api = "cpu_level_provider" +namespace = "cpu_level" + +[provider.properties] +x86_64_level = [] diff --git a/files/variant-lock.toml b/files/variant-lock.toml new file mode 100644 index 000000000..68fc32261 --- /dev/null +++ b/files/variant-lock.toml @@ -0,0 +1,20 @@ +[metadata] +created-by = "tool-that-created-the-file" # Freeform string +version = "0.1" # PEP 440 version + +[[provider]] +resolved = ["cpu_level_provider==0.1"] +plugin-api = "cpu_level_provider" +namespace = "cpu_level" + +[provider.properties] +x86_64_level = ["v1", "v2"] + +[[provider]] +resolved = ["nvidia-variant-provider==0.0.1"] +plugin-api = "nvidia_variant_provider.plugin:NvidiaVariantPlugin" +namespace = "nvidia" + +[provider.properties] +cuda_version_lower_bound = ["12.8"] +sm_arch = ["100_real", "120_real", "70_real", "75_real", "80_real", "86_real", "90_real"] diff --git a/files_sdist/built_by_uv-0.1.0.tar.gz b/files_sdist/built_by_uv-0.1.0.tar.gz new file mode 100644 index 000000000..fadd6cec2 Binary files /dev/null and b/files_sdist/built_by_uv-0.1.0.tar.gz differ diff --git a/files_wheel/built_by_uv-0.1.0-py3-none-any.whl b/files_wheel/built_by_uv-0.1.0-py3-none-any.whl new file mode 100644 index 000000000..995f390ce Binary files /dev/null and b/files_wheel/built_by_uv-0.1.0-py3-none-any.whl differ diff --git a/scripts/packages/built-by-uv/pyproject.toml b/scripts/packages/built-by-uv/pyproject.toml index fe590308f..61c1d80ef 100644 --- a/scripts/packages/built-by-uv/pyproject.toml +++ b/scripts/packages/built-by-uv/pyproject.toml @@ -23,6 +23,19 @@ scripts = "scripts" data = "assets" headers = "header" +[variant.default-priorities] +namespace = ["cpu_level"] + +[variant.providers.cpu_level] +requires = ["cpu_level_provider >= 0.1, <0.2"] +enable-if = "platform_machine == 'x86_64' or platform_machine == 'AMD64'" + +[variant.providers.mathlib] +install-time = false + +[variant.static-properties] +mathlib = { "kind" = ["very_fast_lib"] } + [build-system] requires = ["uv_build>=0.8.0,<0.10.0"] build-backend = "uv_build" diff --git a/scripts/packages/cpu_level_provider/.python-version b/scripts/packages/cpu_level_provider/.python-version new file mode 100644 index 000000000..3e388a4ac --- /dev/null +++ b/scripts/packages/cpu_level_provider/.python-version @@ -0,0 +1 @@ +3.13.2 diff --git a/scripts/packages/cpu_level_provider/README.md b/scripts/packages/cpu_level_provider/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/packages/cpu_level_provider/pyproject.toml b/scripts/packages/cpu_level_provider/pyproject.toml new file mode 100644 index 000000000..e1e33a81b --- /dev/null +++ b/scripts/packages/cpu_level_provider/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "cpu-level-provider" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [ + { name = "konstin", email = "konstin@mailbox.org" } +] +requires-python = ">=3.13.2" +dependencies = [] + +[project.scripts] +cpu-level-provider = "cpu_level_provider:main" + +[build-system] +requires = ["uv_build>=0.7.19,<0.8"] +build-backend = "uv_build" diff --git a/scripts/packages/cpu_level_provider/src/cpu_level_provider/__init__.py b/scripts/packages/cpu_level_provider/src/cpu_level_provider/__init__.py new file mode 100644 index 000000000..55168196f --- /dev/null +++ b/scripts/packages/cpu_level_provider/src/cpu_level_provider/__init__.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +import os +import platform +import sys +from dataclasses import dataclass + +namespace: str = "cpu_level" +dynamic: bool = False + + +@dataclass +class VariantFeatureConfigType: + name: str + values: list[str] + + +@dataclass +class VariantPropertyType: + name: str + values: list[str] + + +def get_supported_configs( +) -> list[VariantFeatureConfigType]: + if override := os.getenv("UV_CPU_LEVEL_OVERRIDE"): + try: + current_level = int(override) + except ValueError: + raise ValueError( + f"Invalid CPU level override (`UV_CPU_LEVEL_OVERRIDE`): {override}" + ) + else: + current_level = get_x86_64_level() or 1 + if current_level is None: + return [] + + supported_levels = [f"v{i}" for i in range(current_level, 0, -1)] + return [VariantFeatureConfigType(name="x86_64_level", values=supported_levels)] + + +def get_x86_64_level() -> int | None: + """Returns the highest supported x86_64 microarchitecture level (v1, v2, v3, or v4). + + TODO(konsti): Replace with non-slop""" + if platform.machine() not in ("x86_64", "AMD64"): + return None + + # Get CPU features from /proc/cpuinfo on Linux + if sys.platform == "linux": + with open("/proc/cpuinfo", "r") as f: + cpuinfo = f.read() + + # Extract flags line + flags_line = None + for line in cpuinfo.splitlines(): + if line.startswith("flags"): + flags_line = line + break + + if not flags_line: + return 1 + + flags = set(flags_line.split()[2:]) # Skip "flags" and ":" + + else: + # For other platforms, we can't easily detect features + return None + + # Check for v4 features (AVX512) + v4_features = {"avx512f", "avx512bw", "avx512cd", "avx512dq", "avx512vl"} + if v4_features.issubset(flags): + return 4 + + # Check for v3 features (AVX2) + v3_features = {"avx", "avx2", "bmi1", "bmi2", "fma", "movbe"} + if v3_features.issubset(flags): + return 3 + + # Check for v2 features (SSE4.2) + v2_features = { + "cmpxchg16b", + "lahf_lm", + "popcnt", + "sse3", + "ssse3", + "sse4_1", + "sse4_2", + } + if v2_features.issubset(flags): + return 2 + + # Default to v1 (baseline x86_64) + return 1 + + +def main(): + print(get_supported_configs()) + + +if __name__ == "__main__": + main() diff --git a/scripts/packages/cpu_level_provider/uv.lock b/scripts/packages/cpu_level_provider/uv.lock new file mode 100644 index 000000000..1cd64b9e1 --- /dev/null +++ b/scripts/packages/cpu_level_provider/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = ">=3.13.2" + +[[package]] +name = "cpu-level-provider" +version = "0.1.0" +source = { editable = "." } diff --git a/scripts/packages/cpu_user/pyproject.toml b/scripts/packages/cpu_user/pyproject.toml new file mode 100644 index 000000000..15a9ce1bc --- /dev/null +++ b/scripts/packages/cpu_user/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "cpu-user" +version = "0.1.0" +description = "A package using a dependency with variants" +authors = [ + { name = "konstin", email = "konstin@mailbox.org" } +] +requires-python = ">=3.13.2" +dependencies = ["built_by_uv", "sniffio"] + +[[tool.uv.index]] +name = "files" +url = "../../../files" +format = "flat" +default = true + +[build-system] +requires = ["uv_build>=0.8.0,<0.9"] +build-backend = "uv_build" diff --git a/scripts/packages/cpu_user/src/cpu_user/__init__.py b/scripts/packages/cpu_user/src/cpu_user/__init__.py new file mode 100644 index 000000000..da4fb3918 --- /dev/null +++ b/scripts/packages/cpu_user/src/cpu_user/__init__.py @@ -0,0 +1,2 @@ +def hello() -> str: + return "Hello from cpu-user!" diff --git a/scripts/packages/cpu_user/src/cpu_user/py.typed b/scripts/packages/cpu_user/src/cpu_user/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/packages/cpu_user/uv.lock b/scripts/packages/cpu_user/uv.lock new file mode 100644 index 000000000..bb0ecff52 --- /dev/null +++ b/scripts/packages/cpu_user/uv.lock @@ -0,0 +1,61 @@ +version = 1 +revision = 3 +requires-python = ">=3.13.2" + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "../../../files" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +wheels = [ + { path = "anyio-4.9.0-py3-none-any.whl" }, +] + +[[package]] +name = "built-by-uv" +version = "0.1.0" +source = { registry = "../../../files" } +dependencies = [ + { name = "anyio" }, +] +wheels = [ + { path = "built_by_uv-0.1.0-py3-none-any-cpu1.whl" }, + { path = "built_by_uv-0.1.0-py3-none-any-cpu2.whl" }, + { path = "built_by_uv-0.1.0-py3-none-any-cpu3.whl" }, + { path = "built_by_uv-0.1.0-py3-none-any-cpu4.whl" }, +] +variants-json = { path = "built_by_uv-0.1.0-variants.json" } + +[[package]] +name = "cpu-user" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "built-by-uv" }, + { name = "sniffio" }, +] + +[package.metadata] +requires-dist = [ + { name = "built-by-uv" }, + { name = "sniffio" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "../../../files" } +wheels = [ + { path = "idna-3.10-py3-none-any.whl" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "../../../files" } +wheels = [ + { path = "sniffio-1.3.1-py3-none-any.whl" }, +] diff --git a/scripts/packages/torch_user/.python-version b/scripts/packages/torch_user/.python-version new file mode 100644 index 000000000..3e388a4ac --- /dev/null +++ b/scripts/packages/torch_user/.python-version @@ -0,0 +1 @@ +3.13.2 diff --git a/scripts/packages/torch_user/README.md b/scripts/packages/torch_user/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/packages/torch_user/pyproject.toml b/scripts/packages/torch_user/pyproject.toml new file mode 100644 index 000000000..b09088339 --- /dev/null +++ b/scripts/packages/torch_user/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "torch-user" +version = "1.0.0" +requires-python = "==3.13.*" +dependencies = ["torch"] + +[[tool.uv.index]] +name = "torch-variants" +url = "https://wheelnext.github.io/variants-index/v0.0.2" +# default = true # triton is missing + +[build-system] +requires = ["uv_build>=0.8.0,<0.9.0"] +build-backend = "uv_build" diff --git a/scripts/packages/torch_user/src/torch_user/__init__.py b/scripts/packages/torch_user/src/torch_user/__init__.py new file mode 100644 index 000000000..15e435ed5 --- /dev/null +++ b/scripts/packages/torch_user/src/torch_user/__init__.py @@ -0,0 +1,2 @@ +def hello() -> str: + return "Hello from torch-user!" diff --git a/scripts/packages/torch_user/src/torch_user/py.typed b/scripts/packages/torch_user/src/torch_user/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/packages/torch_user/uv.lock b/scripts/packages/torch_user/uv.lock new file mode 100644 index 000000000..82ddf3f38 --- /dev/null +++ b/scripts/packages/torch_user/uv.lock @@ -0,0 +1,1433 @@ +version = 1 +revision = 3 +requires-python = "==3.13.*" +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform != 'linux' or (variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)", +] + +[[package]] +name = "dpcpp-cpp-rt" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-openmp", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-sycl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/79/04d766ab2585b2e5586dfc5e39fd1a29eb61d415b1538ec5c2d02e7a4995/dpcpp_cpp_rt-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:e601799b8698342116d165d52b9a86bddd1acca6b3522e656417e5128229f66e", size = 28795, upload-time = "2025-08-13T18:31:52.648Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, +] + +[[package]] +name = "impi-rt" +version = "2021.16.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/44/6867aebd60d8b8bcf8f7e3b1fa781debd4fc3df16bfb1525e732570ea214/impi_rt-2021.16.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:0f70499bc42eab6923c0ae8fa6dd409c047d93626d686ac21d04e295b120bb95", size = 84635349, upload-time = "2025-08-13T18:32:10.885Z" }, +] + +[[package]] +name = "intel-cmplr-lib-rt" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a9/a3d904890e2c5ea906db65c90b912553a94da44d4e81f26641b0d124cedb/intel_cmplr_lib_rt-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:13e53cf4ee3d910db5ed46cf994c99dd391b50d6efb553cf3dd029b866d79a86", size = 47494270, upload-time = "2025-08-13T18:32:03.056Z" }, +] + +[[package]] +name = "intel-cmplr-lib-ur" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "umf", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/18/28198666e0ee1709a471c3e376001146e629214d67553bc9fa3e7bbc9b8e/intel_cmplr_lib_ur-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:ecc6eba009ead8ea819d931107ba11e2b502f5d8ebbd287e4901074a764f9792", size = 29313080, upload-time = "2025-08-13T18:31:36.833Z" }, +] + +[[package]] +name = "intel-cmplr-lic-rt" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/87/c2229d6dbda1a9b949f009c9a0b1f14141b2725bb526964e6e263328935a/intel_cmplr_lic_rt-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:aeaad4648cc0baaefffb0a0f8c1c2d1de01e5396711c3e69e36fab90030889d8", size = 18909, upload-time = "2025-08-13T18:31:54.25Z" }, +] + +[[package]] +name = "intel-opencl-rt" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "intel-cmplr-lic-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "tbb", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2d/bb5fdf03f267afa3b9d0e1a6d92ebb487e35aee3462d19b38669b4f8b455/intel_opencl_rt-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:5f8c5f9c2840ebc0360524f46fb13f28a1c45a98483f78f0c488b3ec4c411d28", size = 193043449, upload-time = "2025-08-13T18:31:45.158Z" }, +] + +[[package]] +name = "intel-openmp" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "intel-cmplr-lib-ur", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0a/160013c2e8920e7f4d5bfb0b46dbe9fdd37ff50adcc11a1bad674f22bd78/intel_openmp-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:4656e8e864998db776dbb6d045067f75c227f78c72943e07e15b0d1d65ff45c2", size = 73411361, upload-time = "2025-08-13T18:31:57.96Z" }, +] + +[[package]] +name = "intel-pti" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/de/170216cc7776b239ddc4825911a109b10c8396a57bab46c177d8ef404e50/intel_pti-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:926982bd57a1f73ba9e604b12394fbcf8443076c75990cb70b10a47b8e97effe", size = 1122645, upload-time = "2025-08-13T18:30:54.3Z" }, +] + +[[package]] +name = "intel-sycl-rt" +version = "2025.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "intel-cmplr-lib-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-cmplr-lib-ur", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-cmplr-lic-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/5621dd3aa9e078ebed8b5b73fe86986e4554398466f218b3125f7f76c7a9/intel_sycl_rt-2025.2.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:d44dd7e76cf7b624de9106f3d9c3d541f6f20d83e861f724b055867fd351b064", size = 113799221, upload-time = "2025-08-13T18:31:30.774Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +] + +[[package]] +name = "mkl" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "intel-openmp", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "tbb", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/7b/f5b1b84eb0a2a6e145fc31b4e6b1c59690dcb088734197da8f299caf7c67/mkl-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:974b4e222cc94e8d3b67213a361c8ac25d432cc4fccc5f2f00aa15c4e67cc203", size = 190225238, upload-time = "2025-06-24T13:16:12.866Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/fc/7b6fd4d22c8c4dc5704430140d8b3f520531d4fe7328b8f8d03f5a7950e8/networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad", size = 2511464, upload-time = "2025-11-24T03:03:47.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c7/d64168da60332c17d24c0d2f08bdf3987e8d1ae9d84b5bbd0eec2eb26a55/networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f", size = 2063713, upload-time = "2025-11-24T03:03:45.21Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "12.6.4.1" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:df7183bf5966e9bc93d3ada2411765361addb5de75771958c9b55e2e97cd6d98" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-12.6.4.1-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:04fcd192f191c4472937b1cb6030a4a3df8fddc06558026f78bb6eb5b50db274" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cublas/nvidia_cublas-12.6.4.1-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cublas" +version = "12.8.4.1" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-12.8.4.1-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:9cf6ddbd74f6c0b482b04fbc1cb090637ee35b5a9824484f614243f103c1b6ad" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-12.8.4.1-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:c5b108a0a39f8f0d62b331fdfa4da3f7b98a109451039c31e55538f2d6d522c1" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cublas/nvidia_cublas-12.8.4.1-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cublas" +version = "12.9.1.4" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-12.9.1.4-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:32d0377efae9e0895132426d9398140016eab1dbb1df4e8f509d0e579b8a4872" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cublas/nvidia_cublas-12.9.1.4-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cublas" +version = "13.0.0.19" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-13.0.0.19-py3-none-manylinux_2_27_aarch64-cu13.whl", hash = "sha256:b19c8b063cdfa3e3219eb27fe9fc88fa628bf8a56c3ec79da5b653f89dabb52f" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cublas/nvidia_cublas-13.0.0.19-py3-none-manylinux_2_27_x86_64-cu13.whl", hash = "sha256:e5e11ff4150e7b4a9e9d6dab9458edfe844d57aa340a1a15f875d1d36081a747" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cublas/nvidia_cublas-13.0.0.19-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cuda-cupti" +version = "12.6.80" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.6.80-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:6fb7f7812e314c17ed2409e533a0fb257c3fe08aedaa789b14011630bd9179f8" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.6.80-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:ece63e9d3f63411fe9773d90ce80f66ca55390fda97af226e82f47173b70ddbc" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.6.80-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:2491292c35867239765f7f565f93e4eba45e096736a2d2e208fce168708de374" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:62b63f089ff3098ff649b6eb8e69f72726b30706dfaee861f5aba1932a5a012c" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-cupti/nvidia_cuda_cupti-12.6.80-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cuda-cupti" +version = "12.8.90" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:875d557ef69a2c9f53b6f3dfc3e0536904656122e966031356a3f2fe2570538d" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:411f9aabbce0550bc0bdabbe34fe484e30b122e5d547065adb6de14db2b9b3c0" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-cupti/nvidia_cuda_cupti-12.8.90-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cuda-cupti" +version = "12.9.79" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-12.9.79-py3-none-manylinux_2_25_x86_64-cu12.whl", hash = "sha256:ee830bc282e18aad3864f0ab483f24797444136405199880f763fcf1e21ded28" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-cupti/nvidia_cuda_cupti-12.9.79-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.48" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-13.0.48-py3-none-manylinux_2_25_aarch64-cu13.whl", hash = "sha256:3ca7a5818ca11eb1e28e34809d02575233718507c2e16dd10f385aa036d702f1" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-cupti/nvidia_cuda_cupti-13.0.48-py3-none-manylinux_2_25_x86_64-cu13.whl", hash = "sha256:e455936be421b5739ec20109f8ed400d49619b64d512704f0f4e2f887c7aa472" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-cupti/nvidia_cuda_cupti-13.0.48-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "12.6.77" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.6.77-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:64fcee4bd3c7a1356dcef29f19e715bd5ceaad4c7008395389fffaebd9b9ac49" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.6.77-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:ddc3abc7a8f5d8d713cf1a84805c9e430ae1912088cb46972c02a019a9d7af73" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.6.77-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "12.8.93" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu12.whl", hash = "sha256:34fb6e7d8f672436ad6f37fb566e1288c2ba293afb03b77cb0f3671b0ea8398f" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:41511a84156f4bef2a69d3d6ddf472b1925a01d4142676b9f58f3c7683b41913" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.8.93-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "12.9.86" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu12.whl", hash = "sha256:3f78b7e790267d82929b6aaba0d66ad5b1fe6d14a5651de6311921d6221143c8" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-12.9.86-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.48" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-13.0.48-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu13.whl", hash = "sha256:a12189cf426291f49f043de75fcacf62b34a5e0334ac4b98062b5eb852755785" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-13.0.48-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:9b3c97de94e92e76685701c84c9687f870d89a538aa8c43a1c5ad3b8d416a312" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-13.0.48-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cuda-runtime" +version = "12.6.77" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.6.77-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:12ab83d25cbadfc66f25ebe1339c2b7abbab76aa2338dc9a3ccdbb1a2a09570e" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:7f44e9b1a562e10db961ecf306c9d6de38b8f14d9441e8edf4707a404a571ae6" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.6.77-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:4d067b25abfd1318d7dee6926c705da07ee7e4d74f2f2e263b7969f069d82a2e" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:729ffd53c9f56c49e8ba7c66cadca658c4ae7bce490082ef30d6e450c5da2c49" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-runtime/nvidia_cuda_runtime-12.6.77-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cuda-runtime" +version = "12.8.90" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:8ddda5b491991e8e19aa0dfb34fd26d804dcfa2648dbd6dab14c0dcab52688c7" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:e9ff83ca5bf5c6c5392c7de9626b13ce098a71a3845f0402e9524c4da3986f2b" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-runtime/nvidia_cuda_runtime-12.8.90-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cuda-runtime" +version = "12.9.79" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:1e40d01b85ebdf7ef70de3ea85e469584a07d8ff14c0171e799e32c74ecb4616" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-runtime/nvidia_cuda_runtime-12.9.79-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.48" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-13.0.48-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:e1e1f077b9398f1ebe287059f0bcafbee48dfe7700c60d2e60d8324174383c0b" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cuda-runtime/nvidia_cuda_runtime-13.0.48-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:0101829c15b9d2ab9a63037cbc7bcf51dd75b9322013ec6df5476680ef27687e" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cuda-runtime/nvidia_cuda_runtime-13.0.48-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cudnn" +version = "9.10.2.21" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "12.6.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)" }, + { name = "nvidia-cublas", version = "12.8.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)" }, + { name = "nvidia-cublas", version = "12.9.1.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)" }, + { name = "nvidia-cublas", version = "13.0.0.19", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.10.2.21-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:e2e67d4a1b1689e8d51285d43f752e6746739e9a725aa0fd3c6d32c1b01c67c5" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.10.2.21-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:e6c42a677b5bc4e9f47c8958e9dbc3951758a5b8d2cbb041bdb9a1aa91533f35" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cudnn/nvidia_cudnn-9.10.2.21-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cudnn" +version = "9.13.0.50" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "13.0.0.19", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.13.0.50-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:04e8717648988aa125248fbf8ba7ec0f93194953998d300d5b928dc8718e81af" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.13.0.50-py3-none-manylinux_2_27_aarch64-cu13.whl", hash = "sha256:ac31f6c1fe6e8b14250d8e8253926ecd60f2055c433478d8ab29e688b8d99842" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.13.0.50-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:b60c0de61ffba41586a09745a24a4cfcdc81c2c269d8e799f6430af415e176d2" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cudnn/nvidia_cudnn-9.13.0.50-py3-none-manylinux_2_27_x86_64-cu13.whl", hash = "sha256:9f338245511dd81d650ab1dbe2842ac19114db6811f78dde1371db6a9911c1aa" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cudnn/nvidia_cudnn-9.13.0.50-variants.json", hash = "sha256:8ebd613dfbd3f403556ecd2edfbb9851cdc92030307ff2bfb0efaa043763783a" } + +[[package]] +name = "nvidia-cufft" +version = "11.3.0.4" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.6.85", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.3.0.4-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:d898d1c310ff66a06763bb1cbc89e0f90615f0e1cf2750f3cea4fc04bec49f4a" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.3.0.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:74f8153ff68c2fae853b171a38a0aeb8a114e1a5f471196727846cc5df161208" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.3.0.4-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:bb277140b70219e4fc64f57e411986cc93ed43fb48e375b6e0de7412fc2ebe9e" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufft/nvidia_cufft-11.3.0.4-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cufft" +version = "11.3.3.83" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:743259f924fd95ee62f779c15411f18d5dd5d86cc468dae3b261143b32e0e9ac" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:a7d515cdd02d61c790fdbb0fbf9214899ca58f4b1936166dffdec78ad7d2a637" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufft/nvidia_cufft-11.3.3.83-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cufft" +version = "11.4.1.4" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.9.86", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:9deb59189a1f8d01fe56969b2a68cd76fe67964b8ea7630f815b6f71a9f4529c" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufft/nvidia_cufft-11.4.1.4-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.15" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "13.0.39", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-12.0.0.15-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:deb86f65cb0878daa0efa1ac5cd1331382add1b1ff455ade9b4619b2a8020634" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufft/nvidia_cufft-12.0.0.15-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:77ec6f42887fe69740c00b196dd318b0af2ad8c7b48cddd11353d5b0d041f951" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufft/nvidia_cufft-12.0.0.15-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cufile" +version = "1.11.1.6" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:b3235a5626019b0e456843c17ab0c26a3e0e6092f2cda54f240136f4ed000ee3" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.11.1.6-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:eb3410223de6ec0e815ff8abbbaf26bc3424414eef33aa0d5b5a4984d8867ea7" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufile/nvidia_cufile-1.11.1.6-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cufile" +version = "1.13.1.3" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:e594828886b8afc91af86eba37e7a224701e86df4e602cf11dd30d381ef0459a" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.13.1.3-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:bf54ba47ed25f66130faddd1cb985cdd404ac5ee3a686b4973440e9f63d20bb6" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufile/nvidia_cufile-1.13.1.3-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cufile" +version = "1.14.1.1" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.14.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:d4a3bb6e6fb5d82324ed92cb72b6c60ea2e7d09c46f36159909dc7710aba5ab1" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufile/nvidia_cufile-1.14.1.1-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cufile" +version = "1.15.0.42" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.15.0.42-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:51c4a853b5ac1ee4afac05de6dd92053e4618889191cd0f01ed1016da2d391d1" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cufile/nvidia_cufile-1.15.0.42-py3-none-manylinux_2_27_aarch64-cu13.whl", hash = "sha256:a54967d8a45294480ab8975f1445df02cc581bcce37fe9a710feaebc12f1a386" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cufile/nvidia_cufile-1.15.0.42-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-curand" +version = "10.3.7.77" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.7.77-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:bb2899d1be2a02022cf4ff98ab709a7a68abd27a5836eb44a09376721f9f9a92" }, + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.7.77-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:5876bcc891f5728520bee6cfc93608410f143d4b833d5453ae5428691be1f9f2" }, + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:c1aad157be2658ed2634f3ec02ebf2c8b227b2de39aedec3a643b9d576223334" }, + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.7.77-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:aa78f7ca090f570464dd5ba924ed9ad4de57468818229c738fcef83905790b99" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-curand/nvidia_curand-10.3.7.77-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-curand" +version = "10.3.9.90" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.9.90-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:b5ae1f2b0d76781e3dc30ab5ac60d6f3e213a9dfba47eb2c9bf897e175d15297" }, + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.9.90-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:443b04de9ee86ff6806b5ef62f9c68cac665699d2253b3fa66ac16b632cc8da9" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-curand/nvidia_curand-10.3.9.90-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-curand" +version = "10.3.10.19" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.3.10.19-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:05a1ef1ab9d10f47c609c7d21e425d9ccb3e440dec79625ef0730733b07463d1" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-curand/nvidia_curand-10.3.10.19-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64-cu13.whl", hash = "sha256:d5a97443fbac16a9fea4b5c70a2a30031bf0813375ad2e91b100aa97b02fd781" }, + { url = "https://variants-index.wheelnext.dev/nvidia-curand/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64-cu13.whl", hash = "sha256:dca5f865cd21461c4aad57472a04367abd3235b41157e8cc3f530e514b298272" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-curand/nvidia_curand-10.4.0.35-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cusolver" +version = "11.7.1.2" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "12.6.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6')" }, + { name = "nvidia-cusparse", version = "12.5.4.2", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6')" }, + { name = "nvidia-nvjitlink", version = "12.6.85", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.1.2-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:967ee47af4f1ddc3c13ddcbf3806a3c2f6ce959cb33bf2420b5297dfb34a5703" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.1.2-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:b69d1d65bc640ba5ae51454999faf0951caac0c7448cebe065370e75f0509f23" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:8ddfbc89aa6fc248d45123e7d42d654bbd245471bcbb8111b8ca3a86fb981bff" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.1.2-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:8c0f7adce9d977f2ec393ad5615f7836b3a3e5f44c84961e4a648f5f00557c93" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusolver/nvidia_cusolver-11.7.1.2-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cusolver" +version = "11.7.3.90" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "12.8.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8')" }, + { name = "nvidia-cusparse", version = "12.5.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8')" }, + { name = "nvidia-nvjitlink", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.3.90-py3-none-manylinux_2_27_aarch64-cu12.whl", hash = "sha256:2d063553162c3dffec9162b5884703cf3ce355a30151e890ba527ad1593a6331" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.3.90-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:7b2f860335e643d3bceaddedaf9b46ada6e5ee65303b103acb39479272ada168" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusolver/nvidia_cusolver-11.7.3.90-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-cusolver" +version = "11.7.5.82" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "12.9.1.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparse", version = "12.5.10.65", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvjitlink", version = "12.9.86", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-11.7.5.82-py3-none-manylinux_2_27_x86_64-cu12.whl", hash = "sha256:88952abbdefe79cbe917fa2c05820c2bcad2744606eafc54e711dd5f0865783f" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusolver/nvidia_cusolver-11.7.5.82-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cusolver" +version = "12.0.3.29" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-cublas", version = "13.0.0.19", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cusparse", version = "12.6.2.49", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-nvjitlink", version = "13.0.39", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-12.0.3.29-py3-none-manylinux_2_27_aarch64-cu13.whl", hash = "sha256:5a3d67c5e14234b2f9e21ab492465acd41e489383f58321131f0b2edba30501e" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusolver/nvidia_cusolver-12.0.3.29-py3-none-manylinux_2_27_x86_64-cu13.whl", hash = "sha256:840d1743ffb61657aca190dcd48d3293f06e74a74377754ff59d9ef8e48db9de" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusolver/nvidia_cusolver-12.0.3.29-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cusparse" +version = "12.5.4.2" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.6.85", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.6')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.4.2-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:8fef7dd012811dfc4ef80cf3cbc6ada707314537107f392d2e42bef6c08f20d6" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.4.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:60c08c59e3d44c226129bfc6e84acde42470c24c8a1412452b0540a1908224bd" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.4.2-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:489c96e5342eaa182c496628685e0918decc77234228273060ebcdde80892192" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:eb9b87d8674ef21a2a988baee541e6b633873f65f6263f5e2c6ba8585304da02" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparse/nvidia_cusparse-12.5.4.2-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cusparse" +version = "12.5.8.93" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda12.8')" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:e9d056d480043fa14ad9757a100a83627f5d72d45967c50ebc49311222d313b4" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:b3bbbdd89ebfeb3fb44a1ce64793b8f8e13669b355036e411a1d2f200b4934a7" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparse/nvidia_cusparse-12.5.8.93-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-cusparse" +version = "12.5.10.65" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "12.9.86", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:8c39ea9a7cd5e486e17b6e532c9e1d50d28c0d28b97165961fa2a2984d5d6b6a" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparse/nvidia_cusparse-12.5.10.65-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cusparse" +version = "12.6.2.49" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +dependencies = [ + { name = "nvidia-nvjitlink", version = "13.0.39", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.6.2.49-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:875d87cab1a1967278e70b6672d882b9141d3b3f19f55e7a487b0285b74126ce" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparse/nvidia_cusparse-12.6.2.49-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:c31c41b346c112e5ccc607a5f2c97d88ff01e15f0f72838afdedf7e9881d29ca" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparse/nvidia_cusparse-12.6.2.49-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-cusparselt" +version = "0.7.1" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.7.1-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:576f525a9f7f30b6094fce8210a91888083b9174914eee0bbe785d2cead333d7" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.7.1-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:66253f3ffbe5e61624cc23459c3eddef4607c70df449afab9523db3c903ff680" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparselt/nvidia_cusparselt-0.7.1-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-cusparselt" +version = "0.8.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.8.0-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:559051ecb23bca76f241091a4fbabcce4c4bcc474a2158fb0b4cad2acf8f354a" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.8.0-py3-none-manylinux2014_aarch64-cu13.whl", hash = "sha256:66516ed350a7d457ba5d20739bada23d6ae8c1210cbeed8049f18c4a8cb26874" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.8.0-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:67d38f817f5fd795f1ffc64f532d08ccdb36b87dd9a5a1e81fc4e72398e6188f" }, + { url = "https://variants-index.wheelnext.dev/nvidia-cusparselt/nvidia_cusparselt-0.8.0-py3-none-manylinux2014_x86_64-cu13.whl", hash = "sha256:db4d61a08af02eee44fe45c40ecd151ddd4861a9efcbf652d70196e72f8df48e" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-cusparselt/nvidia_cusparselt-0.8.0-variants.json", hash = "sha256:8ebd613dfbd3f403556ecd2edfbb9851cdc92030307ff2bfb0efaa043763783a" } + +[[package]] +name = "nvidia-nccl" +version = "2.27.3" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:6a9c0d86d20ca0c928a4b1f64733ae21dfabbe514a0060bd5c8ffe303090f2d6" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nccl/nvidia_nccl-2.27.3-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-nccl" +version = "2.27.5" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:0c85291aba4bee19090d3ec579670636fe3dbfb2c1f47768fb3e1b1ed93fd340" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:0456b674b214f231df447c01943c64b36d52580633f31c2c4cdd49448bbdc813" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nccl/nvidia_nccl-2.27.5-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-nccl" +version = "2.27.7" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.7-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:a3f74b320c70a5e3292b2ab263a95f4ab406a5560fcc00b038ea152c5efe71be" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.7-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:0e762e9b7d4f06d3f5e291953e153f1ccf39c0a2824b77cbc40a11a0492cc3f1" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.7-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:cb418c194e22d7202d9e1047c5789faabeafedd7e60023e4d68046ca12110947" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nccl/nvidia_nccl-2.27.7-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:f3b940ee80fa150fcfa67d3bc51ba21a8ef72c064c523c583390f7b30a9eaca4" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nccl/nvidia_nccl-2.27.7-variants.json", hash = "sha256:8ebd613dfbd3f403556ecd2edfbb9851cdc92030307ff2bfb0efaa043763783a" } + +[[package]] +name = "nvidia-nvjitlink" +version = "12.6.85" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu12.whl", hash = "sha256:a0796d484e5d6c5aa5424008e1cf52873d321ee8203de2d506c054f19372e4db" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:5bbc08463331124f70f0765ce560431ec214fe6da207b6ce3635f60d0660adde" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvjitlink/nvidia_nvjitlink-12.6.85-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-nvjitlink" +version = "12.8.93" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu12.whl", hash = "sha256:ffead13b94832e40d202f368c38d4205ed68d365c032ff8386123b287c1c3c13" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:653cdf7ac818be9e65326a1ab9f51d76ce701dabd4d272f3aa9c8a3fb824094e" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvjitlink/nvidia_nvjitlink-12.8.93-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-nvjitlink" +version = "12.9.86" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu12.whl", hash = "sha256:b42fb65fe63056478985eeab24f47cbb3864771b0c02d74d12f5aad014bbea65" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvjitlink/nvidia_nvjitlink-12.9.86-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.39" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-13.0.39-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64-cu13.whl", hash = "sha256:3c9e2efec0355b9fc5b5fa6711a53babc0e586bf554369df074ee1660d78ec46" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvjitlink/nvidia_nvjitlink-13.0.39-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:d80b97aa6fb33a05e768594845125c4156aeb54dd57e205096977f274f9846bb" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvjitlink/nvidia_nvjitlink-13.0.39-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "nvidia-nvshmem" +version = "3.3.20" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.20-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:10f58daec4b13d975ab50aa90d4c3d582079ead5b2a007f4b279ec835dd751aa" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:bf13e3407a17f9abd7e69be53a3111271e1222cf0dc3920f7f4e3f3fd310f289" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvshmem/nvidia_nvshmem-3.3.20-variants.json", hash = "sha256:6dfc48eda07dd47ef0d6f85d1ca97e929334b3eee0e8085ebcf64bbe81c0c87e" } + +[[package]] +name = "nvidia-nvshmem" +version = "3.3.24" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.24-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:8b56d04129a46a17a253011aaec302db472c5de4b83dfcd5c9983447fa34c320" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.24-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:b80ae57784905972b0cda2cd9df2293287ba92df21c1ffee8e9fd86fa9d33cfa" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.24-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:d8b65ea474b1d71ed2b7a3b23b6d4ad2b8af113042218da160ffa62d091ffa8f" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvshmem/nvidia_nvshmem-3.3.24-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu13.whl", hash = "sha256:516383b8ddf9e4342556fa70351a7f53bd8b1fef7f8c14e51aba0b02b2a5329c" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvshmem/nvidia_nvshmem-3.3.24-variants.json", hash = "sha256:8ebd613dfbd3f403556ecd2edfbb9851cdc92030307ff2bfb0efaa043763783a" } + +[[package]] +name = "nvidia-nvtx" +version = "12.6.77" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.6.77-py3-none-manylinux2014_aarch64-cu12.whl", hash = "sha256:d83abfaa2f2ec35ef0c04ad4e912114261db64dcf4d80451523ef55f39ff8a1b" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:f703eb0458b14194a0a514731906b2b6438d6046d268443da94f48a305270b63" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.6.77-py3-none-manylinux2014_x86_64-cu12.whl", hash = "sha256:5a9de2a7716900912dc205fcd7eb995615fb8b272aef04e2723056660b073d3f" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:5735d04b75d19753f935d49969065b94af77b786a89733b0eebeb8b899a4b8d5" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvtx/nvidia_nvtx-12.6.77-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-nvtx" +version = "12.8.90" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu12.whl", hash = "sha256:20d21f5914fb63fce467f75d14953be9c294b9786872167bc273f0280cfd19ed" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64-cu12.whl", hash = "sha256:d71ca95f3f0670d986b1ed4661e3c80b9c840eaab672f74e9706e839160e0a70" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvtx/nvidia_nvtx-12.8.90-variants.json", hash = "sha256:e886a6907c222711bef14696698cb5974bf85f7b0c66df6b4628b74c47a8789f" } + +[[package]] +name = "nvidia-nvtx" +version = "12.9.79" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-12.9.79-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64-cu12.whl", hash = "sha256:bd281dfd05fd2b39c31392f5881028c2fe5dd802d353a9a751e77b139dc0bdd8" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvtx/nvidia_nvtx-12.9.79-variants.json", hash = "sha256:98d8a5e601c78a5edfb92ab575002ebb00bef4cfc8802b64cc3d3825f6669e42" } + +[[package]] +name = "nvidia-nvtx" +version = "13.0.39" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", +] +wheels = [ + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-13.0.39-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64-cu13.whl", hash = "sha256:37ac800419c7523c83d4b955d8971ad84d27c861dda722f87730ea5ddd6ec6e7" }, + { url = "https://variants-index.wheelnext.dev/nvidia-nvtx/nvidia_nvtx-13.0.39-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64-cu13.whl", hash = "sha256:5167a9b97bafa8a24da1dd048c76b5793cc2715debdbb6d4b2a0ffcd33e1cae5" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/nvidia-nvtx/nvidia_nvtx-13.0.39-variants.json", hash = "sha256:ea0fbd6a71a2fe9982eccb9617ee101f3c811f6447e4dd0f4c3e99acf888dc05" } + +[[package]] +name = "oneccl" +version = "2021.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "impi-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-sycl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/38/e31cc932bd62ede7813dad2062caef6b745550e699766cdf6ba3a8e2ff8e/oneccl-2021.16.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:371c2ebd6590216a1cbbeaf0ba70b26e9a275a708993aba43c9ee82a1947a3c6", size = 104428094, upload-time = "2025-08-13T17:22:16.448Z" }, +] + +[[package]] +name = "oneccl-devel" +version = "2021.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oneccl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/05/fd082b1da0fdd8082b99d721fd69a4a954f61639fbe7d024ca93b16e4dc1/oneccl_devel-2021.16.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9154ab9c10e9864464c7163857f87c3de05b80030b4dc4ad9bdbd8084cb85ac2", size = 35075212, upload-time = "2025-08-13T17:22:21.894Z" }, +] + +[[package]] +name = "onemkl-sycl-blas" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/c2/b37cf1963b73a3c971efc36d53b97efccaa30f52f1f523a5452d582dbe08/onemkl_sycl_blas-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:28ad1e49c821f34b57ba217dc64ab6f95d5f8f262b582296d8551d298408fd72", size = 23585330, upload-time = "2025-06-24T13:16:37.562Z" }, +] + +[[package]] +name = "onemkl-sycl-dft" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/5b/5d26447da4cf01abab3020c82924b1661684adb0286a99c692cd1c08461d/onemkl_sycl_dft-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1f05b0b8315d7b59867ad512e7e06297b24778d0dcb9deac5201c1982bc8af57", size = 6859958, upload-time = "2025-06-24T13:15:48.466Z" }, +] + +[[package]] +name = "onemkl-sycl-lapack" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "onemkl-sycl-blas", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/2e/6e87e5a96bb16fe757d82de185b2706b2e4d3eb372f5eaa266508fb3c1ed/onemkl_sycl_lapack-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2b2395fd1c267db2719b8fff59486b478568f61567b596b6f8c1f7008c4aba4d", size = 10818002, upload-time = "2025-06-24T13:16:04.779Z" }, +] + +[[package]] +name = "onemkl-sycl-rng" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/c8/81bab1b7838a9229ed078b9628c8e05ce8073045b64c642971bc5ce0cce8/onemkl_sycl_rng-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2fb7f8978a26e089cc29d862776cd4acdb66bbc5614cc59fa992d55e073ac093", size = 25350619, upload-time = "2025-06-24T13:16:01.236Z" }, +] + +[[package]] +name = "onemkl-sycl-sparse" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "intel-opencl-rt", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "onemkl-sycl-blas", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/68/af04fa6a7d8fa5c9c3af4055abab86ed04af24b2cf316dd00f2f16694557/onemkl_sycl_sparse-2025.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b122f08aa80d0bc4330b5e218fcf810123c0efbf029b4460912e638b3cd75c5a", size = 22410699, upload-time = "2025-06-24T13:15:43.166Z" }, +] + +[[package]] +name = "pytorch-triton-rocm" +version = "3.5.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +wheels = [ + { url = "https://download.pytorch.org/whl/variant/pytorch_triton_rocm-3.5.0-cp313-cp313-linux_x86_64.whl", hash = "sha256:bd2fab6dfb077dfc11fb581c5d33a0ee2d43ea13415530d095d1d5326d85d8b2" }, + { url = "https://download.pytorch.org/whl/variant/pytorch_triton_rocm-3.5.0-cp313-cp313t-linux_x86_64.whl", hash = "sha256:0922f3ed8cd09f7d7f3a62138808efc9cdf9c9ee2913e00786e948b10f091fb0" }, +] + +[[package]] +name = "pytorch-triton-xpu" +version = "3.5.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +dependencies = [ + { name = "setuptools", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/variant/pytorch_triton_xpu-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d24c1716088f2764d0d24c64227732195b6a42706c3c5fc89eeb4904bfa0818" }, + { url = "https://download.pytorch.org/whl/variant/pytorch_triton_xpu-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74f00689800e2fe4e75b5db26ace8084eaa44e6df9a30e2ad65ddb89abf5a152" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tbb" +version = "2022.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tcmlib", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/5c/019acaccf0038b8e05b0a54189439d0987891017a84ca43675589f7e460c/tbb-2022.2.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:522189f3e370a6b9c92b8a7fbdecf3633f7c53f0ea4eb8d6891a7f5f00c78099", size = 6393815, upload-time = "2025-06-24T13:15:13.482Z" }, +] + +[[package]] +name = "tcmlib" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/9d/97d81fa340b9f1a0e33d6260daeb8bd7bbc2ef5b686be193491de5c9880a/tcmlib-1.4.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b2a2b68c100cc2a6163d394353b3013ab2479e70300b9bc1cb7f7822bcc38a40", size = 2731275, upload-time = "2025-06-24T13:15:40.134Z" }, +] + +[[package]] +name = "torch" +version = "2.8.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", +] +dependencies = [ + { name = "filelock", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "fsspec", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "jinja2", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "networkx", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cublas", version = "12.6.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cublas", version = "12.8.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cublas", version = "12.9.1.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-cupti", version = "12.6.80", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-cupti", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-cupti", version = "12.9.79", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-nvrtc", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-nvrtc", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-nvrtc", version = "12.9.86", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-runtime", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-runtime", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cuda-runtime", version = "12.9.79", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cudnn", version = "9.10.2.21", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufft", version = "11.3.0.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufft", version = "11.3.3.83", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufft", version = "11.4.1.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufile", version = "1.11.1.6", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufile", version = "1.13.1.3", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cufile", version = "1.14.1.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-curand", version = "10.3.7.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-curand", version = "10.3.9.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-curand", version = "10.3.10.19", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusolver", version = "11.7.1.2", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusolver", version = "11.7.3.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusolver", version = "11.7.5.82", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparse", version = "12.5.4.2", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparse", version = "12.5.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparse", version = "12.5.10.65", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparselt", version = "0.7.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nccl", version = "2.27.3", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvjitlink", version = "12.6.85", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvjitlink", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvjitlink", version = "12.9.86", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvtx", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvtx", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvtx", version = "12.9.79", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "setuptools", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "sympy", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "triton", version = "3.4.0", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'nvidia' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "typing-extensions", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64-00000000.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64-cu129.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64-00000000.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64-cu126.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64-cu128.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64-cu129.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64-00000000.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64-cu129.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64-00000000.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64-cu126.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64-cu128.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64-cu129.whl" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/torch/torch-2.8.0-variants.json", hash = "sha256:f73788697773b8d02c30e21358921bee2ca214ce5a04e3cf947f8399ed7a2a73" } + +[[package]] +name = "torch" +version = "2.9.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform != 'linux' or (variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)", +] +dependencies = [ + { name = "dpcpp-cpp-rt", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "filelock", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "fsspec", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "impi-rt", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (platform_machine == 'x86_64' and sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-cmplr-lib-rt", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-cmplr-lib-ur", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-cmplr-lic-rt", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-opencl-rt", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-openmp", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-pti", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "intel-sycl-rt", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "jinja2", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "mkl", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "networkx", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cublas", version = "12.6.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cublas", version = "12.8.4.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cublas", version = "13.0.0.19", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cuda-cupti", version = "12.6.80", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cuda-cupti", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cuda-cupti", version = "13.0.48", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cuda-nvrtc", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cuda-nvrtc", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cuda-nvrtc", version = "13.0.48", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cuda-runtime", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cuda-runtime", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cuda-runtime", version = "13.0.48", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cudnn", version = "9.10.2.21", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label != 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cudnn", version = "9.13.0.50", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cufft", version = "11.3.0.4", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cufft", version = "11.3.3.83", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cufft", version = "12.0.0.15", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cufile", version = "1.11.1.6", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cufile", version = "1.13.1.3", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cufile", version = "1.15.0.42", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-curand", version = "10.3.7.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-curand", version = "10.3.9.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-curand", version = "10.4.0.35", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cusolver", version = "11.7.1.2", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cusolver", version = "11.7.3.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cusolver", version = "12.0.3.29", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cusparse", version = "12.5.4.2", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-cusparse", version = "12.5.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-cusparse", version = "12.6.2.49", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-cusparselt", version = "0.7.1", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label != 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-cusparselt", version = "0.8.0", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-nccl", version = "2.27.5", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label != 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nccl", version = "2.27.7", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-nvjitlink", version = "12.6.85", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-nvjitlink", version = "12.8.93", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-nvjitlink", version = "13.0.39", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-nvshmem", version = "3.3.20", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label != 'cuda13.0' and 'nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "nvidia-nvshmem", version = "3.3.24", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "nvidia-nvtx", version = "12.6.77", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.6'" }, + { name = "nvidia-nvtx", version = "12.8.90", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda12.8'" }, + { name = "nvidia-nvtx", version = "13.0.39", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "oneccl", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (platform_machine == 'x86_64' and sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "oneccl-devel", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (platform_machine == 'x86_64' and sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "onemkl-sycl-blas", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "onemkl-sycl-dft", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "onemkl-sycl-lapack", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "onemkl-sycl-rng", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "onemkl-sycl-sparse", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "pytorch-triton-rocm", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label != 'cuda13.0' and 'amd' in variant_namespaces) or (platform_machine == 'x86_64' and sys_platform == 'linux' and 'amd' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "pytorch-triton-xpu", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "setuptools", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "sympy", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "tbb", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "tcmlib", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "triton", version = "3.5.0", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'nvidia' in variant_namespaces) or (sys_platform == 'linux' and 'nvidia' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, + { name = "typing-extensions", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, + { name = "umf", marker = "(sys_platform == 'linux' and variant_label != 'cuda13.0' and 'intel' in variant_namespaces) or (sys_platform == 'linux' and 'intel' in variant_namespaces and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-linux_x86_64-xpu.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-macosx_11_0_arm64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-rocm6.3.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64-rocm6.4.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-win_amd64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-win_amd64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-win_amd64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-win_amd64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313-win_amd64-xpu.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-linux_x86_64-xpu.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-rocm6.3.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64-rocm6.4.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-win_amd64-cuda12.6.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-win_amd64-cuda12.8.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-win_amd64-cuda13.0.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-win_amd64-null.whl" }, + { url = "https://download.pytorch.org/whl/variant/torch-2.9.0-cp313-cp313t-win_amd64-xpu.whl" }, +] +variants-json = { url = "https://wheelnext.github.io/variants-index/v0.0.2/torch/torch-2.9.0-variants.json", hash = "sha256:84ebd7f16e01cc71a8f56999f792caf6202c3dd977e37ea325dd87f9303d1a3e" } + +[[package]] +name = "torch-user" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "torch", version = "2.8.0", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, + { name = "torch", version = "2.9.0", source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" }, marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] + +[package.metadata] +requires-dist = [{ name = "torch" }] + +[[package]] +name = "triton" +version = "3.4.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "platform_machine == 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "(platform_machine != 'x86_64' and sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties) or (sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.8' not in variant_properties and 'torch==2.8.0 | nvidia :: cuda_version_lower_bound :: 12.9' not in variant_properties and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties)", +] +dependencies = [ + { name = "setuptools", marker = "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/variant/triton-3.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, +] + +[[package]] +name = "triton" +version = "3.5.0" +source = { registry = "https://wheelnext.github.io/variants-index/v0.0.2" } +resolution-markers = [ + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.6' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label == 'cuda12.8' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties", + "sys_platform == 'linux' and variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' in variant_properties", + "sys_platform != 'linux' or (variant_label != 'cuda12.6' and variant_label != 'cuda12.8' and variant_label != 'cuda13.0' and 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties)", +] +wheels = [ + { url = "https://download.pytorch.org/whl/variant/triton-3.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.5.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/variant/triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "umf" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tcmlib", marker = "sys_platform != 'linux' or variant_label != 'cuda13.0' or 'torch==2.9.0 | nvidia :: cuda_version_lower_bound :: 12.0' not in variant_properties" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/3a/63b40f833c7b27ba767e467fdf52cf972cdd149aab72d3d9761ec200fc9f/umf-0.11.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9f2a6be0de4202fdcdddf9045c54ae6eb4c4afaec38e1871a603db3f72d36bab", size = 329971, upload-time = "2025-06-24T13:19:35.567Z" }, +] diff --git a/test_torch.sh b/test_torch.sh new file mode 100755 index 000000000..a35c0ade0 --- /dev/null +++ b/test_torch.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -ex + +cargo build +uv="$(pwd)/target/debug/uv" +unset VIRTUAL_ENV +export RUST_LOG=uv_distribution_types=debug,uv_distribution::distribution_database=debug + +# 1. Single platform +${uv} venv --clear -q -p 3.13 && echo "torch" | ${uv} pip compile - --index https://wheelnext.github.io/variants-index/v0.0.2 --no-annotate --refresh + +export NV_VARIANT_PROVIDER_FORCE_CUDA_DRIVER_VERSION=12.8 +export NV_VARIANT_PROVIDER_FORCE_SM_ARCH=9.0 +${uv} venv --clear -q -p 3.13 && echo "torch" | ${uv} pip compile - --index https://wheelnext.github.io/variants-index/v0.0.2 --no-annotate +unset NV_VARIANT_PROVIDER_FORCE_CUDA_DRIVER_VERSION +unset NV_VARIANT_PROVIDER_FORCE_SM_ARCH + +# 2. Universal with lockfile +# Three cases: Sync (new/updated lock), Sync (fresh lock), Sync (noop) +( cd scripts/packages/torch_user && ${uv} venv -p 3.13 -c -q && rm -f uv.lock && ${uv} sync ) +( cd scripts/packages/torch_user && ${uv} venv -p 3.13 -c -q && ${uv} sync ) +( cd scripts/packages/torch_user && ${uv} sync ) + +export NV_VARIANT_PROVIDER_FORCE_CUDA_DRIVER_VERSION=12.8 +export NV_VARIANT_PROVIDER_FORCE_SM_ARCH=9.0 +( cd scripts/packages/torch_user && ${uv} venv -p 3.13 -c -q && rm -f uv.lock && ${uv} sync ) +( cd scripts/packages/torch_user && ${uv} venv -p 3.13 -c -q && ${uv} sync ) +( cd scripts/packages/torch_user && ${uv} sync ) +unset NV_VARIANT_PROVIDER_FORCE_CUDA_DRIVER_VERSION +unset NV_VARIANT_PROVIDER_FORCE_SM_ARCH + diff --git a/test_variants.sh b/test_variants.sh new file mode 100755 index 000000000..3617efed2 --- /dev/null +++ b/test_variants.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +cargo build +uv="$(pwd)/target/debug/uv" +unset VIRTUAL_ENV +export RUST_LOG=uv_distribution_types=debug,uv_distribution::distribution_database=debug + +echo "# No matching variant wheel, no non-variant wheel or sdist" +uv venv -c -q && ( ( UV_CPU_LEVEL_OVERRIDE=0 ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files && exit 1 ) || exit 0 ) +echo "# No matching variant wheel, but a non-variant wheel" +uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=0 ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files --find-links ./files_wheel +echo "# No matching variant wheel, but a non-variant sdist" +uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=0 ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files --find-links ./files_sdist +echo "# Matching cpu2 variant wheel, to be preferred over the non-variant wheel" +uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=2 ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files --find-links ./files_wheel +echo "# Matching cpu2 variant wheel, to be preferred over the non-variant wheel and the sdist" +uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=2 RUST_LOG=uv_distribution_types=debug ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files --find-links ./files_wheel --find-links ./files_sdist + +echo "Valid variants lock" +uv venv -c -q && UV_VARIANT_LOCK=files/variant-lock.toml RUST_LOG=uv_distribution_types=debug ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files +echo "No matching variants wheel with variants lock" +uv venv -c -q && ( ( UV_VARIANT_LOCK=files/variant-lock-none.toml RUST_LOG=uv_distribution_types=debug ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files && exit 1 ) || exit 0 ) +echo "No matching variant provider in variants lock" +uv venv -c -q && ( ( UV_VARIANT_LOCK=files/variant-lock-incomplete.toml RUST_LOG=uv_distribution_types=debug ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files && exit 1 ) || exit 0 ) +echo "No matching variants in variants lock, but UV_VARIANT_LOCK_INCOMPLETE set" +uv venv -c -q && UV_VARIANT_LOCK_INCOMPLETE=1 UV_VARIANT_LOCK=files/variant-lock-incomplete.toml RUST_LOG=uv_distribution_types=debug ${uv} pip install built-by-uv --no-index --no-cache --no-progress --find-links ./files + +echo "# sync without a compatible variant wheel (fresh)" +( cd scripts/packages/cpu_user && rm -f uv.lock && ( ( uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=0 ${uv} sync && exit 1 ) || exit 0 ) ) +echo "# sync without a compatible variant wheel (existing lockfile)" +( cd scripts/packages/cpu_user && rm -f uv.lock && ( ( uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=0 ${uv} sync && exit 1 ) || exit 0 ) ) +echo "# sync with a compatible variant wheel" +# TODO(konsti): Selecting a different level currently selects the right wheel, but doesn't reinstall. +( cd scripts/packages/cpu_user && rm -f uv.lock && uv venv -c -q && UV_CPU_LEVEL_OVERRIDE=2 ${uv} sync && UV_CPU_LEVEL_OVERRIDE=2 ${uv} sync && UV_CPU_LEVEL_OVERRIDE=3 ${uv} sync ) diff --git a/uv.schema.json b/uv.schema.json index 724503c07..9dd990b6e 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -4,47 +4,37 @@ "description": "Metadata and configuration for uv.", "type": "object", "properties": { - "add-bounds": { - "description": "The default version specifier when adding a dependency.\n\nWhen adding a dependency to the project, if no constraint or URL is provided, a constraint\nis added based on the latest compatible version of the package. By default, a lower bound\nconstraint is used, e.g., `>=1.2.3`.\n\nWhen `--frozen` is provided, no resolution is performed, and dependencies are always added\nwithout constraints.\n\nThis option is in preview and may change in any future release.", + "required-version": { + "description": "Enforce a requirement on the version of uv.\n\nIf the version of uv does not meet the requirement at runtime, uv will exit\nwith an error.\n\nAccepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.", "anyOf": [ { - "$ref": "#/definitions/AddBoundsKind" + "$ref": "#/definitions/RequiredVersion" }, { "type": "null" } ] }, - "allow-insecure-host": { - "description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,\n`localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate\nstore. Only use `--allow-insecure-host` in a secure network with verified sources, as it\nbypasses SSL verification and could expose you to MITM attacks.", + "native-tls": { + "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The\n`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv\nimproves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store,\nespecially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's\nincluded in your system's certificate store.", "type": [ - "array", + "boolean", "null" - ], - "items": { - "$ref": "#/definitions/TrustedHost" - } - }, - "build-backend": { - "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.", - "anyOf": [ - { - "$ref": "#/definitions/BuildBackendSettings" - }, - { - "type": "null" - } ] }, - "build-constraint-dependencies": { - "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "offline": { + "description": "Disable network access, relying only on locally cached data and locally available files.", "type": [ - "array", + "boolean", "null" - ], - "items": { - "type": "string" - } + ] + }, + "no-cache": { + "description": "Avoid reading from or writing to the cache, instead using a temporary directory for the\nduration of the operation.", + "type": [ + "boolean", + "null" + ] }, "cache-dir": { "description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and\n`%LOCALAPPDATA%\\uv\\cache` on Windows.", @@ -53,36 +43,37 @@ "null" ] }, - "cache-keys": { - "description": "The keys to consider when caching builds for the project.\n\nCache keys enable you to specify the files or directories that should trigger a rebuild when\nmodified. By default, uv will rebuild a project whenever the `pyproject.toml`, `setup.py`,\nor `setup.cfg` files in the project directory are modified, or if a `src` directory is\nadded or removed, i.e.:\n\n```toml\ncache-keys = [{ file = \"pyproject.toml\" }, { file = \"setup.py\" }, { file = \"setup.cfg\" }, { dir = \"src\" }]\n```\n\nAs an example: if a project uses dynamic metadata to read its dependencies from a\n`requirements.txt` file, you can specify `cache-keys = [{ file = \"requirements.txt\" }, { file = \"pyproject.toml\" }]`\nto ensure that the project is rebuilt whenever the `requirements.txt` file is modified (in\naddition to watching the `pyproject.toml`).\n\nGlobs are supported, following the syntax of the [`glob`](https://docs.rs/glob/0.3.1/glob/struct.Pattern.html)\ncrate. For example, to invalidate the cache whenever a `.toml` file in the project directory\nor any of its subdirectories is modified, you can specify `cache-keys = [{ file = \"**/*.toml\" }]`.\nNote that the use of globs can be expensive, as uv may need to walk the filesystem to\ndetermine whether any files have changed.\n\nCache keys can also include version control information. For example, if a project uses\n`setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = \"pyproject.toml\" }]`\nto include the current Git commit hash in the cache key (in addition to the\n`pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`.\n\nCache keys can also include environment variables. For example, if a project relies on\n`MACOSX_DEPLOYMENT_TARGET` or other environment variables to determine its behavior, you can\nspecify `cache-keys = [{ env = \"MACOSX_DEPLOYMENT_TARGET\" }]` to invalidate the cache\nwhenever the environment variable changes.\n\nCache keys only affect the project defined by the `pyproject.toml` in which they're\nspecified (as opposed to, e.g., affecting all members in a workspace), and all paths and\nglobs are interpreted as relative to the project directory.", + "preview": { + "description": "Whether to enable experimental, preview features.", "type": [ - "array", + "boolean", "null" - ], - "items": { - "$ref": "#/definitions/CacheKey" - } + ] }, - "check-url": { - "description": "Check an index URL for existing files to skip duplicate uploads.\n\nThis option allows retrying publishing that failed after only some, but not all files have\nbeen uploaded, and handles error due to parallel uploads of the same file.\n\nBefore uploading, the index is checked. If the exact same file already exists in the index,\nthe file will not be uploaded. If an error occurred during the upload, the index is checked\nagain, to handle cases where the identical file was uploaded twice in parallel.\n\nThe exact behavior will vary based on the index. When uploading to PyPI, uploading the same\nfile succeeds even without `--check-url`, while most other indexes error.\n\nThe index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).", + "python-preference": { + "description": "Whether to prefer using Python installations that are already present on the system, or\nthose that are downloaded and installed by uv.", "anyOf": [ { - "$ref": "#/definitions/IndexUrl" + "$ref": "#/definitions/PythonPreference" }, { "type": "null" } ] }, - "compile-bytecode": { - "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", - "type": [ - "boolean", - "null" + "python-downloads": { + "description": "Whether to allow Python downloads.", + "anyOf": [ + { + "$ref": "#/definitions/PythonDownloads" + }, + { + "type": "null" + } ] }, - "concurrent-builds": { - "description": "The maximum number of source distributions that uv will build concurrently at any given\ntime.\n\nDefaults to the number of available CPU cores.", + "concurrent-downloads": { + "description": "The maximum number of in-flight concurrent downloads that uv will perform at any given\ntime.", "type": [ "integer", "null" @@ -90,8 +81,8 @@ "format": "uint", "minimum": 1 }, - "concurrent-downloads": { - "description": "The maximum number of in-flight concurrent downloads that uv will perform at any given\ntime.", + "concurrent-builds": { + "description": "The maximum number of source distributions that uv will build concurrently at any given\ntime.\n\nDefaults to the number of available CPU cores.", "type": [ "integer", "null" @@ -108,6 +99,130 @@ "format": "uint", "minimum": 1 }, + "allow-insecure-host": { + "description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,\n`localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate\nstore. Only use `--allow-insecure-host` in a secure network with verified sources, as it\nbypasses SSL verification and could expose you to MITM attacks.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/TrustedHost" + } + }, + "index": { + "description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined\nindex has the highest priority. Further, the indexes provided by this setting are given\nhigher priority than any indexes specified via [`index_url`](#index-url) or\n[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains\na given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for the\ndependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is\ngiven the lowest priority when resolving packages. Additionally, marking an index as default will disable the\nPyPI default index.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Index" + }, + "default": null + }, + "index-url": { + "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url) or [`index`](#index).\n\n(Deprecated: use `index` instead.)", + "anyOf": [ + { + "$ref": "#/definitions/IndexUrl" + }, + { + "type": "null" + } + ] + }, + "extra-index-url": { + "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes\nare provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/IndexUrl" + } + }, + "no-index": { + "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", + "type": [ + "boolean", + "null" + ] + }, + "find-links": { + "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/IndexUrl" + } + }, + "index-strategy": { + "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", + "anyOf": [ + { + "$ref": "#/definitions/IndexStrategy" + }, + { + "type": "null" + } + ] + }, + "keyring-provider": { + "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", + "anyOf": [ + { + "$ref": "#/definitions/KeyringProviderType" + }, + { + "type": "null" + } + ] + }, + "resolution": { + "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", + "anyOf": [ + { + "$ref": "#/definitions/ResolutionMode" + }, + { + "type": "null" + } + ] + }, + "prerelease": { + "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", + "anyOf": [ + { + "$ref": "#/definitions/PrereleaseMode" + }, + { + "type": "null" + } + ] + }, + "fork-strategy": { + "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", + "anyOf": [ + { + "$ref": "#/definitions/ForkStrategy" + }, + { + "type": "null" + } + ] + }, + "dependency-metadata": { + "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/StaticMetadata" + } + }, "config-settings": { "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,\nspecified as `KEY=VALUE` pairs.", "anyOf": [ @@ -130,111 +245,23 @@ } ] }, - "conflicts": { - "description": "A list of sets of conflicting groups or extras.", - "anyOf": [ - { - "$ref": "#/definitions/SchemaConflicts" - }, - { - "type": "null" - } + "no-build-isolation": { + "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", + "type": [ + "boolean", + "null" ] }, - "constraint-dependencies": { - "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "no-build-isolation-package": { + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", "type": [ "array", "null" ], "items": { - "type": "string" + "$ref": "#/definitions/PackageName" } }, - "default-groups": { - "description": "The list of `dependency-groups` to install by default.\n\nCan also be the literal `\"all\"` to default enable all groups.", - "anyOf": [ - { - "$ref": "#/definitions/DefaultGroups" - }, - { - "type": "null" - } - ] - }, - "dependency-groups": { - "description": "Additional settings for `dependency-groups`.\n\nCurrently this can only be used to add `requires-python` constraints\nto dependency groups (typically to inform uv that your dev tooling\nhas a higher python requirement than your actual project).\n\nThis cannot be used to define dependency groups, use the top-level\n`[dependency-groups]` table for that.", - "anyOf": [ - { - "$ref": "#/definitions/ToolUvDependencyGroups" - }, - { - "type": "null" - } - ] - }, - "dependency-metadata": { - "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/StaticMetadata" - } - }, - "dev-dependencies": { - "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "environments": { - "description": "A list of environment markers, e.g., `python_version >= '3.6'`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "exclude-dependencies": { - "description": "Package names to exclude, e.g., `werkzeug`, `numpy`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "exclude-newer": { - "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", - "anyOf": [ - { - "$ref": "#/definitions/ExcludeNewerTimestamp" - }, - { - "type": "null" - } - ] - }, - "exclude-newer-package": { - "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.", - "anyOf": [ - { - "$ref": "#/definitions/ExcludeNewerPackage" - }, - { - "type": "null" - } - ] - }, "extra-build-dependencies": { "description": "Additional build dependencies for packages.\n\nThis allows extending the PEP 517 build environment for the project's dependencies with\nadditional packages. This is useful for packages that assume the presence of packages like\n`pip`, and do not declare them as build dependencies.", "anyOf": [ @@ -257,75 +284,22 @@ } ] }, - "extra-index-url": { - "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes\nare provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/IndexUrl" - } - }, - "find-links": { - "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/IndexUrl" - } - }, - "fork-strategy": { - "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", + "exclude-newer": { + "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", "anyOf": [ { - "$ref": "#/definitions/ForkStrategy" + "$ref": "#/definitions/ExcludeNewerTimestamp" }, { "type": "null" } ] }, - "index": { - "description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined\nindex has the highest priority. Further, the indexes provided by this setting are given\nhigher priority than any indexes specified via [`index_url`](#index-url) or\n[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains\na given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for the\ndependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is\ngiven the lowest priority when resolving packages. Additionally, marking an index as default will disable the\nPyPI default index.", - "type": [ - "array", - "null" - ], - "default": null, - "items": { - "$ref": "#/definitions/Index" - } - }, - "index-strategy": { - "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", + "exclude-newer-package": { + "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.", "anyOf": [ { - "$ref": "#/definitions/IndexStrategy" - }, - { - "type": "null" - } - ] - }, - "index-url": { - "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url) or [`index`](#index).\n\n(Deprecated: use `index` instead.)", - "anyOf": [ - { - "$ref": "#/definitions/IndexUrl" - }, - { - "type": "null" - } - ] - }, - "keyring-provider": { - "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", - "anyOf": [ - { - "$ref": "#/definitions/KeyringProviderType" + "$ref": "#/definitions/ExcludeNewerPackage" }, { "type": "null" @@ -343,80 +317,8 @@ } ] }, - "managed": { - "description": "Whether the project is managed by uv. If `false`, uv will ignore the project when\n`uv run` is invoked.", - "type": [ - "boolean", - "null" - ] - }, - "native-tls": { - "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The\n`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv\nimproves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store,\nespecially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's\nincluded in your system's certificate store.", - "type": [ - "boolean", - "null" - ] - }, - "no-binary": { - "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.", - "type": [ - "boolean", - "null" - ] - }, - "no-binary-package": { - "description": "Don't install pre-built wheels for a specific package.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "no-build": { - "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.", - "type": [ - "boolean", - "null" - ] - }, - "no-build-isolation": { - "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", - "type": [ - "boolean", - "null" - ] - }, - "no-build-isolation-package": { - "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "no-build-package": { - "description": "Don't build source distributions for a specific package.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "no-cache": { - "description": "Avoid reading from or writing to the cache, instead using a temporary directory for the\nduration of the operation.", - "type": [ - "boolean", - "null" - ] - }, - "no-index": { - "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", + "compile-bytecode": { + "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", "type": [ "boolean", "null" @@ -429,183 +331,6 @@ "null" ] }, - "offline": { - "description": "Disable network access, relying only on locally cached data and locally available files.", - "type": [ - "boolean", - "null" - ] - }, - "override-dependencies": { - "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "package": { - "description": "Whether the project should be considered a Python package, or a non-package (\"virtual\")\nproject.\n\nPackages are built and installed into the virtual environment in editable mode and thus\nrequire a build backend, while virtual projects are _not_ built or installed; instead, only\ntheir dependencies are included in the virtual environment.\n\nCreating a package requires that a `build-system` is present in the `pyproject.toml`, and\nthat the project adheres to a structure that adheres to the build backend's expectations\n(e.g., a `src` layout).", - "type": [ - "boolean", - "null" - ] - }, - "pip": { - "anyOf": [ - { - "$ref": "#/definitions/PipOptions" - }, - { - "type": "null" - } - ] - }, - "prerelease": { - "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", - "anyOf": [ - { - "$ref": "#/definitions/PrereleaseMode" - }, - { - "type": "null" - } - ] - }, - "preview": { - "description": "Whether to enable experimental, preview features.", - "type": [ - "boolean", - "null" - ] - }, - "publish-url": { - "description": "The URL for publishing packages to the Python package index (by default:\n).", - "anyOf": [ - { - "$ref": "#/definitions/DisplaySafeUrl" - }, - { - "type": "null" - } - ] - }, - "pypy-install-mirror": { - "description": "Mirror URL to use for downloading managed PyPy installations.\n\nBy default, managed PyPy installations are downloaded from [downloads.python.org](https://downloads.python.org/).\nThis variable can be set to a mirror URL to use a different source for PyPy installations.\nThe provided URL will replace `https://downloads.python.org/pypy` in, e.g., `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`.\n\nDistributions can be read from a\nlocal directory by using the `file://` URL scheme.", - "type": [ - "string", - "null" - ] - }, - "python-downloads": { - "description": "Whether to allow Python downloads.", - "anyOf": [ - { - "$ref": "#/definitions/PythonDownloads" - }, - { - "type": "null" - } - ] - }, - "python-downloads-json-url": { - "description": "URL pointing to JSON of custom Python installations.", - "type": [ - "string", - "null" - ] - }, - "python-install-mirror": { - "description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone).\nThis variable can be set to a mirror URL to use a different source for Python installations.\nThe provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", - "type": [ - "string", - "null" - ] - }, - "python-preference": { - "description": "Whether to prefer using Python installations that are already present on the system, or\nthose that are downloaded and installed by uv.", - "anyOf": [ - { - "$ref": "#/definitions/PythonPreference" - }, - { - "type": "null" - } - ] - }, - "reinstall": { - "description": "Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.", - "type": [ - "boolean", - "null" - ] - }, - "reinstall-package": { - "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "required-environments": { - "description": "A list of environment markers, e.g., `sys_platform == 'darwin'.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "required-version": { - "description": "Enforce a requirement on the version of uv.\n\nIf the version of uv does not meet the requirement at runtime, uv will exit\nwith an error.\n\nAccepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.", - "anyOf": [ - { - "$ref": "#/definitions/RequiredVersion" - }, - { - "type": "null" - } - ] - }, - "resolution": { - "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", - "anyOf": [ - { - "$ref": "#/definitions/ResolutionMode" - }, - { - "type": "null" - } - ] - }, - "sources": { - "description": "The sources to use when resolving dependencies.\n\n`tool.uv.sources` enriches the dependency metadata with additional sources, incorporated\nduring development. A dependency source can be a Git repository, a URL, a local path, or an\nalternative registry.\n\nSee [Dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/) for more.", - "anyOf": [ - { - "$ref": "#/definitions/ToolUvSources" - }, - { - "type": "null" - } - ] - }, - "trusted-publishing": { - "description": "Configure trusted publishing.\n\nBy default, uv checks for trusted publishing when running in a supported environment, but\nignores it if it isn't configured.\n\nuv's supported environments for trusted publishing include GitHub Actions and GitLab CI/CD.", - "anyOf": [ - { - "$ref": "#/definitions/TrustedPublishing" - }, - { - "type": "null" - } - ] - }, "upgrade": { "description": "Allow package upgrades, ignoring pinned versions in any existing output file.", "type": [ @@ -623,6 +348,153 @@ "$ref": "#/definitions/Requirement" } }, + "reinstall": { + "description": "Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.", + "type": [ + "boolean", + "null" + ] + }, + "reinstall-package": { + "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "no-build": { + "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.", + "type": [ + "boolean", + "null" + ] + }, + "no-build-package": { + "description": "Don't build source distributions for a specific package.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "no-binary": { + "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.", + "type": [ + "boolean", + "null" + ] + }, + "no-binary-package": { + "description": "Don't install pre-built wheels for a specific package.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "python-install-mirror": { + "description": "Mirror URL for downloading managed Python installations.\n\nBy default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone).\nThis variable can be set to a mirror URL to use a different source for Python installations.\nThe provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.\n\nDistributions can be read from a local directory by using the `file://` URL scheme.", + "type": [ + "string", + "null" + ] + }, + "pypy-install-mirror": { + "description": "Mirror URL to use for downloading managed PyPy installations.\n\nBy default, managed PyPy installations are downloaded from [downloads.python.org](https://downloads.python.org/).\nThis variable can be set to a mirror URL to use a different source for PyPy installations.\nThe provided URL will replace `https://downloads.python.org/pypy` in, e.g., `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`.\n\nDistributions can be read from a\nlocal directory by using the `file://` URL scheme.", + "type": [ + "string", + "null" + ] + }, + "python-downloads-json-url": { + "description": "URL pointing to JSON of custom Python installations.", + "type": [ + "string", + "null" + ] + }, + "publish-url": { + "description": "The URL for publishing packages to the Python package index (by default:\n).", + "anyOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + }, + { + "type": "null" + } + ] + }, + "trusted-publishing": { + "description": "Configure trusted publishing.\n\nBy default, uv checks for trusted publishing when running in a supported environment, but\nignores it if it isn't configured.\n\nuv's supported environments for trusted publishing include GitHub Actions and GitLab CI/CD.", + "anyOf": [ + { + "$ref": "#/definitions/TrustedPublishing" + }, + { + "type": "null" + } + ] + }, + "check-url": { + "description": "Check an index URL for existing files to skip duplicate uploads.\n\nThis option allows retrying publishing that failed after only some, but not all files have\nbeen uploaded, and handles error due to parallel uploads of the same file.\n\nBefore uploading, the index is checked. If the exact same file already exists in the index,\nthe file will not be uploaded. If an error occurred during the upload, the index is checked\nagain, to handle cases where the identical file was uploaded twice in parallel.\n\nThe exact behavior will vary based on the index. When uploading to PyPI, uploading the same\nfile succeeds even without `--check-url`, while most other indexes error.\n\nThe index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).", + "anyOf": [ + { + "$ref": "#/definitions/IndexUrl" + }, + { + "type": "null" + } + ] + }, + "add-bounds": { + "description": "The default version specifier when adding a dependency.\n\nWhen adding a dependency to the project, if no constraint or URL is provided, a constraint\nis added based on the latest compatible version of the package. By default, a lower bound\nconstraint is used, e.g., `>=1.2.3`.\n\nWhen `--frozen` is provided, no resolution is performed, and dependencies are always added\nwithout constraints.\n\nThis option is in preview and may change in any future release.", + "anyOf": [ + { + "$ref": "#/definitions/AddBoundsKind" + }, + { + "type": "null" + } + ] + }, + "pip": { + "anyOf": [ + { + "$ref": "#/definitions/PipOptions" + }, + { + "type": "null" + } + ] + }, + "cache-keys": { + "description": "The keys to consider when caching builds for the project.\n\nCache keys enable you to specify the files or directories that should trigger a rebuild when\nmodified. By default, uv will rebuild a project whenever the `pyproject.toml`, `setup.py`,\nor `setup.cfg` files in the project directory are modified, or if a `src` directory is\nadded or removed, i.e.:\n\n```toml\ncache-keys = [{ file = \"pyproject.toml\" }, { file = \"setup.py\" }, { file = \"setup.cfg\" }, { dir = \"src\" }]\n```\n\nAs an example: if a project uses dynamic metadata to read its dependencies from a\n`requirements.txt` file, you can specify `cache-keys = [{ file = \"requirements.txt\" }, { file = \"pyproject.toml\" }]`\nto ensure that the project is rebuilt whenever the `requirements.txt` file is modified (in\naddition to watching the `pyproject.toml`).\n\nGlobs are supported, following the syntax of the [`glob`](https://docs.rs/glob/0.3.1/glob/struct.Pattern.html)\ncrate. For example, to invalidate the cache whenever a `.toml` file in the project directory\nor any of its subdirectories is modified, you can specify `cache-keys = [{ file = \"**/*.toml\" }]`.\nNote that the use of globs can be expensive, as uv may need to walk the filesystem to\ndetermine whether any files have changed.\n\nCache keys can also include version control information. For example, if a project uses\n`setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = \"pyproject.toml\" }]`\nto include the current Git commit hash in the cache key (in addition to the\n`pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`.\n\nCache keys can also include environment variables. For example, if a project relies on\n`MACOSX_DEPLOYMENT_TARGET` or other environment variables to determine its behavior, you can\nspecify `cache-keys = [{ env = \"MACOSX_DEPLOYMENT_TARGET\" }]` to invalidate the cache\nwhenever the environment variable changes.\n\nCache keys only affect the project defined by the `pyproject.toml` in which they're\nspecified (as opposed to, e.g., affecting all members in a workspace), and all paths and\nglobs are interpreted as relative to the project directory.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/CacheKey" + } + }, + "sources": { + "description": "The sources to use when resolving dependencies.\n\n`tool.uv.sources` enriches the dependency metadata with additional sources, incorporated\nduring development. A dependency source can be a Git repository, a URL, a local path, or an\nalternative registry.\n\nSee [Dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/) for more.", + "anyOf": [ + { + "$ref": "#/definitions/ToolUvSources" + }, + { + "type": "null" + } + ] + }, "workspace": { "description": "The workspace definition for the project, if any.", "anyOf": [ @@ -633,50 +505,305 @@ "type": "null" } ] + }, + "managed": { + "description": "Whether the project is managed by uv. If `false`, uv will ignore the project when\n`uv run` is invoked.", + "type": [ + "boolean", + "null" + ] + }, + "package": { + "description": "Whether the project should be considered a Python package, or a non-package (\"virtual\")\nproject.\n\nPackages are built and installed into the virtual environment in editable mode and thus\nrequire a build backend, while virtual projects are _not_ built or installed; instead, only\ntheir dependencies are included in the virtual environment.\n\nCreating a package requires that a `build-system` is present in the `pyproject.toml`, and\nthat the project adheres to a structure that adheres to the build backend's expectations\n(e.g., a `src` layout).", + "type": [ + "boolean", + "null" + ] + }, + "default-groups": { + "description": "The list of `dependency-groups` to install by default.\n\nCan also be the literal `\"all\"` to default enable all groups.", + "anyOf": [ + { + "$ref": "#/definitions/DefaultGroups" + }, + { + "type": "null" + } + ] + }, + "dependency-groups": { + "description": "Additional settings for `dependency-groups`.\n\nCurrently this can only be used to add `requires-python` constraints\nto dependency groups (typically to inform uv that your dev tooling\nhas a higher python requirement than your actual project).\n\nThis cannot be used to define dependency groups, use the top-level\n`[dependency-groups]` table for that.", + "anyOf": [ + { + "$ref": "#/definitions/ToolUvDependencyGroups" + }, + { + "type": "null" + } + ] + }, + "dev-dependencies": { + "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "override-dependencies": { + "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "exclude-dependencies": { + "description": "Package names to exclude, e.g., `werkzeug`, `numpy`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "constraint-dependencies": { + "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "build-constraint-dependencies": { + "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "environments": { + "description": "A list of environment markers, e.g., `python_version >= '3.6'`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "required-environments": { + "description": "A list of environment markers, e.g., `sys_platform == 'darwin'.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "conflicts": { + "description": "A list of sets of conflicting groups or extras.", + "anyOf": [ + { + "$ref": "#/definitions/SchemaConflicts" + }, + { + "type": "null" + } + ] + }, + "build-backend": { + "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.", + "anyOf": [ + { + "$ref": "#/definitions/BuildBackendSettings" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, "definitions": { - "AddBoundsKind": { - "description": "The default version specifier when adding a dependency.", + "RequiredVersion": { + "description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`.", + "type": "string" + }, + "PythonPreference": { "oneOf": [ { - "description": "Only a lower bound, e.g., `>=1.2.3`.", + "description": "Only use managed Python installations; never use system Python installations.", "type": "string", - "const": "lower" + "const": "only-managed" }, { - "description": "Allow the same major version, similar to the semver caret, e.g., `>=1.2.3, <2.0.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.2.0`.", + "description": "Prefer managed Python installations over system Python installations.\n\nSystem Python installations are still preferred over downloading managed Python versions.\nUse `only-managed` to always fetch a managed Python version.", "type": "string", - "const": "major" + "const": "managed" }, { - "description": "Allow the same minor version, similar to the semver tilde, e.g., `>=1.2.3, <1.3.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.1.3`.", + "description": "Prefer system Python installations over managed Python installations.\n\nIf a system Python installation cannot be found, a managed Python installation can be used.", "type": "string", - "const": "minor" + "const": "system" }, { - "description": "Pin the exact version, e.g., `==1.2.3`.\n\nThis option is not recommended, as versions are already pinned in the uv lockfile.", + "description": "Only use system Python installations; never use managed Python installations.", "type": "string", - "const": "exact" + "const": "only-system" } ] }, - "AnnotationStyle": { - "description": "Indicate the style of annotation comments, used to indicate the dependencies that requested each\npackage.", + "PythonDownloads": { "oneOf": [ { - "description": "Render the annotations on a single, comma-separated line.", + "description": "Automatically download managed Python installations when needed.", "type": "string", - "const": "line" + "const": "automatic" }, { - "description": "Render each annotation on its own line.", + "description": "Do not automatically download managed Python installations; require explicit installation.", "type": "string", - "const": "split" + "const": "manual" + }, + { + "description": "Do not ever allow Python downloads.", + "type": "string", + "const": "never" } ] }, + "TrustedHost": { + "description": "A host or host-port pair.", + "type": "string" + }, + "Index": { + "type": "object", + "properties": { + "name": { + "description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example,\nyou can pin a package to a specific index by name:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", + "anyOf": [ + { + "$ref": "#/definitions/IndexName" + }, + { + "type": "null" + } + ] + }, + "url": { + "description": "The URL of the index.\n\nExpects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.", + "allOf": [ + { + "$ref": "#/definitions/IndexUrl" + } + ] + }, + "explicit": { + "description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]`\ndefinition, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", + "type": "boolean", + "default": false + }, + "default": { + "description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are\ndefined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that\naren't found elsewhere. To disable the PyPI default, set `default = true` on at least one\nother index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it\nis given the highest priority when resolving packages.", + "type": "boolean", + "default": false + }, + "format": { + "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple\nAPI) or structured as a flat list of distributions (e.g., `--find-links`). In both cases,\nindexes can point to either local or remote resources.", + "default": "simple", + "allOf": [ + { + "$ref": "#/definitions/IndexFormat" + } + ] + }, + "publish-url": { + "description": "The URL of the upload endpoint.\n\nWhen using `uv publish --index `, this URL is used for publishing.\n\nA configuration for the default index PyPI would look as follows:\n\n```toml\n[[tool.uv.index]]\nname = \"pypi\"\nurl = \"https://pypi.org/simple\"\npublish-url = \"https://upload.pypi.org/legacy/\"\n```", + "anyOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + }, + { + "type": "null" + } + ] + }, + "authenticate": { + "description": "When uv should use authentication for requests to the index.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nauthenticate = \"always\"\n```", + "default": "auto", + "allOf": [ + { + "$ref": "#/definitions/AuthPolicy" + } + ] + }, + "ignore-error-codes": { + "description": "Status codes that uv should ignore when deciding whether\nto continue searching in the next index after a failure.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nignore-error-codes = [401, 403]\n```", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/StatusCode" + }, + "default": null + }, + "cache-control": { + "description": "Cache control configuration for this index.\n\nWhen set, these headers will override the server's cache control headers\nfor both package metadata requests and artifact downloads.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\ncache-control = { api = \"max-age=600\", files = \"max-age=3600\" }\n```", + "anyOf": [ + { + "$ref": "#/definitions/IndexCacheControl" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "url" + ] + }, + "IndexName": { + "description": "The normalized name of an index.\n\nIndex names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII.", + "type": "string" + }, + "IndexUrl": { + "description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path.", + "type": "string" + }, + "IndexFormat": { + "oneOf": [ + { + "description": "A PyPI-style index implementing the Simple Repository API.", + "type": "string", + "const": "simple" + }, + { + "description": "A `--find-links`-style index containing a flat list of wheels and source distributions.", + "type": "string", + "const": "flat" + } + ] + }, + "DisplaySafeUrl": { + "description": "A [`Url`] wrapper that redacts credentials when displaying the URL.\n\n`DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask\nsecrets by default when the URL is displayed or logged. This helps prevent accidental\nexposure of sensitive information in logs and debug output.\n\n# Examples\n\n```\nuse uv_redacted::DisplaySafeUrl;\nuse std::str::FromStr;\n\n// Create a `DisplaySafeUrl` from a `&str`\nlet mut url = DisplaySafeUrl::parse(\"https://user:password@example.com\").unwrap();\n\n// Display will mask secrets\nassert_eq!(url.to_string(), \"https://user:****@example.com/\");\n\n// You can still access the username and password\nassert_eq!(url.username(), \"user\");\nassert_eq!(url.password(), Some(\"password\"));\n\n// And you can still update the username and password\nlet _ = url.set_username(\"new_user\");\nlet _ = url.set_password(Some(\"new_password\"));\nassert_eq!(url.username(), \"new_user\");\nassert_eq!(url.password(), Some(\"new_password\"));\n\n// It is also possible to remove the credentials entirely\nurl.remove_credentials();\nassert_eq!(url.username(), \"\");\nassert_eq!(url.password(), None);\n```", + "type": "string", + "format": "uri" + }, "AuthPolicy": { "description": "When to use authentication.", "oneOf": [ @@ -697,384 +824,11 @@ } ] }, - "BuildBackendSettings": { - "description": "Settings for the uv build backend (`uv_build`).\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from\n[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", - "type": "object", - "properties": { - "data": { - "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or\n `\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to use these two options.", - "allOf": [ - { - "$ref": "#/definitions/WheelDataIncludes" - } - ], - "default": { - "data": null, - "headers": null, - "platlib": null, - "purelib": null, - "scripts": null - } - }, - "default-excludes": { - "description": "If set to `false`, the default excludes aren't applied.\n\nDefault excludes: `__pycache__`, `*.pyc`, and `*.pyo`.", - "type": "boolean", - "default": true - }, - "module-name": { - "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a\n`__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem\nbeing the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or\n`foo-stubs.bar`.\n\nFor namespace packages with multiple modules, the path can be a list, e.g.,\n`[\"foo\", \"bar\"]`. We recommend using a single module per package, splitting multiple\npackages into a workspace.\n\nNote that using this option runs the risk of creating two packages with different names but\nthe same module names. Installing such packages together leads to unspecified behavior,\noften with corrupted files or directory trees.", - "anyOf": [ - { - "$ref": "#/definitions/ModuleName" - }, - { - "type": "null" - } - ], - "default": null - }, - "module-root": { - "description": "The directory that contains the module directory.\n\nCommon values are `src` (src layout, the default) or an empty path (flat layout).", - "type": "string", - "default": "src" - }, - "namespace": { - "description": "Build a namespace package.\n\nBuild a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.\n\nUse this option when the namespace package contains multiple root `__init__.py`, for\nnamespace packages with a single root `__init__.py` use a dotted `module-name` instead.\n\nTo compare dotted `module-name` and `namespace = true`, the first example below can be\nexpressed with `module-name = \"cloud.database\"`: There is one root `__init__.py` `database`.\nIn the second example, we have three roots (`cloud.database`, `cloud.database_pro`,\n`billing.modules.database_pro`), so `namespace = true` is required.\n\n```text\nsrc\n└── cloud\n └── database\n ├── __init__.py\n ├── query_builder\n │ └── __init__.py\n └── sql\n ├── parser.py\n └── __init__.py\n```\n\n```text\nsrc\n├── cloud\n│ ├── database\n│ │ ├── __init__.py\n│ │ ├── query_builder\n│ │ │ └── __init__.py\n│ │ └── sql\n│ │ ├── __init__.py\n│ │ └── parser.py\n│ └── database_pro\n│ ├── __init__.py\n│ └── query_builder.py\n└── billing\n └── modules\n └── database_pro\n ├── __init__.py\n └── sql.py\n```", - "type": "boolean", - "default": false - }, - "source-exclude": { - "description": "Glob expressions which files and directories to exclude from the source distribution.", - "type": "array", - "default": [], - "items": { - "type": "string" - } - }, - "source-include": { - "description": "Glob expressions which files and directories to additionally include in the source\ndistribution.\n\n`pyproject.toml` and the contents of the module directory are always included.", - "type": "array", - "default": [], - "items": { - "type": "string" - } - }, - "wheel-exclude": { - "description": "Glob expressions which files and directories to exclude from the wheel.", - "type": "array", - "default": [], - "items": { - "type": "string" - } - } - } - }, - "CacheKey": { - "anyOf": [ - { - "description": "Ex) `\"Cargo.lock\"` or `\"**/*.toml\"`", - "type": "string" - }, - { - "description": "Ex) `{ file = \"Cargo.lock\" }` or `{ file = \"**/*.toml\" }`", - "type": "object", - "properties": { - "file": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "file" - ] - }, - { - "description": "Ex) `{ dir = \"src\" }`", - "type": "object", - "properties": { - "dir": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "dir" - ] - }, - { - "description": "Ex) `{ git = true }` or `{ git = { commit = true, tags = false } }`", - "type": "object", - "properties": { - "git": { - "$ref": "#/definitions/GitPattern" - } - }, - "additionalProperties": false, - "required": [ - "git" - ] - }, - { - "description": "Ex) `{ env = \"UV_CACHE_INFO\" }`", - "type": "object", - "properties": { - "env": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "env" - ] - } - ] - }, - "ConfigSettingValue": { - "anyOf": [ - { - "description": "The value consists of a single string.", - "type": "string" - }, - { - "description": "The value consists of a list of strings.", - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "ConfigSettings": { - "description": "Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or\nlist of strings.\n\nSee: ", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ConfigSettingValue" - } - }, - "DefaultGroups": { - "description": "Either the literal \"all\" or a list of groups", - "oneOf": [ - { - "description": "All groups are defaulted", - "type": "string", - "const": "all" - }, - { - "description": "A list of groups", - "type": "array", - "items": { - "$ref": "#/definitions/GroupName" - } - } - ] - }, - "DependencyGroupSettings": { - "type": "object", - "properties": { - "requires-python": { - "description": "Version of python to require when installing this group", - "type": [ - "string", - "null" - ] - } - } - }, - "DisplaySafeUrl": { - "description": "A [`Url`] wrapper that redacts credentials when displaying the URL.\n\n`DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask\nsecrets by default when the URL is displayed or logged. This helps prevent accidental\nexposure of sensitive information in logs and debug output.\n\n# Examples\n\n```\nuse uv_redacted::DisplaySafeUrl;\nuse std::str::FromStr;\n\n// Create a `DisplaySafeUrl` from a `&str`\nlet mut url = DisplaySafeUrl::parse(\"https://user:password@example.com\").unwrap();\n\n// Display will mask secrets\nassert_eq!(url.to_string(), \"https://user:****@example.com/\");\n\n// You can still access the username and password\nassert_eq!(url.username(), \"user\");\nassert_eq!(url.password(), Some(\"password\"));\n\n// And you can still update the username and password\nlet _ = url.set_username(\"new_user\");\nlet _ = url.set_password(Some(\"new_password\"));\nassert_eq!(url.username(), \"new_user\");\nassert_eq!(url.password(), Some(\"new_password\"));\n\n// It is also possible to remove the credentials entirely\nurl.remove_credentials();\nassert_eq!(url.username(), \"\");\nassert_eq!(url.password(), None);\n```", - "type": "string", - "format": "uri" - }, - "ExcludeNewerPackage": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ExcludeNewerTimestamp" - } - }, - "ExcludeNewerTimestamp": { - "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).", - "type": "string", - "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))?$" - }, - "ExtraBuildDependencies": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/definitions/ExtraBuildDependency" - } - } - }, - "ExtraBuildDependency": { - "anyOf": [ - { - "$ref": "#/definitions/Requirement" - }, - { - "type": "object", - "properties": { - "match-runtime": { - "type": "boolean" - }, - "requirement": { - "$ref": "#/definitions/Requirement" - } - }, - "required": [ - "requirement", - "match-runtime" - ] - } - ] - }, - "ExtraBuildVariables": { - "description": "Extra environment variables to set during builds, on a per-package basis.", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "ExtraName": { - "description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee:\n- \n- ", - "type": "string" - }, - "ForkStrategy": { - "oneOf": [ - { - "description": "Optimize for selecting the fewest number of versions for each package. Older versions may\nbe preferred if they are compatible with a wider range of supported Python versions or\nplatforms.", - "type": "string", - "const": "fewest" - }, - { - "description": "Optimize for selecting latest supported version of each package, for each supported Python\nversion.", - "type": "string", - "const": "requires-python" - } - ] - }, - "GitPattern": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#/definitions/GitSet" - } - ] - }, - "GitSet": { - "type": "object", - "properties": { - "commit": { - "type": [ - "boolean", - "null" - ] - }, - "tags": { - "type": [ - "boolean", - "null" - ] - } - }, - "additionalProperties": false - }, - "GroupName": { - "description": "The normalized name of a dependency group.\n\nSee:\n- \n- ", - "type": "string" - }, - "Index": { - "type": "object", - "properties": { - "authenticate": { - "description": "When uv should use authentication for requests to the index.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nauthenticate = \"always\"\n```", - "allOf": [ - { - "$ref": "#/definitions/AuthPolicy" - } - ], - "default": "auto" - }, - "cache-control": { - "description": "Cache control configuration for this index.\n\nWhen set, these headers will override the server's cache control headers\nfor both package metadata requests and artifact downloads.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\ncache-control = { api = \"max-age=600\", files = \"max-age=3600\" }\n```", - "anyOf": [ - { - "$ref": "#/definitions/IndexCacheControl" - }, - { - "type": "null" - } - ], - "default": null - }, - "default": { - "description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are\ndefined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that\naren't found elsewhere. To disable the PyPI default, set `default = true` on at least one\nother index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it\nis given the highest priority when resolving packages.", - "type": "boolean", - "default": false - }, - "explicit": { - "description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]`\ndefinition, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", - "type": "boolean", - "default": false - }, - "format": { - "description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style registry implementing the Simple\nAPI) or structured as a flat list of distributions (e.g., `--find-links`). In both cases,\nindexes can point to either local or remote resources.", - "allOf": [ - { - "$ref": "#/definitions/IndexFormat" - } - ], - "default": "simple" - }, - "ignore-error-codes": { - "description": "Status codes that uv should ignore when deciding whether\nto continue searching in the next index after a failure.\n\n```toml\n[[tool.uv.index]]\nname = \"my-index\"\nurl = \"https:///simple\"\nignore-error-codes = [401, 403]\n```", - "type": [ - "array", - "null" - ], - "default": null, - "items": { - "$ref": "#/definitions/StatusCode" - } - }, - "name": { - "description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example,\nyou can pin a package to a specific index by name:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```", - "anyOf": [ - { - "$ref": "#/definitions/IndexName" - }, - { - "type": "null" - } - ] - }, - "publish-url": { - "description": "The URL of the upload endpoint.\n\nWhen using `uv publish --index `, this URL is used for publishing.\n\nA configuration for the default index PyPI would look as follows:\n\n```toml\n[[tool.uv.index]]\nname = \"pypi\"\nurl = \"https://pypi.org/simple\"\npublish-url = \"https://upload.pypi.org/legacy/\"\n```", - "anyOf": [ - { - "$ref": "#/definitions/DisplaySafeUrl" - }, - { - "type": "null" - } - ] - }, - "url": { - "description": "The URL of the index.\n\nExpects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.", - "allOf": [ - { - "$ref": "#/definitions/IndexUrl" - } - ] - } - }, - "required": [ - "url" - ] + "StatusCode": { + "description": "HTTP status code (100-599)", + "type": "number", + "minimum": 100, + "maximum": 599 }, "IndexCacheControl": { "description": "Cache control configuration for an index.", @@ -1096,24 +850,6 @@ } } }, - "IndexFormat": { - "oneOf": [ - { - "description": "A PyPI-style index implementing the Simple Repository API.", - "type": "string", - "const": "simple" - }, - { - "description": "A `--find-links`-style index containing a flat list of wheels and source distributions.", - "type": "string", - "const": "flat" - } - ] - }, - "IndexName": { - "description": "The normalized name of an index.\n\nIndex names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII.", - "type": "string" - }, "IndexStrategy": { "oneOf": [ { @@ -1133,10 +869,6 @@ } ] }, - "IndexUrl": { - "description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path.", - "type": "string" - }, "KeyringProviderType": { "description": "Keyring provider type to use for credential lookup.", "oneOf": [ @@ -1152,618 +884,25 @@ } ] }, - "LinkMode": { + "ResolutionMode": { "oneOf": [ { - "description": "Clone (i.e., copy-on-write) packages from the wheel into the `site-packages` directory.", + "description": "Resolve the highest compatible version of each package.", "type": "string", - "const": "clone" + "const": "highest" }, { - "description": "Copy packages from the wheel into the `site-packages` directory.", + "description": "Resolve the lowest compatible version of each package.", "type": "string", - "const": "copy" + "const": "lowest" }, { - "description": "Hard link packages from the wheel into the `site-packages` directory.", + "description": "Resolve the lowest compatible version of any direct dependencies, and the highest\ncompatible version of any transitive dependencies.", "type": "string", - "const": "hardlink" - }, - { - "description": "Symbolically link packages from the wheel into the `site-packages` directory.", - "type": "string", - "const": "symlink" + "const": "lowest-direct" } ] }, - "MarkerTree": { - "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`", - "type": "string" - }, - "ModuleName": { - "description": "Whether to include a single module or multiple modules.", - "anyOf": [ - { - "description": "A single module name.", - "type": "string" - }, - { - "description": "Multiple module names, which are all included.", - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "PackageConfigSettings": { - "description": "Settings to pass to PEP 517 build backends on a per-package basis.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ConfigSettings" - } - }, - "PackageName": { - "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", - "type": "string" - }, - "PackageNameSpecifier": { - "description": "The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.", - "type": "string", - "pattern": "^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$" - }, - "PipGroupName": { - "description": "The pip-compatible variant of a [`GroupName`].\n\nEither or :.\nIf is omitted it defaults to \"pyproject.toml\".", - "type": "object", - "properties": { - "name": { - "$ref": "#/definitions/GroupName" - }, - "path": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "name" - ] - }, - "PipOptions": { - "description": "Settings that are specific to the `uv pip` command-line interface.\n\nThese values will be ignored when running commands outside the `uv pip` namespace (e.g.,\n`uv lock`, `uvx`).", - "type": "object", - "properties": { - "all-extras": { - "description": "Include all optional dependencies.\n\nOnly applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.", - "type": [ - "boolean", - "null" - ] - }, - "allow-empty-requirements": { - "description": "Allow `uv pip sync` with empty requirements, which will clear the environment of all\npackages.", - "type": [ - "boolean", - "null" - ] - }, - "annotation-style": { - "description": "The style of the annotation comments included in the output file, used to indicate the\nsource of each package.", - "anyOf": [ - { - "$ref": "#/definitions/AnnotationStyle" - }, - { - "type": "null" - } - ] - }, - "break-system-packages": { - "description": "Allow uv to modify an `EXTERNALLY-MANAGED` Python installation.\n\nWARNING: `--break-system-packages` is intended for use in continuous integration (CI)\nenvironments, when installing into Python installations that are managed by an external\npackage manager, like `apt`. It should be used with caution, as such Python installations\nexplicitly recommend against modifications by other package managers (like uv or pip).", - "type": [ - "boolean", - "null" - ] - }, - "compile-bytecode": { - "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", - "type": [ - "boolean", - "null" - ] - }, - "config-settings": { - "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,\nspecified as `KEY=VALUE` pairs.", - "anyOf": [ - { - "$ref": "#/definitions/ConfigSettings" - }, - { - "type": "null" - } - ] - }, - "config-settings-package": { - "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.", - "anyOf": [ - { - "$ref": "#/definitions/PackageConfigSettings" - }, - { - "type": "null" - } - ] - }, - "custom-compile-command": { - "description": "The header comment to include at the top of the output file generated by `uv pip compile`.\n\nUsed to reflect custom build scripts and commands that wrap `uv pip compile`.", - "type": [ - "string", - "null" - ] - }, - "dependency-metadata": { - "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/StaticMetadata" - } - }, - "emit-build-options": { - "description": "Include `--no-binary` and `--only-binary` entries in the output file generated by `uv pip compile`.", - "type": [ - "boolean", - "null" - ] - }, - "emit-find-links": { - "description": "Include `--find-links` entries in the output file generated by `uv pip compile`.", - "type": [ - "boolean", - "null" - ] - }, - "emit-index-annotation": { - "description": "Include comment annotations indicating the index used to resolve each package (e.g.,\n`# from https://pypi.org/simple`).", - "type": [ - "boolean", - "null" - ] - }, - "emit-index-url": { - "description": "Include `--index-url` and `--extra-index-url` entries in the output file generated by `uv pip compile`.", - "type": [ - "boolean", - "null" - ] - }, - "emit-marker-expression": { - "description": "Whether to emit a marker string indicating the conditions under which the set of pinned\ndependencies is valid.\n\nThe pinned dependencies may be valid even when the marker expression is\nfalse, but when the expression is true, the requirements are known to\nbe correct.", - "type": [ - "boolean", - "null" - ] - }, - "exclude-newer": { - "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", - "anyOf": [ - { - "$ref": "#/definitions/ExcludeNewerTimestamp" - }, - { - "type": "null" - } - ] - }, - "exclude-newer-package": { - "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.", - "anyOf": [ - { - "$ref": "#/definitions/ExcludeNewerPackage" - }, - { - "type": "null" - } - ] - }, - "extra": { - "description": "Include optional dependencies from the specified extra; may be provided more than once.\n\nOnly applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/ExtraName" - } - }, - "extra-build-dependencies": { - "description": "Additional build dependencies for packages.\n\nThis allows extending the PEP 517 build environment for the project's dependencies with\nadditional packages. This is useful for packages that assume the presence of packages like\n`pip`, and do not declare them as build dependencies.", - "anyOf": [ - { - "$ref": "#/definitions/ExtraBuildDependencies" - }, - { - "type": "null" - } - ] - }, - "extra-build-variables": { - "description": "Extra environment variables to set when building certain packages.\n\nEnvironment variables will be added to the environment when building the\nspecified packages.", - "anyOf": [ - { - "$ref": "#/definitions/ExtraBuildVariables" - }, - { - "type": "null" - } - ] - }, - "extra-index-url": { - "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/IndexUrl" - } - }, - "find-links": { - "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/IndexUrl" - } - }, - "fork-strategy": { - "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", - "anyOf": [ - { - "$ref": "#/definitions/ForkStrategy" - }, - { - "type": "null" - } - ] - }, - "generate-hashes": { - "description": "Include distribution hashes in the output file.", - "type": [ - "boolean", - "null" - ] - }, - "group": { - "description": "Include the following dependency groups.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PipGroupName" - } - }, - "index-strategy": { - "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", - "anyOf": [ - { - "$ref": "#/definitions/IndexStrategy" - }, - { - "type": "null" - } - ] - }, - "index-url": { - "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url).", - "anyOf": [ - { - "$ref": "#/definitions/IndexUrl" - }, - { - "type": "null" - } - ] - }, - "keyring-provider": { - "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", - "anyOf": [ - { - "$ref": "#/definitions/KeyringProviderType" - }, - { - "type": "null" - } - ] - }, - "link-mode": { - "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and\nWindows.\n\nWARNING: The use of symlink link mode is discouraged, as they create tight coupling between\nthe cache and the target environment. For example, clearing the cache (`uv cache clean`)\nwill break all installed packages by way of removing the underlying source files. Use\nsymlinks with caution.", - "anyOf": [ - { - "$ref": "#/definitions/LinkMode" - }, - { - "type": "null" - } - ] - }, - "no-annotate": { - "description": "Exclude comment annotations indicating the source of each package from the output file\ngenerated by `uv pip compile`.", - "type": [ - "boolean", - "null" - ] - }, - "no-binary": { - "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageNameSpecifier" - } - }, - "no-build": { - "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.\n\nAlias for `--only-binary :all:`.", - "type": [ - "boolean", - "null" - ] - }, - "no-build-isolation": { - "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", - "type": [ - "boolean", - "null" - ] - }, - "no-build-isolation-package": { - "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "no-deps": { - "description": "Ignore package dependencies, instead only add those packages explicitly listed\non the command line to the resulting requirements file.", - "type": [ - "boolean", - "null" - ] - }, - "no-emit-package": { - "description": "Specify a package to omit from the output resolution. Its dependencies will still be\nincluded in the resolution. Equivalent to pip-compile's `--unsafe-package` option.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "no-extra": { - "description": "Exclude the specified optional dependencies if `all-extras` is supplied.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/ExtraName" - } - }, - "no-header": { - "description": "Exclude the comment header at the top of output file generated by `uv pip compile`.", - "type": [ - "boolean", - "null" - ] - }, - "no-index": { - "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", - "type": [ - "boolean", - "null" - ] - }, - "no-sources": { - "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the\nstandards-compliant, publishable package metadata, as opposed to using any local or Git\nsources.", - "type": [ - "boolean", - "null" - ] - }, - "no-strip-extras": { - "description": "Include extras in the output file.\n\nBy default, uv strips extras, as any packages pulled in by the extras are already included\nas dependencies in the output file directly. Further, output files generated with\n`--no-strip-extras` cannot be used as constraints files in `install` and `sync` invocations.", - "type": [ - "boolean", - "null" - ] - }, - "no-strip-markers": { - "description": "Include environment markers in the output file generated by `uv pip compile`.\n\nBy default, uv strips environment markers, as the resolution generated by `compile` is\nonly guaranteed to be correct for the target environment.", - "type": [ - "boolean", - "null" - ] - }, - "only-binary": { - "description": "Only use pre-built wheels; don't build source distributions.\n\nWhen enabled, resolving will not run code from the given packages. The cached wheels of already-built\nsource distributions will be reused, but operations that require building distributions will\nexit with an error.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageNameSpecifier" - } - }, - "output-file": { - "description": "Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.\n\nIf the file already exists, the existing versions will be preferred when resolving\ndependencies, unless `--upgrade` is also specified.", - "type": [ - "string", - "null" - ] - }, - "prefix": { - "description": "Install packages into `lib`, `bin`, and other top-level folders under the specified\ndirectory, as if a virtual environment were present at that location.\n\nIn general, prefer the use of `--python` to install into an alternate environment, as\nscripts and other artifacts installed via `--prefix` will reference the installing\ninterpreter, rather than any interpreter added to the `--prefix` directory, rendering them\nnon-portable.", - "type": [ - "string", - "null" - ] - }, - "prerelease": { - "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", - "anyOf": [ - { - "$ref": "#/definitions/PrereleaseMode" - }, - { - "type": "null" - } - ] - }, - "python": { - "description": "The Python interpreter into which packages should be installed.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--python` option allows you to specify a different interpreter,\nwhich is intended for use in continuous integration (CI) environments or other automated\nworkflows.\n\nSupported formats:\n- `3.10` looks for an installed Python 3.10 in the registry on Windows (see\n `py --list-paths`), or `python3.10` on Linux and macOS.\n- `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.\n- `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.", - "type": [ - "string", - "null" - ] - }, - "python-platform": { - "description": "The platform for which requirements should be resolved.\n\nRepresented as a \"target triple\", a string that describes the target platform in terms of\nits CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or\n`aarch64-apple-darwin`.", - "anyOf": [ - { - "$ref": "#/definitions/TargetTriple" - }, - { - "type": "null" - } - ] - }, - "python-version": { - "description": "The minimum Python version that should be supported by the resolved requirements (e.g.,\n`3.8` or `3.8.17`).\n\nIf a patch version is omitted, the minimum patch version is assumed. For example, `3.8` is\nmapped to `3.8.0`.", - "anyOf": [ - { - "$ref": "#/definitions/PythonVersion" - }, - { - "type": "null" - } - ] - }, - "reinstall": { - "description": "Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.", - "type": [ - "boolean", - "null" - ] - }, - "reinstall-package": { - "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/PackageName" - } - }, - "require-hashes": { - "description": "Require a matching hash for each requirement.\n\nHash-checking mode is all or nothing. If enabled, _all_ requirements must be provided\nwith a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements\nmust either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.\n\nHash-checking mode introduces a number of additional constraints:\n\n- Git dependencies are not supported.\n- Editable installations are not supported.\n- Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or\n source archive (`.zip`, `.tar.gz`), as opposed to a directory.", - "type": [ - "boolean", - "null" - ] - }, - "resolution": { - "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", - "anyOf": [ - { - "$ref": "#/definitions/ResolutionMode" - }, - { - "type": "null" - } - ] - }, - "strict": { - "description": "Validate the Python environment, to detect packages with missing dependencies and other\nissues.", - "type": [ - "boolean", - "null" - ] - }, - "system": { - "description": "Install packages into the system Python environment.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--system` option instructs uv to instead use the first Python\nfound in the system `PATH`.\n\nWARNING: `--system` is intended for use in continuous integration (CI) environments and\nshould be used with caution, as it can modify the system Python installation.", - "type": [ - "boolean", - "null" - ] - }, - "target": { - "description": "Install packages into the specified directory, rather than into the virtual or system Python\nenvironment. The packages will be installed at the top-level of the directory.", - "type": [ - "string", - "null" - ] - }, - "torch-backend": { - "description": "The backend to use when fetching packages in the PyTorch ecosystem.\n\nWhen set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem,\nand will instead use the defined backend.\n\nFor example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`,\nuv will use the PyTorch index for CUDA 12.6.\n\nThe `auto` mode will attempt to detect the appropriate PyTorch index based on the currently\ninstalled CUDA drivers.\n\nThis option is in preview and may change in any future release.", - "anyOf": [ - { - "$ref": "#/definitions/TorchMode" - }, - { - "type": "null" - } - ] - }, - "universal": { - "description": "Perform a universal resolution, attempting to generate a single `requirements.txt` output\nfile that is compatible with all operating systems, architectures, and Python\nimplementations.\n\nIn universal mode, the current Python version (or user-provided `--python-version`) will be\ntreated as a lower bound. For example, `--universal --python-version 3.7` would produce a\nuniversal resolution for Python 3.7 and later.", - "type": [ - "boolean", - "null" - ] - }, - "upgrade": { - "description": "Allow package upgrades, ignoring pinned versions in any existing output file.", - "type": [ - "boolean", - "null" - ] - }, - "upgrade-package": { - "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output\nfile.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Requirement" - } - }, - "verify-hashes": { - "description": "Validate any hashes provided in the requirements file.\n\nUnlike `--require-hashes`, `--verify-hashes` does not require that all requirements have\nhashes; instead, it will limit itself to verifying the hashes of those requirements that do\ninclude them.", - "type": [ - "boolean", - "null" - ] - } - }, - "additionalProperties": false - }, - "PortablePathBuf": { - "type": "string" - }, "PrereleaseMode": { "oneOf": [ { @@ -1793,406 +932,17 @@ } ] }, - "PythonDownloads": { + "ForkStrategy": { "oneOf": [ { - "description": "Automatically download managed Python installations when needed.", + "description": "Optimize for selecting the fewest number of versions for each package. Older versions may\nbe preferred if they are compatible with a wider range of supported Python versions or\nplatforms.", "type": "string", - "const": "automatic" + "const": "fewest" }, { - "description": "Do not automatically download managed Python installations; require explicit installation.", + "description": "Optimize for selecting latest supported version of each package, for each supported Python\nversion.", "type": "string", - "const": "manual" - }, - { - "description": "Do not ever allow Python downloads.", - "type": "string", - "const": "never" - } - ] - }, - "PythonPreference": { - "oneOf": [ - { - "description": "Only use managed Python installations; never use system Python installations.", - "type": "string", - "const": "only-managed" - }, - { - "description": "Prefer managed Python installations over system Python installations.\n\nSystem Python installations are still preferred over downloading managed Python versions.\nUse `only-managed` to always fetch a managed Python version.", - "type": "string", - "const": "managed" - }, - { - "description": "Prefer system Python installations over managed Python installations.\n\nIf a system Python installation cannot be found, a managed Python installation can be used.", - "type": "string", - "const": "system" - }, - { - "description": "Only use system Python installations; never use managed Python installations.", - "type": "string", - "const": "only-system" - } - ] - }, - "PythonVersion": { - "description": "A Python version specifier, e.g. `3.11` or `3.12.4`.", - "type": "string", - "pattern": "^3\\.\\d+(\\.\\d+)?$" - }, - "RequiredVersion": { - "description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`.", - "type": "string" - }, - "Requirement": { - "description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`", - "type": "string" - }, - "ResolutionMode": { - "oneOf": [ - { - "description": "Resolve the highest compatible version of each package.", - "type": "string", - "const": "highest" - }, - { - "description": "Resolve the lowest compatible version of each package.", - "type": "string", - "const": "lowest" - }, - { - "description": "Resolve the lowest compatible version of any direct dependencies, and the highest\ncompatible version of any transitive dependencies.", - "type": "string", - "const": "lowest-direct" - } - ] - }, - "SchemaConflictItem": { - "description": "A single item in a conflicting set.\n\nEach item is a pair of an (optional) package and a corresponding extra or group name for that\npackage.", - "type": "object", - "properties": { - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ], - "default": null - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ], - "default": null - }, - "package": { - "anyOf": [ - { - "$ref": "#/definitions/PackageName" - }, - { - "type": "null" - } - ], - "default": null - } - } - }, - "SchemaConflictSet": { - "description": "Like [`ConflictSet`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.", - "type": "array", - "items": { - "$ref": "#/definitions/SchemaConflictItem" - } - }, - "SchemaConflicts": { - "description": "Like [`Conflicts`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.\n\nN.B. `Conflicts` is still used for (de)serialization. Specifically, in the\nlock file, where the package name is required.", - "type": "array", - "items": { - "$ref": "#/definitions/SchemaConflictSet" - } - }, - "SerdePattern": { - "type": "string" - }, - "Source": { - "description": "A `tool.uv.sources` value.", - "anyOf": [ - { - "description": "A remote Git repository, available over HTTPS or SSH.\n\nExample:\n```toml\nflask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" }\n```", - "type": "object", - "properties": { - "branch": { - "type": [ - "string", - "null" - ] - }, - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ] - }, - "git": { - "description": "The repository URL (without the `git+` prefix).", - "allOf": [ - { - "$ref": "#/definitions/DisplaySafeUrl" - } - ] - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ] - }, - "lfs": { - "description": "Whether to use Git LFS when cloning the repository.", - "type": [ - "boolean", - "null" - ] - }, - "marker": { - "$ref": "#/definitions/MarkerTree" - }, - "rev": { - "type": [ - "string", - "null" - ] - }, - "subdirectory": { - "description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.", - "anyOf": [ - { - "$ref": "#/definitions/PortablePathBuf" - }, - { - "type": "null" - } - ] - }, - "tag": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "required": [ - "git" - ] - }, - { - "description": "A remote `http://` or `https://` URL, either a wheel (`.whl`) or a source distribution\n(`.zip`, `.tar.gz`).\n\nExample:\n```toml\nflask = { url = \"https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl\" }\n```", - "type": "object", - "properties": { - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ] - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ] - }, - "marker": { - "$ref": "#/definitions/MarkerTree" - }, - "subdirectory": { - "description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's\nnot in the archive root.", - "anyOf": [ - { - "$ref": "#/definitions/PortablePathBuf" - }, - { - "type": "null" - } - ] - }, - "url": { - "$ref": "#/definitions/DisplaySafeUrl" - } - }, - "additionalProperties": false, - "required": [ - "url" - ] - }, - { - "description": "The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or\n`.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or\n`setup.py` file in the root).", - "type": "object", - "properties": { - "editable": { - "description": "`false` by default.", - "type": [ - "boolean", - "null" - ] - }, - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ] - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ] - }, - "marker": { - "$ref": "#/definitions/MarkerTree" - }, - "package": { - "description": "Whether to treat the dependency as a buildable Python package (`true`) or as a virtual\npackage (`false`). If `false`, the package will not be built or installed, but its\ndependencies will be included in the virtual environment.\n\nWhen omitted, the package status is inferred based on the presence of a `[build-system]`\nin the project's `pyproject.toml`.", - "type": [ - "boolean", - "null" - ] - }, - "path": { - "$ref": "#/definitions/PortablePathBuf" - } - }, - "additionalProperties": false, - "required": [ - "path" - ] - }, - { - "description": "A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.", - "type": "object", - "properties": { - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ] - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ] - }, - "index": { - "$ref": "#/definitions/IndexName" - }, - "marker": { - "$ref": "#/definitions/MarkerTree" - } - }, - "additionalProperties": false, - "required": [ - "index" - ] - }, - { - "description": "A dependency on another package in the workspace.", - "type": "object", - "properties": { - "editable": { - "description": "Whether the package should be installed as editable. Defaults to `true`.", - "type": [ - "boolean", - "null" - ] - }, - "extra": { - "anyOf": [ - { - "$ref": "#/definitions/ExtraName" - }, - { - "type": "null" - } - ] - }, - "group": { - "anyOf": [ - { - "$ref": "#/definitions/GroupName" - }, - { - "type": "null" - } - ] - }, - "marker": { - "$ref": "#/definitions/MarkerTree" - }, - "workspace": { - "description": "When set to `false`, the package will be fetched from the remote index, rather than\nincluded as a workspace package.", - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "workspace" - ] - } - ] - }, - "Sources": { - "anyOf": [ - { - "$ref": "#/definitions/Source" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/Source" - } + "const": "requires-python" } ] }, @@ -2203,19 +953,19 @@ "name": { "$ref": "#/definitions/PackageName" }, - "provides-extra": { - "type": "array", - "default": [], - "items": { - "$ref": "#/definitions/ExtraName" - } + "version": { + "description": "PEP 440-style package version, e.g., `1.2.3`", + "type": [ + "string", + "null" + ] }, "requires-dist": { "type": "array", - "default": [], "items": { "$ref": "#/definitions/Requirement" - } + }, + "default": [] }, "requires-python": { "description": "PEP 508-style Python requirement, e.g., `>=3.10`", @@ -2224,12 +974,12 @@ "null" ] }, - "version": { - "description": "PEP 440-style package version, e.g., `1.2.3`", - "type": [ - "string", - "null" - ] + "provides-extra": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtraName" + }, + "default": [] } }, "additionalProperties": false, @@ -2237,11 +987,726 @@ "name" ] }, - "StatusCode": { - "description": "HTTP status code (100-599)", - "type": "number", - "maximum": 599, - "minimum": 100 + "PackageName": { + "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", + "type": "string" + }, + "Requirement": { + "description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`", + "type": "string" + }, + "ExtraName": { + "description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee:\n- \n- ", + "type": "string" + }, + "ConfigSettings": { + "description": "Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or\nlist of strings.\n\nSee: ", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ConfigSettingValue" + } + }, + "ConfigSettingValue": { + "anyOf": [ + { + "description": "The value consists of a single string.", + "type": "string" + }, + { + "description": "The value consists of a list of strings.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "PackageConfigSettings": { + "description": "Settings to pass to PEP 517 build backends on a per-package basis.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ConfigSettings" + } + }, + "ExtraBuildDependencies": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtraBuildDependency" + } + } + }, + "ExtraBuildDependency": { + "anyOf": [ + { + "$ref": "#/definitions/Requirement" + }, + { + "type": "object", + "properties": { + "requirement": { + "$ref": "#/definitions/Requirement" + }, + "match-runtime": { + "type": "boolean" + } + }, + "required": [ + "requirement", + "match-runtime" + ] + } + ] + }, + "ExtraBuildVariables": { + "description": "Extra environment variables to set during builds, on a per-package basis.", + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "ExcludeNewerTimestamp": { + "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).", + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))?$" + }, + "ExcludeNewerPackage": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ExcludeNewerTimestamp" + } + }, + "LinkMode": { + "oneOf": [ + { + "description": "Clone (i.e., copy-on-write) packages from the wheel into the `site-packages` directory.", + "type": "string", + "const": "clone" + }, + { + "description": "Copy packages from the wheel into the `site-packages` directory.", + "type": "string", + "const": "copy" + }, + { + "description": "Hard link packages from the wheel into the `site-packages` directory.", + "type": "string", + "const": "hardlink" + }, + { + "description": "Symbolically link packages from the wheel into the `site-packages` directory.", + "type": "string", + "const": "symlink" + } + ] + }, + "TrustedPublishing": { + "oneOf": [ + { + "type": "string", + "enum": [ + "always", + "never" + ] + }, + { + "description": "Attempt trusted publishing when we're in a supported environment, continue if that fails.\n\nSupported environments include GitHub Actions and GitLab CI/CD.", + "type": "string", + "const": "automatic" + } + ] + }, + "AddBoundsKind": { + "description": "The default version specifier when adding a dependency.", + "oneOf": [ + { + "description": "Only a lower bound, e.g., `>=1.2.3`.", + "type": "string", + "const": "lower" + }, + { + "description": "Allow the same major version, similar to the semver caret, e.g., `>=1.2.3, <2.0.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.2.0`.", + "type": "string", + "const": "major" + }, + { + "description": "Allow the same minor version, similar to the semver tilde, e.g., `>=1.2.3, <1.3.0`.\n\nLeading zeroes are skipped, e.g. `>=0.1.2, <0.1.3`.", + "type": "string", + "const": "minor" + }, + { + "description": "Pin the exact version, e.g., `==1.2.3`.\n\nThis option is not recommended, as versions are already pinned in the uv lockfile.", + "type": "string", + "const": "exact" + } + ] + }, + "PipOptions": { + "description": "Settings that are specific to the `uv pip` command-line interface.\n\nThese values will be ignored when running commands outside the `uv pip` namespace (e.g.,\n`uv lock`, `uvx`).", + "type": "object", + "properties": { + "python": { + "description": "The Python interpreter into which packages should be installed.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--python` option allows you to specify a different interpreter,\nwhich is intended for use in continuous integration (CI) environments or other automated\nworkflows.\n\nSupported formats:\n- `3.10` looks for an installed Python 3.10 in the registry on Windows (see\n `py --list-paths`), or `python3.10` on Linux and macOS.\n- `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.\n- `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.", + "type": [ + "string", + "null" + ] + }, + "system": { + "description": "Install packages into the system Python environment.\n\nBy default, uv installs into the virtual environment in the current working directory or\nany parent directory. The `--system` option instructs uv to instead use the first Python\nfound in the system `PATH`.\n\nWARNING: `--system` is intended for use in continuous integration (CI) environments and\nshould be used with caution, as it can modify the system Python installation.", + "type": [ + "boolean", + "null" + ] + }, + "break-system-packages": { + "description": "Allow uv to modify an `EXTERNALLY-MANAGED` Python installation.\n\nWARNING: `--break-system-packages` is intended for use in continuous integration (CI)\nenvironments, when installing into Python installations that are managed by an external\npackage manager, like `apt`. It should be used with caution, as such Python installations\nexplicitly recommend against modifications by other package managers (like uv or pip).", + "type": [ + "boolean", + "null" + ] + }, + "target": { + "description": "Install packages into the specified directory, rather than into the virtual or system Python\nenvironment. The packages will be installed at the top-level of the directory.", + "type": [ + "string", + "null" + ] + }, + "prefix": { + "description": "Install packages into `lib`, `bin`, and other top-level folders under the specified\ndirectory, as if a virtual environment were present at that location.\n\nIn general, prefer the use of `--python` to install into an alternate environment, as\nscripts and other artifacts installed via `--prefix` will reference the installing\ninterpreter, rather than any interpreter added to the `--prefix` directory, rendering them\nnon-portable.", + "type": [ + "string", + "null" + ] + }, + "index-url": { + "description": "The URL of the Python package index (by default: ).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via\n[`extra_index_url`](#extra-index-url).", + "anyOf": [ + { + "$ref": "#/definitions/IndexUrl" + }, + { + "type": "null" + } + ] + }, + "extra-index-url": { + "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/IndexUrl" + } + }, + "no-index": { + "description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.", + "type": [ + "boolean", + "null" + ] + }, + "find-links": { + "description": "Locations to search for candidate distributions, in addition to those found in the registry\nindexes.\n\nIf a path, the target must be a directory that contains packages as wheel files (`.whl`) or\nsource distributions (e.g., `.tar.gz` or `.zip`) at the top level.\n\nIf a URL, the page must contain a flat list of links to package files adhering to the\nformats described above.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/IndexUrl" + } + }, + "index-strategy": { + "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and\nlimit resolutions to those present on that first index (`first-index`). This prevents\n\"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the\nsame name to an alternate index.", + "anyOf": [ + { + "$ref": "#/definitions/IndexStrategy" + }, + { + "type": "null" + } + ] + }, + "keyring-provider": { + "description": "Attempt to use `keyring` for authentication for index URLs.\n\nAt present, only `--keyring-provider subprocess` is supported, which configures uv to\nuse the `keyring` CLI to handle authentication.", + "anyOf": [ + { + "$ref": "#/definitions/KeyringProviderType" + }, + { + "type": "null" + } + ] + }, + "no-build": { + "description": "Don't build source distributions.\n\nWhen enabled, resolving will not run arbitrary Python code. The cached wheels of\nalready-built source distributions will be reused, but operations that require building\ndistributions will exit with an error.\n\nAlias for `--only-binary :all:`.", + "type": [ + "boolean", + "null" + ] + }, + "no-binary": { + "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageNameSpecifier" + } + }, + "only-binary": { + "description": "Only use pre-built wheels; don't build source distributions.\n\nWhen enabled, resolving will not run code from the given packages. The cached wheels of already-built\nsource distributions will be reused, but operations that require building distributions will\nexit with an error.\n\nMultiple packages may be provided. Disable binaries for all packages with `:all:`.\nClear previously specified packages with `:none:`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageNameSpecifier" + } + }, + "no-build-isolation": { + "description": "Disable isolation when building source distributions.\n\nAssumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", + "type": [ + "boolean", + "null" + ] + }, + "no-build-isolation-package": { + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)\nare already installed.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "extra-build-dependencies": { + "description": "Additional build dependencies for packages.\n\nThis allows extending the PEP 517 build environment for the project's dependencies with\nadditional packages. This is useful for packages that assume the presence of packages like\n`pip`, and do not declare them as build dependencies.", + "anyOf": [ + { + "$ref": "#/definitions/ExtraBuildDependencies" + }, + { + "type": "null" + } + ] + }, + "extra-build-variables": { + "description": "Extra environment variables to set when building certain packages.\n\nEnvironment variables will be added to the environment when building the\nspecified packages.", + "anyOf": [ + { + "$ref": "#/definitions/ExtraBuildVariables" + }, + { + "type": "null" + } + ] + }, + "strict": { + "description": "Validate the Python environment, to detect packages with missing dependencies and other\nissues.", + "type": [ + "boolean", + "null" + ] + }, + "extra": { + "description": "Include optional dependencies from the specified extra; may be provided more than once.\n\nOnly applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/ExtraName" + } + }, + "all-extras": { + "description": "Include all optional dependencies.\n\nOnly applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.", + "type": [ + "boolean", + "null" + ] + }, + "no-extra": { + "description": "Exclude the specified optional dependencies if `all-extras` is supplied.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/ExtraName" + } + }, + "no-deps": { + "description": "Ignore package dependencies, instead only add those packages explicitly listed\non the command line to the resulting requirements file.", + "type": [ + "boolean", + "null" + ] + }, + "group": { + "description": "Include the following dependency groups.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PipGroupName" + } + }, + "allow-empty-requirements": { + "description": "Allow `uv pip sync` with empty requirements, which will clear the environment of all\npackages.", + "type": [ + "boolean", + "null" + ] + }, + "resolution": { + "description": "The strategy to use when selecting between the different compatible versions for a given\npackage requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", + "anyOf": [ + { + "$ref": "#/definitions/ResolutionMode" + }, + { + "type": "null" + } + ] + }, + "prerelease": { + "description": "The strategy to use when considering pre-release versions.\n\nBy default, uv will accept pre-releases for packages that _only_ publish pre-releases,\nalong with first-party requirements that contain an explicit pre-release marker in the\ndeclared specifiers (`if-necessary-or-explicit`).", + "anyOf": [ + { + "$ref": "#/definitions/PrereleaseMode" + }, + { + "type": "null" + } + ] + }, + "fork-strategy": { + "description": "The strategy to use when selecting multiple versions of a given package across Python\nversions and platforms.\n\nBy default, uv will optimize for selecting the latest version of each package for each\nsupported Python version (`requires-python`), while minimizing the number of selected\nversions across platforms.\n\nUnder `fewest`, uv will minimize the number of selected versions for each package,\npreferring older versions that are compatible with a wider range of supported Python\nversions or platforms.", + "anyOf": [ + { + "$ref": "#/definitions/ForkStrategy" + }, + { + "type": "null" + } + ] + }, + "dependency-metadata": { + "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/StaticMetadata" + } + }, + "output-file": { + "description": "Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.\n\nIf the file already exists, the existing versions will be preferred when resolving\ndependencies, unless `--upgrade` is also specified.", + "type": [ + "string", + "null" + ] + }, + "no-strip-extras": { + "description": "Include extras in the output file.\n\nBy default, uv strips extras, as any packages pulled in by the extras are already included\nas dependencies in the output file directly. Further, output files generated with\n`--no-strip-extras` cannot be used as constraints files in `install` and `sync` invocations.", + "type": [ + "boolean", + "null" + ] + }, + "no-strip-markers": { + "description": "Include environment markers in the output file generated by `uv pip compile`.\n\nBy default, uv strips environment markers, as the resolution generated by `compile` is\nonly guaranteed to be correct for the target environment.", + "type": [ + "boolean", + "null" + ] + }, + "no-annotate": { + "description": "Exclude comment annotations indicating the source of each package from the output file\ngenerated by `uv pip compile`.", + "type": [ + "boolean", + "null" + ] + }, + "no-header": { + "description": "Exclude the comment header at the top of output file generated by `uv pip compile`.", + "type": [ + "boolean", + "null" + ] + }, + "custom-compile-command": { + "description": "The header comment to include at the top of the output file generated by `uv pip compile`.\n\nUsed to reflect custom build scripts and commands that wrap `uv pip compile`.", + "type": [ + "string", + "null" + ] + }, + "generate-hashes": { + "description": "Include distribution hashes in the output file.", + "type": [ + "boolean", + "null" + ] + }, + "config-settings": { + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,\nspecified as `KEY=VALUE` pairs.", + "anyOf": [ + { + "$ref": "#/definitions/ConfigSettings" + }, + { + "type": "null" + } + ] + }, + "config-settings-package": { + "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.", + "anyOf": [ + { + "$ref": "#/definitions/PackageConfigSettings" + }, + { + "type": "null" + } + ] + }, + "python-version": { + "description": "The minimum Python version that should be supported by the resolved requirements (e.g.,\n`3.8` or `3.8.17`).\n\nIf a patch version is omitted, the minimum patch version is assumed. For example, `3.8` is\nmapped to `3.8.0`.", + "anyOf": [ + { + "$ref": "#/definitions/PythonVersion" + }, + { + "type": "null" + } + ] + }, + "python-platform": { + "description": "The platform for which requirements should be resolved.\n\nRepresented as a \"target triple\", a string that describes the target platform in terms of\nits CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or\n`aarch64-apple-darwin`.", + "anyOf": [ + { + "$ref": "#/definitions/TargetTriple" + }, + { + "type": "null" + } + ] + }, + "universal": { + "description": "Perform a universal resolution, attempting to generate a single `requirements.txt` output\nfile that is compatible with all operating systems, architectures, and Python\nimplementations.\n\nIn universal mode, the current Python version (or user-provided `--python-version`) will be\ntreated as a lower bound. For example, `--universal --python-version 3.7` would produce a\nuniversal resolution for Python 3.7 and later.", + "type": [ + "boolean", + "null" + ] + }, + "exclude-newer": { + "description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.", + "anyOf": [ + { + "$ref": "#/definitions/ExcludeNewerTimestamp" + }, + { + "type": "null" + } + ] + }, + "exclude-newer-package": { + "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.", + "anyOf": [ + { + "$ref": "#/definitions/ExcludeNewerPackage" + }, + { + "type": "null" + } + ] + }, + "no-emit-package": { + "description": "Specify a package to omit from the output resolution. Its dependencies will still be\nincluded in the resolution. Equivalent to pip-compile's `--unsafe-package` option.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "emit-index-url": { + "description": "Include `--index-url` and `--extra-index-url` entries in the output file generated by `uv pip compile`.", + "type": [ + "boolean", + "null" + ] + }, + "emit-find-links": { + "description": "Include `--find-links` entries in the output file generated by `uv pip compile`.", + "type": [ + "boolean", + "null" + ] + }, + "emit-build-options": { + "description": "Include `--no-binary` and `--only-binary` entries in the output file generated by `uv pip compile`.", + "type": [ + "boolean", + "null" + ] + }, + "emit-marker-expression": { + "description": "Whether to emit a marker string indicating the conditions under which the set of pinned\ndependencies is valid.\n\nThe pinned dependencies may be valid even when the marker expression is\nfalse, but when the expression is true, the requirements are known to\nbe correct.", + "type": [ + "boolean", + "null" + ] + }, + "emit-index-annotation": { + "description": "Include comment annotations indicating the index used to resolve each package (e.g.,\n`# from https://pypi.org/simple`).", + "type": [ + "boolean", + "null" + ] + }, + "annotation-style": { + "description": "The style of the annotation comments included in the output file, used to indicate the\nsource of each package.", + "anyOf": [ + { + "$ref": "#/definitions/AnnotationStyle" + }, + { + "type": "null" + } + ] + }, + "link-mode": { + "description": "The method to use when installing packages from the global cache.\n\nDefaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and\nWindows.\n\nWARNING: The use of symlink link mode is discouraged, as they create tight coupling between\nthe cache and the target environment. For example, clearing the cache (`uv cache clean`)\nwill break all installed packages by way of removing the underlying source files. Use\nsymlinks with caution.", + "anyOf": [ + { + "$ref": "#/definitions/LinkMode" + }, + { + "type": "null" + } + ] + }, + "compile-bytecode": { + "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);\ninstead, compilation is performed lazily the first time a module is imported. For use-cases\nin which start time is critical, such as CLI applications and Docker containers, this option\ncan be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that\nare not being modified by the current operation) for consistency. Like pip, it will also\nignore errors.", + "type": [ + "boolean", + "null" + ] + }, + "require-hashes": { + "description": "Require a matching hash for each requirement.\n\nHash-checking mode is all or nothing. If enabled, _all_ requirements must be provided\nwith a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements\nmust either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.\n\nHash-checking mode introduces a number of additional constraints:\n\n- Git dependencies are not supported.\n- Editable installations are not supported.\n- Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or\n source archive (`.zip`, `.tar.gz`), as opposed to a directory.", + "type": [ + "boolean", + "null" + ] + }, + "verify-hashes": { + "description": "Validate any hashes provided in the requirements file.\n\nUnlike `--require-hashes`, `--verify-hashes` does not require that all requirements have\nhashes; instead, it will limit itself to verifying the hashes of those requirements that do\ninclude them.", + "type": [ + "boolean", + "null" + ] + }, + "no-sources": { + "description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the\nstandards-compliant, publishable package metadata, as opposed to using any local or Git\nsources.", + "type": [ + "boolean", + "null" + ] + }, + "upgrade": { + "description": "Allow package upgrades, ignoring pinned versions in any existing output file.", + "type": [ + "boolean", + "null" + ] + }, + "upgrade-package": { + "description": "Allow upgrades for a specific package, ignoring pinned versions in any existing output\nfile.\n\nAccepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Requirement" + } + }, + "reinstall": { + "description": "Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.", + "type": [ + "boolean", + "null" + ] + }, + "reinstall-package": { + "description": "Reinstall a specific package, regardless of whether it's already installed. Implies\n`refresh-package`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "torch-backend": { + "description": "The backend to use when fetching packages in the PyTorch ecosystem.\n\nWhen set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem,\nand will instead use the defined backend.\n\nFor example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`,\nuv will use the PyTorch index for CUDA 12.6.\n\nThe `auto` mode will attempt to detect the appropriate PyTorch index based on the currently\ninstalled CUDA drivers.\n\nThis option is in preview and may change in any future release.", + "anyOf": [ + { + "$ref": "#/definitions/TorchMode" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "PackageNameSpecifier": { + "description": "The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.", + "type": "string", + "pattern": "^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$" + }, + "PipGroupName": { + "description": "The pip-compatible variant of a [`GroupName`].\n\nEither or :.\nIf is omitted it defaults to \"pyproject.toml\".", + "type": "object", + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "name": { + "$ref": "#/definitions/GroupName" + } + }, + "required": [ + "name" + ] + }, + "GroupName": { + "description": "The normalized name of a dependency group.\n\nSee:\n- \n- ", + "type": "string" + }, + "PythonVersion": { + "description": "A Python version specifier, e.g. `3.11` or `3.12.4`.", + "type": "string", + "pattern": "^3\\.\\d+(\\.\\d+)?$" }, "TargetTriple": { "description": "The supported target triples. Each triple consists of an architecture, vendor, and operating\nsystem.\n\nSee: ", @@ -2473,43 +1938,20 @@ } ] }, - "ToolUvDependencyGroups": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/DependencyGroupSettings" - } - }, - "ToolUvSources": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Sources" - } - }, - "ToolUvWorkspace": { - "type": "object", - "properties": { - "exclude": { - "description": "Packages to exclude as workspace members. If a package matches both `members` and\n`exclude`, it will be excluded.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/SerdePattern" - } + "AnnotationStyle": { + "description": "Indicate the style of annotation comments, used to indicate the dependencies that requested each\npackage.", + "oneOf": [ + { + "description": "Render the annotations on a single, comma-separated line.", + "type": "string", + "const": "line" }, - "members": { - "description": "Packages to include as workspace members.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/SerdePattern" - } + { + "description": "Render each annotation on its own line.", + "type": "string", + "const": "split" } - }, - "additionalProperties": false + ] }, "TorchMode": { "description": "The strategy to use when determining the appropriate PyTorch index.", @@ -2746,23 +2188,581 @@ } ] }, - "TrustedHost": { - "description": "A host or host-port pair.", - "type": "string" - }, - "TrustedPublishing": { - "oneOf": [ + "CacheKey": { + "anyOf": [ { - "type": "string", - "enum": [ - "always", - "never" + "description": "Ex) `\"Cargo.lock\"` or `\"**/*.toml\"`", + "type": "string" + }, + { + "description": "Ex) `{ file = \"Cargo.lock\" }` or `{ file = \"**/*.toml\" }`", + "type": "object", + "properties": { + "file": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "file" ] }, { - "description": "Attempt trusted publishing when we're in a supported environment, continue if that fails.\n\nSupported environments include GitHub Actions and GitLab CI/CD.", + "description": "Ex) `{ dir = \"src\" }`", + "type": "object", + "properties": { + "dir": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "dir" + ] + }, + { + "description": "Ex) `{ git = true }` or `{ git = { commit = true, tags = false } }`", + "type": "object", + "properties": { + "git": { + "$ref": "#/definitions/GitPattern" + } + }, + "additionalProperties": false, + "required": [ + "git" + ] + }, + { + "description": "Ex) `{ env = \"UV_CACHE_INFO\" }`", + "type": "object", + "properties": { + "env": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "env" + ] + } + ] + }, + "GitPattern": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/GitSet" + } + ] + }, + "GitSet": { + "type": "object", + "properties": { + "commit": { + "type": [ + "boolean", + "null" + ] + }, + "tags": { + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + }, + "ToolUvSources": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Sources" + } + }, + "Sources": { + "anyOf": [ + { + "$ref": "#/definitions/Source" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/Source" + } + } + ] + }, + "Source": { + "description": "A `tool.uv.sources` value.", + "anyOf": [ + { + "description": "A remote Git repository, available over HTTPS or SSH.\n\nExample:\n```toml\nflask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" }\n```", + "type": "object", + "properties": { + "git": { + "description": "The repository URL (without the `git+` prefix).", + "allOf": [ + { + "$ref": "#/definitions/DisplaySafeUrl" + } + ] + }, + "subdirectory": { + "description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.", + "anyOf": [ + { + "$ref": "#/definitions/PortablePathBuf" + }, + { + "type": "null" + } + ] + }, + "rev": { + "type": [ + "string", + "null" + ] + }, + "tag": { + "type": [ + "string", + "null" + ] + }, + "branch": { + "type": [ + "string", + "null" + ] + }, + "lfs": { + "description": "Whether to use Git LFS when cloning the repository.", + "type": [ + "boolean", + "null" + ] + }, + "marker": { + "$ref": "#/definitions/MarkerTree" + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ] + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "git" + ] + }, + { + "description": "A remote `http://` or `https://` URL, either a wheel (`.whl`) or a source distribution\n(`.zip`, `.tar.gz`).\n\nExample:\n```toml\nflask = { url = \"https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl\" }\n```", + "type": "object", + "properties": { + "url": { + "$ref": "#/definitions/DisplaySafeUrl" + }, + "subdirectory": { + "description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's\nnot in the archive root.", + "anyOf": [ + { + "$ref": "#/definitions/PortablePathBuf" + }, + { + "type": "null" + } + ] + }, + "marker": { + "$ref": "#/definitions/MarkerTree" + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ] + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "url" + ] + }, + { + "description": "The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or\n`.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or\n`setup.py` file in the root).", + "type": "object", + "properties": { + "path": { + "$ref": "#/definitions/PortablePathBuf" + }, + "editable": { + "description": "`false` by default.", + "type": [ + "boolean", + "null" + ] + }, + "package": { + "description": "Whether to treat the dependency as a buildable Python package (`true`) or as a virtual\npackage (`false`). If `false`, the package will not be built or installed, but its\ndependencies will be included in the virtual environment.\n\nWhen omitted, the package status is inferred based on the presence of a `[build-system]`\nin the project's `pyproject.toml`.", + "type": [ + "boolean", + "null" + ] + }, + "marker": { + "$ref": "#/definitions/MarkerTree" + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ] + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "path" + ] + }, + { + "description": "A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.", + "type": "object", + "properties": { + "index": { + "$ref": "#/definitions/IndexName" + }, + "marker": { + "$ref": "#/definitions/MarkerTree" + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ] + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "index" + ] + }, + { + "description": "A dependency on another package in the workspace.", + "type": "object", + "properties": { + "workspace": { + "description": "When set to `false`, the package will be fetched from the remote index, rather than\nincluded as a workspace package.", + "type": "boolean" + }, + "editable": { + "description": "Whether the package should be installed as editable. Defaults to `true`.", + "type": [ + "boolean", + "null" + ] + }, + "marker": { + "$ref": "#/definitions/MarkerTree" + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ] + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "workspace" + ] + } + ] + }, + "PortablePathBuf": { + "type": "string" + }, + "MarkerTree": { + "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`", + "type": "string" + }, + "ToolUvWorkspace": { + "type": "object", + "properties": { + "members": { + "description": "Packages to include as workspace members.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/SerdePattern" + } + }, + "exclude": { + "description": "Packages to exclude as workspace members. If a package matches both `members` and\n`exclude`, it will be excluded.\n\nSupports both globs and explicit paths.\n\nFor more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html).", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/SerdePattern" + } + } + }, + "additionalProperties": false + }, + "SerdePattern": { + "type": "string" + }, + "DefaultGroups": { + "description": "Either the literal \"all\" or a list of groups", + "oneOf": [ + { + "description": "All groups are defaulted", "type": "string", - "const": "automatic" + "const": "all" + }, + { + "description": "A list of groups", + "type": "array", + "items": { + "$ref": "#/definitions/GroupName" + } + } + ] + }, + "ToolUvDependencyGroups": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/DependencyGroupSettings" + } + }, + "DependencyGroupSettings": { + "type": "object", + "properties": { + "requires-python": { + "description": "Version of python to require when installing this group", + "type": [ + "string", + "null" + ] + } + } + }, + "SchemaConflicts": { + "description": "Like [`Conflicts`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.\n\nN.B. `Conflicts` is still used for (de)serialization. Specifically, in the\nlock file, where the package name is required.", + "type": "array", + "items": { + "$ref": "#/definitions/SchemaConflictSet" + } + }, + "SchemaConflictSet": { + "description": "Like [`ConflictSet`], but for deserialization in `pyproject.toml`.\n\nThe schema format is different from the in-memory format. Specifically, the\nschema format does not allow specifying the package name (or will make it\noptional in the future), where as the in-memory format needs the package\nname.", + "type": "array", + "items": { + "$ref": "#/definitions/SchemaConflictItem" + } + }, + "SchemaConflictItem": { + "description": "A single item in a conflicting set.\n\nEach item is a pair of an (optional) package and a corresponding extra or group name for that\npackage.", + "type": "object", + "properties": { + "package": { + "anyOf": [ + { + "$ref": "#/definitions/PackageName" + }, + { + "type": "null" + } + ], + "default": null + }, + "extra": { + "anyOf": [ + { + "$ref": "#/definitions/ExtraName" + }, + { + "type": "null" + } + ], + "default": null + }, + "group": { + "anyOf": [ + { + "$ref": "#/definitions/GroupName" + }, + { + "type": "null" + } + ], + "default": null + } + } + }, + "BuildBackendSettings": { + "description": "Settings for the uv build backend (`uv_build`).\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from\n[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", + "type": "object", + "properties": { + "module-root": { + "description": "The directory that contains the module directory.\n\nCommon values are `src` (src layout, the default) or an empty path (flat layout).", + "type": "string", + "default": "src" + }, + "module-name": { + "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nPackage names need to be valid Python identifiers, and the directory needs to contain a\n`__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem\nbeing the module name, and which contain a `__init__.pyi` file.\n\nFor namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or\n`foo-stubs.bar`.\n\nFor namespace packages with multiple modules, the path can be a list, e.g.,\n`[\"foo\", \"bar\"]`. We recommend using a single module per package, splitting multiple\npackages into a workspace.\n\nNote that using this option runs the risk of creating two packages with different names but\nthe same module names. Installing such packages together leads to unspecified behavior,\noften with corrupted files or directory trees.", + "anyOf": [ + { + "$ref": "#/definitions/ModuleName" + }, + { + "type": "null" + } + ], + "default": null + }, + "source-include": { + "description": "Glob expressions which files and directories to additionally include in the source\ndistribution.\n\n`pyproject.toml` and the contents of the module directory are always included.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "default-excludes": { + "description": "If set to `false`, the default excludes aren't applied.\n\nDefault excludes: `__pycache__`, `*.pyc`, and `*.pyo`.", + "type": "boolean", + "default": true + }, + "source-exclude": { + "description": "Glob expressions which files and directories to exclude from the source distribution.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "wheel-exclude": { + "description": "Glob expressions which files and directories to exclude from the wheel.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "namespace": { + "description": "Build a namespace package.\n\nBuild a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.\n\nUse this option when the namespace package contains multiple root `__init__.py`, for\nnamespace packages with a single root `__init__.py` use a dotted `module-name` instead.\n\nTo compare dotted `module-name` and `namespace = true`, the first example below can be\nexpressed with `module-name = \"cloud.database\"`: There is one root `__init__.py` `database`.\nIn the second example, we have three roots (`cloud.database`, `cloud.database_pro`,\n`billing.modules.database_pro`), so `namespace = true` is required.\n\n```text\nsrc\n└── cloud\n └── database\n ├── __init__.py\n ├── query_builder\n │ └── __init__.py\n └── sql\n ├── parser.py\n └── __init__.py\n```\n\n```text\nsrc\n├── cloud\n│ ├── database\n│ │ ├── __init__.py\n│ │ ├── query_builder\n│ │ │ └── __init__.py\n│ │ └── sql\n│ │ ├── __init__.py\n│ │ └── parser.py\n│ └── database_pro\n│ ├── __init__.py\n│ └── query_builder.py\n└── billing\n └── modules\n └── database_pro\n ├── __init__.py\n └── sql.py\n```", + "type": "boolean", + "default": false + }, + "data": { + "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or\n `\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to use these two options.", + "default": { + "purelib": null, + "platlib": null, + "headers": null, + "scripts": null, + "data": null + }, + "allOf": [ + { + "$ref": "#/definitions/WheelDataIncludes" + } + ] + } + } + }, + "ModuleName": { + "description": "Whether to include a single module or multiple modules.", + "anyOf": [ + { + "description": "A single module name.", + "type": "string" + }, + { + "description": "Multiple module names, which are all included.", + "type": "array", + "items": { + "type": "string" + } } ] }, @@ -2770,14 +2770,7 @@ "description": "Data includes for wheels.\n\nSee `BuildBackendSettings::data`.", "type": "object", "properties": { - "data": { - "type": [ - "string", - "null" - ], - "default": null - }, - "headers": { + "purelib": { "type": [ "string", "null" @@ -2791,7 +2784,7 @@ ], "default": null }, - "purelib": { + "headers": { "type": [ "string", "null" @@ -2804,6 +2797,13 @@ "null" ], "default": null + }, + "data": { + "type": [ + "string", + "null" + ], + "default": null } }, "additionalProperties": false