mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Allow multiple pinned indexes in tool.uv.sources (#7769)
## Summary
This PR lifts the restriction that a package must come from a single
index. For example, you can now do:
```toml
[project]
name = "project"
version = "0.1.0"
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["jinja2"]
[tool.uv.sources]
jinja2 = [
{ index = "torch-cu118", marker = "sys_platform == 'darwin'"},
{ index = "torch-cu124", marker = "sys_platform != 'darwin'"},
]
[[tool.uv.index]]
name = "torch-cu118"
url = "https://download.pytorch.org/whl/cu118"
[[tool.uv.index]]
name = "torch-cu124"
url = "https://download.pytorch.org/whl/cu124"
```
The construction is very similar to the way we handle URLs today: you
can have multiple URLs for a given package, but they must appear in
disjoint forks. So most of the code is just adding that abstraction to
the resolver, following our handling of URLs.
Closes #7761.
This commit is contained in:
parent
ad24cee7c6
commit
9a76e47888
14 changed files with 715 additions and 109 deletions
|
|
@ -53,6 +53,16 @@ pub enum ResolveError {
|
|||
fork_markers: MarkerTree,
|
||||
},
|
||||
|
||||
#[error("Requirements contain conflicting indexes for package `{0}`:\n- {}", _1.join("\n- "))]
|
||||
ConflictingIndexesUniversal(PackageName, Vec<String>),
|
||||
|
||||
#[error("Requirements contain conflicting indexes for package `{package_name}` in split `{fork_markers:?}`:\n- {}", indexes.join("\n- "))]
|
||||
ConflictingIndexesFork {
|
||||
package_name: PackageName,
|
||||
indexes: Vec<String>,
|
||||
fork_markers: MarkerTree,
|
||||
},
|
||||
|
||||
#[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
|
||||
ConflictingIndexes(PackageName, String, String),
|
||||
|
||||
|
|
|
|||
48
crates/uv-resolver/src/fork_indexes.rs
Normal file
48
crates/uv-resolver/src/fork_indexes.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use rustc_hash::FxHashMap;
|
||||
use uv_distribution_types::IndexUrl;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::resolver::ResolverMarkers;
|
||||
use crate::ResolveError;
|
||||
|
||||
/// See [`crate::resolver::ForkState`].
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub(crate) struct ForkIndexes(FxHashMap<PackageName, IndexUrl>);
|
||||
|
||||
impl ForkIndexes {
|
||||
/// Get the [`IndexUrl`] previously used for a package in this fork.
|
||||
pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexUrl> {
|
||||
self.0.get(package_name)
|
||||
}
|
||||
|
||||
/// Check that this is the only [`IndexUrl`] used for this package in this fork.
|
||||
pub(crate) fn insert(
|
||||
&mut self,
|
||||
package_name: &PackageName,
|
||||
index: &IndexUrl,
|
||||
fork_markers: &ResolverMarkers,
|
||||
) -> Result<(), ResolveError> {
|
||||
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
|
||||
if &previous != index {
|
||||
let mut conflicts = vec![previous.to_string(), index.to_string()];
|
||||
conflicts.sort();
|
||||
return match fork_markers {
|
||||
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
|
||||
Err(ResolveError::ConflictingIndexesUniversal(
|
||||
package_name.clone(),
|
||||
conflicts,
|
||||
))
|
||||
}
|
||||
ResolverMarkers::Fork(fork_markers) => {
|
||||
Err(ResolveError::ConflictingIndexesFork {
|
||||
package_name: package_name.clone(),
|
||||
indexes: conflicts,
|
||||
fork_markers: fork_markers.clone(),
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ use uv_pypi_types::VerbatimParsedUrl;
|
|||
use crate::resolver::ResolverMarkers;
|
||||
use crate::ResolveError;
|
||||
|
||||
/// See [`crate::resolver::SolveState`].
|
||||
/// See [`crate::resolver::ForkState`].
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub(crate) struct ForkUrls(FxHashMap<PackageName, VerbatimParsedUrl>);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ mod error;
|
|||
mod exclude_newer;
|
||||
mod exclusions;
|
||||
mod flat_index;
|
||||
mod fork_indexes;
|
||||
mod fork_urls;
|
||||
mod graph_ops;
|
||||
mod lock;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
|||
use uv_configuration::{Constraints, Overrides};
|
||||
use uv_distribution::Metadata;
|
||||
use uv_distribution_types::{
|
||||
Dist, DistributionMetadata, Name, ResolutionDiagnostic, ResolvedDist, VersionId,
|
||||
Dist, DistributionMetadata, IndexUrl, Name, ResolutionDiagnostic, ResolvedDist, VersionId,
|
||||
VersionOrUrlRef,
|
||||
};
|
||||
use uv_git::GitResolver;
|
||||
|
|
@ -88,6 +88,7 @@ struct PackageRef<'a> {
|
|||
package_name: &'a PackageName,
|
||||
version: &'a Version,
|
||||
url: Option<&'a VerbatimParsedUrl>,
|
||||
index: Option<&'a IndexUrl>,
|
||||
extra: Option<&'a ExtraName>,
|
||||
group: Option<&'a GroupName>,
|
||||
}
|
||||
|
|
@ -284,6 +285,7 @@ impl ResolutionGraph {
|
|||
package_name: from,
|
||||
version: &edge.from_version,
|
||||
url: edge.from_url.as_ref(),
|
||||
index: edge.from_index.as_ref(),
|
||||
extra: edge.from_extra.as_ref(),
|
||||
group: edge.from_dev.as_ref(),
|
||||
}]
|
||||
|
|
@ -292,6 +294,7 @@ impl ResolutionGraph {
|
|||
package_name: &edge.to,
|
||||
version: &edge.to_version,
|
||||
url: edge.to_url.as_ref(),
|
||||
index: edge.to_index.as_ref(),
|
||||
extra: edge.to_extra.as_ref(),
|
||||
group: edge.to_dev.as_ref(),
|
||||
}];
|
||||
|
|
@ -320,7 +323,7 @@ impl ResolutionGraph {
|
|||
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
||||
preferences: &Preferences,
|
||||
pins: &FilePins,
|
||||
index: &InMemoryIndex,
|
||||
in_memory: &InMemoryIndex,
|
||||
git: &GitResolver,
|
||||
package: &'a ResolutionPackage,
|
||||
version: &'a Version,
|
||||
|
|
@ -330,16 +333,18 @@ impl ResolutionGraph {
|
|||
extra,
|
||||
dev,
|
||||
url,
|
||||
index,
|
||||
} = &package;
|
||||
// Map the package to a distribution.
|
||||
let (dist, hashes, metadata) = Self::parse_dist(
|
||||
name,
|
||||
index.as_ref(),
|
||||
url.as_ref(),
|
||||
version,
|
||||
pins,
|
||||
diagnostics,
|
||||
preferences,
|
||||
index,
|
||||
in_memory,
|
||||
git,
|
||||
)?;
|
||||
|
||||
|
|
@ -366,7 +371,7 @@ impl ResolutionGraph {
|
|||
}
|
||||
|
||||
// Add the distribution to the graph.
|
||||
let index = petgraph.add_node(ResolutionGraphNode::Dist(AnnotatedDist {
|
||||
let node = petgraph.add_node(ResolutionGraphNode::Dist(AnnotatedDist {
|
||||
dist,
|
||||
name: name.clone(),
|
||||
version: version.clone(),
|
||||
|
|
@ -381,22 +386,24 @@ impl ResolutionGraph {
|
|||
package_name: name,
|
||||
version,
|
||||
url: url.as_ref(),
|
||||
index: index.as_ref(),
|
||||
extra: extra.as_ref(),
|
||||
group: dev.as_ref(),
|
||||
},
|
||||
index,
|
||||
node,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_dist(
|
||||
name: &PackageName,
|
||||
index: Option<&IndexUrl>,
|
||||
url: Option<&VerbatimParsedUrl>,
|
||||
version: &Version,
|
||||
pins: &FilePins,
|
||||
diagnostics: &mut Vec<ResolutionDiagnostic>,
|
||||
preferences: &Preferences,
|
||||
index: &InMemoryIndex,
|
||||
in_memory: &InMemoryIndex,
|
||||
git: &GitResolver,
|
||||
) -> Result<(ResolvedDist, Vec<HashDigest>, Option<Metadata>), ResolveError> {
|
||||
Ok(if let Some(url) = url {
|
||||
|
|
@ -406,14 +413,24 @@ impl ResolutionGraph {
|
|||
let version_id = VersionId::from_url(&url.verbatim);
|
||||
|
||||
// Extract the hashes.
|
||||
let hashes =
|
||||
Self::get_hashes(name, Some(url), &version_id, version, preferences, index);
|
||||
let hashes = Self::get_hashes(
|
||||
name,
|
||||
index,
|
||||
Some(url),
|
||||
&version_id,
|
||||
version,
|
||||
preferences,
|
||||
in_memory,
|
||||
);
|
||||
|
||||
// Extract the metadata.
|
||||
let metadata = {
|
||||
let response = index.distributions().get(&version_id).unwrap_or_else(|| {
|
||||
panic!("Every URL distribution should have metadata: {version_id:?}")
|
||||
});
|
||||
let response = in_memory
|
||||
.distributions()
|
||||
.get(&version_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Every URL distribution should have metadata: {version_id:?}")
|
||||
});
|
||||
|
||||
let MetadataResponse::Found(archive) = &*response else {
|
||||
panic!("Every URL distribution should have metadata: {version_id:?}")
|
||||
|
|
@ -449,17 +466,28 @@ impl ResolutionGraph {
|
|||
}
|
||||
|
||||
// Extract the hashes.
|
||||
let hashes = Self::get_hashes(name, None, &version_id, version, preferences, index);
|
||||
let hashes = Self::get_hashes(
|
||||
name,
|
||||
index,
|
||||
None,
|
||||
&version_id,
|
||||
version,
|
||||
preferences,
|
||||
in_memory,
|
||||
);
|
||||
|
||||
// Extract the metadata.
|
||||
let metadata = {
|
||||
index.distributions().get(&version_id).and_then(|response| {
|
||||
if let MetadataResponse::Found(archive) = &*response {
|
||||
Some(archive.metadata.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
in_memory
|
||||
.distributions()
|
||||
.get(&version_id)
|
||||
.and_then(|response| {
|
||||
if let MetadataResponse::Found(archive) = &*response {
|
||||
Some(archive.metadata.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
(dist, hashes, metadata)
|
||||
|
|
@ -470,11 +498,12 @@ impl ResolutionGraph {
|
|||
/// lockfile.
|
||||
fn get_hashes(
|
||||
name: &PackageName,
|
||||
index: Option<&IndexUrl>,
|
||||
url: Option<&VerbatimParsedUrl>,
|
||||
version_id: &VersionId,
|
||||
version: &Version,
|
||||
preferences: &Preferences,
|
||||
index: &InMemoryIndex,
|
||||
in_memory: &InMemoryIndex,
|
||||
) -> Vec<HashDigest> {
|
||||
// 1. Look for hashes from the lockfile.
|
||||
if let Some(digests) = preferences.match_hashes(name, version) {
|
||||
|
|
@ -484,7 +513,7 @@ impl ResolutionGraph {
|
|||
}
|
||||
|
||||
// 2. Look for hashes for the distribution (i.e., the specific wheel or source distribution).
|
||||
if let Some(metadata_response) = index.distributions().get(version_id) {
|
||||
if let Some(metadata_response) = in_memory.distributions().get(version_id) {
|
||||
if let MetadataResponse::Found(ref archive) = *metadata_response {
|
||||
let mut digests = archive.hashes.clone();
|
||||
digests.sort_unstable();
|
||||
|
|
@ -496,7 +525,13 @@ impl ResolutionGraph {
|
|||
|
||||
// 3. Look for hashes from the registry, which are served at the package level.
|
||||
if url.is_none() {
|
||||
if let Some(versions_response) = index.packages().get(name) {
|
||||
let versions_response = if let Some(index) = index {
|
||||
in_memory.explicit().get(&(name.clone(), index.clone()))
|
||||
} else {
|
||||
in_memory.implicit().get(name)
|
||||
};
|
||||
|
||||
if let Some(versions_response) = versions_response {
|
||||
if let VersionsResponse::Found(ref version_maps) = *versions_response {
|
||||
if let Some(digests) = version_maps
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ use rustc_hash::FxHashMap;
|
|||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities};
|
||||
use uv_pep440::Version;
|
||||
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
||||
use crate::resolver::Request;
|
||||
use crate::{InMemoryIndex, PythonRequirement, ResolveError, ResolverMarkers, VersionsResponse};
|
||||
use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities, IndexUrl};
|
||||
use uv_pep440::Version;
|
||||
|
||||
enum BatchPrefetchStrategy {
|
||||
/// Go through the next versions assuming the existing selection and its constraints
|
||||
|
|
@ -47,11 +46,12 @@ impl BatchPrefetcher {
|
|||
pub(crate) fn prefetch_batches(
|
||||
&mut self,
|
||||
next: &PubGrubPackage,
|
||||
index: Option<&IndexUrl>,
|
||||
version: &Version,
|
||||
current_range: &Range<Version>,
|
||||
python_requirement: &PythonRequirement,
|
||||
request_sink: &Sender<Request>,
|
||||
index: &InMemoryIndex,
|
||||
in_memory: &InMemoryIndex,
|
||||
capabilities: &IndexCapabilities,
|
||||
selector: &CandidateSelector,
|
||||
markers: &ResolverMarkers,
|
||||
|
|
@ -73,10 +73,17 @@ impl BatchPrefetcher {
|
|||
let total_prefetch = min(num_tried, 50);
|
||||
|
||||
// This is immediate, we already fetched the version map.
|
||||
let versions_response = index
|
||||
.packages()
|
||||
.wait_blocking(name)
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?;
|
||||
let versions_response = if let Some(index) = index {
|
||||
in_memory
|
||||
.explicit()
|
||||
.wait_blocking(&(name.clone(), index.clone()))
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||
} else {
|
||||
in_memory
|
||||
.implicit()
|
||||
.wait_blocking(name)
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||
};
|
||||
|
||||
let VersionsResponse::Found(ref version_map) = *versions_response else {
|
||||
return Ok(());
|
||||
|
|
@ -191,7 +198,7 @@ impl BatchPrefetcher {
|
|||
);
|
||||
prefetch_count += 1;
|
||||
|
||||
if index.distributions().register(candidate.version_id()) {
|
||||
if in_memory.distributions().register(candidate.version_id()) {
|
||||
let request = Request::from(dist);
|
||||
request_sink.blocking_send(request)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ impl<T> ForkMap<T> {
|
|||
!self.get(package_name, markers).is_empty()
|
||||
}
|
||||
|
||||
/// Returns `true` if the map contains any values for a package.
|
||||
pub(crate) fn contains_key(&self, package_name: &PackageName) -> bool {
|
||||
self.0.contains_key(package_name)
|
||||
}
|
||||
|
||||
/// Returns a list of values associated with a package that are compatible with the given fork.
|
||||
///
|
||||
/// Compatibility implies that the markers on the requirement that contained this value
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::hash::BuildHasherDefault;
|
|||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
use uv_distribution_types::VersionId;
|
||||
use uv_distribution_types::{IndexUrl, VersionId};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_once_map::OnceMap;
|
||||
|
||||
|
|
@ -16,7 +16,9 @@ pub struct InMemoryIndex(Arc<SharedInMemoryIndex>);
|
|||
struct SharedInMemoryIndex {
|
||||
/// A map from package name to the metadata for that package and the index where the metadata
|
||||
/// came from.
|
||||
packages: FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
||||
implicit: FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
||||
|
||||
explicit: FxOnceMap<(PackageName, IndexUrl), Arc<VersionsResponse>>,
|
||||
|
||||
/// A map from package ID to metadata for that distribution.
|
||||
distributions: FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
||||
|
|
@ -26,8 +28,13 @@ pub(crate) type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
|
|||
|
||||
impl InMemoryIndex {
|
||||
/// Returns a reference to the package metadata map.
|
||||
pub fn packages(&self) -> &FxOnceMap<PackageName, Arc<VersionsResponse>> {
|
||||
&self.0.packages
|
||||
pub fn implicit(&self) -> &FxOnceMap<PackageName, Arc<VersionsResponse>> {
|
||||
&self.0.implicit
|
||||
}
|
||||
|
||||
/// Returns a reference to the package metadata map.
|
||||
pub fn explicit(&self) -> &FxOnceMap<(PackageName, IndexUrl), Arc<VersionsResponse>> {
|
||||
&self.0.explicit
|
||||
}
|
||||
|
||||
/// Returns a reference to the distribution metadata map.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use crate::{DependencyMode, Manifest, ResolveError, ResolverMarkers};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use uv_distribution_types::IndexUrl;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_pypi_types::RequirementSource;
|
||||
|
||||
use crate::resolver::ForkMap;
|
||||
use crate::{DependencyMode, Manifest, ResolverMarkers};
|
||||
|
||||
/// A map of package names to their explicit index.
|
||||
///
|
||||
/// For example, given:
|
||||
|
|
@ -21,7 +20,7 @@ use uv_pypi_types::RequirementSource;
|
|||
///
|
||||
/// [`Indexes`] would contain a single entry mapping `torch` to `https://download.pytorch.org/whl/cu121`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct Indexes(FxHashMap<PackageName, IndexUrl>);
|
||||
pub(crate) struct Indexes(ForkMap<IndexUrl>);
|
||||
|
||||
impl Indexes {
|
||||
/// Determine the set of explicit, pinned indexes in the [`Manifest`].
|
||||
|
|
@ -29,8 +28,8 @@ impl Indexes {
|
|||
manifest: &Manifest,
|
||||
markers: &ResolverMarkers,
|
||||
dependencies: DependencyMode,
|
||||
) -> Result<Self, ResolveError> {
|
||||
let mut indexes = FxHashMap::<PackageName, IndexUrl>::default();
|
||||
) -> Self {
|
||||
let mut indexes = ForkMap::default();
|
||||
|
||||
for requirement in manifest.requirements(markers, dependencies) {
|
||||
let RequirementSource::Registry {
|
||||
|
|
@ -40,28 +39,23 @@ impl Indexes {
|
|||
continue;
|
||||
};
|
||||
let index = IndexUrl::from(VerbatimUrl::from_url(index.clone()));
|
||||
match indexes.entry(requirement.name.clone()) {
|
||||
Entry::Occupied(entry) => {
|
||||
let existing = entry.get();
|
||||
if *existing != index {
|
||||
return Err(ResolveError::ConflictingIndexes(
|
||||
requirement.name.clone(),
|
||||
existing.to_string(),
|
||||
index.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(index);
|
||||
}
|
||||
}
|
||||
indexes.add(&requirement, index);
|
||||
}
|
||||
|
||||
Ok(Self(indexes))
|
||||
Self(indexes)
|
||||
}
|
||||
|
||||
/// Return the explicit index for a given [`PackageName`].
|
||||
pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexUrl> {
|
||||
self.0.get(package_name)
|
||||
/// Returns `true` if the map contains any indexes for a package.
|
||||
pub(crate) fn contains_key(&self, name: &PackageName) -> bool {
|
||||
self.0.contains_key(name)
|
||||
}
|
||||
|
||||
/// Return the explicit index used for a package in the given fork.
|
||||
pub(crate) fn get(
|
||||
&self,
|
||||
package_name: &PackageName,
|
||||
markers: &ResolverMarkers,
|
||||
) -> Vec<&IndexUrl> {
|
||||
self.0.get(package_name, markers)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ use uv_warnings::warn_user_once;
|
|||
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
||||
use crate::dependency_provider::UvDependencyProvider;
|
||||
use crate::error::{NoSolutionError, ResolveError};
|
||||
use crate::fork_indexes::ForkIndexes;
|
||||
use crate::fork_urls::ForkUrls;
|
||||
use crate::manifest::Manifest;
|
||||
use crate::pins::FilePins;
|
||||
|
|
@ -208,7 +209,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
|||
dependency_mode: options.dependency_mode,
|
||||
urls: Urls::from_manifest(&manifest, &markers, git, options.dependency_mode)?,
|
||||
locals: Locals::from_manifest(&manifest, &markers, options.dependency_mode),
|
||||
indexes: Indexes::from_manifest(&manifest, &markers, options.dependency_mode)?,
|
||||
indexes: Indexes::from_manifest(&manifest, &markers, options.dependency_mode),
|
||||
groups: Groups::from_manifest(&manifest, &markers),
|
||||
project: manifest.project,
|
||||
workspace_members: manifest.workspace_members,
|
||||
|
|
@ -334,6 +335,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
return Err(self.convert_no_solution_err(
|
||||
err,
|
||||
state.fork_urls,
|
||||
&state.fork_indexes,
|
||||
state.markers,
|
||||
&visited,
|
||||
&self.locations,
|
||||
|
|
@ -345,6 +347,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
Self::pre_visit(
|
||||
state.pubgrub.partial_solution.prioritized_packages(),
|
||||
&self.urls,
|
||||
&self.indexes,
|
||||
&state.python_requirement,
|
||||
&request_sink,
|
||||
)?;
|
||||
|
|
@ -384,7 +387,10 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
state.next = highest_priority_pkg;
|
||||
|
||||
let url = state.next.name().and_then(|name| state.fork_urls.get(name));
|
||||
let index = state.next.name().and_then(|name| self.indexes.get(name));
|
||||
let index = state
|
||||
.next
|
||||
.name()
|
||||
.and_then(|name| state.fork_indexes.get(name));
|
||||
|
||||
// Consider:
|
||||
// ```toml
|
||||
|
|
@ -408,6 +414,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.expect("a package was chosen but we don't have a term");
|
||||
let decision = self.choose_version(
|
||||
&state.next,
|
||||
index,
|
||||
term_intersection.unwrap_positive(),
|
||||
&mut state.pins,
|
||||
&preferences,
|
||||
|
|
@ -465,6 +472,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
if url.is_none() {
|
||||
prefetcher.prefetch_batches(
|
||||
&state.next,
|
||||
index,
|
||||
&version,
|
||||
term_intersection.unwrap_positive(),
|
||||
&state.python_requirement,
|
||||
|
|
@ -521,6 +529,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
for_package.as_deref(),
|
||||
&version,
|
||||
&self.urls,
|
||||
&self.indexes,
|
||||
&self.locals,
|
||||
dependencies.clone(),
|
||||
&self.git,
|
||||
|
|
@ -536,7 +545,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
url: _,
|
||||
} = dependency;
|
||||
let url = package.name().and_then(|name| state.fork_urls.get(name));
|
||||
let index = package.name().and_then(|name| self.indexes.get(name));
|
||||
let index =
|
||||
package.name().and_then(|name| state.fork_indexes.get(name));
|
||||
self.visit_package(package, url, index, &request_sink)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -689,6 +699,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
for_package,
|
||||
version,
|
||||
&self.urls,
|
||||
&self.indexes,
|
||||
&self.locals,
|
||||
fork.dependencies.clone(),
|
||||
&self.git,
|
||||
|
|
@ -705,7 +716,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
let url = package
|
||||
.name()
|
||||
.and_then(|name| forked_state.fork_urls.get(name));
|
||||
let index = package.name().and_then(|name| self.indexes.get(name));
|
||||
let index = package
|
||||
.name()
|
||||
.and_then(|name| forked_state.fork_indexes.get(name));
|
||||
self.visit_package(package, url, index, request_sink)?;
|
||||
}
|
||||
Ok(forked_state)
|
||||
|
|
@ -768,10 +781,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
if self.index.distributions().register(dist.version_id()) {
|
||||
request_sink.blocking_send(Request::Dist(dist))?;
|
||||
}
|
||||
} else if let Some(index) = index {
|
||||
// Emit a request to fetch the metadata for this package on the index.
|
||||
if self
|
||||
.index
|
||||
.explicit()
|
||||
.register((name.clone(), index.clone()))
|
||||
{
|
||||
request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
|
||||
}
|
||||
} else {
|
||||
// Emit a request to fetch the metadata for this package.
|
||||
if self.index.packages().register(name.clone()) {
|
||||
request_sink.blocking_send(Request::Package(name.clone(), index.cloned()))?;
|
||||
if self.index.implicit().register(name.clone()) {
|
||||
request_sink.blocking_send(Request::Package(name.clone(), None))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -782,6 +804,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
fn pre_visit<'data>(
|
||||
packages: impl Iterator<Item = (&'data PubGrubPackage, &'data Range<Version>)>,
|
||||
urls: &Urls,
|
||||
indexes: &Indexes,
|
||||
python_requirement: &PythonRequirement,
|
||||
request_sink: &Sender<Request>,
|
||||
) -> Result<(), ResolveError> {
|
||||
|
|
@ -802,6 +825,10 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
if urls.any_url(name) {
|
||||
continue;
|
||||
}
|
||||
// Avoid visiting packages that may use an explicit index.
|
||||
if indexes.contains_key(name) {
|
||||
continue;
|
||||
}
|
||||
request_sink.blocking_send(Request::Prefetch(
|
||||
name.clone(),
|
||||
range.clone(),
|
||||
|
|
@ -819,6 +846,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
fn choose_version(
|
||||
&self,
|
||||
package: &PubGrubPackage,
|
||||
index: Option<&IndexUrl>,
|
||||
range: &Range<Version>,
|
||||
pins: &mut FilePins,
|
||||
preferences: &Preferences,
|
||||
|
|
@ -849,6 +877,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
} else {
|
||||
self.choose_version_registry(
|
||||
name,
|
||||
index,
|
||||
range,
|
||||
package,
|
||||
preferences,
|
||||
|
|
@ -964,6 +993,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
fn choose_version_registry(
|
||||
&self,
|
||||
name: &PackageName,
|
||||
index: Option<&IndexUrl>,
|
||||
range: &Range<Version>,
|
||||
package: &PubGrubPackage,
|
||||
preferences: &Preferences,
|
||||
|
|
@ -974,11 +1004,17 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
request_sink: &Sender<Request>,
|
||||
) -> Result<Option<ResolverVersion>, ResolveError> {
|
||||
// Wait for the metadata to be available.
|
||||
let versions_response = self
|
||||
.index
|
||||
.packages()
|
||||
.wait_blocking(name)
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?;
|
||||
let versions_response = if let Some(index) = index {
|
||||
self.index
|
||||
.explicit()
|
||||
.wait_blocking(&(name.clone(), index.clone()))
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||
} else {
|
||||
self.index
|
||||
.implicit()
|
||||
.wait_blocking(name)
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||
};
|
||||
visited.insert(name.clone());
|
||||
|
||||
let version_maps = match *versions_response {
|
||||
|
|
@ -1654,11 +1690,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
|
||||
while let Some(response) = response_stream.next().await {
|
||||
match response? {
|
||||
Some(Response::Package(package_name, version_map)) => {
|
||||
trace!("Received package metadata for: {package_name}");
|
||||
self.index
|
||||
.packages()
|
||||
.done(package_name, Arc::new(version_map));
|
||||
Some(Response::Package(name, index, version_map)) => {
|
||||
trace!("Received package metadata for: {name}");
|
||||
if let Some(index) = index {
|
||||
self.index
|
||||
.explicit()
|
||||
.done((name, index), Arc::new(version_map));
|
||||
} else {
|
||||
self.index.implicit().done(name, Arc::new(version_map));
|
||||
}
|
||||
}
|
||||
Some(Response::Installed { dist, metadata }) => {
|
||||
trace!("Received installed distribution metadata for: {dist}");
|
||||
|
|
@ -1727,7 +1767,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.await
|
||||
.map_err(ResolveError::Client)?;
|
||||
|
||||
Ok(Some(Response::Package(package_name, package_versions)))
|
||||
Ok(Some(Response::Package(
|
||||
package_name,
|
||||
index,
|
||||
package_versions,
|
||||
)))
|
||||
}
|
||||
|
||||
// Fetch distribution metadata from the distribution database.
|
||||
|
|
@ -1771,7 +1815,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
// Wait for the package metadata to become available.
|
||||
let versions_response = self
|
||||
.index
|
||||
.packages()
|
||||
.implicit()
|
||||
.wait(&package_name)
|
||||
.await
|
||||
.ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?;
|
||||
|
|
@ -1924,6 +1968,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
&self,
|
||||
mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
|
||||
fork_urls: ForkUrls,
|
||||
fork_indexes: &ForkIndexes,
|
||||
markers: ResolverMarkers,
|
||||
visited: &FxHashSet<PackageName>,
|
||||
index_locations: &IndexLocations,
|
||||
|
|
@ -1965,7 +2010,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
// we represent the self of the resolver at the time of failure.
|
||||
continue;
|
||||
}
|
||||
if let Some(response) = self.index.packages().get(name) {
|
||||
let versions_response = if let Some(index) = fork_indexes.get(name) {
|
||||
self.index.explicit().get(&(name.clone(), index.clone()))
|
||||
} else {
|
||||
self.index.implicit().get(name)
|
||||
};
|
||||
if let Some(response) = versions_response {
|
||||
if let VersionsResponse::Found(ref version_maps) = *response {
|
||||
// Track the available versions, across all indexes.
|
||||
for version_map in version_maps {
|
||||
|
|
@ -2049,12 +2099,17 @@ struct ForkState {
|
|||
/// After resolution is finished, this maps is consulted in order to select
|
||||
/// the wheel chosen during resolution.
|
||||
pins: FilePins,
|
||||
/// Ensure we don't have duplicate urls in any branch.
|
||||
/// Ensure we don't have duplicate URLs in any branch.
|
||||
///
|
||||
/// Unlike [`Urls`], we add only the URLs we have seen in this branch, and there can be only
|
||||
/// one URL per package. By prioritizing direct URL dependencies over registry dependencies,
|
||||
/// this map is populated for all direct URL packages before we look at any registry packages.
|
||||
fork_urls: ForkUrls,
|
||||
/// Ensure we don't have duplicate indexes in any branch.
|
||||
///
|
||||
/// Unlike [`Indexes`], we add only the indexes we have seen in this branch, and there can be
|
||||
/// only one index per package.
|
||||
fork_indexes: ForkIndexes,
|
||||
/// When dependencies for a package are retrieved, this map of priorities
|
||||
/// is updated based on how each dependency was specified. Certain types
|
||||
/// of dependencies have more "priority" than others (like direct URL
|
||||
|
|
@ -2106,6 +2161,7 @@ impl ForkState {
|
|||
next: root,
|
||||
pins: FilePins::default(),
|
||||
fork_urls: ForkUrls::default(),
|
||||
fork_indexes: ForkIndexes::default(),
|
||||
priorities: PubGrubPriorities::default(),
|
||||
added_dependencies: FxHashMap::default(),
|
||||
markers,
|
||||
|
|
@ -2120,6 +2176,7 @@ impl ForkState {
|
|||
for_package: Option<&str>,
|
||||
version: &Version,
|
||||
urls: &Urls,
|
||||
indexes: &Indexes,
|
||||
locals: &Locals,
|
||||
mut dependencies: Vec<PubGrubDependency>,
|
||||
git: &GitResolver,
|
||||
|
|
@ -2170,6 +2227,11 @@ impl ForkState {
|
|||
*version = version.union(&local);
|
||||
}
|
||||
}
|
||||
|
||||
// If the package is pinned to an exact index, add it to the fork.
|
||||
for index in indexes.get(name, &self.markers) {
|
||||
self.fork_indexes.insert(name, index, &self.markers)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(for_package) = for_package {
|
||||
|
|
@ -2319,6 +2381,9 @@ impl ForkState {
|
|||
_ => continue,
|
||||
};
|
||||
let self_url = self_name.as_ref().and_then(|name| self.fork_urls.get(name));
|
||||
let self_index = self_name
|
||||
.as_ref()
|
||||
.and_then(|name| self.fork_indexes.get(name));
|
||||
|
||||
match **dependency_package {
|
||||
PubGrubPackageInner::Package {
|
||||
|
|
@ -2331,15 +2396,18 @@ impl ForkState {
|
|||
continue;
|
||||
}
|
||||
let to_url = self.fork_urls.get(dependency_name);
|
||||
let to_index = self.fork_indexes.get(dependency_name);
|
||||
let edge = ResolutionDependencyEdge {
|
||||
from: self_name.cloned(),
|
||||
from_version: self_version.clone(),
|
||||
from_url: self_url.cloned(),
|
||||
from_index: self_index.cloned(),
|
||||
from_extra: self_extra.cloned(),
|
||||
from_dev: self_dev.cloned(),
|
||||
to: dependency_name.clone(),
|
||||
to_version: dependency_version.clone(),
|
||||
to_url: to_url.cloned(),
|
||||
to_index: to_index.cloned(),
|
||||
to_extra: dependency_extra.clone(),
|
||||
to_dev: dependency_dev.clone(),
|
||||
marker: MarkerTree::TRUE,
|
||||
|
|
@ -2356,15 +2424,18 @@ impl ForkState {
|
|||
continue;
|
||||
}
|
||||
let to_url = self.fork_urls.get(dependency_name);
|
||||
let to_index = self.fork_indexes.get(dependency_name);
|
||||
let edge = ResolutionDependencyEdge {
|
||||
from: self_name.cloned(),
|
||||
from_version: self_version.clone(),
|
||||
from_url: self_url.cloned(),
|
||||
from_index: self_index.cloned(),
|
||||
from_extra: self_extra.cloned(),
|
||||
from_dev: self_dev.cloned(),
|
||||
to: dependency_name.clone(),
|
||||
to_version: dependency_version.clone(),
|
||||
to_url: to_url.cloned(),
|
||||
to_index: to_index.cloned(),
|
||||
to_extra: None,
|
||||
to_dev: None,
|
||||
marker: dependency_marker.clone(),
|
||||
|
|
@ -2382,15 +2453,18 @@ impl ForkState {
|
|||
continue;
|
||||
}
|
||||
let to_url = self.fork_urls.get(dependency_name);
|
||||
let to_index = self.fork_indexes.get(dependency_name);
|
||||
let edge = ResolutionDependencyEdge {
|
||||
from: self_name.cloned(),
|
||||
from_version: self_version.clone(),
|
||||
from_url: self_url.cloned(),
|
||||
from_index: self_index.cloned(),
|
||||
from_extra: self_extra.cloned(),
|
||||
from_dev: self_dev.cloned(),
|
||||
to: dependency_name.clone(),
|
||||
to_version: dependency_version.clone(),
|
||||
to_url: to_url.cloned(),
|
||||
to_index: to_index.cloned(),
|
||||
to_extra: Some(dependency_extra.clone()),
|
||||
to_dev: None,
|
||||
marker: MarkerTree::from(dependency_marker.clone()),
|
||||
|
|
@ -2408,15 +2482,18 @@ impl ForkState {
|
|||
continue;
|
||||
}
|
||||
let to_url = self.fork_urls.get(dependency_name);
|
||||
let to_index = self.fork_indexes.get(dependency_name);
|
||||
let edge = ResolutionDependencyEdge {
|
||||
from: self_name.cloned(),
|
||||
from_version: self_version.clone(),
|
||||
from_url: self_url.cloned(),
|
||||
from_index: self_index.cloned(),
|
||||
from_extra: self_extra.cloned(),
|
||||
from_dev: self_dev.cloned(),
|
||||
to: dependency_name.clone(),
|
||||
to_version: dependency_version.clone(),
|
||||
to_url: to_url.cloned(),
|
||||
to_index: to_index.cloned(),
|
||||
to_extra: None,
|
||||
to_dev: Some(dependency_dev.clone()),
|
||||
marker: MarkerTree::from(dependency_marker.clone()),
|
||||
|
|
@ -2445,6 +2522,7 @@ impl ForkState {
|
|||
extra: extra.clone(),
|
||||
dev: dev.clone(),
|
||||
url: self.fork_urls.get(name).cloned(),
|
||||
index: self.fork_indexes.get(name).cloned(),
|
||||
},
|
||||
version,
|
||||
))
|
||||
|
|
@ -2485,6 +2563,9 @@ pub(crate) struct ResolutionPackage {
|
|||
pub(crate) dev: Option<GroupName>,
|
||||
/// For index packages, this is `None`.
|
||||
pub(crate) url: Option<VerbatimParsedUrl>,
|
||||
/// For URL packages, this is `None`, and is only `Some` for packages that are pinned to a
|
||||
/// specific index via `tool.uv.sources`.
|
||||
pub(crate) index: Option<IndexUrl>,
|
||||
}
|
||||
|
||||
/// The `from_` fields and the `to_` fields allow mapping to the originating and target
|
||||
|
|
@ -2495,11 +2576,13 @@ pub(crate) struct ResolutionDependencyEdge {
|
|||
pub(crate) from: Option<PackageName>,
|
||||
pub(crate) from_version: Version,
|
||||
pub(crate) from_url: Option<VerbatimParsedUrl>,
|
||||
pub(crate) from_index: Option<IndexUrl>,
|
||||
pub(crate) from_extra: Option<ExtraName>,
|
||||
pub(crate) from_dev: Option<GroupName>,
|
||||
pub(crate) to: PackageName,
|
||||
pub(crate) to_version: Version,
|
||||
pub(crate) to_url: Option<VerbatimParsedUrl>,
|
||||
pub(crate) to_index: Option<IndexUrl>,
|
||||
pub(crate) to_extra: Option<ExtraName>,
|
||||
pub(crate) to_dev: Option<GroupName>,
|
||||
pub(crate) marker: MarkerTree,
|
||||
|
|
@ -2585,7 +2668,7 @@ impl Display for Request {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
enum Response {
|
||||
/// The returned metadata for a package hosted on a registry.
|
||||
Package(PackageName, VersionsResponse),
|
||||
Package(PackageName, Option<IndexUrl>, VersionsResponse),
|
||||
/// The returned metadata for a distribution.
|
||||
Dist {
|
||||
dist: Dist,
|
||||
|
|
|
|||
|
|
@ -548,16 +548,6 @@ impl TryFrom<SourcesWire> for Sources {
|
|||
return Err(SourceError::EmptySources);
|
||||
}
|
||||
|
||||
// Ensure that there is at most one registry source.
|
||||
if sources
|
||||
.iter()
|
||||
.filter(|source| matches!(source, Source::Registry { .. }))
|
||||
.nth(1)
|
||||
.is_some()
|
||||
{
|
||||
return Err(SourceError::MultipleIndexes);
|
||||
}
|
||||
|
||||
Ok(Self(sources))
|
||||
}
|
||||
}
|
||||
|
|
@ -967,8 +957,6 @@ pub enum SourceError {
|
|||
OverlappingMarkers(String, String, String),
|
||||
#[error("Must provide at least one source")]
|
||||
EmptySources,
|
||||
#[error("Sources can only include a single index source")]
|
||||
MultipleIndexes,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
|
|
|
|||
|
|
@ -7352,8 +7352,8 @@ fn lock_warn_missing_transitive_lower_bounds() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
warning: The transitive dependency `packaging` is unpinned. Consider setting a lower bound with a constraint when using `--resolution-strategy lowest` to avoid using outdated versions.
|
||||
warning: The transitive dependency `colorama` is unpinned. Consider setting a lower bound with a constraint when using `--resolution-strategy lowest` to avoid using outdated versions.
|
||||
warning: The transitive dependency `packaging` is unpinned. Consider setting a lower bound with a constraint when using `--resolution-strategy lowest` to avoid using outdated versions.
|
||||
warning: The transitive dependency `iniconfig` is unpinned. Consider setting a lower bound with a constraint when using `--resolution-strategy lowest` to avoid using outdated versions.
|
||||
"###);
|
||||
|
||||
|
|
@ -14625,7 +14625,6 @@ fn lock_multiple_sources_conflict() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Multiple `index` entries is not yet supported.
|
||||
#[test]
|
||||
fn lock_multiple_sources_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
|
@ -14637,29 +14636,411 @@ fn lock_multiple_sources_index() -> Result<()> {
|
|||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig"]
|
||||
dependencies = ["jinja2>=3"]
|
||||
|
||||
[tool.uv.sources]
|
||||
iniconfig = [
|
||||
{ index = "pytorch", marker = "sys_platform != 'win32'" },
|
||||
{ index = "internal", marker = "sys_platform == 'win32'" },
|
||||
jinja2 = [
|
||||
{ index = "torch-cu118", marker = "sys_platform == 'win32'"},
|
||||
{ index = "torch-cu124", marker = "sys_platform != 'win32'"},
|
||||
]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu118"
|
||||
url = "https://download.pytorch.org/whl/cu118"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu124"
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
uv_snapshot!(context.filters(), context.lock().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'win32'",
|
||||
"sys_platform != 'win32'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu118" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu124" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu118" }
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "jinja2", version = "3.1.3", source = { registry = "https://download.pytorch.org/whl/cu118" }, marker = "sys_platform == 'win32'" },
|
||||
{ name = "jinja2", version = "3.1.3", source = { registry = "https://download.pytorch.org/whl/cu124" }, marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "jinja2", marker = "sys_platform != 'win32'", specifier = ">=3", index = "https://download.pytorch.org/whl/cu124" },
|
||||
{ name = "jinja2", marker = "sys_platform == 'win32'", specifier = ">=3", index = "https://download.pytorch.org/whl/cu118" },
|
||||
]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_multiple_sources_index_mixed() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["jinja2>=3"]
|
||||
|
||||
[tool.uv.sources]
|
||||
jinja2 = [
|
||||
{ index = "torch-cu118", marker = "sys_platform == 'win32'"},
|
||||
{ url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", marker = "sys_platform != 'win32'"},
|
||||
]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu118"
|
||||
url = "https://download.pytorch.org/whl/cu118"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'win32'",
|
||||
"sys_platform != 'win32'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu118" }
|
||||
resolution-markers = [
|
||||
"sys_platform == 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
source = { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl" }
|
||||
resolution-markers = [
|
||||
"sys_platform != 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "babel", marker = "extra == 'i18n'", specifier = ">=2.7" },
|
||||
{ name = "markupsafe", specifier = ">=2.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu118" }
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b" },
|
||||
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "jinja2", version = "3.1.3", source = { registry = "https://download.pytorch.org/whl/cu118" }, marker = "sys_platform == 'win32'" },
|
||||
{ name = "jinja2", version = "3.1.4", source = { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl" }, marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "jinja2", marker = "sys_platform != 'win32'", url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl" },
|
||||
{ name = "jinja2", marker = "sys_platform == 'win32'", specifier = ">=3", index = "https://download.pytorch.org/whl/cu118" },
|
||||
]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_multiple_sources_index_non_total() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["jinja2>=3"]
|
||||
|
||||
[tool.uv.sources]
|
||||
jinja2 = [
|
||||
{ index = "torch-cu118", marker = "sys_platform == 'win32'"},
|
||||
]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu118"
|
||||
url = "https://download.pytorch.org/whl/cu118"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse: `pyproject.toml`
|
||||
Caused by: TOML parse error at line 9, column 21
|
||||
|
|
||||
9 | iniconfig = [
|
||||
| ^
|
||||
Sources can only include a single index source
|
||||
Resolved 4 packages in [TIME]
|
||||
error: Found duplicate package `jinja2==3.1.3 @ registry+https://download.pytorch.org/whl/cu118`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_multiple_sources_index_explicit() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["jinja2>=3"]
|
||||
|
||||
[tool.uv.sources]
|
||||
jinja2 = [
|
||||
{ index = "torch-cu118", marker = "sys_platform == 'win32'"},
|
||||
]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "torch-cu118"
|
||||
url = "https://download.pytorch.org/whl/cu118"
|
||||
explicit = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'win32'",
|
||||
"sys_platform != 'win32'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu118" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/5e/3a21abf3cd467d7876045335e681d276ac32492febe6d98ad89562d1a7e1/Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90", size = 268261 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", size = 133236 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b4/d2/38ff920762f2247c3af5cbbbbc40756f575d9692d381d7c520f45deb9b8f/markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", size = 20249 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/6d/72ed58d42a12bd9fc288dbff6dd8d03ea973a232ac0538d7f88d105b5251/MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4", size = 14322 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/f5/241238f89cdd6461ac9f521af8389f9a48fab97e4f315c69e9e0d52bc919/MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5", size = 12380 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/94/79751928bca5841416d8ca02e22198672e021d5c7120338e2a6e3771f8fc/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346", size = 24099 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6e/1b8070bbfc467429c7983cd5ffd4ec57e1d501763d974c7caaa0a9a79f4c/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729", size = 23249 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/50/9389ae6cdff78d7481a2a2641830b5eb1d1f62177550e73355a810a889c9/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc", size = 23149 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/02/5dddff5366fde47133186efb847fa88bddef85914bbe623e25cfeccb3517/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9", size = 23864 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f1/700ee6655561cfda986e03f7afc309e3738918551afa7dedd99225586227/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b", size = 23440 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/3e/d26623ac7f16709823b4c80e0b4a1c9196eeb46182a6c1d47b5e0c8434f4/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38", size = 23610 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/04/1f8da0810c39cb9fcff96b6baed62272c97065e9cf11471965a161439e20/MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa", size = 15113 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/24/a36dc37365bdd358b1e583cc40475593e36ab02cb7da6b3d0b9c05b0da7a/MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f", size = 15611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/60/4572a8aa1beccbc24b133aa0670781a5d2697f4fa3fecf0a87b46383174b/MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772", size = 14325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/42/849915b99a765ec104bfd07ee933de5fc9c58fa9570efa7db81717f495d8/MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da", size = 12373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/82/4caaebd963c6d60b28e4445f38841d24f8b49bc10594a09956c9d73bfc08/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a", size = 24059 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/15/6b319be2f79fcfa3173f479d69f4e950b5c9b642db4f22cf73ae5ade745f/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c", size = 23211 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/3f/8963bdf4962feb2154475acb7dc350f04217b5e0be7763a39b432291e229/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd", size = 23095 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/93/f770bc70953d32de0c6ce4bcb76271512123a1ead91aaef625a020c5bfaf/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7", size = 23901 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/92/1e5a33aa0a1190161238628fb68eb1bc5e67b56a5c89f0636328704b463a/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd", size = 23463 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/fe/657efdfe385d2a3a701f2c4fcc9577c63c438aeefdd642d0d956c4ecd225/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5", size = 23569 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/24/587dea40304046ace60f846cedaebc0d33d967a3ce46c11395a10e7a78ba/MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c", size = 15117 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/8f/d8961d633f26a011b4fe054f3bfff52f673423b8c431553268741dfb089e/MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f", size = 15613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/93/d6367ffbcd0c5c371370767f768eaa32af60bc411245b8517e383c6a2b12/MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a", size = 14563 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/37/f813c3835747dec08fe19ac9b9eced01fdf93a4b3e626521675dc7f423a9/MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d", size = 12505 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/bf/800b4d1580298ca91ccd6c95915bbd147142dad1b8cf91d57b93b28670dd/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396", size = 25358 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/78/26e209abc8f0a379f031f0acc151231974e5b153d7eda5759d17d8f329f2/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453", size = 23797 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/e1/918496a9390891756efee818880e71c1bbaf587f4dc8ede3f3852357310a/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4", size = 23743 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c6/26f576cd58d6c2decd9045e4e3f3c5dbc01ea6cb710916e7bbb6ebd95b6b/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8", size = 25076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/fa/10b24fb3b0e15fe5389dc88ecc6226ede08297e0ba7130610efbe0cdfb27/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984", size = 24037 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/81/4b3f5537d9f6cc4f5c80d6c4b78af9a5247fd37b5aba95807b2cbc336b9a/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a", size = 24015 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/07/8e8dcecd53216c5e01a51e84c32a2bce166690ed19c184774b38cd41921d/MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b", size = 15213 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/87/4c364e0f109eea2402079abecbe33fef4f347b551a11423d1f4e187ea497/MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", size = 15741 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "jinja2", version = "3.1.3", source = { registry = "https://download.pytorch.org/whl/cu118" }, marker = "sys_platform == 'win32'" },
|
||||
{ name = "jinja2", version = "3.1.3", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "jinja2", marker = "sys_platform != 'win32'", specifier = ">=3" },
|
||||
{ name = "jinja2", marker = "sys_platform == 'win32'", specifier = ">=3", index = "https://download.pytorch.org/whl/cu118" },
|
||||
]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue