mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
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:
parent
a78e7468a7
commit
654ff8015a
4 changed files with 113 additions and 9 deletions
|
@ -180,9 +180,10 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use flate2::bufread::GzDecoder;
|
use flate2::bufread::GzDecoder;
|
||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
|
use indoc::indoc;
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::io::BufReader;
|
use std::io::{BufReader, Read};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use uv_fs::{copy_dir_all, relative_to};
|
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()
|
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
|
||||||
|
"###);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,8 +119,31 @@ impl PyProjectToml {
|
||||||
self.project.readme.as_ref()
|
self.project.readme.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn license_files(&self) -> Option<&[String]> {
|
/// The license files that need to be included in the source distribution.
|
||||||
self.project.license_files.as_deref()
|
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> {
|
pub(crate) fn settings(&self) -> Option<&BuildBackendSettings> {
|
||||||
|
@ -682,10 +705,20 @@ pub(crate) enum License {
|
||||||
},
|
},
|
||||||
File {
|
File {
|
||||||
/// The file containing the license text.
|
/// 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
|
/// A `project.authors` or `project.maintainers` entry as specified in
|
||||||
/// <https://packaging.python.org/en/latest/specifications/pyproject-toml/#authors-maintainers>.
|
/// <https://packaging.python.org/en/latest/specifications/pyproject-toml/#authors-maintainers>.
|
||||||
///
|
///
|
||||||
|
|
|
@ -95,7 +95,7 @@ fn source_dist_matcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the license files
|
// 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}`");
|
trace!("Including license files at: `{license_files}`");
|
||||||
let glob = parse_portable_glob(license_files).map_err(|err| Error::PortableGlob {
|
let glob = parse_portable_glob(license_files).map_err(|err| Error::PortableGlob {
|
||||||
field: "project.license-files".to_string(),
|
field: "project.license-files".to_string(),
|
||||||
|
|
|
@ -175,7 +175,7 @@ fn write_wheel(
|
||||||
debug!("Visited {files_visited} files for wheel build");
|
debug!("Visited {files_visited} files for wheel build");
|
||||||
|
|
||||||
// Add the license files
|
// 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");
|
debug!("Adding license files");
|
||||||
let license_dir = format!(
|
let license_dir = format!(
|
||||||
"{}-{}.dist-info/licenses/",
|
"{}-{}.dist-info/licenses/",
|
||||||
|
@ -186,7 +186,7 @@ fn write_wheel(
|
||||||
wheel_subdir_from_globs(
|
wheel_subdir_from_globs(
|
||||||
source_tree,
|
source_tree,
|
||||||
&license_dir,
|
&license_dir,
|
||||||
license_files,
|
pyproject_toml.license_files_wheel(),
|
||||||
&mut wheel_writer,
|
&mut wheel_writer,
|
||||||
"project.license-files",
|
"project.license-files",
|
||||||
)?;
|
)?;
|
||||||
|
@ -429,14 +429,15 @@ pub(crate) fn build_exclude_matcher(
|
||||||
fn wheel_subdir_from_globs(
|
fn wheel_subdir_from_globs(
|
||||||
src: &Path,
|
src: &Path,
|
||||||
target: &str,
|
target: &str,
|
||||||
globs: &[String],
|
globs: impl IntoIterator<Item = impl AsRef<str>>,
|
||||||
wheel_writer: &mut impl DirectoryWriter,
|
wheel_writer: &mut impl DirectoryWriter,
|
||||||
// For error messages
|
// For error messages
|
||||||
globs_field: &str,
|
globs_field: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let license_files_globs: Vec<_> = globs
|
let license_files_globs: Vec<_> = globs
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|license_files| {
|
.map(|license_files| {
|
||||||
|
let license_files = license_files.as_ref();
|
||||||
trace!(
|
trace!(
|
||||||
"Including {} at `{}` with `{}`",
|
"Including {} at `{}` with `{}`",
|
||||||
globs_field,
|
globs_field,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue