Skip to content

Commit 86c3677

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

File tree

3 files changed

+215
-23
lines changed

3 files changed

+215
-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: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13208,6 +13208,205 @@ 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 = "anyio"
13351+
version = "3.7.0"
13352+
source = { registry = "https://pypi.org/simple" }
13353+
dependencies = [
13354+
{ name = "idna" },
13355+
{ name = "sniffio" },
13356+
]
13357+
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737 }
13358+
wheels = [
13359+
{ url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873 },
13360+
]
13361+
13362+
[[package]]
13363+
name = "idna"
13364+
version = "3.6"
13365+
source = { registry = "https://pypi.org/simple" }
13366+
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 }
13367+
wheels = [
13368+
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 },
13369+
]
13370+
13371+
[[package]]
13372+
name = "iniconfig"
13373+
version = "2.0.0"
13374+
source = { registry = "https://test.pypi.org/simple" }
13375+
sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
13376+
wheels = [
13377+
{ url = "https://test-files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
13378+
]
13379+
13380+
[[package]]
13381+
name = "project"
13382+
version = "0.1.0"
13383+
source = { editable = "." }
13384+
dependencies = [
13385+
{ name = "anyio" },
13386+
{ name = "iniconfig" },
13387+
]
13388+
13389+
[package.metadata]
13390+
requires-dist = [
13391+
{ name = "anyio", specifier = "==3.7.0" },
13392+
{ name = "iniconfig", specifier = "==2.0.0", index = "https://test.pypi.org/simple" },
13393+
]
13394+
13395+
[[package]]
13396+
name = "sniffio"
13397+
version = "1.3.1"
13398+
source = { registry = "https://pypi.org/simple" }
13399+
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
13400+
wheels = [
13401+
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
13402+
]
13403+
"###
13404+
);
13405+
});
13406+
13407+
Ok(())
13408+
}
13409+
1321113410
#[test]
1321213411
fn lock_named_index() -> Result<()> {
1321313412
let context = TestContext::new("3.12");

0 commit comments

Comments
 (0)