|
"""sdist tests""" |
|
|
|
import os |
|
import sys |
|
import tempfile |
|
import unicodedata |
|
import contextlib |
|
import io |
|
import tarfile |
|
import logging |
|
import distutils |
|
from inspect import cleandoc |
|
from unittest import mock |
|
|
|
import pytest |
|
|
|
from distutils.core import run_setup |
|
from setuptools import Command |
|
from setuptools._importlib import metadata |
|
from setuptools import SetuptoolsDeprecationWarning |
|
from setuptools.command.sdist import sdist |
|
from setuptools.command.egg_info import manifest_maker |
|
from setuptools.dist import Distribution |
|
from setuptools.extension import Extension |
|
from setuptools.tests import fail_on_ascii |
|
from .text import Filenames |
|
|
|
import jaraco.path |
|
|
|
|
|
SETUP_ATTRS = { |
|
'name': 'sdist_test', |
|
'version': '0.0', |
|
'packages': ['sdist_test'], |
|
'package_data': {'sdist_test': ['*.txt']}, |
|
'data_files': [("data", [os.path.join("d", "e.dat")])], |
|
} |
|
|
|
SETUP_PY = ( |
|
"""\ |
|
from setuptools import setup |
|
|
|
setup(**%r) |
|
""" |
|
% SETUP_ATTRS |
|
) |
|
|
|
EXTENSION = Extension( |
|
name="sdist_test.f", |
|
sources=[os.path.join("sdist_test", "f.c")], |
|
depends=[os.path.join("sdist_test", "f.h")], |
|
) |
|
EXTENSION_SOURCES = EXTENSION.sources + EXTENSION.depends |
|
|
|
|
|
@contextlib.contextmanager |
|
def quiet(): |
|
old_stdout, old_stderr = sys.stdout, sys.stderr |
|
sys.stdout, sys.stderr = io.StringIO(), io.StringIO() |
|
try: |
|
yield |
|
finally: |
|
sys.stdout, sys.stderr = old_stdout, old_stderr |
|
|
|
|
|
|
|
def posix(path): |
|
if not isinstance(path, str): |
|
return path.replace(os.sep.encode('ascii'), b'/') |
|
else: |
|
return path.replace(os.sep, '/') |
|
|
|
|
|
|
|
def decompose(path): |
|
if isinstance(path, str): |
|
return unicodedata.normalize('NFD', path) |
|
try: |
|
path = path.decode('utf-8') |
|
path = unicodedata.normalize('NFD', path) |
|
path = path.encode('utf-8') |
|
except UnicodeError: |
|
pass |
|
return path |
|
|
|
|
|
def read_all_bytes(filename): |
|
with open(filename, 'rb') as fp: |
|
return fp.read() |
|
|
|
|
|
def latin1_fail(): |
|
try: |
|
desc, filename = tempfile.mkstemp(suffix=Filenames.latin_1) |
|
os.close(desc) |
|
os.remove(filename) |
|
except Exception: |
|
return True |
|
|
|
|
|
fail_on_latin1_encoded_filenames = pytest.mark.xfail( |
|
latin1_fail(), |
|
reason="System does not support latin-1 filenames", |
|
) |
|
|
|
|
|
skip_under_xdist = pytest.mark.skipif( |
|
"os.environ.get('PYTEST_XDIST_WORKER')", |
|
reason="pytest-dev/pytest-xdist#843", |
|
) |
|
skip_under_stdlib_distutils = pytest.mark.skipif( |
|
not distutils.__package__.startswith('setuptools'), |
|
reason="the test is not supported with stdlib distutils", |
|
) |
|
|
|
|
|
def touch(path): |
|
open(path, 'wb').close() |
|
return path |
|
|
|
|
|
def symlink_or_skip_test(src, dst): |
|
try: |
|
os.symlink(src, dst) |
|
except (OSError, NotImplementedError): |
|
pytest.skip("symlink not supported in OS") |
|
return None |
|
return dst |
|
|
|
|
|
class TestSdistTest: |
|
@pytest.fixture(autouse=True) |
|
def source_dir(self, tmpdir): |
|
tmpdir = tmpdir / "project_root" |
|
tmpdir.mkdir() |
|
|
|
(tmpdir / 'setup.py').write_text(SETUP_PY, encoding='utf-8') |
|
|
|
|
|
test_pkg = tmpdir / 'sdist_test' |
|
test_pkg.mkdir() |
|
data_folder = tmpdir / 'd' |
|
data_folder.mkdir() |
|
|
|
|
|
for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: |
|
touch(test_pkg / fname) |
|
touch(data_folder / 'e.dat') |
|
|
|
|
|
for fname in EXTENSION_SOURCES: |
|
touch(tmpdir / fname) |
|
|
|
with tmpdir.as_cwd(): |
|
yield tmpdir |
|
|
|
def assert_package_data_in_manifest(self, cmd): |
|
manifest = cmd.filelist.files |
|
assert os.path.join('sdist_test', 'a.txt') in manifest |
|
assert os.path.join('sdist_test', 'b.txt') in manifest |
|
assert os.path.join('sdist_test', 'c.rst') not in manifest |
|
assert os.path.join('d', 'e.dat') in manifest |
|
|
|
def setup_with_extension(self): |
|
setup_attrs = {**SETUP_ATTRS, 'ext_modules': [EXTENSION]} |
|
|
|
dist = Distribution(setup_attrs) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
return cmd |
|
|
|
def test_package_data_in_sdist(self): |
|
"""Regression test for pull request #4: ensures that files listed in |
|
package_data are included in the manifest even if they're not added to |
|
version control. |
|
""" |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
self.assert_package_data_in_manifest(cmd) |
|
|
|
def test_package_data_and_include_package_data_in_sdist(self): |
|
""" |
|
Ensure package_data and include_package_data work |
|
together. |
|
""" |
|
setup_attrs = {**SETUP_ATTRS, 'include_package_data': True} |
|
assert setup_attrs['package_data'] |
|
|
|
dist = Distribution(setup_attrs) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
self.assert_package_data_in_manifest(cmd) |
|
|
|
def test_extension_sources_in_sdist(self): |
|
""" |
|
Ensure that the files listed in Extension.sources and Extension.depends |
|
are automatically included in the manifest. |
|
""" |
|
cmd = self.setup_with_extension() |
|
self.assert_package_data_in_manifest(cmd) |
|
manifest = cmd.filelist.files |
|
for path in EXTENSION_SOURCES: |
|
assert path in manifest |
|
|
|
def test_missing_extension_sources(self): |
|
""" |
|
Similar to test_extension_sources_in_sdist but the referenced files don't exist. |
|
Missing files should not be included in distribution (with no error raised). |
|
""" |
|
for path in EXTENSION_SOURCES: |
|
os.remove(path) |
|
|
|
cmd = self.setup_with_extension() |
|
self.assert_package_data_in_manifest(cmd) |
|
manifest = cmd.filelist.files |
|
for path in EXTENSION_SOURCES: |
|
assert path not in manifest |
|
|
|
def test_symlinked_extension_sources(self): |
|
""" |
|
Similar to test_extension_sources_in_sdist but the referenced files are |
|
instead symbolic links to project-local files. Referenced file paths |
|
should be included. Symlink targets themselves should NOT be included. |
|
""" |
|
symlinked = [] |
|
for path in EXTENSION_SOURCES: |
|
base, ext = os.path.splitext(path) |
|
target = base + "_target." + ext |
|
|
|
os.rename(path, target) |
|
symlink_or_skip_test(os.path.basename(target), path) |
|
symlinked.append(target) |
|
|
|
cmd = self.setup_with_extension() |
|
self.assert_package_data_in_manifest(cmd) |
|
manifest = cmd.filelist.files |
|
for path in EXTENSION_SOURCES: |
|
assert path in manifest |
|
for path in symlinked: |
|
assert path not in manifest |
|
|
|
_INVALID_PATHS = { |
|
"must be relative": lambda: ( |
|
os.path.abspath(os.path.join("sdist_test", "f.h")) |
|
), |
|
"can't have `..` segments": lambda: ( |
|
os.path.join("sdist_test", "..", "sdist_test", "f.h") |
|
), |
|
"doesn't exist": lambda: ( |
|
os.path.join("sdist_test", "this_file_does_not_exist.h") |
|
), |
|
"must be inside the project root": lambda: ( |
|
symlink_or_skip_test( |
|
touch(os.path.join("..", "outside_of_project_root.h")), |
|
"symlink.h", |
|
) |
|
), |
|
} |
|
|
|
@skip_under_stdlib_distutils |
|
@pytest.mark.parametrize("reason", _INVALID_PATHS.keys()) |
|
def test_invalid_extension_depends(self, reason, caplog): |
|
""" |
|
Due to backwards compatibility reasons, `Extension.depends` should accept |
|
invalid/weird paths, but then ignore them when building a sdist. |
|
|
|
This test verifies that the source distribution is still built |
|
successfully with such paths, but that instead of adding these paths to |
|
the manifest, we emit an informational message, notifying the user that |
|
the invalid path won't be automatically included. |
|
""" |
|
invalid_path = self._INVALID_PATHS[reason]() |
|
extension = Extension( |
|
name="sdist_test.f", |
|
sources=[], |
|
depends=[invalid_path], |
|
) |
|
setup_attrs = {**SETUP_ATTRS, 'ext_modules': [extension]} |
|
|
|
dist = Distribution(setup_attrs) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(), caplog.at_level(logging.INFO): |
|
cmd.run() |
|
|
|
self.assert_package_data_in_manifest(cmd) |
|
manifest = cmd.filelist.files |
|
assert invalid_path not in manifest |
|
|
|
expected_message = [ |
|
message |
|
for (logger, level, message) in caplog.record_tuples |
|
if ( |
|
logger == "root" |
|
and level == logging.INFO |
|
and invalid_path in message |
|
) |
|
] |
|
assert len(expected_message) == 1 |
|
(expected_message,) = expected_message |
|
assert reason in expected_message |
|
|
|
def test_custom_build_py(self): |
|
""" |
|
Ensure projects defining custom build_py don't break |
|
when creating sdists (issue #2849) |
|
""" |
|
from distutils.command.build_py import build_py as OrigBuildPy |
|
|
|
using_custom_command_guard = mock.Mock() |
|
|
|
class CustomBuildPy(OrigBuildPy): |
|
""" |
|
Some projects have custom commands inheriting from `distutils` |
|
""" |
|
|
|
def get_data_files(self): |
|
using_custom_command_guard() |
|
return super().get_data_files() |
|
|
|
setup_attrs = {**SETUP_ATTRS, 'include_package_data': True} |
|
assert setup_attrs['package_data'] |
|
|
|
dist = Distribution(setup_attrs) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
|
|
cmd.cmdclass = {'build_py': CustomBuildPy} |
|
cmd.distribution.cmdclass = {'build_py': CustomBuildPy} |
|
assert cmd.distribution.get_command_class('build_py') == CustomBuildPy |
|
|
|
msg = "setuptools instead of distutils" |
|
with quiet(), pytest.warns(SetuptoolsDeprecationWarning, match=msg): |
|
cmd.run() |
|
|
|
using_custom_command_guard.assert_called() |
|
self.assert_package_data_in_manifest(cmd) |
|
|
|
def test_setup_py_exists(self): |
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'foo.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
manifest = cmd.filelist.files |
|
assert 'setup.py' in manifest |
|
|
|
def test_setup_py_missing(self): |
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'foo.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
if os.path.exists("setup.py"): |
|
os.remove("setup.py") |
|
with quiet(): |
|
cmd.run() |
|
|
|
manifest = cmd.filelist.files |
|
assert 'setup.py' not in manifest |
|
|
|
def test_setup_py_excluded(self): |
|
with open("MANIFEST.in", "w", encoding="utf-8") as manifest_file: |
|
manifest_file.write("exclude setup.py") |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'foo.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
manifest = cmd.filelist.files |
|
assert 'setup.py' not in manifest |
|
|
|
def test_defaults_case_sensitivity(self, source_dir): |
|
""" |
|
Make sure default files (README.*, etc.) are added in a case-sensitive |
|
way to avoid problems with packages built on Windows. |
|
""" |
|
|
|
touch(source_dir / 'readme.rst') |
|
touch(source_dir / 'SETUP.cfg') |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
|
|
|
|
|
|
dist.script_name = 'setup.PY' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
|
|
|
|
|
|
manifest = map(lambda x: x.lower(), cmd.filelist.files) |
|
assert 'readme.rst' not in manifest, manifest |
|
assert 'setup.py' not in manifest, manifest |
|
assert 'setup.cfg' not in manifest, manifest |
|
|
|
@fail_on_ascii |
|
def test_manifest_is_written_with_utf8_encoding(self): |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
mm = manifest_maker(dist) |
|
mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
|
os.mkdir('sdist_test.egg-info') |
|
|
|
|
|
filename = os.path.join('sdist_test', 'smörbröd.py') |
|
|
|
|
|
touch(filename) |
|
|
|
|
|
with quiet(): |
|
mm.run() |
|
mm.filelist.append(filename) |
|
mm.write_manifest() |
|
|
|
contents = read_all_bytes(mm.manifest) |
|
|
|
|
|
u_contents = contents.decode('UTF-8') |
|
|
|
|
|
assert posix(filename) in u_contents |
|
|
|
@fail_on_ascii |
|
def test_write_manifest_allows_utf8_filenames(self): |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
mm = manifest_maker(dist) |
|
mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
|
os.mkdir('sdist_test.egg-info') |
|
|
|
filename = os.path.join(b'sdist_test', Filenames.utf_8) |
|
|
|
|
|
touch(filename) |
|
|
|
|
|
with quiet(): |
|
mm.run() |
|
u_filename = filename.decode('utf-8') |
|
mm.filelist.files.append(u_filename) |
|
|
|
mm.write_manifest() |
|
|
|
contents = read_all_bytes(mm.manifest) |
|
|
|
|
|
contents.decode('UTF-8') |
|
|
|
|
|
assert posix(filename) in contents |
|
|
|
|
|
assert u_filename in mm.filelist.files |
|
|
|
@skip_under_xdist |
|
def test_write_manifest_skips_non_utf8_filenames(self): |
|
""" |
|
Files that cannot be encoded to UTF-8 (specifically, those that |
|
weren't originally successfully decoded and have surrogate |
|
escapes) should be omitted from the manifest. |
|
See https://bitbucket.org/tarek/distribute/issue/303 for history. |
|
""" |
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
mm = manifest_maker(dist) |
|
mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
|
os.mkdir('sdist_test.egg-info') |
|
|
|
|
|
filename = os.path.join(b'sdist_test', Filenames.latin_1) |
|
|
|
|
|
with quiet(): |
|
mm.run() |
|
u_filename = filename.decode('utf-8', 'surrogateescape') |
|
mm.filelist.append(u_filename) |
|
|
|
mm.write_manifest() |
|
|
|
contents = read_all_bytes(mm.manifest) |
|
|
|
|
|
contents.decode('UTF-8') |
|
|
|
|
|
assert posix(filename) not in contents |
|
|
|
|
|
assert u_filename not in mm.filelist.files |
|
|
|
@fail_on_ascii |
|
def test_manifest_is_read_with_utf8_encoding(self): |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
|
|
filename = os.path.join(b'sdist_test', Filenames.utf_8) |
|
cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
|
manifest = open(cmd.manifest, 'ab') |
|
manifest.write(b'\n' + filename) |
|
manifest.close() |
|
|
|
|
|
touch(filename) |
|
|
|
|
|
cmd.filelist.files = [] |
|
with quiet(): |
|
cmd.read_manifest() |
|
|
|
|
|
filename = filename.decode('utf-8') |
|
assert filename in cmd.filelist.files |
|
|
|
@fail_on_latin1_encoded_filenames |
|
def test_read_manifest_skips_non_utf8_filenames(self): |
|
|
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
|
|
filename = os.path.join(b'sdist_test', Filenames.latin_1) |
|
cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt') |
|
manifest = open(cmd.manifest, 'ab') |
|
manifest.write(b'\n' + filename) |
|
manifest.close() |
|
|
|
|
|
touch(filename) |
|
|
|
|
|
cmd.filelist.files = [] |
|
with quiet(): |
|
cmd.read_manifest() |
|
|
|
|
|
filename = filename.decode('latin-1') |
|
assert filename not in cmd.filelist.files |
|
|
|
@fail_on_ascii |
|
@fail_on_latin1_encoded_filenames |
|
def test_sdist_with_utf8_encoded_filename(self): |
|
|
|
dist = Distribution(self.make_strings(SETUP_ATTRS)) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
filename = os.path.join(b'sdist_test', Filenames.utf_8) |
|
touch(filename) |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
if sys.platform == 'darwin': |
|
filename = decompose(filename) |
|
|
|
fs_enc = sys.getfilesystemencoding() |
|
|
|
if sys.platform == 'win32': |
|
if fs_enc == 'cp1252': |
|
|
|
filename = filename.decode('cp1252') |
|
assert filename in cmd.filelist.files |
|
else: |
|
filename = filename.decode('mbcs') |
|
assert filename in cmd.filelist.files |
|
else: |
|
filename = filename.decode('utf-8') |
|
assert filename in cmd.filelist.files |
|
|
|
@classmethod |
|
def make_strings(cls, item): |
|
if isinstance(item, dict): |
|
return {key: cls.make_strings(value) for key, value in item.items()} |
|
if isinstance(item, list): |
|
return list(map(cls.make_strings, item)) |
|
return str(item) |
|
|
|
@fail_on_latin1_encoded_filenames |
|
@skip_under_xdist |
|
def test_sdist_with_latin1_encoded_filename(self): |
|
|
|
dist = Distribution(self.make_strings(SETUP_ATTRS)) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
|
|
|
|
filename = os.path.join(b'sdist_test', Filenames.latin_1) |
|
touch(filename) |
|
assert os.path.isfile(filename) |
|
|
|
with quiet(): |
|
cmd.run() |
|
|
|
|
|
if sys.platform == 'win32': |
|
|
|
|
|
fs_enc = sys.getfilesystemencoding() |
|
if fs_enc != 'mbcs': |
|
fs_enc = 'latin-1' |
|
filename = filename.decode(fs_enc) |
|
|
|
assert filename in cmd.filelist.files |
|
else: |
|
|
|
filename = filename.decode('latin-1') |
|
assert filename not in cmd.filelist.files |
|
|
|
_EXAMPLE_DIRECTIVES = { |
|
"setup.cfg - long_description and version": """ |
|
[metadata] |
|
name = testing |
|
version = file: src/VERSION.txt |
|
license_files = DOWHATYOUWANT |
|
long_description = file: README.rst, USAGE.rst |
|
""", |
|
"pyproject.toml - static readme/license files and dynamic version": """ |
|
[project] |
|
name = "testing" |
|
readme = "USAGE.rst" |
|
license = {file = "DOWHATYOUWANT"} |
|
dynamic = ["version"] |
|
[tool.setuptools.dynamic] |
|
version = {file = ["src/VERSION.txt"]} |
|
""", |
|
"pyproject.toml - directive with str instead of list": """ |
|
[project] |
|
name = "testing" |
|
readme = "USAGE.rst" |
|
license = {file = "DOWHATYOUWANT"} |
|
dynamic = ["version"] |
|
[tool.setuptools.dynamic] |
|
version = {file = "src/VERSION.txt"} |
|
""", |
|
} |
|
|
|
@pytest.mark.parametrize("config", _EXAMPLE_DIRECTIVES.keys()) |
|
def test_add_files_referenced_by_config_directives(self, source_dir, config): |
|
config_file, _, _ = config.partition(" - ") |
|
config_text = self._EXAMPLE_DIRECTIVES[config] |
|
(source_dir / 'src').mkdir() |
|
(source_dir / 'src/VERSION.txt').write_text("0.42", encoding="utf-8") |
|
(source_dir / 'README.rst').write_text("hello world!", encoding="utf-8") |
|
(source_dir / 'USAGE.rst').write_text("hello world!", encoding="utf-8") |
|
(source_dir / 'DOWHATYOUWANT').write_text("hello world!", encoding="utf-8") |
|
(source_dir / config_file).write_text(config_text, encoding="utf-8") |
|
|
|
dist = Distribution({"packages": []}) |
|
dist.script_name = 'setup.py' |
|
dist.parse_config_files() |
|
|
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
with quiet(): |
|
cmd.run() |
|
|
|
assert ( |
|
'src/VERSION.txt' in cmd.filelist.files |
|
or 'src\\VERSION.txt' in cmd.filelist.files |
|
) |
|
assert 'USAGE.rst' in cmd.filelist.files |
|
assert 'DOWHATYOUWANT' in cmd.filelist.files |
|
assert '/' not in cmd.filelist.files |
|
assert '\\' not in cmd.filelist.files |
|
|
|
def test_pyproject_toml_in_sdist(self, source_dir): |
|
""" |
|
Check if pyproject.toml is included in source distribution if present |
|
""" |
|
touch(source_dir / 'pyproject.toml') |
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
with quiet(): |
|
cmd.run() |
|
manifest = cmd.filelist.files |
|
assert 'pyproject.toml' in manifest |
|
|
|
def test_pyproject_toml_excluded(self, source_dir): |
|
""" |
|
Check that pyproject.toml can excluded even if present |
|
""" |
|
touch(source_dir / 'pyproject.toml') |
|
with open('MANIFEST.in', 'w', encoding="utf-8") as mts: |
|
print('exclude pyproject.toml', file=mts) |
|
dist = Distribution(SETUP_ATTRS) |
|
dist.script_name = 'setup.py' |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
with quiet(): |
|
cmd.run() |
|
manifest = cmd.filelist.files |
|
assert 'pyproject.toml' not in manifest |
|
|
|
def test_build_subcommand_source_files(self, source_dir): |
|
touch(source_dir / '.myfile~') |
|
|
|
|
|
dist = Distribution({**SETUP_ATTRS, "script_name": "setup.py"}) |
|
cmd = sdist(dist) |
|
cmd.ensure_finalized() |
|
with quiet(): |
|
cmd.run() |
|
manifest = cmd.filelist.files |
|
assert '.myfile~' not in manifest |
|
|
|
|
|
dist = Distribution({**SETUP_ATTRS, "script_name": "setup.py"}) |
|
build = dist.get_command_obj("build") |
|
build.sub_commands = [*build.sub_commands, ("build_custom", None)] |
|
|
|
class build_custom(Command): |
|
def initialize_options(self): ... |
|
|
|
def finalize_options(self): ... |
|
|
|
def run(self): ... |
|
|
|
def get_source_files(self): |
|
return ['.myfile~'] |
|
|
|
dist.cmdclass.update(build_custom=build_custom) |
|
|
|
cmd = sdist(dist) |
|
cmd.use_defaults = True |
|
cmd.ensure_finalized() |
|
with quiet(): |
|
cmd.run() |
|
manifest = cmd.filelist.files |
|
assert '.myfile~' in manifest |
|
|
|
|
|
def test_default_revctrl(): |
|
""" |
|
When _default_revctrl was removed from the `setuptools.command.sdist` |
|
module in 10.0, it broke some systems which keep an old install of |
|
setuptools (Distribute) around. Those old versions require that the |
|
setuptools package continue to implement that interface, so this |
|
function provides that interface, stubbed. See #320 for details. |
|
|
|
This interface must be maintained until Ubuntu 12.04 is no longer |
|
supported (by Setuptools). |
|
""" |
|
(ep,) = metadata.EntryPoints._from_text( |
|
""" |
|
[setuptools.file_finders] |
|
svn_cvs = setuptools.command.sdist:_default_revctrl |
|
""" |
|
) |
|
res = ep.load() |
|
assert hasattr(res, '__iter__') |
|
|
|
|
|
class TestRegressions: |
|
""" |
|
Can be removed/changed if the project decides to change how it handles symlinks |
|
or external files. |
|
""" |
|
|
|
@staticmethod |
|
def files_for_symlink_in_extension_depends(tmp_path, dep_path): |
|
return { |
|
"external": { |
|
"dir": {"file.h": ""}, |
|
}, |
|
"project": { |
|
"setup.py": cleandoc( |
|
f""" |
|
from setuptools import Extension, setup |
|
setup( |
|
name="myproj", |
|
version="42", |
|
ext_modules=[ |
|
Extension( |
|
"hello", sources=["hello.pyx"], |
|
depends=[{dep_path!r}] |
|
) |
|
], |
|
) |
|
""" |
|
), |
|
"hello.pyx": "", |
|
"MANIFEST.in": "global-include *.h", |
|
}, |
|
} |
|
|
|
@pytest.mark.parametrize( |
|
"dep_path", ("myheaders/dir/file.h", "myheaders/dir/../dir/file.h") |
|
) |
|
def test_symlink_in_extension_depends(self, monkeypatch, tmp_path, dep_path): |
|
|
|
files = self.files_for_symlink_in_extension_depends(tmp_path, dep_path) |
|
jaraco.path.build(files, prefix=str(tmp_path)) |
|
symlink_or_skip_test(tmp_path / "external", tmp_path / "project/myheaders") |
|
|
|
|
|
members = run_sdist(monkeypatch, tmp_path / "project") |
|
|
|
for expected in ( |
|
"myproj-42/hello.pyx", |
|
"myproj-42/myheaders/dir/file.h", |
|
): |
|
assert expected in members |
|
|
|
@staticmethod |
|
def files_for_external_path_in_extension_depends(tmp_path, dep_path): |
|
head, _, tail = dep_path.partition("$tmp_path$/") |
|
dep_path = tmp_path / tail if tail else head |
|
|
|
return { |
|
"external": { |
|
"dir": {"file.h": ""}, |
|
}, |
|
"project": { |
|
"setup.py": cleandoc( |
|
f""" |
|
from setuptools import Extension, setup |
|
setup( |
|
name="myproj", |
|
version="42", |
|
ext_modules=[ |
|
Extension( |
|
"hello", sources=["hello.pyx"], |
|
depends=[{str(dep_path)!r}] |
|
) |
|
], |
|
) |
|
""" |
|
), |
|
"hello.pyx": "", |
|
"MANIFEST.in": "global-include *.h", |
|
}, |
|
} |
|
|
|
@pytest.mark.parametrize( |
|
"dep_path", ("$tmp_path$/external/dir/file.h", "../external/dir/file.h") |
|
) |
|
def test_external_path_in_extension_depends(self, monkeypatch, tmp_path, dep_path): |
|
|
|
files = self.files_for_external_path_in_extension_depends(tmp_path, dep_path) |
|
jaraco.path.build(files, prefix=str(tmp_path)) |
|
|
|
members = run_sdist(monkeypatch, tmp_path / "project") |
|
|
|
for name in members: |
|
assert "file.h" not in name |
|
|
|
|
|
def run_sdist(monkeypatch, project): |
|
"""Given a project directory, run the sdist and return its contents""" |
|
monkeypatch.chdir(project) |
|
with quiet(): |
|
run_setup("setup.py", ["sdist"]) |
|
|
|
archive = next((project / "dist").glob("*.tar.gz")) |
|
with tarfile.open(str(archive)) as tar: |
|
return set(tar.getnames()) |
|
|