diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index cf3a964293b5..9c5072647391 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -799,23 +799,23 @@ impl TryFrom for Sources { .zip(sources.iter().skip(1).map(Source::marker)) { if !lhs.is_disjoint(&rhs) { + let Some(left) = lhs.contents().map(|contents| contents.to_string()) else { + return Err(SourceError::MissingMarkers); + }; + + let Some(right) = rhs.contents().map(|contents| contents.to_string()) + else { + return Err(SourceError::MissingMarkers); + }; + let mut hint = lhs.negate(); hint.and(rhs.clone()); - - let lhs = lhs - .contents() - .map(|contents| contents.to_string()) - .unwrap_or_else(|| "true".to_string()); - let rhs = rhs - .contents() - .map(|contents| contents.to_string()) - .unwrap_or_else(|| "true".to_string()); let hint = hint .contents() .map(|contents| contents.to_string()) .unwrap_or_else(|| "true".to_string()); - return Err(SourceError::OverlappingMarkers(lhs, rhs, hint)); + return Err(SourceError::OverlappingMarkers(left, right, hint)); } } @@ -1231,6 +1231,8 @@ pub enum SourceError { NonUtf8Path(PathBuf), #[error("Source markers must be disjoint, but the following markers overlap: `{0}` and `{1}`.\n\n{hint}{colon} replace `{1}` with `{2}`.", hint = "hint".bold().cyan(), colon = ":".bold())] OverlappingMarkers(String, String, String), + #[error("When multiple sources are provided, each source must include a platform markers (e.g., `marker = \"sys_platform == 'linux'\"`)")] + MissingMarkers, #[error("Must provide at least one source")] EmptySources, } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index b640383ed1ab..cf4f811d7efe 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18036,6 +18036,44 @@ fn lock_multiple_sources_conflict() -> Result<()> { Ok(()) } +#[test] +fn lock_multiple_sources_no_marker() -> 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 = ["iniconfig"] + + [tool.uv.sources] + iniconfig = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz" }, + ] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @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 = [ + | ^ + When multiple sources are provided, each source must include a platform markers (e.g., `marker = "sys_platform == 'linux'"`) + "###); + + Ok(()) +} + #[test] fn lock_multiple_sources_index() -> Result<()> { let context = TestContext::new("3.12");