mirror of https://github.com/astral-sh/uv
Tweak language for build backend validation errors (#16720)
Validation errors can also come from files pulled in by `pyproject.toml`, and `pyproject.toml` can be in a subdirectory.
This commit is contained in:
parent
8390b311f8
commit
eaa1882c51
|
|
@ -32,9 +32,9 @@ use crate::settings::ModuleName;
|
|||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Invalid pyproject.toml")]
|
||||
Toml(#[from] toml::de::Error),
|
||||
#[error("Invalid pyproject.toml")]
|
||||
#[error("Invalid metadata format in: {}", _0.user_display())]
|
||||
Toml(PathBuf, #[source] toml::de::Error),
|
||||
#[error("Invalid project metadata")]
|
||||
Validation(#[from] ValidationError),
|
||||
#[error("Invalid module name: {0}")]
|
||||
InvalidModuleName(String, #[source] IdentifierParseError),
|
||||
|
|
|
|||
|
|
@ -154,8 +154,11 @@ impl PyProjectToml {
|
|||
&self.project.version
|
||||
}
|
||||
|
||||
pub(crate) fn parse(contents: &str) -> Result<Self, Error> {
|
||||
Ok(toml::from_str(contents)?)
|
||||
pub(crate) fn parse(path: &Path) -> Result<Self, Error> {
|
||||
let contents = fs_err::read_to_string(path)?;
|
||||
let pyproject_toml =
|
||||
toml::from_str(&contents).map_err(|err| Error::Toml(path.to_path_buf(), err))?;
|
||||
Ok(pyproject_toml)
|
||||
}
|
||||
|
||||
pub(crate) fn readme(&self) -> Option<&Readme> {
|
||||
|
|
@ -949,7 +952,7 @@ mod tests {
|
|||
requires = ["uv_build>=0.4.15,<0.5.0"]
|
||||
build-backend = "uv_build"
|
||||
"#;
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
|
||||
|
|
@ -1034,7 +1037,7 @@ mod tests {
|
|||
"#
|
||||
};
|
||||
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
|
||||
|
||||
assert_snapshot!(metadata.core_metadata_format(), @r###"
|
||||
|
|
@ -1128,7 +1131,7 @@ mod tests {
|
|||
"#
|
||||
};
|
||||
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
|
||||
|
||||
assert_snapshot!(metadata.core_metadata_format(), @r"
|
||||
|
|
@ -1220,7 +1223,7 @@ mod tests {
|
|||
"#
|
||||
};
|
||||
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
|
||||
|
||||
assert_snapshot!(metadata.core_metadata_format(), @r###"
|
||||
|
|
@ -1281,7 +1284,7 @@ mod tests {
|
|||
#[test]
|
||||
fn build_system_valid() {
|
||||
let contents = extend_project("");
|
||||
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(&contents).unwrap();
|
||||
assert_snapshot!(
|
||||
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
|
||||
@""
|
||||
|
|
@ -1299,7 +1302,7 @@ mod tests {
|
|||
requires = ["uv_build"]
|
||||
build-backend = "uv_build"
|
||||
"#};
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
assert_snapshot!(
|
||||
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
|
||||
@r###"`build_system.requires = ["uv_build"]` is missing an upper bound on the `uv_build` version such as `<0.5`. Without bounding the `uv_build` version, the source distribution will break when a future, breaking version of `uv_build` is released."###
|
||||
|
|
@ -1317,7 +1320,7 @@ mod tests {
|
|||
requires = ["uv_build>=0.4.15,<0.5.0", "wheel"]
|
||||
build-backend = "uv_build"
|
||||
"#};
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
assert_snapshot!(
|
||||
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
|
||||
@"Expected a single uv requirement in `build-system.requires`, found ``"
|
||||
|
|
@ -1335,7 +1338,7 @@ mod tests {
|
|||
requires = ["setuptools"]
|
||||
build-backend = "uv_build"
|
||||
"#};
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
assert_snapshot!(
|
||||
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
|
||||
@"Expected a single uv requirement in `build-system.requires`, found ``"
|
||||
|
|
@ -1353,7 +1356,7 @@ mod tests {
|
|||
requires = ["uv_build>=0.4.15,<0.5.0"]
|
||||
build-backend = "setuptools"
|
||||
"#};
|
||||
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
|
||||
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
|
||||
assert_snapshot!(
|
||||
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
|
||||
@r###"The value for `build_system.build-backend` should be `"uv_build"`, not `"setuptools"`"###
|
||||
|
|
@ -1364,7 +1367,7 @@ mod tests {
|
|||
fn minimal() {
|
||||
let contents = extend_project("");
|
||||
|
||||
let metadata = PyProjectToml::parse(&contents)
|
||||
let metadata = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap();
|
||||
|
|
@ -1383,15 +1386,14 @@ mod tests {
|
|||
"#
|
||||
});
|
||||
|
||||
let err = PyProjectToml::parse(&contents).unwrap_err();
|
||||
assert_snapshot!(format_err(err), @r###"
|
||||
Invalid pyproject.toml
|
||||
Caused by: TOML parse error at line 4, column 10
|
||||
let err = toml::from_str::<PyProjectToml>(&contents).unwrap_err();
|
||||
assert_snapshot!(format_err(err), @r#"
|
||||
TOML parse error at line 4, column 10
|
||||
|
|
||||
4 | readme = { path = "Readme.md" }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
data did not match any variant of untagged enum Readme
|
||||
"###);
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1401,7 +1403,7 @@ mod tests {
|
|||
"#
|
||||
});
|
||||
|
||||
let err = PyProjectToml::parse(&contents)
|
||||
let err = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap_err();
|
||||
|
|
@ -1423,14 +1425,14 @@ mod tests {
|
|||
"#
|
||||
});
|
||||
|
||||
let err = PyProjectToml::parse(&contents)
|
||||
let err = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap_err();
|
||||
assert_snapshot!(format_err(err), @r###"
|
||||
Invalid pyproject.toml
|
||||
assert_snapshot!(format_err(err), @r"
|
||||
Invalid project metadata
|
||||
Caused by: `project.description` must be a single line
|
||||
"###);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1441,14 +1443,14 @@ mod tests {
|
|||
"#
|
||||
});
|
||||
|
||||
let err = PyProjectToml::parse(&contents)
|
||||
let err = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap_err();
|
||||
assert_snapshot!(format_err(err), @r###"
|
||||
Invalid pyproject.toml
|
||||
assert_snapshot!(format_err(err), @r"
|
||||
Invalid project metadata
|
||||
Caused by: When `project.license-files` is defined, `project.license` must be an SPDX expression string
|
||||
"###);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1457,7 +1459,7 @@ mod tests {
|
|||
license = "MIT OR Apache-2.0"
|
||||
"#
|
||||
});
|
||||
let metadata = PyProjectToml::parse(&contents)
|
||||
let metadata = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap();
|
||||
|
|
@ -1475,13 +1477,13 @@ mod tests {
|
|||
license = "MIT XOR Apache-2"
|
||||
"#
|
||||
});
|
||||
let err = PyProjectToml::parse(&contents)
|
||||
let err = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap_err();
|
||||
// TODO(konsti): We mess up the indentation in the error.
|
||||
assert_snapshot!(format_err(err), @r"
|
||||
Invalid pyproject.toml
|
||||
Invalid project metadata
|
||||
Caused by: `project.license` is not a valid SPDX expression: MIT XOR Apache-2
|
||||
Caused by: MIT XOR Apache-2
|
||||
^^^ unknown term
|
||||
|
|
@ -1495,18 +1497,18 @@ mod tests {
|
|||
"#
|
||||
});
|
||||
|
||||
let err = PyProjectToml::parse(&contents)
|
||||
let err = toml::from_str::<PyProjectToml>(&contents)
|
||||
.unwrap()
|
||||
.to_metadata(Path::new("/do/not/read"))
|
||||
.unwrap_err();
|
||||
assert_snapshot!(format_err(err), @r###"
|
||||
Invalid pyproject.toml
|
||||
assert_snapshot!(format_err(err), @r"
|
||||
Invalid project metadata
|
||||
Caused by: Dynamic metadata is not supported
|
||||
"###);
|
||||
");
|
||||
}
|
||||
|
||||
fn script_error(contents: &str) -> String {
|
||||
let err = PyProjectToml::parse(contents)
|
||||
let err = toml::from_str::<PyProjectToml>(contents)
|
||||
.unwrap()
|
||||
.to_entry_points()
|
||||
.unwrap_err();
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ pub fn build_source_dist(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<SourceDistFilename, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
let filename = SourceDistFilename {
|
||||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
|
|
@ -45,8 +44,7 @@ pub fn list_source_dist(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<(SourceDistFilename, FileList), Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
let filename = SourceDistFilename {
|
||||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
|
|
@ -188,8 +186,7 @@ fn write_source_dist(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<SourceDistFilename, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
for warning in pyproject_toml.check_build_system(uv_version) {
|
||||
warn_user_once!("{warning}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ pub fn build_wheel(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<WheelFilename, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
for warning in pyproject_toml.check_build_system(uv_version) {
|
||||
warn_user_once!("{warning}");
|
||||
}
|
||||
|
|
@ -71,8 +70,7 @@ pub fn list_wheel(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<(WheelFilename, FileList), Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
for warning in pyproject_toml.check_build_system(uv_version) {
|
||||
warn_user_once!("{warning}");
|
||||
}
|
||||
|
|
@ -273,8 +271,7 @@ pub fn build_editable(
|
|||
uv_version: &str,
|
||||
show_warnings: bool,
|
||||
) -> Result<WheelFilename, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
for warning in pyproject_toml.check_build_system(uv_version) {
|
||||
warn_user_once!("{warning}");
|
||||
}
|
||||
|
|
@ -335,8 +332,7 @@ pub fn metadata(
|
|||
metadata_directory: &Path,
|
||||
uv_version: &str,
|
||||
) -> Result<String, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
|
||||
for warning in pyproject_toml.check_build_system(uv_version) {
|
||||
warn_user_once!("{warning}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -792,15 +792,15 @@ fn license_glob_without_matches_errors() -> Result<()> {
|
|||
.build_backend()
|
||||
.arg("build-wheel")
|
||||
.arg(context.temp_dir.path())
|
||||
.current_dir(project.path()), @r###"
|
||||
.current_dir(project.path()), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Invalid pyproject.toml
|
||||
error: Invalid project metadata
|
||||
Caused by: `project.license-files` glob `abc` did not match any files
|
||||
"###);
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -835,15 +835,15 @@ fn license_file_must_be_utf8() -> Result<()> {
|
|||
.build_backend()
|
||||
.arg("build-wheel")
|
||||
.arg(context.temp_dir.path())
|
||||
.current_dir(project.path()), @r###"
|
||||
.current_dir(project.path()), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Invalid pyproject.toml
|
||||
error: Invalid project metadata
|
||||
Caused by: License file `LICENSE.bin` must be UTF-8 encoded
|
||||
"###);
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1185,3 +1185,40 @@ fn warn_on_redundant_module_names() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_pyproject_toml() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("child")
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = 1
|
||||
version = "1.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.9,<10000"]
|
||||
build-backend = "uv_build"
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.build().arg("child"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Building source distribution (uv build backend)...
|
||||
× Failed to build `[TEMP_DIR]/child`
|
||||
├─▶ Invalid metadata format in: child/pyproject.toml
|
||||
╰─▶ TOML parse error at line 2, column 8
|
||||
|
|
||||
2 | name = 1
|
||||
| ^
|
||||
invalid type: integer `1`, expected a string
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue