diff options
author | Mick Koch <mick@kochm.co> | 2019-05-20 18:25:19 -0400 |
---|---|---|
committer | Mick Koch <mick@kochm.co> | 2019-10-28 18:30:31 -0400 |
commit | 823ab9d2ec4ab89f90c0a781d872c9071b4afc13 (patch) | |
tree | 8c6e9f23a72bb970b8d01592aacaee4e71a442bb | |
parent | 7748921de342160ca2dc9c9539562bb9c924e14c (diff) | |
download | python-setuptools-git-823ab9d2ec4ab89f90c0a781d872c9071b4afc13.tar.gz |
Add support for `license_files` option in metadata
-rw-r--r-- | changelog.d/1767.change.rst | 2 | ||||
-rw-r--r-- | docs/setuptools.txt | 1 | ||||
-rw-r--r-- | setuptools/command/sdist.py | 26 | ||||
-rw-r--r-- | setuptools/config.py | 1 | ||||
-rw-r--r-- | setuptools/dist.py | 1 | ||||
-rw-r--r-- | setuptools/tests/test_egg_info.py | 198 |
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` |