summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/2645.change.rst4
-rw-r--r--setuptools/command/egg_info.py9
-rw-r--r--setuptools/command/sdist.py46
-rw-r--r--setuptools/config.py5
-rw-r--r--setuptools/dist.py37
-rw-r--r--setuptools/tests/test_egg_info.py29
-rw-r--r--setuptools/tests/test_manifest.py1
7 files changed, 81 insertions, 50 deletions
diff --git a/changelog.d/2645.change.rst b/changelog.d/2645.change.rst
new file mode 100644
index 00000000..b22385c1
--- /dev/null
+++ b/changelog.d/2645.change.rst
@@ -0,0 +1,4 @@
+Added ``License-File`` (multiple) to the output package metadata.
+The field will contain the path of a license file, matched by the
+``license_file`` (deprecated) and ``license_files`` options,
+relative to ``.dist-info``. - by :user:`cdce8p`
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 1f120b67..26ff9a4c 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -539,6 +539,7 @@ class manifest_maker(sdist):
if not os.path.exists(self.manifest):
self.write_manifest() # it must exist so it'll get in the list
self.add_defaults()
+ self.add_license_files()
if os.path.exists(self.template):
self.read_template()
self.prune_file_list()
@@ -575,7 +576,6 @@ class manifest_maker(sdist):
def add_defaults(self):
sdist.add_defaults(self)
- self.check_license()
self.filelist.append(self.template)
self.filelist.append(self.manifest)
rcfiles = list(walk_revctrl())
@@ -592,6 +592,13 @@ class manifest_maker(sdist):
ei_cmd = self.get_finalized_command('egg_info')
self.filelist.graft(ei_cmd.egg_info)
+ def add_license_files(self):
+ license_files = self.distribution.metadata.license_files_computed
+ for lf in license_files:
+ log.info("adding license file '%s'", lf)
+ pass
+ self.filelist.extend(license_files)
+
def prune_file_list(self):
build = self.get_finalized_command('build')
base_dir = self.distribution.get_fullname()
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index a6ea814a..4a014283 100644
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -4,9 +4,6 @@ import os
import sys
import io
import contextlib
-from glob import iglob
-
-from setuptools.extern import ordered_set
from .py36compat import sdist_add_defaults
@@ -190,46 +187,3 @@ class sdist(sdist_add_defaults, orig.sdist):
continue
self.filelist.append(line)
manifest.close()
-
- def check_license(self):
- """Checks if license_file' or 'license_files' is configured and adds any
- valid paths to 'self.filelist'.
- """
- opts = self.distribution.get_option_dict('metadata')
-
- files = ordered_set.OrderedSet()
- try:
- license_files = self.distribution.metadata.license_files
- except TypeError:
- log.warn("warning: 'license_files' option is malformed")
- license_files = ordered_set.OrderedSet()
- patterns = license_files if isinstance(license_files, ordered_set.OrderedSet) \
- else ordered_set.OrderedSet(license_files)
-
- if 'license_file' in opts:
- log.warn(
- "warning: the 'license_file' option is deprecated, "
- "use 'license_files' instead")
- patterns.append(opts['license_file'][1])
-
- if 'license_file' not in opts and 'license_files' not in opts:
- # Default patterns match the ones wheel uses
- # See https://wheel.readthedocs.io/en/stable/user_guide.html
- # -> 'Including license files in the generated wheel file'
- patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
-
- for pattern in patterns:
- for path in iglob(pattern):
- if path.endswith('~'):
- log.debug(
- "ignoring license file '%s' as it looks like a backup",
- path)
- continue
-
- if path not in files and os.path.isfile(path):
- log.info(
- "adding license file '%s' (matched pattern '%s')",
- path, pattern)
- files.add(path)
-
- self.filelist.extend(sorted(files))
diff --git a/setuptools/config.py b/setuptools/config.py
index 4a6cd469..44de7cf5 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -520,6 +520,11 @@ class ConfigMetadataHandler(ConfigHandler):
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': exclude_files_parser('license'),
+ 'license_file': self._deprecated_config_handler(
+ exclude_files_parser('license_file'),
+ "The license_file parameter is deprecated, "
+ "use license_files instead.",
+ DeprecationWarning),
'license_files': parse_list,
'description': parse_file,
'long_description': parse_file,
diff --git a/setuptools/dist.py b/setuptools/dist.py
index bf3b9461..9d7fb751 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -15,9 +15,10 @@ import distutils.command
from distutils.util import strtobool
from distutils.debug import DEBUG
from distutils.fancy_getopt import translate_longopt
+from glob import iglob
import itertools
import textwrap
-from typing import List, Optional, TYPE_CHECKING
+from typing import List, Optional, Set, TYPE_CHECKING
from collections import defaultdict
from email import message_from_file
@@ -141,6 +142,8 @@ def read_pkg_file(self, file):
self.provides = None
self.obsoletes = None
+ self.license_files_computed = _read_list_from_msg(msg, 'license-file')
+
def single_line(val):
# quick and dirty validation for description pypa/setuptools#1390
@@ -213,6 +216,8 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
for extra in self.provides_extras:
write_field('Provides-Extra', extra)
+ self._write_list(file, 'License-File', self.license_files_computed)
+
file.write("\n%s\n\n" % self.get_long_description())
@@ -414,7 +419,9 @@ class Distribution(_Distribution):
'long_description_content_type': lambda: None,
'project_urls': dict,
'provides_extras': ordered_set.OrderedSet,
- 'license_files': ordered_set.OrderedSet,
+ 'license_file': None,
+ 'license_files': None,
+ 'license_files_computed': list,
}
_patched_dist = None
@@ -573,6 +580,31 @@ class Distribution(_Distribution):
req.marker = None
return req
+ def _finalize_license_files(self):
+ """Compute names of all license files which should be included."""
+ files = set()
+ license_files: Optional[List[str]] = self.metadata.license_files
+ patterns: Set[str] = set(license_files) if license_files else set()
+
+ license_file: Optional[str] = self.metadata.license_file
+ if license_file:
+ patterns.add(license_file)
+
+ if license_files is None and license_file is None:
+ # Default patterns match the ones wheel uses
+ # See https://wheel.readthedocs.io/en/stable/user_guide.html
+ # -> 'Including license files in the generated wheel file'
+ patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
+
+ for pattern in patterns:
+ for path in iglob(pattern):
+ if path.endswith('~'):
+ continue
+ if path not in files and os.path.isfile(path):
+ files.add(path)
+
+ self.metadata.license_files_computed = sorted(files)
+
# FIXME: 'Distribution._parse_config_files' is too complex (14)
def _parse_config_files(self, filenames=None): # noqa: C901
"""
@@ -737,6 +769,7 @@ class Distribution(_Distribution):
parse_configuration(self, self.command_options,
ignore_option_errors=ignore_option_errors)
self._finalize_requires()
+ self._finalize_license_files()
def fetch_build_eggs(self, requires):
"""Resolve pre-setup requirements"""
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index d7657a47..29fb2062 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -835,6 +835,35 @@ class TestEggInfo:
for lf in excl_licenses:
assert sources_lines.count(lf) == 0
+ def test_license_file_attr_pkg_info(self, tmpdir_cwd, env):
+ """All matched license files should have a corresponding License-File."""
+ self._create_project()
+ path.build({
+ "setup.cfg": DALS("""
+ [metadata]
+ license_files =
+ LICENSE*
+ """),
+ "LICENSE-ABC": "ABC license",
+ "LICENSE-XYZ": "XYZ license",
+ "NOTICE": "not included",
+ })
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)])
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
+ pkg_info_lines = pkginfo_file.read().split('\n')
+ license_file_lines = [
+ line for line in pkg_info_lines if line.startswith('License-File:')]
+
+ # Only 'LICENSE-ABC' and 'LICENSE-XYZ' should have been matched
+ assert len(license_file_lines) == 2
+ assert "License-File: LICENSE-ABC" in license_file_lines
+ assert "License-File: LICENSE-XYZ" in license_file_lines
+
def test_metadata_version(self, tmpdir_cwd, env):
"""Make sure latest metadata version is used by default."""
self._setup_script_with_requires("")
diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py
index 589cefb2..82bdb9c6 100644
--- a/setuptools/tests/test_manifest.py
+++ b/setuptools/tests/test_manifest.py
@@ -55,7 +55,6 @@ def touch(filename):
default_files = frozenset(map(make_local_path, [
'README.rst',
'MANIFEST.in',
- 'LICENSE',
'setup.py',
'app.egg-info/PKG-INFO',
'app.egg-info/SOURCES.txt',