summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMick Koch <mick@kochm.co>2019-05-20 18:25:19 -0400
committerMick Koch <mick@kochm.co>2019-10-28 18:30:31 -0400
commit823ab9d2ec4ab89f90c0a781d872c9071b4afc13 (patch)
tree8c6e9f23a72bb970b8d01592aacaee4e71a442bb
parent7748921de342160ca2dc9c9539562bb9c924e14c (diff)
downloadpython-setuptools-git-823ab9d2ec4ab89f90c0a781d872c9071b4afc13.tar.gz
Add support for `license_files` option in metadata
-rw-r--r--changelog.d/1767.change.rst2
-rw-r--r--docs/setuptools.txt1
-rw-r--r--setuptools/command/sdist.py26
-rw-r--r--setuptools/config.py1
-rw-r--r--setuptools/dist.py1
-rw-r--r--setuptools/tests/test_egg_info.py198
6 files changed, 221 insertions, 8 deletions
diff --git a/changelog.d/1767.change.rst b/changelog.d/1767.change.rst
new file mode 100644
index 00000000..5d42aedc
--- /dev/null
+++ b/changelog.d/1767.change.rst
@@ -0,0 +1,2 @@
+Add support for the ``license_files`` option in ``setup.cfg`` to automatically
+include multiple license files in a source distribution.
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index 344ea5bc..22025f61 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -2276,6 +2276,7 @@ maintainer_email maintainer-email str
classifiers classifier file:, list-comma
license str
license_file str
+license_files list-comma
description summary file:, str
long_description long-description file:, str
long_description_content_type str 38.6.0
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index dc253981..24316640 100644
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -200,10 +200,12 @@ class sdist(sdist_add_defaults, orig.sdist):
manifest.close()
def check_license(self):
- """Checks if license_file' is configured and adds it to
- 'self.filelist' if the value contains a valid path.
+ """Checks if license_file' or 'license_files' is configured and adds any
+ valid paths to 'self.filelist'.
"""
+ files = set()
+
opts = self.distribution.get_option_dict('metadata')
# ignore the source of the value
@@ -211,11 +213,19 @@ class sdist(sdist_add_defaults, orig.sdist):
if license_file is None:
log.debug("'license_file' option was not specified")
- return
+ else:
+ files.add(license_file)
- if not os.path.exists(license_file):
- log.warn("warning: Failed to find the configured license file '%s'",
- license_file)
- return
+ try:
+ files.update(self.distribution.metadata.license_files)
+ except TypeError:
+ log.warn("warning: 'license_files' option is malformed")
+
+ for f in files:
+ if not os.path.exists(f):
+ log.warn(
+ "warning: Failed to find the configured license file '%s'",
+ f)
+ continue
- self.filelist.append(license_file)
+ self.filelist.append(f)
diff --git a/setuptools/config.py b/setuptools/config.py
index 2d50e25e..9b9a0c45 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -483,6 +483,7 @@ class ConfigMetadataHandler(ConfigHandler):
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': exclude_files_parser('license'),
+ 'license_files': parse_list,
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 2e5ad4bd..fb379a20 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -409,6 +409,7 @@ class Distribution(_Distribution):
'long_description_content_type': None,
'project_urls': dict,
'provides_extras': ordered_set.OrderedSet,
+ 'license_files': list,
}
_patched_dist = None
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 316eb2ed..61da1bda 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -567,6 +567,204 @@ class TestEggInfo:
assert 'LICENSE' not in sources_text
assert 'INVALID_LICENSE' not in sources_text # for invalid license test
+ @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE-ABC, LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ INVALID_LICENSE
+ """),
+ 'LICENSE-ABC': DALS("Test license")
+ }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license
+ ({
+ 'setup.cfg': DALS("""
+ """),
+ 'LICENSE': DALS("Test license")
+ }, [], ['LICENSE']), # no license_files attribute
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE
+ """),
+ 'MANIFEST.in': DALS("exclude LICENSE"),
+ 'LICENSE': DALS("Test license")
+ }, [], ['LICENSE']), # license file is manually excluded
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'MANIFEST.in': DALS("exclude LICENSE-XYZ"),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded
+ ])
+ def test_setup_cfg_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
+ self._create_project()
+ build_files(files)
+
+ 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, 'SOURCES.txt')) as sources_file:
+ sources_lines = list(line.strip() for line in sources_file)
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
+ @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file =
+ license_files =
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # license_file is still singular
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-PQR': DALS("PQR license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-PQR': DALS("PQR license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-PQR': DALS("PQR license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-PQR': DALS("Test license")
+ }, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']), # with invalid licenses
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-PQR
+ LICENSE-XYZ
+ """),
+ 'MANIFEST.in': DALS("exclude LICENSE-ABC\nexclude LICENSE-PQR"),
+ 'LICENSE-ABC': DALS("ABC license"),
+ 'LICENSE-PQR': DALS("PQR license"),
+ 'LICENSE-XYZ': DALS("XYZ license")
+ }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']) # manually excluded
+ ])
+ def test_setup_cfg_license_file_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
+ self._create_project()
+ build_files(files)
+
+ 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, 'SOURCES.txt')) as sources_file:
+ sources_lines = list(line.strip() for line in sources_file)
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
def test_long_description_content_type(self, tmpdir_cwd, env):
# Test that specifying a `long_description_content_type` keyword arg to
# the `setup` function results in writing a `Description-Content-Type`