Skip to content

Commit

Permalink
feat: support PEP 639 (except normalization)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Oct 21, 2024
1 parent 7a603c4 commit 1ee619e
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 27 deletions.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ version.source = "vcs"
build.hooks.vcs.version-file = "src/scikit_build_core/_version.py"


[tool.uv]
dev-dependencies = ["scikit-build-core[test]"]
environments = ["python_version >= '3.10'"]

[tool.uv.pip]
reinstall-package = ["scikit-build-core"]

Expand Down
43 changes: 29 additions & 14 deletions src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ def _build_wheel_impl_impl(
msg = "project.version is not specified, must be statically present or tool.scikit-build metadata.version.provider configured when dynamic"
raise AssertionError(msg)

# Verify PEP 639 replaces license-files
if metadata.license_files is not None and settings.wheel.license_files:
msg = "Both project.license-files and tool.scikit-build.wheel.license-files are set, use only one"
raise AssertionError(msg)

# Get the closest (normally) importable name
normalized_name = metadata.name.replace("-", "_").replace(".", "_")

Expand Down Expand Up @@ -313,20 +318,30 @@ def _build_wheel_impl_impl(
install_dir = wheel_dirs[targetlib] / settings.wheel.install_dir

# Include the metadata license.file entry if provided
license_file_globs = list(settings.wheel.license_files)
if (
metadata.license
and not isinstance(metadata.license, str)
and metadata.license.file
):
license_file_globs.append(str(metadata.license.file))

for y in license_file_globs:
for x in Path().glob(y):
if x.is_file():
path = wheel_dirs["metadata"] / "licenses" / x
path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(x, path)
if metadata.license_files:
license_paths = metadata.license_files
else:
license_file_globs = settings.wheel.license_files or [
"LICEN[CS]E*",
"COPYING*",
"NOTICE*",
"AUTHORS*",
]
if (
metadata.license
and not isinstance(metadata.license, str)
and metadata.license.file
):
license_file_globs.append(str(metadata.license.file))

license_paths = [
x for y in license_file_globs for x in Path().glob(y) if x.is_file()
]

for x in license_paths:
path = wheel_dirs["metadata"] / "licenses" / x
path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(x, path)

if (
settings.wheel.license_files
Expand Down
6 changes: 3 additions & 3 deletions src/scikit_build_core/settings/skbuild_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,11 @@ class WheelSettings:
root, giving access to "/platlib", "/data", "/headers", and "/scripts".
"""

license_files: List[str] = dataclasses.field(
default_factory=lambda: ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"]
)
license_files: Optional[List[str]] = None
"""
A list of license files to include in the wheel. Supports glob patterns.
The default is ``["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"]``.
Must not be set if ``project.license-files`` is set.
"""

cmake: bool = True
Expand Down
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,15 @@ def package_simple_purelib_package(
return package


@pytest.fixture
def package_pep639_pure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
package = PackageInfo(
"pep639_pure",
)
process_package(package, tmp_path, monkeypatch)
return package


def which_mock(name: str) -> str | None:
if name in {"ninja", "ninja-build", "cmake3", "samu", "gmake", "make"}:
return None
Expand Down
Empty file.
Empty file.
12 changes: 12 additions & 0 deletions tests/packages/pep639_pure/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"

[project]
name = "pep639_pure"
version = "0.1.0"
license = "MIT"
license-files = ["LICENSE1.txt", "nested/more/LICENSE2.txt"]

[tool.scikit-build]
wheel.cmake = false
104 changes: 94 additions & 10 deletions tests/test_pyproject_pep517.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gzip
import hashlib
import inspect
import shutil
import sys
import tarfile
Expand Down Expand Up @@ -27,15 +28,6 @@
[gui_scripts]
guithing = a.b:c
"""
METADATA = """\
Metadata-Version: 2.1
Name: CMake.Example
Version: 0.0.1
Requires-Python: >=3.7
Provides-Extra: test
Requires-Dist: pytest>=6.0; extra == "test"
"""

mark_hashes_different = pytest.mark.xfail(
Expand All @@ -52,6 +44,19 @@ def compute_uncompressed_hash(inp: Path) -> str:

@pytest.mark.usefixtures("package_simple_pyproject_ext")
def test_pep517_sdist():
expected_metadata = (
inspect.cleandoc(
"""
Metadata-Version: 2.1
Name: CMake.Example
Version: 0.0.1
Requires-Python: >=3.7
Provides-Extra: test
Requires-Dist: pytest>=6.0; extra == "test"
"""
)
+ "\n\n"
)
dist = Path("dist")
out = build_sdist("dist")

Expand All @@ -74,7 +79,7 @@ def test_pep517_sdist():
pkg_info = f.extractfile("cmake_example-0.0.1/PKG-INFO")
assert pkg_info
pkg_info_contents = pkg_info.read().decode()
assert pkg_info_contents == METADATA
assert pkg_info_contents == expected_metadata


@mark_hashes_different
Expand Down Expand Up @@ -360,3 +365,82 @@ def test_prepare_metdata_for_build_wheel_by_hand(tmp_path):
assert metadata.get(k, None) == b

assert len(metadata) == len(answer)


@pytest.mark.usefixtures("package_pep639_pure")
def test_pep639_license_files_metadata():
metadata = build.util.project_wheel_metadata(str(Path.cwd()), isolated=False)
answer = {
"Metadata-Version": ["2.4"],
"Name": ["pep639_pure"],
"Version": ["0.1.0"],
"License-Expression": ["MIT"],
"License-File": ["LICENSE1.txt", "nested/more/LICENSE2.txt"],
}

for k, b in answer.items():
assert metadata.get_all(k, None) == b

assert len(metadata) == sum(len(v) for v in answer.values())


@pytest.mark.usefixtures("package_pep639_pure")
def test_pep639_license_files_sdist():
expected_metadata = (
inspect.cleandoc(
"""
Metadata-Version: 2.4
Name: pep639_pure
Version: 0.1.0
License-Expression: MIT
License-File: LICENSE1.txt
License-File: nested/more/LICENSE2.txt
"""
)
+ "\n\n"
)

dist = Path("dist")
out = build_sdist("dist")

(sdist,) = dist.iterdir()
assert sdist.name == "pep639_pure-0.1.0.tar.gz"
assert sdist == dist / out

with tarfile.open(sdist) as f:
file_names = set(f.getnames())
assert file_names == {
f"pep639_pure-0.1.0/{x}"
for x in (
"pyproject.toml",
"PKG-INFO",
"LICENSE1.txt",
"nested/more/LICENSE2.txt",
)
}
pkg_info = f.extractfile("pep639_pure-0.1.0/PKG-INFO")
assert pkg_info
pkg_info_contents = pkg_info.read().decode()
assert pkg_info_contents == expected_metadata


@pytest.mark.usefixtures("package_pep639_pure")
def test_pep639_license_files_wheel():
dist = Path("dist")
out = build_wheel("dist", {})
(wheel,) = dist.glob("pep639_pure-0.1.0-*.whl")
assert wheel == dist / out

with zipfile.ZipFile(wheel) as zf:
file_paths = {Path(p) for p in zf.namelist()}
with zf.open("pep639_pure-0.1.0.dist-info/METADATA") as f:
metadata = f.read().decode("utf-8")

assert Path("pep639_pure-0.1.0.dist-info/licenses/LICENSE1.txt") in file_paths
assert (
Path("pep639_pure-0.1.0.dist-info/licenses/nested/more/LICENSE2.txt")
in file_paths
)

assert "LICENSE1.txt" in metadata
assert "nested/more/LICENSE2.txt" in metadata

0 comments on commit 1ee619e

Please sign in to comment.