Imported Upstream version 40.8.0 upstream/40.8.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 28 Dec 2020 02:25:56 +0000 (11:25 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 28 Dec 2020 02:25:56 +0000 (11:25 +0900)
15 files changed:
CHANGES.rst
MANIFEST.in
docs/pkg_resources.txt
docs/setuptools.txt
pkg_resources/__init__.py
pkg_resources/tests/test_pkg_resources.py
pyproject.toml
setup.cfg
setup.py
setuptools/build_meta.py
setuptools/command/egg_info.py
setuptools/command/sdist.py
setuptools/tests/test_build_meta.py
setuptools/tests/test_build_py.py
setuptools/tests/test_egg_info.py

index 6289622..b043e44 100644 (file)
@@ -1,3 +1,11 @@
+v40.8.0
+-------
+
+* #1652: Added the ``build_meta:__legacy__`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``.
+* #1635: Resource paths are passed to ``pkg_resources.resource_string`` and similar no longer accept paths that traverse parents, that begin with a leading ``/``. Violations of this expectation raise DeprecationWarnings and will become errors. Additionally, any paths that are absolute on Windows are strictly disallowed and will raise ValueErrors.
+* #1536: ``setuptools`` will now automatically include licenses if ``setup.cfg`` contains a ``license_file`` attribute, unless this file is manually excluded inside ``MANIFEST.in``.
+
+
 v40.7.3
 -------
 
index 9cce3c9..16d60e5 100644 (file)
@@ -12,3 +12,4 @@ include launcher.c
 include msvc-build-launcher.cmd
 include pytest.ini
 include tox.ini
+exclude pyproject.toml  # Temporary workaround for #1644.
index 0c9fb5f..806f1b1 100644 (file)
@@ -1132,8 +1132,8 @@ relative to the root of the identified distribution; i.e. its first path
 segment will be treated as a peer of the top-level modules or packages in the
 distribution.
 
-Note that resource names must be ``/``-separated paths and cannot be absolute
-(i.e. no leading ``/``) or contain relative names like ``".."``.  Do *not* use
+Note that resource names must be ``/``-separated paths rooted at the package,
+cannot contain relative names like ``".."``, and cannot be absolute.  Do *not* use
 ``os.path`` routines to manipulate resource paths, as they are *not* filesystem
 paths.
 
index cf166e9..a4e05d7 100644 (file)
@@ -2371,6 +2371,7 @@ maintainer                                         str
 maintainer_email                maintainer-email   str
 classifiers                     classifier         file:, list-comma
 license                                            str
+license_file                                       str
 description                     summary            file:, str
 long_description                long-description   file:, str
 long_description_content_type                      str                38.6.0
index 6ca68da..dcfa1d0 100644 (file)
@@ -39,6 +39,8 @@ import tempfile
 import textwrap
 import itertools
 import inspect
+import ntpath
+import posixpath
 from pkgutil import get_importer
 
 try:
@@ -1466,10 +1468,73 @@ class NullProvider:
         )
 
     def _fn(self, base, resource_name):
+        self._validate_resource_path(resource_name)
         if resource_name:
             return os.path.join(base, *resource_name.split('/'))
         return base
 
+    @staticmethod
+    def _validate_resource_path(path):
+        """
+        Validate the resource paths according to the docs.
+        https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
+
+        >>> warned = getfixture('recwarn')
+        >>> warnings.simplefilter('always')
+        >>> vrp = NullProvider._validate_resource_path
+        >>> vrp('foo/bar.txt')
+        >>> bool(warned)
+        False
+        >>> vrp('../foo/bar.txt')
+        >>> bool(warned)
+        True
+        >>> warned.clear()
+        >>> vrp('/foo/bar.txt')
+        >>> bool(warned)
+        True
+        >>> vrp('foo/../../bar.txt')
+        >>> bool(warned)
+        True
+        >>> warned.clear()
+        >>> vrp('foo/f../bar.txt')
+        >>> bool(warned)
+        False
+
+        Windows path separators are straight-up disallowed.
+        >>> vrp(r'\\foo/bar.txt')
+        Traceback (most recent call last):
+        ...
+        ValueError: Use of .. or absolute path in a resource path \
+is not allowed.
+
+        >>> vrp(r'C:\\foo/bar.txt')
+        Traceback (most recent call last):
+        ...
+        ValueError: Use of .. or absolute path in a resource path \
+is not allowed.
+        """
+        invalid = (
+            os.path.pardir in path.split(posixpath.sep) or
+            posixpath.isabs(path) or
+            ntpath.isabs(path)
+        )
+        if not invalid:
+            return
+
+        msg = "Use of .. or absolute path in a resource path is not allowed."
+
+        # Aggressively disallow Windows absolute paths
+        if ntpath.isabs(path) and not posixpath.isabs(path):
+            raise ValueError(msg)
+
+        # for compatibility, warn; in future
+        # raise ValueError(msg)
+        warnings.warn(
+            msg[:-1] + " and will raise exceptions in a future release.",
+            DeprecationWarning,
+            stacklevel=4,
+        )
+
     def _get(self, path):
         if hasattr(self.loader, 'get_data'):
             return self.loader.get_data(path)
@@ -1888,7 +1953,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
     if only:
         # don't yield nested distros
         return
-    for subitem in metadata.resource_listdir('/'):
+    for subitem in metadata.resource_listdir(''):
         if _is_egg_path(subitem):
             subpath = os.path.join(path_item, subitem)
             dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
index 416f9af..2c2c9c7 100644 (file)
@@ -93,7 +93,6 @@ class TestZipProvider:
 
         expected_root = ['data.dat', 'mod.py', 'subdir']
         assert sorted(zp.resource_listdir('')) == expected_root
-        assert sorted(zp.resource_listdir('/')) == expected_root
 
         expected_subdir = ['data2.dat', 'mod2.py']
         assert sorted(zp.resource_listdir('subdir')) == expected_subdir
@@ -106,7 +105,6 @@ class TestZipProvider:
         zp2 = pkg_resources.ZipProvider(mod2)
 
         assert sorted(zp2.resource_listdir('')) == expected_subdir
-        assert sorted(zp2.resource_listdir('/')) == expected_subdir
 
         assert zp2.resource_listdir('subdir') == []
         assert zp2.resource_listdir('subdir/') == []
index 4ef804e..07c23bb 100644 (file)
@@ -1,6 +1,5 @@
 [build-system]
 requires = ["wheel"]
-build-backend = "setuptools.build_meta"
 
 [tool.towncrier]
     package = "setuptools"
index 4261f9b..f0932e1 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 40.7.3
+current_version = 40.8.0
 commit = True
 tag = True
 
index b3e1cc7..8ec7ce0 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -89,7 +89,7 @@ def pypi_link(pkg_filename):
 
 setup_params = dict(
     name="setuptools",
-    version="40.7.3",
+    version="40.8.0",
     description=(
         "Easily download, build, install, upgrade, and uninstall "
         "Python packages"
index c883d92..70b7ab2 100644 (file)
@@ -35,6 +35,13 @@ import contextlib
 import setuptools
 import distutils
 
+__all__ = ['get_requires_for_build_sdist',
+           'get_requires_for_build_wheel',
+           'prepare_metadata_for_build_wheel',
+           'build_wheel',
+           'build_sdist',
+           '__legacy__',
+           'SetupRequirementsError']
 
 class SetupRequirementsError(BaseException):
     def __init__(self, specifiers):
@@ -74,81 +81,11 @@ def _to_str(s):
     return s
 
 
-def _run_setup(setup_script='setup.py'):
-    # Note that we can reuse our build directory between calls
-    # Correctness comes first, then optimization later
-    __file__ = setup_script
-    __name__ = '__main__'
-    f = getattr(tokenize, 'open', open)(__file__)
-    code = f.read().replace('\\r\\n', '\\n')
-    f.close()
-    exec(compile(code, __file__, 'exec'), locals())
-
-
-def _fix_config(config_settings):
-    config_settings = config_settings or {}
-    config_settings.setdefault('--global-option', [])
-    return config_settings
-
-
-def _get_build_requires(config_settings, requirements):
-    config_settings = _fix_config(config_settings)
-
-    sys.argv = sys.argv[:1] + ['egg_info'] + \
-        config_settings["--global-option"]
-    try:
-        with Distribution.patch():
-            _run_setup()
-    except SetupRequirementsError as e:
-        requirements += e.specifiers
-
-    return requirements
-
-
 def _get_immediate_subdirectories(a_dir):
     return [name for name in os.listdir(a_dir)
             if os.path.isdir(os.path.join(a_dir, name))]
 
 
-def get_requires_for_build_wheel(config_settings=None):
-    config_settings = _fix_config(config_settings)
-    return _get_build_requires(config_settings, requirements=['wheel'])
-
-
-def get_requires_for_build_sdist(config_settings=None):
-    config_settings = _fix_config(config_settings)
-    return _get_build_requires(config_settings, requirements=[])
-
-
-def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
-    sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)]
-    _run_setup()
-
-    dist_info_directory = metadata_directory
-    while True:
-        dist_infos = [f for f in os.listdir(dist_info_directory)
-                      if f.endswith('.dist-info')]
-
-        if len(dist_infos) == 0 and \
-                len(_get_immediate_subdirectories(dist_info_directory)) == 1:
-            dist_info_directory = os.path.join(
-                dist_info_directory, os.listdir(dist_info_directory)[0])
-            continue
-
-        assert len(dist_infos) == 1
-        break
-
-    # PEP 517 requires that the .dist-info directory be placed in the
-    # metadata_directory. To comply, we MUST copy the directory to the root
-    if dist_info_directory != metadata_directory:
-        shutil.move(
-            os.path.join(dist_info_directory, dist_infos[0]),
-            metadata_directory)
-        shutil.rmtree(dist_info_directory, ignore_errors=True)
-
-    return dist_infos[0]
-
-
 def _file_with_extension(directory, extension):
     matching = (
         f for f in os.listdir(directory)
@@ -158,26 +95,137 @@ def _file_with_extension(directory, extension):
     return file
 
 
-def build_wheel(wheel_directory, config_settings=None,
-                metadata_directory=None):
-    config_settings = _fix_config(config_settings)
-    wheel_directory = os.path.abspath(wheel_directory)
-    sys.argv = sys.argv[:1] + ['bdist_wheel'] + \
-        config_settings["--global-option"]
-    _run_setup()
-    if wheel_directory != 'dist':
-        shutil.rmtree(wheel_directory)
-        shutil.copytree('dist', wheel_directory)
+class _BuildMetaBackend(object):
+
+    def _fix_config(self, config_settings):
+        config_settings = config_settings or {}
+        config_settings.setdefault('--global-option', [])
+        return config_settings
+
+    def _get_build_requires(self, config_settings, requirements):
+        config_settings = self._fix_config(config_settings)
+
+        sys.argv = sys.argv[:1] + ['egg_info'] + \
+            config_settings["--global-option"]
+        try:
+            with Distribution.patch():
+                self.run_setup()
+        except SetupRequirementsError as e:
+            requirements += e.specifiers
+
+        return requirements
+
+    def run_setup(self, setup_script='setup.py'):
+        # Note that we can reuse our build directory between calls
+        # Correctness comes first, then optimization later
+        __file__ = setup_script
+        __name__ = '__main__'
+        f = getattr(tokenize, 'open', open)(__file__)
+        code = f.read().replace('\\r\\n', '\\n')
+        f.close()
+        exec(compile(code, __file__, 'exec'), locals())
+
+    def get_requires_for_build_wheel(self, config_settings=None):
+        config_settings = self._fix_config(config_settings)
+        return self._get_build_requires(config_settings, requirements=['wheel'])
+
+    def get_requires_for_build_sdist(self, config_settings=None):
+        config_settings = self._fix_config(config_settings)
+        return self._get_build_requires(config_settings, requirements=[])
+
+    def prepare_metadata_for_build_wheel(self, metadata_directory,
+                                         config_settings=None):
+        sys.argv = sys.argv[:1] + ['dist_info', '--egg-base',
+                                   _to_str(metadata_directory)]
+        self.run_setup()
+
+        dist_info_directory = metadata_directory
+        while True:
+            dist_infos = [f for f in os.listdir(dist_info_directory)
+                          if f.endswith('.dist-info')]
+
+            if (len(dist_infos) == 0 and
+                len(_get_immediate_subdirectories(dist_info_directory)) == 1):
+
+                dist_info_directory = os.path.join(
+                    dist_info_directory, os.listdir(dist_info_directory)[0])
+                continue
+
+            assert len(dist_infos) == 1
+            break
+
+        # PEP 517 requires that the .dist-info directory be placed in the
+        # metadata_directory. To comply, we MUST copy the directory to the root
+        if dist_info_directory != metadata_directory:
+            shutil.move(
+                os.path.join(dist_info_directory, dist_infos[0]),
+                metadata_directory)
+            shutil.rmtree(dist_info_directory, ignore_errors=True)
+
+        return dist_infos[0]
+
+    def build_wheel(self, wheel_directory, config_settings=None,
+                    metadata_directory=None):
+        config_settings = self._fix_config(config_settings)
+        wheel_directory = os.path.abspath(wheel_directory)
+        sys.argv = sys.argv[:1] + ['bdist_wheel'] + \
+            config_settings["--global-option"]
+        self.run_setup()
+        if wheel_directory != 'dist':
+            shutil.rmtree(wheel_directory)
+            shutil.copytree('dist', wheel_directory)
+
+        return _file_with_extension(wheel_directory, '.whl')
+
+    def build_sdist(self, sdist_directory, config_settings=None):
+        config_settings = self._fix_config(config_settings)
+        sdist_directory = os.path.abspath(sdist_directory)
+        sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
+            config_settings["--global-option"] + \
+            ["--dist-dir", sdist_directory]
+        self.run_setup()
+
+        return _file_with_extension(sdist_directory, '.tar.gz')
+
+
+class _BuildMetaLegacyBackend(_BuildMetaBackend):
+    """Compatibility backend for setuptools
+
+    This is a version of setuptools.build_meta that endeavors to maintain backwards
+    compatibility with pre-PEP 517 modes of invocation. It exists as a temporary
+    bridge between the old packaging mechanism and the new packaging mechanism,
+    and will eventually be removed.
+    """
+    def run_setup(self, setup_script='setup.py'):
+        # In order to maintain compatibility with scripts assuming that
+        # the setup.py script is in a directory on the PYTHONPATH, inject
+        # '' into sys.path. (pypa/setuptools#1642)
+        sys_path = list(sys.path)           # Save the original path
+
+        script_dir = os.path.dirname(os.path.abspath(setup_script))
+        if script_dir not in sys.path:
+            sys.path.insert(0, script_dir)
+
+        try:
+            super(_BuildMetaLegacyBackend,
+                  self).run_setup(setup_script=setup_script)
+        finally:
+            # While PEP 517 frontends should be calling each hook in a fresh
+            # subprocess according to the standard (and thus it should not be
+            # strictly necessary to restore the old sys.path), we'll restore
+            # the original path so that the path manipulation does not persist
+            # within the hook after run_setup is called.
+            sys.path[:] = sys_path
 
-    return _file_with_extension(wheel_directory, '.whl')
+# The primary backend
+_BACKEND = _BuildMetaBackend()
 
+get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
+get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
+prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
+build_wheel = _BACKEND.build_wheel
+build_sdist = _BACKEND.build_sdist
 
-def build_sdist(sdist_directory, config_settings=None):
-    config_settings = _fix_config(config_settings)
-    sdist_directory = os.path.abspath(sdist_directory)
-    sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
-        config_settings["--global-option"] + \
-        ["--dist-dir", sdist_directory]
-    _run_setup()
 
-    return _file_with_extension(sdist_directory, '.tar.gz')
+# The legacy backend
+__legacy__ = _BuildMetaLegacyBackend()
index d9fe3da..5d8f451 100644 (file)
@@ -568,6 +568,7 @@ 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())
index bcfae4d..dc25398 100644 (file)
@@ -198,3 +198,24 @@ class sdist(sdist_add_defaults, orig.sdist):
                 continue
             self.filelist.append(line)
         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.
+        """
+
+        opts = self.distribution.get_option_dict('metadata')
+
+        # ignore the source of the value
+        _, license_file = opts.get('license_file', (None, None))
+
+        if license_file is None:
+            log.debug("'license_file' option was not specified")
+            return
+
+        if not os.path.exists(license_file):
+            log.warn("warning: Failed to find the configured license file '%s'",
+                    license_file)
+            return
+
+        self.filelist.append(license_file)
index 82b44c8..6236b9f 100644 (file)
@@ -6,7 +6,6 @@ import tarfile
 
 import pytest
 
-from setuptools.build_meta import build_sdist
 from .files import build_files
 from .textwrap import DALS
 from . import py2_only
@@ -24,7 +23,6 @@ class BuildBackendBase:
         self.env = env
         self.backend_name = backend_name
 
-
 class BuildBackend(BuildBackendBase):
     """PEP 517 Build Backend"""
 
@@ -44,12 +42,24 @@ class BuildBackend(BuildBackendBase):
 
 
 class BuildBackendCaller(BuildBackendBase):
+    def __init__(self, *args, **kwargs):
+        super(BuildBackendCaller, self).__init__(*args, **kwargs)
+
+        (self.backend_name, _,
+         self.backend_obj) = self.backend_name.partition(':')
+
     def __call__(self, name, *args, **kw):
         """Handles aribrary function invocations on the build backend."""
         os.chdir(self.cwd)
         os.environ.update(self.env)
         mod = importlib.import_module(self.backend_name)
-        return getattr(mod, name)(*args, **kw)
+
+        if self.backend_obj:
+            backend = getattr(mod, self.backend_obj)
+        else:
+            backend = mod
+
+        return getattr(backend, name)(*args, **kw)
 
 
 defns = [
@@ -103,136 +113,169 @@ defns = [
 ]
 
 
-@pytest.fixture(params=defns)
-def build_backend(tmpdir, request):
-    build_files(request.param, prefix=str(tmpdir))
-    with tmpdir.as_cwd():
-        yield BuildBackend(cwd='.')
-
-
-def test_get_requires_for_build_wheel(build_backend):
-    actual = build_backend.get_requires_for_build_wheel()
-    expected = ['six', 'wheel']
-    assert sorted(actual) == sorted(expected)
-
-
-def test_get_requires_for_build_sdist(build_backend):
-    actual = build_backend.get_requires_for_build_sdist()
-    expected = ['six']
-    assert sorted(actual) == sorted(expected)
-
-
-def test_build_wheel(build_backend):
-    dist_dir = os.path.abspath('pip-wheel')
-    os.makedirs(dist_dir)
-    wheel_name = build_backend.build_wheel(dist_dir)
-
-    assert os.path.isfile(os.path.join(dist_dir, wheel_name))
-
-
-def test_build_sdist(build_backend):
-    dist_dir = os.path.abspath('pip-sdist')
-    os.makedirs(dist_dir)
-    sdist_name = build_backend.build_sdist(dist_dir)
-
-    assert os.path.isfile(os.path.join(dist_dir, sdist_name))
-
-
-def test_prepare_metadata_for_build_wheel(build_backend):
-    dist_dir = os.path.abspath('pip-dist-info')
-    os.makedirs(dist_dir)
-
-    dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
-
-    assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-
-
-@py2_only
-def test_prepare_metadata_for_build_wheel_with_str(build_backend):
-    dist_dir = os.path.abspath(str('pip-dist-info'))
-    os.makedirs(dist_dir)
-
-    dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
-
-    assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-
-
-def test_build_sdist_explicit_dist(build_backend):
-    # explicitly specifying the dist folder should work
-    # the folder sdist_directory and the ``--dist-dir`` can be the same
-    dist_dir = os.path.abspath('dist')
-    sdist_name = build_backend.build_sdist(dist_dir)
-    assert os.path.isfile(os.path.join(dist_dir, sdist_name))
-
-
-def test_build_sdist_version_change(build_backend):
-    sdist_into_directory = os.path.abspath("out_sdist")
-    os.makedirs(sdist_into_directory)
-
-    sdist_name = build_backend.build_sdist(sdist_into_directory)
-    assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
-
-    # if the setup.py changes subsequent call of the build meta
-    # should still succeed, given the
-    # sdist_directory the frontend specifies is empty
-    with open(os.path.abspath("setup.py"), 'rt') as file_handler:
-        content = file_handler.read()
-    with open(os.path.abspath("setup.py"), 'wt') as file_handler:
-        file_handler.write(
-            content.replace("version='0.0.0'", "version='0.0.1'"))
-
-    shutil.rmtree(sdist_into_directory)
-    os.makedirs(sdist_into_directory)
-
-    sdist_name = build_backend.build_sdist("out_sdist")
-    assert os.path.isfile(
-        os.path.join(os.path.abspath("out_sdist"), sdist_name))
-
-
-def test_build_sdist_setup_py_exists(tmpdir_cwd):
-    # If build_sdist is called from a script other than setup.py,
-    # ensure setup.py is include
-    build_files(defns[0])
-    targz_path = build_sdist("temp")
-    with tarfile.open(os.path.join("temp", targz_path)) as tar:
-        assert any('setup.py' in name for name in tar.getnames())
-
-
-def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
-    # Ensure that MANIFEST.in can exclude setup.py
-    files = {
-        'setup.py': DALS("""
-    __import__('setuptools').setup(
-        name='foo',
-        version='0.0.0',
-        py_modules=['hello']
-    )"""),
-        'hello.py': '',
-        'MANIFEST.in': DALS("""
-    exclude setup.py
-    """)
-    }
-
-    build_files(files)
-    targz_path = build_sdist("temp")
-    with tarfile.open(os.path.join("temp", targz_path)) as tar:
-        assert not any('setup.py' in name for name in tar.getnames())
-
-
-def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd):
-    files = {
+class TestBuildMetaBackend:
+    backend_name = 'setuptools.build_meta'
+
+    def get_build_backend(self):
+        return BuildBackend(cwd='.', backend_name=self.backend_name)
+
+    @pytest.fixture(params=defns)
+    def build_backend(self, tmpdir, request):
+        build_files(request.param, prefix=str(tmpdir))
+        with tmpdir.as_cwd():
+            yield self.get_build_backend()
+
+    def test_get_requires_for_build_wheel(self, build_backend):
+        actual = build_backend.get_requires_for_build_wheel()
+        expected = ['six', 'wheel']
+        assert sorted(actual) == sorted(expected)
+
+    def test_get_requires_for_build_sdist(self, build_backend):
+        actual = build_backend.get_requires_for_build_sdist()
+        expected = ['six']
+        assert sorted(actual) == sorted(expected)
+
+    def test_build_wheel(self, build_backend):
+        dist_dir = os.path.abspath('pip-wheel')
+        os.makedirs(dist_dir)
+        wheel_name = build_backend.build_wheel(dist_dir)
+
+        assert os.path.isfile(os.path.join(dist_dir, wheel_name))
+
+    def test_build_sdist(self, build_backend):
+        dist_dir = os.path.abspath('pip-sdist')
+        os.makedirs(dist_dir)
+        sdist_name = build_backend.build_sdist(dist_dir)
+
+        assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+    def test_prepare_metadata_for_build_wheel(self, build_backend):
+        dist_dir = os.path.abspath('pip-dist-info')
+        os.makedirs(dist_dir)
+
+        dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+        assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
+
+    @py2_only
+    def test_prepare_metadata_for_build_wheel_with_str(self, build_backend):
+        dist_dir = os.path.abspath(str('pip-dist-info'))
+        os.makedirs(dist_dir)
+
+        dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+        assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
+
+    def test_build_sdist_explicit_dist(self, build_backend):
+        # explicitly specifying the dist folder should work
+        # the folder sdist_directory and the ``--dist-dir`` can be the same
+        dist_dir = os.path.abspath('dist')
+        sdist_name = build_backend.build_sdist(dist_dir)
+        assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+    def test_build_sdist_version_change(self, build_backend):
+        sdist_into_directory = os.path.abspath("out_sdist")
+        os.makedirs(sdist_into_directory)
+
+        sdist_name = build_backend.build_sdist(sdist_into_directory)
+        assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
+
+        # if the setup.py changes subsequent call of the build meta
+        # should still succeed, given the
+        # sdist_directory the frontend specifies is empty
+        with open(os.path.abspath("setup.py"), 'rt') as file_handler:
+            content = file_handler.read()
+        with open(os.path.abspath("setup.py"), 'wt') as file_handler:
+            file_handler.write(
+                content.replace("version='0.0.0'", "version='0.0.1'"))
+
+        shutil.rmtree(sdist_into_directory)
+        os.makedirs(sdist_into_directory)
+
+        sdist_name = build_backend.build_sdist("out_sdist")
+        assert os.path.isfile(
+            os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+    def test_build_sdist_setup_py_exists(self, tmpdir_cwd):
+        # If build_sdist is called from a script other than setup.py,
+        # ensure setup.py is included
+        build_files(defns[0])
+
+        build_backend = self.get_build_backend()
+        targz_path = build_backend.build_sdist("temp")
+        with tarfile.open(os.path.join("temp", targz_path)) as tar:
+            assert any('setup.py' in name for name in tar.getnames())
+
+    def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
+        # Ensure that MANIFEST.in can exclude setup.py
+        files = {
+            'setup.py': DALS("""
+        __import__('setuptools').setup(
+            name='foo',
+            version='0.0.0',
+            py_modules=['hello']
+        )"""),
+            'hello.py': '',
+            'MANIFEST.in': DALS("""
+        exclude setup.py
+        """)
+        }
+
+        build_files(files)
+
+        build_backend = self.get_build_backend()
+        targz_path = build_backend.build_sdist("temp")
+        with tarfile.open(os.path.join("temp", targz_path)) as tar:
+            assert not any('setup.py' in name for name in tar.getnames())
+
+    def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
+        files = {
+            'setup.py': DALS("""
+                __import__('setuptools').setup(
+                    name='foo',
+                    version='0.0.0',
+                    py_modules=['hello']
+                )"""),
+            'hello.py': '',
+            'setup.cfg': DALS("""
+                [sdist]
+                formats=zip
+                """)
+        }
+
+        build_files(files)
+
+        build_backend = self.get_build_backend()
+        build_backend.build_sdist("temp")
+
+    _relative_path_import_files = {
         'setup.py': DALS("""
             __import__('setuptools').setup(
                 name='foo',
-                version='0.0.0',
+                version=__import__('hello').__version__,
                 py_modules=['hello']
             )"""),
-        'hello.py': '',
+        'hello.py': '__version__ = "0.0.0"',
         'setup.cfg': DALS("""
             [sdist]
             formats=zip
             """)
     }
 
-    build_files(files)
-    build_sdist("temp")
+    def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+        build_files(self._relative_path_import_files)
+        build_backend = self.get_build_backend()
+        with pytest.raises(ImportError):
+            build_backend.build_sdist("temp")
+
+
+class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
+    backend_name = 'setuptools.build_meta:__legacy__'
+
+    # build_meta_legacy-specific tests
+    def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+        # This must fail in build_meta, but must pass in build_meta_legacy
+        build_files(self._relative_path_import_files)
+
+        build_backend = self.get_build_backend()
+        build_backend.build_sdist("temp")
index cc701ae..b3a99f5 100644 (file)
@@ -1,17 +1,9 @@
 import os
 
-import pytest
-
 from setuptools.dist import Distribution
 
 
-@pytest.yield_fixture
-def tmpdir_as_cwd(tmpdir):
-    with tmpdir.as_cwd():
-        yield tmpdir
-
-
-def test_directories_in_package_data_glob(tmpdir_as_cwd):
+def test_directories_in_package_data_glob(tmpdir_cwd):
     """
     Directories matching the glob in package_data should
     not be included in the package data.
index d5fa255..db9c387 100644 (file)
@@ -518,6 +518,55 @@ class TestEggInfo:
             pkg_info_text = pkginfo_file.read()
         assert 'Provides-Extra:' not in pkg_info_text
 
+    @pytest.mark.parametrize("files, license_in_sources", [
+        ({
+            'setup.cfg': DALS("""
+                              [metadata]
+                              license_file = LICENSE
+                              """),
+            'LICENSE': DALS("Test license")
+        }, True), # with license
+        ({
+            'setup.cfg': DALS("""
+                              [metadata]
+                              license_file = INVALID_LICENSE
+                              """),
+            'LICENSE': DALS("Test license")
+        }, False), # with an invalid license
+        ({
+            'setup.cfg': DALS("""
+                              """),
+            'LICENSE': DALS("Test license")
+        }, False), # no license_file attribute
+        ({
+            'setup.cfg': DALS("""
+                              [metadata]
+                              license_file = LICENSE
+                              """),
+            'MANIFEST.in': DALS("exclude LICENSE"),
+            'LICENSE': DALS("Test license")
+        }, False) # license file is manually excluded
+    ])
+    def test_setup_cfg_license_file(
+            self, tmpdir_cwd, env, files, license_in_sources):
+        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_text = sources_file.read()
+
+        if license_in_sources:
+            assert 'LICENSE' in sources_text
+        else:
+            assert 'LICENSE' not in sources_text
+            assert 'INVALID_LICENSE' not in sources_text # for invalid license test
+
     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`