Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store license-files in licenses subfolder #4728

Open
wants to merge 2 commits into
base: feature/pep639
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/4728.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Store ``License-File``s in ``.dist-info/licenses`` subfolder and added support for recursive globs for ``license_files`` (`PEP 639 <https://peps.python.org/pep-0639/#add-license-expression-field>`_). -- by :user:`cdce8p`
6 changes: 4 additions & 2 deletions setuptools/command/bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,9 +590,11 @@ def adios(p: str) -> None:
metadata_path = os.path.join(distinfo_path, "METADATA")
shutil.copy(pkginfo_path, metadata_path)

licenses_folder_path = os.path.join(distinfo_path, "licenses")
for license_path in self.license_paths:
filename = os.path.basename(license_path)
shutil.copy(license_path, os.path.join(distinfo_path, filename))
dist_info_license_path = os.path.join(licenses_folder_path, license_path)
os.makedirs(os.path.dirname(dist_info_license_path), exist_ok=True)
shutil.copy(license_path, dist_info_license_path)

adios(egginfo_path)

Expand Down
7 changes: 5 additions & 2 deletions setuptools/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,10 @@ def _finalize_license_files(self) -> None:
patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']

self.metadata.license_files = list(
unique_everseen(self._expand_patterns(patterns))
map(
lambda path: path.replace("\\", "/"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use os.sep here instead of \\ (e.g. if somehow \\ ends showing up on a linux build, we want the build to crash right, because it would mean incorrect input from the user)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that's necessary here. path isn't a user input but rather the glob matched license path from self._expand_patterns. The idea with this line is just to normalize the separator since the core metadata field only allows forward /.

unique_everseen(self._expand_patterns(patterns)),
)
)

@staticmethod
Expand All @@ -432,7 +435,7 @@ def _expand_patterns(patterns):
return (
path
for pattern in patterns
for path in sorted(iglob(pattern))
for path in sorted(iglob(pattern, recursive=True))
if not path.endswith('~') and os.path.isfile(path)
)

Expand Down
56 changes: 48 additions & 8 deletions setuptools/tests/test_bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
EXAMPLES = {
"dummy-dist": {
"setup.py": SETUPPY_EXAMPLE,
"licenses": {"DUMMYFILE": ""},
"licenses_dir": {"DUMMYFILE": ""},
**dict.fromkeys(DEFAULT_LICENSE_FILES | OTHER_IGNORED_FILES, ""),
},
"simple-dist": {
Expand Down Expand Up @@ -172,6 +172,20 @@
),
"README.rst": "UTF-8 描述 説明",
},
"licenses-dist": {
"setup.cfg": cleandoc(
"""
[metadata]
name = licenses-dist
version = 1.0
license_files = **/LICENSE
"""
),
"LICENSE": "",
"src": {
"vendor": {"LICENSE": ""},
},
},
}


Expand Down Expand Up @@ -238,6 +252,11 @@ def dummy_dist(tmp_path_factory):
return mkexample(tmp_path_factory, "dummy-dist")


@pytest.fixture
def licenses_dist(tmp_path_factory):
return mkexample(tmp_path_factory, "licenses-dist")


def test_no_scripts(wheel_paths):
"""Make sure entry point scripts are not generated."""
path = next(path for path in wheel_paths if "complex_dist" in path)
Expand Down Expand Up @@ -297,33 +316,34 @@ def test_licenses_default(dummy_dist, monkeypatch, tmp_path):
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
license_files = {
"dummy_dist-1.0.dist-info/" + fname for fname in DEFAULT_LICENSE_FILES
"dummy_dist-1.0.dist-info/licenses/" + fname
for fname in DEFAULT_LICENSE_FILES
}
assert set(wf.namelist()) == DEFAULT_FILES | license_files


def test_licenses_deprecated(dummy_dist, monkeypatch, tmp_path):
dummy_dist.joinpath("setup.cfg").write_text(
"[metadata]\nlicense_file=licenses/DUMMYFILE", encoding="utf-8"
"[metadata]\nlicense_file=licenses_dir/DUMMYFILE", encoding="utf-8"
)
monkeypatch.chdir(dummy_dist)

bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()

with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
license_files = {"dummy_dist-1.0.dist-info/DUMMYFILE"}
license_files = {"dummy_dist-1.0.dist-info/licenses/licenses_dir/DUMMYFILE"}
assert set(wf.namelist()) == DEFAULT_FILES | license_files


@pytest.mark.parametrize(
("config_file", "config"),
[
("setup.cfg", "[metadata]\nlicense_files=licenses/*\n LICENSE"),
("setup.cfg", "[metadata]\nlicense_files=licenses/*, LICENSE"),
("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*\n LICENSE"),
("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*, LICENSE"),
(
"setup.py",
SETUPPY_EXAMPLE.replace(
")", " license_files=['licenses/DUMMYFILE', 'LICENSE'])"
")", " license_files=['licenses_dir/DUMMYFILE', 'LICENSE'])"
),
),
],
Expand All @@ -334,9 +354,29 @@ def test_licenses_override(dummy_dist, monkeypatch, tmp_path, config_file, confi
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
license_files = {
"dummy_dist-1.0.dist-info/" + fname for fname in {"DUMMYFILE", "LICENSE"}
"dummy_dist-1.0.dist-info/licenses/" + fname
for fname in {"licenses_dir/DUMMYFILE", "LICENSE"}
}
assert set(wf.namelist()) == DEFAULT_FILES | license_files
metadata = wf.read("dummy_dist-1.0.dist-info/METADATA").decode("utf8")
assert "License-File: licenses_dir/DUMMYFILE" in metadata
assert "License-File: LICENSE" in metadata


def test_licenses_preserve_folder_structure(licenses_dist, monkeypatch, tmp_path):
monkeypatch.chdir(licenses_dist)
bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
print(os.listdir("dist"))
with ZipFile("dist/licenses_dist-1.0-py3-none-any.whl") as wf:
default_files = {name.replace("dummy_", "licenses_") for name in DEFAULT_FILES}
license_files = {
"licenses_dist-1.0.dist-info/licenses/LICENSE",
"licenses_dist-1.0.dist-info/licenses/src/vendor/LICENSE",
}
assert set(wf.namelist()) == default_files | license_files
metadata = wf.read("licenses_dist-1.0.dist-info/METADATA").decode("utf8")
assert "License-File: src/vendor/LICENSE" in metadata
assert "License-File: LICENSE" in metadata


def test_licenses_disabled(dummy_dist, monkeypatch, tmp_path):
Expand Down
7 changes: 5 additions & 2 deletions setuptools/tests/test_build_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script):
with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
wheel_contents = set(zipfile.namelist())
metadata = str(zipfile.read("foo-0.1.dist-info/METADATA"), "utf-8")
license = str(zipfile.read("foo-0.1.dist-info/LICENSE.txt"), "utf-8")
license = str(
zipfile.read("foo-0.1.dist-info/licenses/LICENSE.txt"), "utf-8"
)
epoints = str(zipfile.read("foo-0.1.dist-info/entry_points.txt"), "utf-8")

assert sdist_contents - {"foo-0.1/setup.py"} == {
Expand Down Expand Up @@ -426,7 +428,7 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script):
"foo/cli.py",
"foo/data.txt", # include_package_data defaults to True
"foo/py.typed", # include type information by default
"foo-0.1.dist-info/LICENSE.txt",
"foo-0.1.dist-info/licenses/LICENSE.txt",
"foo-0.1.dist-info/METADATA",
"foo-0.1.dist-info/WHEEL",
"foo-0.1.dist-info/entry_points.txt",
Expand All @@ -438,6 +440,7 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script):
for line in (
"Summary: This is a Python package",
"License: MIT",
"License-File: LICENSE.txt",
"Classifier: Intended Audience :: Developers",
"Requires-Dist: appdirs",
"Requires-Dist: " + str(Requirement('tomli>=1 ; extra == "all"')),
Expand Down
20 changes: 20 additions & 0 deletions setuptools/tests/test_egg_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,22 @@ def test_setup_cfg_license_file(self, tmpdir_cwd, env, files, license_in_sources
[],
id="files_only_added_once",
),
pytest.param(
{
'setup.cfg': DALS(
"""
[metadata]
license_files = **/LICENSE
"""
),
'LICENSE': "ABC license",
'LICENSE-OTHER': "Don't include",
'vendor': {'LICENSE': "Vendor license"},
},
['LICENSE', 'vendor/LICENSE'],
['LICENSE-OTHER'],
id="recursive_glob",
),
],
)
def test_setup_cfg_license_files(
Expand Down Expand Up @@ -1032,12 +1048,14 @@ def test_license_file_attr_pkg_info(self, tmpdir_cwd, env):
license_files =
NOTICE*
LICENSE*
**/LICENSE
"""
),
"LICENSE-ABC": "ABC license",
"LICENSE-XYZ": "XYZ license",
"NOTICE": "included",
"IGNORE": "not include",
"vendor": {'LICENSE': "Vendor license"},
})

environment.run_setup_py(
Expand All @@ -1053,9 +1071,11 @@ def test_license_file_attr_pkg_info(self, tmpdir_cwd, env):

# Only 'NOTICE', LICENSE-ABC', and 'LICENSE-XYZ' should have been matched
# Also assert that order from license_files is keeped
assert len(license_file_lines) == 4
assert "License-File: NOTICE" == license_file_lines[0]
assert "License-File: LICENSE-ABC" in license_file_lines[1:]
assert "License-File: LICENSE-XYZ" in license_file_lines[1:]
assert "License-File: vendor/LICENSE" in license_file_lines[3]

def test_metadata_version(self, tmpdir_cwd, env):
"""Make sure latest metadata version is used by default."""
Expand Down
Loading