Skip to content

Commit a23f983

Browse files
committed
Fix missing dependencies with setup.py install (#3212)
2 parents 6310e32 + bf5c69f commit a23f983

File tree

5 files changed

+82
-10
lines changed

5 files changed

+82
-10
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ Documentation changes
704704
:user:`abravalheri`
705705

706706

707+
.. _setup_install_deprecation_note:
708+
707709
v58.3.0
708710
-------
709711

changelog.d/3212.misc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed missing dependencies when running ``setup.py install``.
2+
Note that calling ``setup.py install`` directly is still deprecated and support
3+
for this command will be removed in future versions of ``setuptools``.
4+
Please check the release notes for :ref:`setup_install_deprecation_note`.

setuptools/command/easy_install.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME
298298

299299
if not self.editable:
300300
self.check_site_dir()
301-
self.index_url = self.index_url or "https://pypi.org/simple/"
301+
default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")
302+
# ^ Private API for testing purposes only
303+
self.index_url = self.index_url or default_index
302304
self.shadow_path = self.all_site_dirs[:]
303305
for path_item in self.install_dir, normalize_path(self.script_dir):
304306
if path_item not in self.shadow_path:

setuptools/command/install.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,21 @@ def _called_from_setup(run_frame):
9191
msg = "For best results, pass -X:Frames to enable call stack."
9292
warnings.warn(msg)
9393
return True
94-
res = inspect.getouterframes(run_frame)[2]
95-
caller, = res[:1]
96-
info = inspect.getframeinfo(caller)
97-
caller_module = caller.f_globals.get('__name__', '')
98-
return (
99-
caller_module == 'distutils.dist'
100-
and info.function == 'run_commands'
101-
)
94+
95+
frames = inspect.getouterframes(run_frame)
96+
for frame in frames[2:4]:
97+
caller, = frame[:1]
98+
info = inspect.getframeinfo(caller)
99+
caller_module = caller.f_globals.get('__name__', '')
100+
101+
if caller_module == "setuptools.dist" and info.function == "run_command":
102+
# Starting from v61.0.0 setuptools overwrites dist.run_command
103+
continue
104+
105+
return (
106+
caller_module == 'distutils.dist'
107+
and info.function == 'run_commands'
108+
)
102109

103110
def do_egg_install(self):
104111

setuptools/tests/test_easy_install.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,63 @@ def test_bdist_egg_available_on_distutils_pkg(self, distutils_package):
448448
run_setup('setup.py', ['bdist_egg'])
449449

450450

451+
class TestInstallRequires:
452+
def test_setup_install_includes_dependencies(self, tmp_path, mock_index):
453+
"""
454+
When ``python setup.py install`` is called directly, it will use easy_install
455+
to fetch dependencies.
456+
"""
457+
# TODO: Remove these tests once `setup.py install` is completely removed
458+
project_root = tmp_path / "project"
459+
project_root.mkdir(exist_ok=True)
460+
install_root = tmp_path / "install"
461+
install_root.mkdir(exist_ok=True)
462+
463+
self.create_project(project_root)
464+
cmd = [
465+
sys.executable,
466+
'-c', '__import__("setuptools").setup()',
467+
'install',
468+
'--install-base', str(install_root),
469+
'--install-lib', str(install_root),
470+
'--install-headers', str(install_root),
471+
'--install-scripts', str(install_root),
472+
'--install-data', str(install_root),
473+
'--install-purelib', str(install_root),
474+
'--install-platlib', str(install_root),
475+
]
476+
env = {"PYTHONPATH": str(install_root), "__EASYINSTALL_INDEX": mock_index.url}
477+
with pytest.raises(subprocess.CalledProcessError) as exc_info:
478+
subprocess.check_output(
479+
cmd, cwd=str(project_root), env=env, stderr=subprocess.STDOUT, text=True
480+
)
481+
try:
482+
assert '/does-not-exist/' in {r.path for r in mock_index.requests}
483+
assert next(
484+
line
485+
for line in exc_info.value.output.splitlines()
486+
if "not find suitable distribution for" in line
487+
and "does-not-exist" in line
488+
)
489+
except Exception:
490+
if "failed to get random numbers" in exc_info.value.output:
491+
pytest.xfail(f"{sys.platform} failure - {exc_info.value.output}")
492+
raise
493+
494+
def create_project(self, root):
495+
config = """
496+
[metadata]
497+
name = project
498+
version = 42
499+
500+
[options]
501+
install_requires = does-not-exist
502+
py_modules = mod
503+
"""
504+
(root / 'setup.cfg').write_text(DALS(config), encoding="utf-8")
505+
(root / 'mod.py').touch()
506+
507+
451508
class TestSetupRequires:
452509

453510
def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch):
@@ -466,7 +523,7 @@ def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch):
466523
with contexts.environment(PYTHONPATH=temp_install_dir):
467524
cmd = [
468525
sys.executable,
469-
'-m', 'setup',
526+
'-c', '__import__("setuptools").setup()',
470527
'easy_install',
471528
'--index-url', mock_index.url,
472529
'--exclude-scripts',

0 commit comments

Comments
 (0)