diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2017-09-03 13:33:20 -0400 |
---|---|---|
committer | Jason R. Coombs <jaraco@jaraco.com> | 2017-09-03 13:33:20 -0400 |
commit | 45bc4b99137a4f7a627439239a193d89ff6cfee9 (patch) | |
tree | a3a2650f355e0a26ef8b04aa41e93aa0de0d4737 | |
parent | e5461b6ccc57596c7e9cf7837084f8a20eeec9b7 (diff) | |
parent | 09e14c771f8dfd08122c63dc7bc1027d0040c26e (diff) | |
download | python-setuptools-git-45bc4b99137a4f7a627439239a193d89ff6cfee9.tar.gz |
Merge branch 'master' into pr1127
-rw-r--r-- | CHANGES.rst | 16 | ||||
-rw-r--r-- | docs/setuptools.txt | 47 | ||||
-rwxr-xr-x | setup.cfg | 4 | ||||
-rwxr-xr-x | setup.py | 3 | ||||
-rw-r--r-- | setuptools/command/bdist_egg.py | 16 | ||||
-rwxr-xr-x | setuptools/command/egg_info.py | 4 | ||||
-rw-r--r-- | setuptools/config.py | 34 | ||||
-rw-r--r-- | setuptools/dist.py | 10 | ||||
-rw-r--r-- | setuptools/tests/test_config.py | 18 | ||||
-rw-r--r-- | setuptools/tests/test_egg_info.py | 25 |
10 files changed, 133 insertions, 44 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 19afc8cb..24a3e4d0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,19 @@ +v36.4.0 +------- + +* #1075: Add new ``Description-Content-Type`` metadata field. `See here for + documentation on how to use this field. + <https://packaging.python.org/specifications/#description-content-type>`_ + +* #1068: Sort files and directories when building eggs for + deterministic order. + +v36.3.0 +------- + +* #1131: Make possible using several files within ``file:`` directive + in metadata.long_description in ``setup.cfg``. + v36.2.7 ------- diff --git a/docs/setuptools.txt b/docs/setuptools.txt index eb9fdbd3..531d727c 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2306,7 +2306,7 @@ boilerplate code in some cases. name = my_package version = attr: src.VERSION description = My package description - long_description = file: README.rst + long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst keywords = one, two license = BSD 3-Clause License classifiers = @@ -2379,7 +2379,7 @@ Type names used below: Special directives: * ``attr:`` - value could be read from module attribute -* ``file:`` - value could be read from a file +* ``file:`` - value could be read from a list of files and then concatenated .. note:: @@ -2394,27 +2394,28 @@ Metadata Aliases given below are supported for compatibility reasons, but not advised. -================= ================= ===== -Key Aliases Accepted value type -================= ================= ===== -name str -version attr:, str -url home-page str -download_url download-url str -author str -author_email author-email str -maintainer str -maintainer_email maintainer-email str -classifiers classifier file:, list-comma -license file:, str -description summary file:, str -long_description long-description file:, str -keywords list-comma -platforms platform list-comma -provides list-comma -requires list-comma -obsoletes list-comma -================= ================= ===== +============================== ================= ===== +Key Aliases Accepted value type +============================== ================= ===== +name str +version attr:, str +url home-page str +download_url download-url str +author str +author_email author-email str +maintainer str +maintainer_email maintainer-email str +classifiers classifier file:, list-comma +license file:, str +description summary file:, str +long_description long-description file:, str +long_description_content_type str +keywords list-comma +platforms platform list-comma +provides list-comma +requires list-comma +obsoletes list-comma +============================== ================= ===== .. note:: @@ -1,5 +1,5 @@ [bumpversion] -current_version = 36.2.7 +current_version = 36.3.0 commit = True tag = True @@ -19,7 +19,7 @@ repository = https://upload.pypi.org/legacy/ [sdist] formats = zip -[wheel] +[bdist_wheel] universal = 1 [bumpversion:file:setup.py] @@ -89,12 +89,13 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="36.2.7", + version="36.3.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", author_email="distutils-sig@python.org", long_description=long_description, + long_description_content_type='text/x-rst; charset=UTF-8', keywords="CPAN PyPI distutils eggs package management", url="https://github.com/pypa/setuptools", src_root=None, diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 8cd9dfef..51755d52 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -38,6 +38,14 @@ def strip_module(filename): filename = filename[:-6] return filename +def sorted_walk(dir): + """Do os.walk in a reproducible way, + independent of indeterministic filesystem readdir order + """ + for base, dirs, files in os.walk(dir): + dirs.sort() + files.sort() + yield base, dirs, files def write_stub(resource, pyfile): _stub_template = textwrap.dedent(""" @@ -302,7 +310,7 @@ class bdist_egg(Command): ext_outputs = [] paths = {self.bdist_dir: ''} - for base, dirs, files in os.walk(self.bdist_dir): + for base, dirs, files in sorted_walk(self.bdist_dir): for filename in files: if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS: all_outputs.append(paths[base] + filename) @@ -329,7 +337,7 @@ NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split()) def walk_egg(egg_dir): """Walk an unpacked egg's contents, skipping the metadata directory""" - walker = os.walk(egg_dir) + walker = sorted_walk(egg_dir) base, dirs, files = next(walker) if 'EGG-INFO' in dirs: dirs.remove('EGG-INFO') @@ -463,10 +471,10 @@ def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED if not dry_run: z = zipfile.ZipFile(zip_filename, mode, compression=compression) - for dirname, dirs, files in os.walk(base_dir): + for dirname, dirs, files in sorted_walk(base_dir): visit(z, dirname, files) z.close() else: - for dirname, dirs, files in os.walk(base_dir): + for dirname, dirs, files in sorted_walk(base_dir): visit(None, dirname, files) return zip_filename diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 6c00b0b7..a183d15d 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -599,6 +599,10 @@ def write_pkg_info(cmd, basename, filename): metadata = cmd.distribution.metadata metadata.version, oldver = cmd.egg_version, metadata.version metadata.name, oldname = cmd.egg_name, metadata.name + metadata.long_description_content_type = getattr( + cmd.distribution, + 'long_description_content_type' + ) try: # write unescaped data to PKG-INFO, so older pkg_resources # can still parse it diff --git a/setuptools/config.py b/setuptools/config.py index 06a61d16..9a62e2ec 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -245,33 +245,39 @@ class ConfigHandler(object): directory with setup.py. Examples: - include: LICENSE - include: src/file.txt + file: LICENSE + file: README.rst, CHANGELOG.md, src/file.txt :param str value: :rtype: str """ + include_directive = 'file:' + if not isinstance(value, string_types): return value - include_directive = 'file:' if not value.startswith(include_directive): return value - current_directory = os.getcwd() - - filepath = value.replace(include_directive, '').strip() - filepath = os.path.abspath(filepath) - - if not filepath.startswith(current_directory): + spec = value[len(include_directive):] + filepaths = (os.path.abspath(path.strip()) for path in spec.split(',')) + return '\n'.join( + cls._read_file(path) + for path in filepaths + if (cls._assert_local(path) or True) + and os.path.isfile(path) + ) + + @staticmethod + def _assert_local(filepath): + if not filepath.startswith(os.getcwd()): raise DistutilsOptionError( '`file:` directive can not access %s' % filepath) - if os.path.isfile(filepath): - with io.open(filepath, encoding='utf-8') as f: - value = f.read() - - return value + @staticmethod + def _read_file(filepath): + with io.open(filepath, encoding='utf-8') as f: + return f.read() @classmethod def _parse_attr(cls, value): diff --git a/setuptools/dist.py b/setuptools/dist.py index e1510b6f..a2ca8795 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -58,6 +58,13 @@ def write_pkg_file(self, file): if self.download_url: file.write('Download-URL: %s\n' % self.download_url) + long_desc_content_type = getattr( + self, + 'long_description_content_type', + None + ) or 'UNKNOWN' + file.write('Description-Content-Type: %s\n' % long_desc_content_type) + long_desc = rfc822_escape(self.get_long_description()) file.write('Description: %s\n' % long_desc) @@ -317,6 +324,9 @@ class Distribution(Distribution_parse_config_files, _Distribution): self.dist_files = [] self.src_root = attrs and attrs.pop("src_root", None) self.patch_missing_pkg_info(attrs) + self.long_description_content_type = _attrs_dict.get( + 'long_description_content_type' + ) # Make sure we have any eggs needed to interpret 'attrs' if attrs is not None: self.dependency_links = attrs.pop('dependency_links', []) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index dbabd69e..cdfa5af4 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -139,6 +139,24 @@ class TestMetadata: assert metadata.download_url == 'http://test.test.com/test/' assert metadata.maintainer_email == 'test@test.com' + def test_file_mixed(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'long_description = file: README.rst, CHANGES.rst\n' + '\n' + ) + + tmpdir.join('README.rst').write('readme contents\nline2') + tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff') + + with get_dist(tmpdir) as dist: + assert dist.metadata.long_description == ( + 'readme contents\nline2\n' + 'changelog contents\nand stuff' + ) + def test_file_sandboxed(self, tmpdir): fake_env( diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 33d6cc52..e454694d 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -398,6 +398,31 @@ class TestEggInfo(object): self._run_install_command(tmpdir_cwd, env) assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] + 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` + # line to the `PKG-INFO` file in the `<distribution>.egg-info` + # directory. + # `Description-Content-Type` is described at + # https://github.com/pypa/python-packaging-user-guide/pull/258 + + self._setup_script_with_requires( + """long_description_content_type='text/markdown',""") + environ = os.environ.copy().update( + HOME=env.paths['home'], + ) + code, data = environment.run_setup_py( + cmd=['egg_info'], + pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]), + data_stream=1, + env=environ, + ) + 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') + expected_line = 'Description-Content-Type: text/markdown' + assert expected_line in pkg_info_lines + def test_python_requires_egg_info(self, tmpdir_cwd, env): self._setup_script_with_requires( """python_requires='>=2.7.12',""") |