Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion docs/userguide/miscellaneous.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ files are included in a source distribution by default:
in ``pyproject.toml`` and/or equivalent in ``setup.cfg``/``setup.py``;
note that if you don't explicitly set this parameter, ``setuptools``
will include any files that match the following glob patterns:
``LICENSE*``, ``LICENCE*``, ``COPYING*``, ``NOTICE*``, ``AUTHORS**``;
``LICEN[CS]E*``, ``COPYING*``, ``NOTICE*``, ``AUTHORS**``;
- ``pyproject.toml``;
- ``setup.cfg``;
- ``setup.py``;
Expand Down
2 changes: 1 addition & 1 deletion docs/userguide/pyproject_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Key Value Type (TOML) Notes
See :doc:`/userguide/datafiles`.
``exclude-package-data`` table/inline-table Empty by default. See :doc:`/userguide/datafiles`.
------------------------- --------------------------- -------------------------
``license-files`` array of glob patterns **Provisional** - likely to change with :pep:`639`
``license-files`` array of glob patterns **Deprecated** - use ``project.license-files`` instead. See :doc:`PyPUG:guides/writing-pyproject-toml/#license-files`
Copy link
Contributor

Choose a reason for hiding this comment

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

I notice that sphinx is issuing the following warning:

/home/runner/work/setuptools/setuptools/docs/userguide/pyproject_config.rst:103: WARNING: unknown document: 'PyPUG:guides/writing-pyproject-toml/#license-files' [ref.doc]

Copy link
Member Author

Choose a reason for hiding this comment

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

Seems the anchor link doesn't exist. Opened pypa/packaging.python.org#1816 upstream and used the generic link here so it doesn't block the PR.

(by default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``)
``data-files`` table/inline-table **Discouraged** - check :doc:`/userguide/datafiles`.
Whenever possible, consider using data files inside the package directories.
Expand Down
3 changes: 3 additions & 0 deletions newsfragments/4837.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deprecated ``tools.setuptools.license-files`` in favor of ``project.license-files``
and added exception if ``project.license-files`` and ``tools.setuptools.license-files``
are used together. -- by :user:`cdce8p`
19 changes: 17 additions & 2 deletions setuptools/config/_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

from .. import _static
from .._path import StrPath
from ..errors import RemovedConfigError
from ..errors import InvalidConfigError, RemovedConfigError
from ..extension import Extension
from ..warnings import SetuptoolsWarning
from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning

if TYPE_CHECKING:
from typing_extensions import TypeAlias
Expand Down Expand Up @@ -89,6 +89,21 @@ def _apply_tool_table(dist: Distribution, config: dict, filename: StrPath):
if not tool_table:
return # short-circuit

if "license-files" in tool_table:
if dist.metadata.license_files:
raise InvalidConfigError(
Copy link

Choose a reason for hiding this comment

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

Why are you throwing an exception when the intention is to deprecate something? Emitting a DeprecationWarning should be surely sufficient? This exception breaks tons of workloads on our side.

Copy link
Contributor

@abravalheri abravalheri Mar 20, 2025

Choose a reason for hiding this comment

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

This configuration has been documented as provisional since its introduction, so it was never stable to require a deprecation period.

The breaking change is documented in the changelogs for the major version bump to inform users adaptation may be required.

Copy link
Contributor

Choose a reason for hiding this comment

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

That said, I had a look on how this exception is being triggered in the wild and I can see pylint as a typical example: pylint-dev/pylint#10289

They did not had project.license-files defined but ratter in setup.cfg.

@cdce8p, was the intention just to double definition inside pyproject.toml or more general to avoid license-files being defined in multiple places?

If it was the first, should we change the condition it to:

if `license-files` in config.get("project", {}):

(Untested)

Copy link
Contributor

Choose a reason for hiding this comment

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

See #4899.

Copy link
Member Author

Choose a reason for hiding this comment

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

That said, I had a look on how this exception is being triggered in the wild and I can see pylint as a typical example: pylint-dev/pylint#10289

Seems for pylint in particular, I was actually the one who introduce the issue almost three years ago 😅
pylint-dev/pylint#7076

The config as I had written it was problematic though. metadata.license-files (from setup.cfg) would always be overwritten by tool.setuptools.license-files. So in a sense, raising an error here was kind of a good thing as it's fixed now.

They did not had project.license-files defined but ratter in setup.cfg.

@cdce8p, was the intention just to double definition inside pyproject.toml or more general to avoid license-files being defined in multiple places?

Left a comment on the PR already (see #4899 (review)) but in a sense the intention was to prohibit using project.license-files and tool.setuptools.license-files together.

"'project.license-files' is defined already. "
"Remove 'tool.setuptools.license-files'."
)

pypa_guides = "guides/writing-pyproject-toml/#license-files"
SetuptoolsDeprecationWarning.emit(
"'tool.setuptools.license-files' is deprecated in favor of "
"'project.license-files'",
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
due_date=(2026, 2, 18), # Warning introduced on 2025-02-18
)

for field, value in tool_table.items():
norm_key = json_compatible_key(field)

Expand Down
19 changes: 16 additions & 3 deletions setuptools/tests/config/test_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from setuptools.config import expand, pyprojecttoml, setupcfg
from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
from setuptools.dist import Distribution
from setuptools.errors import RemovedConfigError
from setuptools.errors import InvalidConfigError, RemovedConfigError
from setuptools.warnings import SetuptoolsDeprecationWarning

from .downloads import retrieve_file, urls_from_file
Expand Down Expand Up @@ -366,7 +366,7 @@ def base_pyproject(self, tmp_path, additional_text):
pyproject.write_text(text, encoding="utf-8")
return pyproject

def base_pyproject_license_pep639(self, tmp_path):
def base_pyproject_license_pep639(self, tmp_path, additional_text=""):
pyproject = _pep621_example_project(tmp_path, "README")
text = pyproject.read_text(encoding="utf-8")

Expand All @@ -381,6 +381,8 @@ def base_pyproject_license_pep639(self, tmp_path):
text,
count=1,
)
if additional_text:
text = f"{text}\n{additional_text}\n"
pyproject.write_text(text, encoding="utf-8")
return pyproject

Expand All @@ -396,7 +398,9 @@ def test_both_license_and_license_files_defined(self, tmp_path):
license = tmp_path / "LICENSE.txt"
license.write_text("LicenseRef-Proprietary\n", encoding="utf-8")

dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
msg = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
assert dist.metadata.license == "LicenseRef-Proprietary\n"

Expand All @@ -412,6 +416,15 @@ def test_both_license_and_license_files_defined_pep639(self, tmp_path):
assert dist.metadata.license is None
assert dist.metadata.license_expression == "LicenseRef-Proprietary"

def test_license_files_defined_twice(self, tmp_path):
# Set project.license-files and tools.setuptools.license-files
setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
pyproject = self.base_pyproject_license_pep639(tmp_path, setuptools_config)

msg = "'project.license-files' is defined already. Remove 'tool.setuptools.license-files'"
with pytest.raises(InvalidConfigError, match=msg):
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)

def test_default_patterns(self, tmp_path):
setuptools_config = '[tool.setuptools]\nzip-safe = false'
# ^ used just to trigger section validation
Expand Down
10 changes: 8 additions & 2 deletions setuptools/tests/test_build_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import signal
import sys
import tarfile
import warnings
from concurrent import futures
from pathlib import Path
from typing import Any, Callable
Expand All @@ -15,6 +16,8 @@
from jaraco import path
from packaging.requirements import Requirement

from setuptools.warnings import SetuptoolsDeprecationWarning

from .textwrap import DALS

SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
Expand Down Expand Up @@ -384,8 +387,11 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script):
build_backend = self.get_build_backend()
with tmpdir.as_cwd():
path.build(files)
sdist_path = build_backend.build_sdist("temp")
wheel_file = build_backend.build_wheel("temp")
with warnings.catch_warnings():
msg = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
warnings.filterwarnings("ignore", msg, SetuptoolsDeprecationWarning)
sdist_path = build_backend.build_sdist("temp")
wheel_file = build_backend.build_wheel("temp")

with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
sdist_contents = set(tar.getnames())
Expand Down
Loading