Build backend: Fix pre-PEP 639 license files (#9965)

We were not copying the license file from a pre-PEP 639 declaration to
the source distribution.

Fixes #9947
This commit is contained in:
konsti 2024-12-17 15:19:59 +01:00 committed by GitHub
parent a78e7468a7
commit 654ff8015a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 113 additions and 9 deletions

View file

@ -180,9 +180,10 @@ mod tests {
use super::*;
use flate2::bufread::GzDecoder;
use fs_err::File;
use indoc::indoc;
use insta::assert_snapshot;
use itertools::Itertools;
use std::io::BufReader;
use std::io::{BufReader, Read};
use tempfile::TempDir;
use uv_fs::{copy_dir_all, relative_to};
@ -391,4 +392,73 @@ mod tests {
fs_err::read(indirect_output_dir.path().join(wheel_filename)).unwrap()
);
}
/// Test that `license = { file = "LICENSE" }` is supported.
#[test]
fn license_file_pre_pep639() {
let src = TempDir::new().unwrap();
fs_err::write(
src.path().join("pyproject.toml"),
indoc! {r#"
[project]
name = "pep-pep639-license"
version = "1.0.0"
license = { file = "license.txt" }
[build-system]
requires = ["uv>=0.5.15,<0.6"]
build-backend = "uv"
"#
},
)
.unwrap();
fs_err::create_dir_all(src.path().join("src").join("pep_pep639_license")).unwrap();
File::create(
src.path()
.join("src")
.join("pep_pep639_license")
.join("__init__.py"),
)
.unwrap();
fs_err::write(
src.path().join("license.txt"),
"Copy carefully.\nSincerely, the authors",
)
.unwrap();
// Build a wheel from a source distribution
let output_dir = TempDir::new().unwrap();
build_source_dist(src.path(), output_dir.path(), "0.5.15").unwrap();
let sdist_tree = TempDir::new().unwrap();
let source_dist_path = output_dir.path().join("pep_pep639_license-1.0.0.tar.gz");
let sdist_reader = BufReader::new(File::open(&source_dist_path).unwrap());
let mut source_dist = tar::Archive::new(GzDecoder::new(sdist_reader));
source_dist.unpack(sdist_tree.path()).unwrap();
build_wheel(
&sdist_tree.path().join("pep_pep639_license-1.0.0"),
output_dir.path(),
None,
"0.5.15",
)
.unwrap();
let wheel = output_dir
.path()
.join("pep_pep639_license-1.0.0-py3-none-any.whl");
let mut wheel = zip::ZipArchive::new(File::open(wheel).unwrap()).unwrap();
let mut metadata = String::new();
wheel
.by_name("pep_pep639_license-1.0.0.dist-info/METADATA")
.unwrap()
.read_to_string(&mut metadata)
.unwrap();
assert_snapshot!(metadata, @r###"
Metadata-Version: 2.3
Name: pep-pep639-license
Version: 1.0.0
License: Copy carefully.
Sincerely, the authors
"###);
}
}

View file

@ -119,8 +119,31 @@ impl PyProjectToml {
self.project.readme.as_ref()
}
pub(crate) fn license_files(&self) -> Option<&[String]> {
self.project.license_files.as_deref()
/// The license files that need to be included in the source distribution.
pub(crate) fn license_files_source_dist(&self) -> impl Iterator<Item = &str> {
let license_file = self
.project
.license
.as_ref()
.and_then(|license| license.file())
.into_iter();
let license_files = self
.project
.license_files
.iter()
.flatten()
.map(String::as_str);
license_files.chain(license_file)
}
/// The license files that need to be included in the wheel.
pub(crate) fn license_files_wheel(&self) -> impl Iterator<Item = &str> {
// The pre-PEP 639 `license = { file = "..." }` is included inline in `METADATA`.
self.project
.license_files
.iter()
.flatten()
.map(String::as_str)
}
pub(crate) fn settings(&self) -> Option<&BuildBackendSettings> {
@ -682,10 +705,20 @@ pub(crate) enum License {
},
File {
/// The file containing the license text.
file: PathBuf,
file: String,
},
}
impl License {
fn file(&self) -> Option<&str> {
if let Self::File { file } = self {
Some(file)
} else {
None
}
}
}
/// A `project.authors` or `project.maintainers` entry as specified in
/// <https://packaging.python.org/en/latest/specifications/pyproject-toml/#authors-maintainers>.
///

View file

@ -95,7 +95,7 @@ fn source_dist_matcher(
}
// Include the license files
for license_files in pyproject_toml.license_files().into_iter().flatten() {
for license_files in pyproject_toml.license_files_source_dist() {
trace!("Including license files at: `{license_files}`");
let glob = parse_portable_glob(license_files).map_err(|err| Error::PortableGlob {
field: "project.license-files".to_string(),

View file

@ -175,7 +175,7 @@ fn write_wheel(
debug!("Visited {files_visited} files for wheel build");
// Add the license files
if let Some(license_files) = &pyproject_toml.license_files() {
if pyproject_toml.license_files_wheel().next().is_some() {
debug!("Adding license files");
let license_dir = format!(
"{}-{}.dist-info/licenses/",
@ -186,7 +186,7 @@ fn write_wheel(
wheel_subdir_from_globs(
source_tree,
&license_dir,
license_files,
pyproject_toml.license_files_wheel(),
&mut wheel_writer,
"project.license-files",
)?;
@ -429,14 +429,15 @@ pub(crate) fn build_exclude_matcher(
fn wheel_subdir_from_globs(
src: &Path,
target: &str,
globs: &[String],
globs: impl IntoIterator<Item = impl AsRef<str>>,
wheel_writer: &mut impl DirectoryWriter,
// For error messages
globs_field: &str,
) -> Result<(), Error> {
let license_files_globs: Vec<_> = globs
.iter()
.into_iter()
.map(|license_files| {
let license_files = license_files.as_ref();
trace!(
"Including {} at `{}` with `{}`",
globs_field,