build in place

This commit is contained in:
Stéphane Bidoul 2020-03-22 14:18:11 +01:00
parent 8cca170ceb
commit 88e6e6bc5c
No known key found for this signature in database
GPG Key ID: BCAB2555446B5B92
4 changed files with 40 additions and 64 deletions

View File

@ -728,8 +728,13 @@ You can install local projects by specifying the project path to pip::
$ pip install path/to/SomeProject $ pip install path/to/SomeProject
During regular installation, pip will copy the entire project directory to a temporary location and install from there. Until version 20.0, pip did copy the entire project directory to a temporary
The exception is that pip will exclude .tox and .nox directories present in the top level of the project from being copied. location and installed from there. This approach was the cause of several
performance and correctness issues. As of version 20.1 pip installs from the
local project directory. Depending on the build backend used by the project,
this may generate secondary build artifacts in the project directory, such as
the ``.egg-info`` and ``build`` directories in the case of the setuptools
backend.
.. _`editable-installs`: .. _`editable-installs`:

9
news/7555.removal Normal file
View File

@ -0,0 +1,9 @@
Building of local directories is now done in place. Previously pip did copy the
local directory tree to a temporary location before building. That approach had
a number of drawbacks, among which performance issues, as well as various
issues arising when the python project directory depends on its parent
directory (such as the presence of a VCS directory). The user visible effect of
this change is that secondary build artifacts, if any, may therefore be created
in the local directory, whereas before they were created in a temporary copy of
the directory and then deleted. This notably includes the ``build`` and
``.egg-info`` directories in the case of the setuptools build backend.

View File

@ -27,12 +27,7 @@ from pip._internal.exceptions import (
from pip._internal.utils.filesystem import copy2_fixed from pip._internal.utils.filesystem import copy2_fixed
from pip._internal.utils.hashes import MissingHashes from pip._internal.utils.hashes import MissingHashes
from pip._internal.utils.logging import indent_log from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ( from pip._internal.utils.misc import display_path, hide_url, path_to_display
display_path,
hide_url,
path_to_display,
rmtree,
)
from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.unpacking import unpack_file from pip._internal.utils.unpacking import unpack_file
@ -239,11 +234,9 @@ def unpack_url(
unpack_vcs_link(link, location) unpack_vcs_link(link, location)
return None return None
# If it's a url to a local directory # If it's a url to a local directory, we build in-place.
# There is nothing to be done here.
if link.is_existing_dir(): if link.is_existing_dir():
if os.path.isdir(location):
rmtree(location)
_copy_source_tree(link.file_path, location)
return None return None
# file urls # file urls
@ -415,21 +408,25 @@ class RequirementPreparer(object):
with indent_log(): with indent_log():
# Since source_dir is only set for editable requirements. # Since source_dir is only set for editable requirements.
assert req.source_dir is None assert req.source_dir is None
req.ensure_has_source_dir(self.build_dir, autodelete_unpacked) if link.is_existing_dir():
# If a checkout exists, it's unwise to keep going. version # Build local directories in place.
# inconsistencies are logged later, but do not fail the req.source_dir = link.file_path
# installation. else:
# FIXME: this won't upgrade when there's an existing req.ensure_has_source_dir(self.build_dir, autodelete_unpacked)
# package unpacked in `req.source_dir` # If a checkout exists, it's unwise to keep going. version
if os.path.exists(os.path.join(req.source_dir, 'setup.py')): # inconsistencies are logged later, but do not fail the
raise PreviousBuildDirError( # installation.
"pip can't proceed with requirements '{}' due to a" # FIXME: this won't upgrade when there's an existing
" pre-existing build directory ({}). This is " # package unpacked in `req.source_dir`
"likely due to a previous installation that failed" if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
". pip is being responsible and not assuming it " raise PreviousBuildDirError(
"can delete this. Please delete it and try again." "pip can't proceed with requirements '{}' due to a"
.format(req, req.source_dir) " pre-existing build directory ({}). This is "
) "likely due to a previous installation that failed"
". pip is being responsible and not assuming it "
"can delete this. Please delete it and try again."
.format(req, req.source_dir)
)
# Now that we have the real link, we can tell what kind of # Now that we have the real link, we can tell what kind of
# requirements we have and raise some more informative errors # requirements we have and raise some more informative errors

View File

@ -214,40 +214,5 @@ class Test_unpack_url(object):
unpack_url(dist_url, self.build_dir, unpack_url(dist_url, self.build_dir,
downloader=self.no_downloader, downloader=self.no_downloader,
download_dir=self.download_dir) download_dir=self.download_dir)
assert os.path.isdir(os.path.join(self.build_dir, 'fspkg')) # test that nothing was copied to build_dir since we build in place
assert not os.path.exists(os.path.join(self.build_dir, 'fspkg'))
@pytest.mark.parametrize('exclude_dir', [
'.nox',
'.tox'
])
def test_unpack_url_excludes_expected_dirs(tmpdir, exclude_dir):
src_dir = tmpdir / 'src'
dst_dir = tmpdir / 'dst'
src_included_file = src_dir.joinpath('file.txt')
src_excluded_dir = src_dir.joinpath(exclude_dir)
src_excluded_file = src_dir.joinpath(exclude_dir, 'file.txt')
src_included_dir = src_dir.joinpath('subdir', exclude_dir)
# set up source directory
src_excluded_dir.mkdir(parents=True)
src_included_dir.mkdir(parents=True)
src_included_file.touch()
src_excluded_file.touch()
dst_included_file = dst_dir.joinpath('file.txt')
dst_excluded_dir = dst_dir.joinpath(exclude_dir)
dst_excluded_file = dst_dir.joinpath(exclude_dir, 'file.txt')
dst_included_dir = dst_dir.joinpath('subdir', exclude_dir)
src_link = Link(path_to_url(src_dir))
unpack_url(
src_link,
dst_dir,
Mock(side_effect=AssertionError),
download_dir=None
)
assert not os.path.isdir(dst_excluded_dir)
assert not os.path.isfile(dst_excluded_file)
assert os.path.isfile(dst_included_file)
assert os.path.isdir(dst_included_dir)