Skip to content

Commit d619aba

Browse files
committed
Provide a better error message for a pyproject.toml editable install.
The message looks like this: File "setup.py" not found. Directory cannot be installed in editable mode: <absolute-dir-path> (A "pyproject.toml" file was found, but editable mode currently requires a setup.py based build.)
1 parent e5f4bbb commit d619aba

File tree

5 files changed

+59
-14
lines changed

5 files changed

+59
-14
lines changed

news/6170.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Provide a better error message if attempting an editable install of a
2+
directory with a ``pyproject.toml`` but no ``setup.py``.

src/pip/_internal/pyproject.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io
44
import os
5+
import sys
56

67
from pip._vendor import pytoml, six
78

@@ -20,6 +21,17 @@ def _is_list_of_str(obj):
2021
)
2122

2223

24+
def make_pyproject_path(setup_py_dir):
25+
# type: (str) -> str
26+
path = os.path.join(setup_py_dir, 'pyproject.toml')
27+
28+
# Python2 __file__ should not be unicode
29+
if six.PY2 and isinstance(path, six.text_type):
30+
path = path.encode(sys.getfilesystemencoding())
31+
32+
return path
33+
34+
2335
def load_pyproject_toml(
2436
use_pep517, # type: Optional[bool]
2537
pyproject_toml, # type: str

src/pip/_internal/req/constructors.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from pip._internal.exceptions import InstallationError
2424
from pip._internal.models.index import PyPI, TestPyPI
2525
from pip._internal.models.link import Link
26+
from pip._internal.pyproject import make_pyproject_path
2627
from pip._internal.req.req_install import InstallRequirement
2728
from pip._internal.utils.misc import is_installable_dir
2829
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -77,10 +78,18 @@ def parse_editable(editable_req):
7778

7879
if os.path.isdir(url_no_extras):
7980
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
80-
raise InstallationError(
81-
"Directory %r is not installable. File 'setup.py' not found." %
82-
url_no_extras
81+
msg = (
82+
'File "setup.py" not found. Directory cannot be installed '
83+
'in editable mode: {}'.format(os.path.abspath(url_no_extras))
8384
)
85+
pyproject_path = make_pyproject_path(url_no_extras)
86+
if os.path.isfile(pyproject_path):
87+
msg += (
88+
'\n(A "pyproject.toml" file was found, but editable '
89+
'mode currently requires a setup.py based build.)'
90+
)
91+
raise InstallationError(msg)
92+
8493
# Treating it as code that has already been checked out
8594
url_no_extras = path_to_url(url_no_extras)
8695

src/pip/_internal/req/req_install.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
2323
)
2424
from pip._internal.models.link import Link
25-
from pip._internal.pyproject import load_pyproject_toml
25+
from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
2626
from pip._internal.req.req_uninstall import UninstallPathSet
2727
from pip._internal.utils.compat import native_str
2828
from pip._internal.utils.hashes import Hashes
@@ -471,13 +471,7 @@ def pyproject_toml(self):
471471
# type: () -> str
472472
assert self.source_dir, "No source dir for %s" % self
473473

474-
pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml')
475-
476-
# Python2 __file__ should not be unicode
477-
if six.PY2 and isinstance(pp_toml, six.text_type):
478-
pp_toml = pp_toml.encode(sys.getfilesystemencoding())
479-
480-
return pp_toml
474+
return make_pyproject_path(self.setup_py_dir)
481475

482476
def load_pyproject_toml(self):
483477
# type: () -> None

tests/functional/test_install.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,13 +491,41 @@ def test_install_from_local_directory_with_no_setup_py(script, data):
491491
assert "Neither 'setup.py' nor 'pyproject.toml' found." in result.stderr
492492

493493

494-
def test_editable_install_from_local_directory_with_no_setup_py(script, data):
494+
def test_editable_install__local_dir_no_setup_py(
495+
script, data, deprecated_python):
495496
"""
496-
Test installing from a local directory with no 'setup.py'.
497+
Test installing in editable mode from a local directory with no setup.py.
497498
"""
498499
result = script.pip('install', '-e', data.root, expect_error=True)
499500
assert not result.files_created
500-
assert "is not installable. File 'setup.py' not found." in result.stderr
501+
502+
msg = result.stderr
503+
if deprecated_python:
504+
assert 'File "setup.py" not found. ' in msg
505+
else:
506+
assert msg.startswith('File "setup.py" not found. ')
507+
assert 'pyproject.toml' not in msg
508+
509+
510+
def test_editable_install__local_dir_no_setup_py_with_pyproject(
511+
script, deprecated_python):
512+
"""
513+
Test installing in editable mode from a local directory with no setup.py
514+
but that does have pyproject.toml.
515+
"""
516+
local_dir = script.scratch_path.join('temp').mkdir()
517+
pyproject_path = local_dir.join('pyproject.toml')
518+
pyproject_path.write('')
519+
520+
result = script.pip('install', '-e', local_dir, expect_error=True)
521+
assert not result.files_created
522+
523+
msg = result.stderr
524+
if deprecated_python:
525+
assert 'File "setup.py" not found. ' in msg
526+
else:
527+
assert msg.startswith('File "setup.py" not found. ')
528+
assert 'A "pyproject.toml" file was found' in msg
501529

502530

503531
@pytest.mark.skipif("sys.version_info >= (3,4)")

0 commit comments

Comments
 (0)