From ec741f1f799af0a7399412bc0042317ea2414f90 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Wed, 30 Dec 2020 07:03:54 +0900 Subject: [PATCH] Imported Upstream version 40.8.0 --- CHANGES.rst | 8 + MANIFEST.in | 1 + docs/pkg_resources.txt | 4 +- docs/setuptools.txt | 1 + pkg_resources/__init__.py | 67 ++++++- pkg_resources/tests/test_pkg_resources.py | 2 - pyproject.toml | 1 - setup.cfg | 2 +- setup.py | 2 +- setuptools/build_meta.py | 226 ++++++++++++++--------- setuptools/command/egg_info.py | 1 + setuptools/command/sdist.py | 21 +++ setuptools/tests/test_build_meta.py | 293 +++++++++++++++++------------- setuptools/tests/test_build_py.py | 10 +- setuptools/tests/test_egg_info.py | 49 +++++ 15 files changed, 457 insertions(+), 231 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6289622..b043e44 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ------- diff --git a/MANIFEST.in b/MANIFEST.in index 9cce3c9..16d60e5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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. diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt index 0c9fb5f..806f1b1 100644 --- a/docs/pkg_resources.txt +++ b/docs/pkg_resources.txt @@ -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. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index cf166e9..a4e05d7 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -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 diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 6ca68da..dcfa1d0 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -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) diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 416f9af..2c2c9c7 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -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/') == [] diff --git a/pyproject.toml b/pyproject.toml index 4ef804e..07c23bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] requires = ["wheel"] -build-backend = "setuptools.build_meta" [tool.towncrier] package = "setuptools" diff --git a/setup.cfg b/setup.cfg index 4261f9b..f0932e1 100644 --- 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 diff --git a/setup.py b/setup.py index b3e1cc7..8ec7ce0 100755 --- 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" diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index c883d92..70b7ab2 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -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() diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index d9fe3da..5d8f451 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -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()) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index bcfae4d..dc25398 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -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) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 82b44c8..6236b9f 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -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") diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index cc701ae..b3a99f5 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -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. diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index d5fa255..db9c387 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -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` -- 2.7.4