Commit 823ab9d2 authored by Mick Koch's avatar Mick Koch

Add support for `license_files` option in metadata

parent 7748921d
Add support for the ``license_files`` option in ``setup.cfg`` to automatically
include multiple license files in a source distribution.
......@@ -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
......
......@@ -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)
......@@ -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,
......
......@@ -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
......
......@@ -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`
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment