Merge pull request #10082 from uranusjr/setup-py-freeze

Unify Python project root detection logic
This commit is contained in:
Tzu-ping Chung 2021-06-20 05:37:49 +08:00 committed by GitHub
commit 4704da4dad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 42 additions and 33 deletions

1
news/10080.bugfix.rst Normal file
View File

@ -0,0 +1 @@
Correctly allow PEP 517 projects to be detected without warnings in ``pip freeze``.

View File

@ -39,7 +39,7 @@ from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.filesystem import copy2_fixed
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import display_path, hide_url, rmtree
from pip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.unpacking import unpack_file
from pip._internal.vcs import vcs
@ -376,7 +376,7 @@ class RequirementPreparer:
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
if is_installable_dir(req.source_dir):
raise PreviousBuildDirError(
"pip can't proceed with requirements '{}' due to a"
"pre-existing build directory ({}). This is likely "

View File

@ -248,8 +248,8 @@ def _looks_like_path(name):
def _get_url_from_path(path, name):
# type: (str, str) -> Optional[str]
"""
First, it checks whether a provided path is an installable directory
(e.g. it has a setup.py). If it is, returns the path.
First, it checks whether a provided path is an installable directory. If it
is, returns the path.
If false, check if the path is an archive file (such as a .whl).
The function checks if the path is a file. If false, if the path has

View File

@ -270,13 +270,20 @@ def tabulate(rows):
def is_installable_dir(path: str) -> bool:
"""Is path is a directory containing pyproject.toml, setup.cfg or setup.py?"""
"""Is path is a directory containing pyproject.toml or setup.py?
If pyproject.toml exists, this is a PEP 517 project. Otherwise we look for
a legacy setuptools layout by identifying setup.py. We don't check for the
setup.cfg because using it without setup.py is only available for PEP 517
projects, which are already covered by the pyproject.toml check.
"""
if not os.path.isdir(path):
return False
return any(
os.path.isfile(os.path.join(path, signifier))
for signifier in ("pyproject.toml", "setup.cfg", "setup.py")
)
if os.path.isfile(os.path.join(path, "pyproject.toml")):
return True
if os.path.isfile(os.path.join(path, "setup.py")):
return True
return False
def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):

View File

@ -18,7 +18,7 @@ from pip._internal.vcs.versioncontrol import (
RemoteNotValidError,
RevOptions,
VersionControl,
find_path_to_setup_from_repo_root,
find_path_to_project_root_from_repo_root,
vcs,
)
@ -410,8 +410,8 @@ class Git(VersionControl):
def get_subdirectory(cls, location):
# type: (str) -> Optional[str]
"""
Return the path to setup.py, relative to the repo root.
Return None if setup.py is in the repo root.
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
"""
# find the repo root
git_dir = cls.run_command(
@ -423,7 +423,7 @@ class Git(VersionControl):
if not os.path.isabs(git_dir):
git_dir = os.path.join(location, git_dir)
repo_root = os.path.abspath(os.path.join(git_dir, '..'))
return find_path_to_setup_from_repo_root(location, repo_root)
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
def get_url_rev_and_auth(cls, url):

View File

@ -10,7 +10,7 @@ from pip._internal.utils.urls import path_to_url
from pip._internal.vcs.versioncontrol import (
RevOptions,
VersionControl,
find_path_to_setup_from_repo_root,
find_path_to_project_root_from_repo_root,
vcs,
)
@ -120,8 +120,8 @@ class Mercurial(VersionControl):
def get_subdirectory(cls, location):
# type: (str) -> Optional[str]
"""
Return the path to setup.py, relative to the repo root.
Return None if setup.py is in the repo root.
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
"""
# find the repo root
repo_root = cls.run_command(
@ -129,7 +129,7 @@ class Mercurial(VersionControl):
).strip()
if not os.path.isabs(repo_root):
repo_root = os.path.abspath(os.path.join(location, repo_root))
return find_path_to_setup_from_repo_root(location, repo_root)
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
def get_repository_root(cls, location):

View File

@ -7,6 +7,7 @@ from pip._internal.utils.misc import (
HiddenText,
display_path,
is_console_interactive,
is_installable_dir,
split_auth_from_netloc,
)
from pip._internal.utils.subprocess import CommandArgs, make_command
@ -111,18 +112,17 @@ class Subversion(VersionControl):
@classmethod
def get_remote_url(cls, location):
# type: (str) -> str
# In cases where the source is in a subdirectory, not alongside
# setup.py we have to look up in the location until we find a real
# setup.py
# In cases where the source is in a subdirectory, we have to look up in
# the location until we find a valid project root.
orig_location = location
while not os.path.exists(os.path.join(location, 'setup.py')):
while not is_installable_dir(location):
last_location = location
location = os.path.dirname(location)
if location == last_location:
# We've traversed up to the root of the filesystem without
# finding setup.py
# finding a Python project.
logger.warning(
"Could not find setup.py for directory %s (tried all "
"Could not find Python project for directory %s (tried all "
"parent directories)",
orig_location,
)

View File

@ -27,6 +27,7 @@ from pip._internal.utils.misc import (
display_path,
hide_url,
hide_value,
is_installable_dir,
rmtree,
)
from pip._internal.utils.subprocess import CommandArgs, call_subprocess, make_command
@ -68,23 +69,23 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
return req
def find_path_to_setup_from_repo_root(location, repo_root):
def find_path_to_project_root_from_repo_root(location, repo_root):
# type: (str, str) -> Optional[str]
"""
Find the path to `setup.py` by searching up the filesystem from `location`.
Return the path to `setup.py` relative to `repo_root`.
Return None if `setup.py` is in `repo_root` or cannot be found.
Find the the Python project's root by searching up the filesystem from
`location`. Return the path to project root relative to `repo_root`.
Return None if the project root is `repo_root`, or cannot be found.
"""
# find setup.py
# find project root.
orig_location = location
while not os.path.exists(os.path.join(location, 'setup.py')):
while not is_installable_dir(location):
last_location = location
location = os.path.dirname(location)
if location == last_location:
# We've traversed up to the root of the filesystem without
# finding setup.py
# finding a Python project.
logger.warning(
"Could not find setup.py for directory %s (tried all "
"Could not find a Python project for directory %s (tried all "
"parent directories)",
orig_location,
)
@ -296,8 +297,8 @@ class VersionControl:
def get_subdirectory(cls, location):
# type: (str) -> Optional[str]
"""
Return the path to setup.py, relative to the repo root.
Return None if setup.py is in the repo root.
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
"""
return None