|
import os |
|
import sys |
|
import shutil |
|
import signal |
|
import tarfile |
|
import importlib |
|
import contextlib |
|
from concurrent import futures |
|
import re |
|
from zipfile import ZipFile |
|
from pathlib import Path |
|
|
|
import pytest |
|
from jaraco import path |
|
|
|
from .textwrap import DALS |
|
|
|
SETUP_SCRIPT_STUB = "__import__('setuptools').setup()" |
|
|
|
|
|
TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180")) |
|
IS_PYPY = '__pypy__' in sys.builtin_module_names |
|
|
|
|
|
pytestmark = pytest.mark.skipif( |
|
sys.platform == "win32" and IS_PYPY, |
|
reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor " |
|
"is flaky and problematic", |
|
) |
|
|
|
|
|
class BuildBackendBase: |
|
def __init__(self, cwd='.', env=None, backend_name='setuptools.build_meta'): |
|
self.cwd = cwd |
|
self.env = env or {} |
|
self.backend_name = backend_name |
|
|
|
|
|
class BuildBackend(BuildBackendBase): |
|
"""PEP 517 Build Backend""" |
|
|
|
def __init__(self, *args, **kwargs): |
|
super().__init__(*args, **kwargs) |
|
self.pool = futures.ProcessPoolExecutor(max_workers=1) |
|
|
|
def __getattr__(self, name): |
|
"""Handles arbitrary function invocations on the build backend.""" |
|
|
|
def method(*args, **kw): |
|
root = os.path.abspath(self.cwd) |
|
caller = BuildBackendCaller(root, self.env, self.backend_name) |
|
pid = None |
|
try: |
|
pid = self.pool.submit(os.getpid).result(TIMEOUT) |
|
return self.pool.submit(caller, name, *args, **kw).result(TIMEOUT) |
|
except futures.TimeoutError: |
|
self.pool.shutdown(wait=False) |
|
self._kill(pid) |
|
pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)") |
|
except (futures.process.BrokenProcessPool, MemoryError, OSError): |
|
if IS_PYPY: |
|
pytest.xfail("PyPy frequently fails tests with ProcessPoolExector") |
|
raise |
|
|
|
return method |
|
|
|
def _kill(self, pid): |
|
if pid is None: |
|
return |
|
with contextlib.suppress(ProcessLookupError, OSError): |
|
os.kill(pid, signal.SIGTERM if os.name == "nt" else signal.SIGKILL) |
|
|
|
|
|
class BuildBackendCaller(BuildBackendBase): |
|
def __init__(self, *args, **kwargs): |
|
super().__init__(*args, **kwargs) |
|
|
|
(self.backend_name, _, self.backend_obj) = self.backend_name.partition(':') |
|
|
|
def __call__(self, name, *args, **kw): |
|
"""Handles arbitrary function invocations on the build backend.""" |
|
os.chdir(self.cwd) |
|
os.environ.update(self.env) |
|
mod = importlib.import_module(self.backend_name) |
|
|
|
if self.backend_obj: |
|
backend = getattr(mod, self.backend_obj) |
|
else: |
|
backend = mod |
|
|
|
return getattr(backend, name)(*args, **kw) |
|
|
|
|
|
defns = [ |
|
{ |
|
'setup.py': DALS( |
|
""" |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'], |
|
setup_requires=['six'], |
|
) |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
}, |
|
{ |
|
'setup.py': DALS( |
|
""" |
|
assert __name__ == '__main__' |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'], |
|
setup_requires=['six'], |
|
) |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
}, |
|
{ |
|
'setup.py': DALS( |
|
""" |
|
variable = True |
|
def function(): |
|
return variable |
|
assert variable |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'], |
|
setup_requires=['six'], |
|
) |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
}, |
|
{ |
|
'setup.py': DALS( |
|
""" |
|
# Some packages construct files on the fly, include them in the package, |
|
# and immediately remove them after `setup()` (e.g. pybind11==2.9.1). |
|
# Therefore, we cannot use `distutils.core.run_setup(..., stop_after=...)` |
|
# to obtain a distribution object first, and then run the distutils |
|
# commands later, because these files will be removed in the meantime. |
|
|
|
with open('world.py', 'w', encoding="utf-8") as f: |
|
f.write('x = 42') |
|
|
|
try: |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['world'], |
|
setup_requires=['six'], |
|
) |
|
finally: |
|
# Some packages will clean temporary files |
|
__import__('os').unlink('world.py') |
|
""" |
|
), |
|
}, |
|
{ |
|
'setup.cfg': DALS( |
|
""" |
|
[metadata] |
|
name = foo |
|
version = 0.0.0 |
|
|
|
[options] |
|
py_modules=hello |
|
setup_requires=six |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
}, |
|
{ |
|
'setup.cfg': DALS( |
|
""" |
|
[metadata] |
|
name = foo |
|
version = 0.0.0 |
|
|
|
[options] |
|
py_modules=hello |
|
setup_requires=six |
|
""" |
|
), |
|
'setup.py': "__import__('setuptools').setup()", |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
}, |
|
] |
|
|
|
|
|
class TestBuildMetaBackend: |
|
backend_name = 'setuptools.build_meta' |
|
|
|
def get_build_backend(self): |
|
return BuildBackend(backend_name=self.backend_name) |
|
|
|
@pytest.fixture(params=defns) |
|
def build_backend(self, tmpdir, request): |
|
path.build(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'] |
|
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) |
|
|
|
wheel_file = os.path.join(dist_dir, wheel_name) |
|
assert os.path.isfile(wheel_file) |
|
|
|
|
|
assert not os.path.isfile('world.py') |
|
|
|
with ZipFile(wheel_file) as zipfile: |
|
wheel_contents = set(zipfile.namelist()) |
|
|
|
|
|
|
|
python_scripts = (f for f in wheel_contents if f.endswith('.py')) |
|
modules = [f for f in python_scripts if not f.endswith('setup.py')] |
|
assert len(modules) == 1 |
|
|
|
@pytest.mark.parametrize('build_type', ('wheel', 'sdist')) |
|
def test_build_with_existing_file_present(self, build_type, tmpdir_cwd): |
|
|
|
|
|
files = { |
|
'setup.py': "from setuptools import setup\nsetup()", |
|
'VERSION': "0.0.1", |
|
'setup.cfg': DALS( |
|
""" |
|
[metadata] |
|
name = foo |
|
version = file: VERSION |
|
""" |
|
), |
|
'pyproject.toml': DALS( |
|
""" |
|
[build-system] |
|
requires = ["setuptools", "wheel"] |
|
build-backend = "setuptools.build_meta" |
|
""" |
|
), |
|
} |
|
|
|
path.build(files) |
|
|
|
dist_dir = os.path.abspath('preexisting-' + build_type) |
|
|
|
build_backend = self.get_build_backend() |
|
build_method = getattr(build_backend, 'build_' + build_type) |
|
|
|
|
|
|
|
|
|
first_result = build_method(dist_dir) |
|
|
|
|
|
with open("VERSION", "wt", encoding="utf-8") as version_file: |
|
version_file.write("0.0.2") |
|
|
|
|
|
second_result = build_method(dist_dir) |
|
|
|
assert os.path.isfile(os.path.join(dist_dir, first_result)) |
|
assert first_result != second_result |
|
|
|
|
|
open(os.path.join(dist_dir, second_result), 'wb').close() |
|
third_result = build_method(dist_dir) |
|
assert third_result == second_result |
|
assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0 |
|
|
|
@pytest.mark.parametrize("setup_script", [None, SETUP_SCRIPT_STUB]) |
|
def test_build_with_pyproject_config(self, tmpdir, setup_script): |
|
files = { |
|
'pyproject.toml': DALS( |
|
""" |
|
[build-system] |
|
requires = ["setuptools", "wheel"] |
|
build-backend = "setuptools.build_meta" |
|
|
|
[project] |
|
name = "foo" |
|
license = {text = "MIT"} |
|
description = "This is a Python package" |
|
dynamic = ["version", "readme"] |
|
classifiers = [ |
|
"Development Status :: 5 - Production/Stable", |
|
"Intended Audience :: Developers" |
|
] |
|
urls = {Homepage = "http://github.com"} |
|
dependencies = [ |
|
"appdirs", |
|
] |
|
|
|
[project.optional-dependencies] |
|
all = [ |
|
"tomli>=1", |
|
"pyscaffold>=4,<5", |
|
'importlib; python_version == "2.6"', |
|
] |
|
|
|
[project.scripts] |
|
foo = "foo.cli:main" |
|
|
|
[tool.setuptools] |
|
zip-safe = false |
|
package-dir = {"" = "src"} |
|
packages = {find = {where = ["src"]}} |
|
license-files = ["LICENSE*"] |
|
|
|
[tool.setuptools.dynamic] |
|
version = {attr = "foo.__version__"} |
|
readme = {file = "README.rst"} |
|
|
|
[tool.distutils.sdist] |
|
formats = "gztar" |
|
|
|
[tool.distutils.bdist_wheel] |
|
universal = true |
|
""" |
|
), |
|
"MANIFEST.in": DALS( |
|
""" |
|
global-include *.py *.txt |
|
global-exclude *.py[cod] |
|
""" |
|
), |
|
"README.rst": "This is a ``README``", |
|
"LICENSE.txt": "---- placeholder MIT license ----", |
|
"src": { |
|
"foo": { |
|
"__init__.py": "__version__ = '0.1'", |
|
"__init__.pyi": "__version__: str", |
|
"cli.py": "def main(): print('hello world')", |
|
"data.txt": "def main(): print('hello world')", |
|
"py.typed": "", |
|
} |
|
}, |
|
} |
|
if setup_script: |
|
files["setup.py"] = setup_script |
|
|
|
build_backend = self.get_build_backend() |
|
with tmpdir.as_cwd(): |
|
path.build(files) |
|
sdist_path = build_backend.build_sdist("temp") |
|
wheel_file = build_backend.build_wheel("temp") |
|
|
|
with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar: |
|
sdist_contents = set(tar.getnames()) |
|
|
|
with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile: |
|
wheel_contents = set(zipfile.namelist()) |
|
metadata = str(zipfile.read("foo-0.1.dist-info/METADATA"), "utf-8") |
|
license = str(zipfile.read("foo-0.1.dist-info/LICENSE.txt"), "utf-8") |
|
epoints = str(zipfile.read("foo-0.1.dist-info/entry_points.txt"), "utf-8") |
|
|
|
assert sdist_contents - {"foo-0.1/setup.py"} == { |
|
'foo-0.1', |
|
'foo-0.1/LICENSE.txt', |
|
'foo-0.1/MANIFEST.in', |
|
'foo-0.1/PKG-INFO', |
|
'foo-0.1/README.rst', |
|
'foo-0.1/pyproject.toml', |
|
'foo-0.1/setup.cfg', |
|
'foo-0.1/src', |
|
'foo-0.1/src/foo', |
|
'foo-0.1/src/foo/__init__.py', |
|
'foo-0.1/src/foo/__init__.pyi', |
|
'foo-0.1/src/foo/cli.py', |
|
'foo-0.1/src/foo/data.txt', |
|
'foo-0.1/src/foo/py.typed', |
|
'foo-0.1/src/foo.egg-info', |
|
'foo-0.1/src/foo.egg-info/PKG-INFO', |
|
'foo-0.1/src/foo.egg-info/SOURCES.txt', |
|
'foo-0.1/src/foo.egg-info/dependency_links.txt', |
|
'foo-0.1/src/foo.egg-info/entry_points.txt', |
|
'foo-0.1/src/foo.egg-info/requires.txt', |
|
'foo-0.1/src/foo.egg-info/top_level.txt', |
|
'foo-0.1/src/foo.egg-info/not-zip-safe', |
|
} |
|
assert wheel_contents == { |
|
"foo/__init__.py", |
|
"foo/__init__.pyi", |
|
"foo/cli.py", |
|
"foo/data.txt", |
|
"foo/py.typed", |
|
"foo-0.1.dist-info/LICENSE.txt", |
|
"foo-0.1.dist-info/METADATA", |
|
"foo-0.1.dist-info/WHEEL", |
|
"foo-0.1.dist-info/entry_points.txt", |
|
"foo-0.1.dist-info/top_level.txt", |
|
"foo-0.1.dist-info/RECORD", |
|
} |
|
assert license == "---- placeholder MIT license ----" |
|
|
|
metadata = metadata.replace("(", "").replace(")", "") |
|
|
|
|
|
for line in ( |
|
"Summary: This is a Python package", |
|
"License: MIT", |
|
"Classifier: Intended Audience :: Developers", |
|
"Requires-Dist: appdirs", |
|
"Requires-Dist: tomli >=1 ; extra == 'all'", |
|
"Requires-Dist: importlib ; python_version == \"2.6\" and extra == 'all'", |
|
): |
|
assert line in metadata |
|
|
|
assert metadata.strip().endswith("This is a ``README``") |
|
assert epoints.strip() == "[console_scripts]\nfoo = foo.cli:main" |
|
|
|
def test_static_metadata_in_pyproject_config(self, tmpdir): |
|
|
|
|
|
files = { |
|
'pyproject.toml': DALS( |
|
""" |
|
[build-system] |
|
requires = ["setuptools", "wheel"] |
|
build-backend = "setuptools.build_meta" |
|
|
|
[project] |
|
name = "foo" |
|
description = "This is a Python package" |
|
version = "42" |
|
dependencies = ["six"] |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
'setup.py': DALS( |
|
""" |
|
__import__('setuptools').setup( |
|
name='bar', |
|
version='13', |
|
) |
|
""" |
|
), |
|
} |
|
build_backend = self.get_build_backend() |
|
with tmpdir.as_cwd(): |
|
path.build(files) |
|
sdist_path = build_backend.build_sdist("temp") |
|
wheel_file = build_backend.build_wheel("temp") |
|
|
|
assert (tmpdir / "temp/foo-42.tar.gz").exists() |
|
assert (tmpdir / "temp/foo-42-py3-none-any.whl").exists() |
|
assert not (tmpdir / "temp/bar-13.tar.gz").exists() |
|
assert not (tmpdir / "temp/bar-42.tar.gz").exists() |
|
assert not (tmpdir / "temp/foo-13.tar.gz").exists() |
|
assert not (tmpdir / "temp/bar-13-py3-none-any.whl").exists() |
|
assert not (tmpdir / "temp/bar-42-py3-none-any.whl").exists() |
|
assert not (tmpdir / "temp/foo-13-py3-none-any.whl").exists() |
|
|
|
with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar: |
|
pkg_info = str(tar.extractfile('foo-42/PKG-INFO').read(), "utf-8") |
|
members = tar.getnames() |
|
assert "bar-13/PKG-INFO" not in members |
|
|
|
with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile: |
|
metadata = str(zipfile.read("foo-42.dist-info/METADATA"), "utf-8") |
|
members = zipfile.namelist() |
|
assert "bar-13.dist-info/METADATA" not in members |
|
|
|
for file in pkg_info, metadata: |
|
for line in ("Name: foo", "Version: 42"): |
|
assert line in file |
|
for line in ("Name: bar", "Version: 13"): |
|
assert line not in file |
|
|
|
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')) |
|
|
|
def test_prepare_metadata_inplace(self, build_backend): |
|
""" |
|
Some users might pass metadata_directory pre-populated with `.tox` or `.venv`. |
|
See issue #3523. |
|
""" |
|
for pre_existing in [ |
|
".tox/python/lib/python3.10/site-packages/attrs-22.1.0.dist-info", |
|
".tox/python/lib/python3.10/site-packages/autocommand-2.2.1.dist-info", |
|
".nox/python/lib/python3.10/site-packages/build-0.8.0.dist-info", |
|
".venv/python3.10/site-packages/click-8.1.3.dist-info", |
|
"venv/python3.10/site-packages/distlib-0.3.5.dist-info", |
|
"env/python3.10/site-packages/docutils-0.19.dist-info", |
|
]: |
|
os.makedirs(pre_existing, exist_ok=True) |
|
dist_info = build_backend.prepare_metadata_for_build_wheel(".") |
|
assert os.path.isfile(os.path.join(dist_info, 'METADATA')) |
|
|
|
def test_build_sdist_explicit_dist(self, build_backend): |
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
|
setup_loc = os.path.abspath("setup.py") |
|
if not os.path.exists(setup_loc): |
|
setup_loc = os.path.abspath("setup.cfg") |
|
|
|
with open(setup_loc, 'rt', encoding="utf-8") as file_handler: |
|
content = file_handler.read() |
|
with open(setup_loc, 'wt', encoding="utf-8") 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_pyproject_toml_exists(self, tmpdir_cwd): |
|
files = { |
|
'setup.py': DALS( |
|
""" |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'] |
|
)""" |
|
), |
|
'hello.py': '', |
|
'pyproject.toml': DALS( |
|
""" |
|
[build-system] |
|
requires = ["setuptools", "wheel"] |
|
build-backend = "setuptools.build_meta" |
|
""" |
|
), |
|
} |
|
path.build(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 any('pyproject.toml' in name for name in tar.getnames()) |
|
|
|
def test_build_sdist_setup_py_exists(self, tmpdir_cwd): |
|
|
|
|
|
path.build(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): |
|
|
|
files = { |
|
'setup.py': DALS( |
|
""" |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'] |
|
)""" |
|
), |
|
'hello.py': '', |
|
'MANIFEST.in': DALS( |
|
""" |
|
exclude setup.py |
|
""" |
|
), |
|
} |
|
|
|
path.build(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 |
|
""" |
|
), |
|
} |
|
|
|
path.build(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=__import__('hello').__version__, |
|
py_modules=['hello'] |
|
)""" |
|
), |
|
'hello.py': '__version__ = "0.0.0"', |
|
'setup.cfg': DALS( |
|
""" |
|
[sdist] |
|
formats=zip |
|
""" |
|
), |
|
} |
|
|
|
def test_build_sdist_relative_path_import(self, tmpdir_cwd): |
|
path.build(self._relative_path_import_files) |
|
build_backend = self.get_build_backend() |
|
with pytest.raises(ImportError, match="^No module named 'hello'$"): |
|
build_backend.build_sdist("temp") |
|
|
|
_simple_pyproject_example = { |
|
"pyproject.toml": DALS( |
|
""" |
|
[project] |
|
name = "proj" |
|
version = "42" |
|
""" |
|
), |
|
"src": {"proj": {"__init__.py": ""}}, |
|
} |
|
|
|
def _assert_link_tree(self, parent_dir): |
|
"""All files in the directory should be either links or hard links""" |
|
files = list(Path(parent_dir).glob("**/*")) |
|
assert files |
|
for file in files: |
|
assert file.is_symlink() or os.stat(file).st_nlink > 0 |
|
|
|
def test_editable_without_config_settings(self, tmpdir_cwd): |
|
""" |
|
Sanity check to ensure tests with --mode=strict are different from the ones |
|
without --mode. |
|
|
|
--mode=strict should create a local directory with a package tree. |
|
The directory should not get created otherwise. |
|
""" |
|
path.build(self._simple_pyproject_example) |
|
build_backend = self.get_build_backend() |
|
assert not Path("build").exists() |
|
build_backend.build_editable("temp") |
|
assert not Path("build").exists() |
|
|
|
def test_build_wheel_inplace(self, tmpdir_cwd): |
|
config_settings = {"--build-option": ["build_ext", "--inplace"]} |
|
path.build(self._simple_pyproject_example) |
|
build_backend = self.get_build_backend() |
|
assert not Path("build").exists() |
|
Path("build").mkdir() |
|
build_backend.prepare_metadata_for_build_wheel("build", config_settings) |
|
build_backend.build_wheel("build", config_settings) |
|
assert Path("build/proj-42-py3-none-any.whl").exists() |
|
|
|
@pytest.mark.parametrize("config_settings", [{"editable-mode": "strict"}]) |
|
def test_editable_with_config_settings(self, tmpdir_cwd, config_settings): |
|
path.build({**self._simple_pyproject_example, '_meta': {}}) |
|
assert not Path("build").exists() |
|
build_backend = self.get_build_backend() |
|
build_backend.prepare_metadata_for_build_editable("_meta", config_settings) |
|
build_backend.build_editable("temp", config_settings, "_meta") |
|
self._assert_link_tree(next(Path("build").glob("__editable__.*"))) |
|
|
|
@pytest.mark.parametrize( |
|
'setup_literal, requirements', |
|
[ |
|
("'foo'", ['foo']), |
|
("['foo']", ['foo']), |
|
(r"'foo\n'", ['foo']), |
|
(r"'foo\n\n'", ['foo']), |
|
("['foo', 'bar']", ['foo', 'bar']), |
|
(r"'# Has a comment line\nfoo'", ['foo']), |
|
(r"'foo # Has an inline comment'", ['foo']), |
|
(r"'foo \\\n >=3.0'", ['foo>=3.0']), |
|
(r"'foo\nbar'", ['foo', 'bar']), |
|
(r"'foo\nbar\n'", ['foo', 'bar']), |
|
(r"['foo\n', 'bar\n']", ['foo', 'bar']), |
|
], |
|
) |
|
@pytest.mark.parametrize('use_wheel', [True, False]) |
|
def test_setup_requires(self, setup_literal, requirements, use_wheel, tmpdir_cwd): |
|
files = { |
|
'setup.py': DALS( |
|
""" |
|
from setuptools import setup |
|
|
|
setup( |
|
name="qux", |
|
version="0.0.0", |
|
py_modules=["hello"], |
|
setup_requires={setup_literal}, |
|
) |
|
""" |
|
).format(setup_literal=setup_literal), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
} |
|
|
|
path.build(files) |
|
|
|
build_backend = self.get_build_backend() |
|
|
|
if use_wheel: |
|
get_requires = build_backend.get_requires_for_build_wheel |
|
else: |
|
get_requires = build_backend.get_requires_for_build_sdist |
|
|
|
|
|
expected = sorted(requirements) |
|
actual = get_requires() |
|
|
|
assert expected == sorted(actual) |
|
|
|
def test_setup_requires_with_auto_discovery(self, tmpdir_cwd): |
|
|
|
|
|
|
|
files = { |
|
'pyproject.toml': DALS( |
|
""" |
|
[project] |
|
name = "proj" |
|
version = "42" |
|
""" |
|
), |
|
"setup.py": DALS( |
|
""" |
|
__import__('setuptools').setup( |
|
setup_requires=["foo"], |
|
py_modules = ["hello", "world"] |
|
) |
|
""" |
|
), |
|
'hello.py': "'hello'", |
|
'world.py': "'world'", |
|
} |
|
path.build(files) |
|
build_backend = self.get_build_backend() |
|
setup_requires = build_backend.get_requires_for_build_wheel() |
|
assert setup_requires == ["foo"] |
|
|
|
def test_dont_install_setup_requires(self, tmpdir_cwd): |
|
files = { |
|
'setup.py': DALS( |
|
""" |
|
from setuptools import setup |
|
|
|
setup( |
|
name="qux", |
|
version="0.0.0", |
|
py_modules=["hello"], |
|
setup_requires=["does-not-exist >99"], |
|
) |
|
""" |
|
), |
|
'hello.py': DALS( |
|
""" |
|
def run(): |
|
print('hello') |
|
""" |
|
), |
|
} |
|
|
|
path.build(files) |
|
|
|
build_backend = self.get_build_backend() |
|
|
|
dist_dir = os.path.abspath('pip-dist-info') |
|
os.makedirs(dist_dir) |
|
|
|
|
|
|
|
build_backend.prepare_metadata_for_build_wheel(dist_dir) |
|
|
|
_sys_argv_0_passthrough = { |
|
'setup.py': DALS( |
|
""" |
|
import os |
|
import sys |
|
|
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
) |
|
|
|
sys_argv = os.path.abspath(sys.argv[0]) |
|
file_path = os.path.abspath('setup.py') |
|
assert sys_argv == file_path |
|
""" |
|
) |
|
} |
|
|
|
def test_sys_argv_passthrough(self, tmpdir_cwd): |
|
path.build(self._sys_argv_0_passthrough) |
|
build_backend = self.get_build_backend() |
|
with pytest.raises(AssertionError): |
|
build_backend.build_sdist("temp") |
|
|
|
_setup_py_file_abspath = { |
|
'setup.py': DALS( |
|
""" |
|
import os |
|
assert os.path.isabs(__file__) |
|
__import__('setuptools').setup( |
|
name='foo', |
|
version='0.0.0', |
|
py_modules=['hello'], |
|
setup_requires=['six'], |
|
) |
|
""" |
|
) |
|
} |
|
|
|
def test_setup_py_file_abspath(self, tmpdir_cwd): |
|
path.build(self._setup_py_file_abspath) |
|
build_backend = self.get_build_backend() |
|
build_backend.build_sdist("temp") |
|
|
|
@pytest.mark.parametrize('build_hook', ('build_sdist', 'build_wheel')) |
|
def test_build_with_empty_setuppy(self, build_backend, build_hook): |
|
files = {'setup.py': ''} |
|
path.build(files) |
|
|
|
msg = re.escape('No distribution was found.') |
|
with pytest.raises(ValueError, match=msg): |
|
getattr(build_backend, build_hook)("temp") |
|
|
|
|
|
class TestBuildMetaLegacyBackend(TestBuildMetaBackend): |
|
backend_name = 'setuptools.build_meta:__legacy__' |
|
|
|
|
|
def test_build_sdist_relative_path_import(self, tmpdir_cwd): |
|
|
|
path.build(self._relative_path_import_files) |
|
|
|
build_backend = self.get_build_backend() |
|
build_backend.build_sdist("temp") |
|
|
|
def test_sys_argv_passthrough(self, tmpdir_cwd): |
|
path.build(self._sys_argv_0_passthrough) |
|
|
|
build_backend = self.get_build_backend() |
|
build_backend.build_sdist("temp") |
|
|
|
|
|
def test_legacy_editable_install(venv, tmpdir, tmpdir_cwd): |
|
pyproject = """ |
|
[build-system] |
|
requires = ["setuptools"] |
|
build-backend = "setuptools.build_meta" |
|
[project] |
|
name = "myproj" |
|
version = "42" |
|
""" |
|
path.build({"pyproject.toml": DALS(pyproject), "mymod.py": ""}) |
|
|
|
|
|
cmd = ["pip", "install", "--no-build-isolation", "-e", "."] |
|
output = venv.run(cmd, cwd=tmpdir).lower() |
|
assert "running setup.py develop for myproj" not in output |
|
assert "created wheel for myproj" in output |
|
|
|
|
|
env = {**os.environ, "SETUPTOOLS_ENABLE_FEATURES": "legacy-editable"} |
|
cmd = ["pip", "install", "--no-build-isolation", "-e", "."] |
|
output = venv.run(cmd, cwd=tmpdir, env=env).lower() |
|
assert "running setup.py develop for myproj" in output |
|
|
|
|
|
@pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning") |
|
def test_sys_exit_0_in_setuppy(monkeypatch, tmp_path): |
|
"""Setuptools should be resilient to setup.py with ``sys.exit(0)`` (#3973).""" |
|
monkeypatch.chdir(tmp_path) |
|
setuppy = """ |
|
import sys, setuptools |
|
setuptools.setup(name='foo', version='0.0.0') |
|
sys.exit(0) |
|
""" |
|
(tmp_path / "setup.py").write_text(DALS(setuppy), encoding="utf-8") |
|
backend = BuildBackend(backend_name="setuptools.build_meta") |
|
assert backend.get_requires_for_build_wheel() == [] |
|
|
|
|
|
def test_system_exit_in_setuppy(monkeypatch, tmp_path): |
|
monkeypatch.chdir(tmp_path) |
|
setuppy = "import sys; sys.exit('some error')" |
|
(tmp_path / "setup.py").write_text(setuppy, encoding="utf-8") |
|
with pytest.raises(SystemExit, match="some error"): |
|
backend = BuildBackend(backend_name="setuptools.build_meta") |
|
backend.get_requires_for_build_wheel() |
|
|