Skip to content

Commit 7f155d6

Browse files
committed
Allow default indexes to be marked as explicit
1 parent 874aa29 commit 7f155d6

File tree

3 files changed

+180
-23
lines changed

3 files changed

+180
-23
lines changed

crates/uv-distribution-types/src/index_url.rs

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,14 @@ impl<'a> IndexLocations {
234234
self.indexes
235235
.iter()
236236
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
237-
.find(|index| index.default && !index.explicit)
237+
.find(|index| index.default)
238238
.or_else(|| Some(&DEFAULT_INDEX))
239239
}
240240
}
241241

242242
/// Return an iterator over the implicit [`Index`] entries.
243+
///
244+
/// Default and explicit indexes are excluded.
243245
pub fn implicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
244246
if self.no_index {
245247
Either::Left(std::iter::empty())
@@ -249,22 +251,7 @@ impl<'a> IndexLocations {
249251
self.indexes
250252
.iter()
251253
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
252-
.filter(|index| !(index.default || index.explicit)),
253-
)
254-
}
255-
}
256-
257-
/// Return an iterator over the explicit [`Index`] entries.
258-
pub fn explicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
259-
if self.no_index {
260-
Either::Left(std::iter::empty())
261-
} else {
262-
let mut seen = FxHashSet::default();
263-
Either::Right(
264-
self.indexes
265-
.iter()
266-
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
267-
.filter(|index| index.explicit),
254+
.filter(|index| !index.default && !index.explicit),
268255
)
269256
}
270257
}
@@ -278,7 +265,9 @@ impl<'a> IndexLocations {
278265
/// If `no_index` was enabled, then this always returns an empty
279266
/// iterator.
280267
pub fn indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
281-
self.implicit_indexes().chain(self.default_index())
268+
self.implicit_indexes()
269+
.chain(self.default_index())
270+
.filter(|index| !index.explicit)
282271
}
283272

284273
/// Return an iterator over the [`FlatIndexLocation`] entries.
@@ -319,7 +308,7 @@ impl<'a> IndexLocations {
319308
.chain(self.flat_index.iter())
320309
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
321310
} {
322-
if index.default && !index.explicit {
311+
if index.default {
323312
if default {
324313
continue;
325314
}
@@ -361,12 +350,14 @@ impl<'a> IndexUrls {
361350
self.indexes
362351
.iter()
363352
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
364-
.find(|index| index.default && !index.explicit)
353+
.find(|index| index.default)
365354
.or_else(|| Some(&DEFAULT_INDEX))
366355
}
367356
}
368357

369358
/// Return an iterator over the implicit [`Index`] entries.
359+
///
360+
/// Default and explicit indexes are excluded.
370361
fn implicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
371362
if self.no_index {
372363
Either::Left(std::iter::empty())
@@ -376,7 +367,7 @@ impl<'a> IndexUrls {
376367
self.indexes
377368
.iter()
378369
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
379-
.filter(|index| !(index.default || index.explicit)),
370+
.filter(|index| !index.default && !index.explicit),
380371
)
381372
}
382373
}
@@ -389,7 +380,9 @@ impl<'a> IndexUrls {
389380
/// If `no_index` was enabled, then this always returns an empty
390381
/// iterator.
391382
pub fn indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
392-
self.implicit_indexes().chain(self.default_index())
383+
self.implicit_indexes()
384+
.chain(self.default_index())
385+
.filter(|index| !index.explicit)
393386
}
394387
}
395388

crates/uv-resolver/src/pubgrub/report.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ impl PubGrubReportFormatter<'_> {
736736
}
737737

738738
// Add hints due to an index returning an unauthorized response.
739-
for index in index_locations.indexes() {
739+
for index in index_locations.allowed_indexes() {
740740
if index_capabilities.unauthorized(&index.url) {
741741
hints.insert(PubGrubHint::UnauthorizedIndex {
742742
index: index.url.clone(),

crates/uv/tests/it/lock.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13208,6 +13208,170 @@ fn lock_explicit_index() -> Result<()> {
1320813208
Ok(())
1320913209
}
1321013210

13211+
#[test]
13212+
fn lock_explicit_default_index() -> Result<()> {
13213+
let context = TestContext::new("3.12");
13214+
13215+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
13216+
pyproject_toml.write_str(
13217+
r#"
13218+
[project]
13219+
name = "project"
13220+
version = "0.1.0"
13221+
requires-python = ">=3.12"
13222+
dependencies = ["iniconfig==2.0.0"]
13223+
13224+
[build-system]
13225+
requires = ["setuptools>=42"]
13226+
build-backend = "setuptools.build_meta"
13227+
13228+
[tool.uv.sources]
13229+
iniconfig = { index = "test" }
13230+
13231+
[[tool.uv.index]]
13232+
name = "test"
13233+
url = "https://test.pypi.org/simple"
13234+
explicit = true
13235+
default = true
13236+
"#,
13237+
)?;
13238+
13239+
uv_snapshot!(context.filters(), context.lock(), @r###"
13240+
success: true
13241+
exit_code: 0
13242+
----- stdout -----
13243+
13244+
----- stderr -----
13245+
Resolved 2 packages in [TIME]
13246+
"###);
13247+
13248+
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
13249+
13250+
insta::with_settings!({
13251+
filters => context.filters(),
13252+
}, {
13253+
assert_snapshot!(
13254+
lock, @r###"
13255+
version = 1
13256+
requires-python = ">=3.12"
13257+
13258+
[options]
13259+
exclude-newer = "2024-03-25T00:00:00Z"
13260+
13261+
[[package]]
13262+
name = "iniconfig"
13263+
version = "2.0.0"
13264+
source = { registry = "https://test.pypi.org/simple" }
13265+
sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
13266+
wheels = [
13267+
{ url = "https://test-files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
13268+
]
13269+
13270+
[[package]]
13271+
name = "project"
13272+
version = "0.1.0"
13273+
source = { editable = "." }
13274+
dependencies = [
13275+
{ name = "iniconfig" },
13276+
]
13277+
13278+
[package.metadata]
13279+
requires-dist = [{ name = "iniconfig", specifier = "==2.0.0", index = "https://test.pypi.org/simple" }]
13280+
"###
13281+
);
13282+
});
13283+
13284+
pyproject_toml.write_str(
13285+
r#"
13286+
[project]
13287+
name = "project"
13288+
version = "0.1.0"
13289+
requires-python = ">=3.12"
13290+
dependencies = ["anyio"]
13291+
13292+
[build-system]
13293+
requires = ["setuptools>=42"]
13294+
build-backend = "setuptools.build_meta"
13295+
13296+
[[tool.uv.index]]
13297+
name = "test"
13298+
url = "https://test.pypi.org/simple"
13299+
explicit = true
13300+
default = true
13301+
"#,
13302+
)?;
13303+
13304+
uv_snapshot!(context.filters(), context.lock().arg("--verbose"), @r###"
13305+
success: false
13306+
exit_code: 1
13307+
----- stdout -----
13308+
13309+
----- stderr -----
13310+
DEBUG uv [VERSION] ([COMMIT] DATE)
13311+
DEBUG Found workspace root: `[TEMP_DIR]/`
13312+
DEBUG Adding current workspace member: `[TEMP_DIR]/`
13313+
DEBUG Using Python request `>=3.12` from `requires-python` metadata
13314+
DEBUG The virtual environment's Python version satisfies `>=3.12`
13315+
DEBUG Using request timeout of [TIME]
13316+
DEBUG Found static `pyproject.toml` for: project @ file://[TEMP_DIR]/
13317+
DEBUG No workspace root found, using project root
13318+
DEBUG Ignoring existing lockfile due to mismatched `requires-dist` for: `project==0.1.0`
13319+
Expected: {Requirement { name: PackageName("anyio"), extras: [], marker: true, source: Registry { specifier: VersionSpecifiers([]), index: None }, origin: None }}
13320+
Actual: {Requirement { name: PackageName("iniconfig"), extras: [], marker: true, source: Registry { specifier: VersionSpecifiers([VersionSpecifier { operator: Equal, version: "2.0.0" }]), index: Some(Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("test.pypi.org")), port: None, path: "/simple", query: None, fragment: None }) }, origin: None }}
13321+
DEBUG Solving with installed Python version: 3.12.[X]
13322+
DEBUG Solving with target Python version: >=3.12
13323+
DEBUG Adding direct dependency: project*
13324+
DEBUG Searching for a compatible version of project @ file://[TEMP_DIR]/ (*)
13325+
DEBUG Adding transitive dependency for project==0.1.0: anyio*
13326+
DEBUG Searching for a compatible version of anyio (*)
13327+
DEBUG No compatible version found for: anyio
13328+
DEBUG Searching for a compatible version of project @ file://[TEMP_DIR]/ (<0.1.0 | >0.1.0)
13329+
DEBUG No compatible version found for: project
13330+
× No solution found when resolving dependencies:
13331+
╰─▶ Because anyio was not found in the provided package locations and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.
13332+
13333+
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
13334+
"###);
13335+
13336+
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
13337+
13338+
insta::with_settings!({
13339+
filters => context.filters(),
13340+
}, {
13341+
assert_snapshot!(
13342+
lock, @r###"
13343+
version = 1
13344+
requires-python = ">=3.12"
13345+
13346+
[options]
13347+
exclude-newer = "2024-03-25T00:00:00Z"
13348+
13349+
[[package]]
13350+
name = "iniconfig"
13351+
version = "2.0.0"
13352+
source = { registry = "https://test.pypi.org/simple" }
13353+
sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
13354+
wheels = [
13355+
{ url = "https://test-files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
13356+
]
13357+
13358+
[[package]]
13359+
name = "project"
13360+
version = "0.1.0"
13361+
source = { editable = "." }
13362+
dependencies = [
13363+
{ name = "iniconfig" },
13364+
]
13365+
13366+
[package.metadata]
13367+
requires-dist = [{ name = "iniconfig", specifier = "==2.0.0", index = "https://test.pypi.org/simple" }]
13368+
"###
13369+
);
13370+
});
13371+
13372+
Ok(())
13373+
}
13374+
1321113375
#[test]
1321213376
fn lock_named_index() -> Result<()> {
1321313377
let context = TestContext::new("3.12");

0 commit comments

Comments
 (0)