Skip to content

New resolver: Avoid polluting dest dir #8843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/8827.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid polluting the destination directory by resolution artifacts
when the new resolver is used for ``pip download`` or ``pip wheel``.
2 changes: 0 additions & 2 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ def make_requirement_preparer(
finder, # type: PackageFinder
use_user_site, # type: bool
download_dir=None, # type: str
wheel_download_dir=None, # type: str
):
# type: (...) -> RequirementPreparer
"""
Expand All @@ -229,7 +228,6 @@ def make_requirement_preparer(
build_dir=temp_build_dir_path,
src_dir=options.src_dir,
download_dir=download_dir,
wheel_download_dir=wheel_download_dir,
build_isolation=options.build_isolation,
req_tracker=req_tracker,
session=session,
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ def run(self, options, args):
cmdoptions.check_dist_restriction(options)

options.download_dir = normalize_path(options.download_dir)

ensure_dir(options.download_dir)

session = self.get_default_session(options)
Expand Down Expand Up @@ -138,6 +137,7 @@ def run(self, options, args):
for req in requirement_set.requirements.values():
if not req.editable and req.satisfied_by is None:
assert req.name is not None
preparer.save_linked_requirement(req)
downloaded.append(req.name)
if downloaded:
write_output('Successfully downloaded %s', ' '.join(downloaded))
Expand Down
13 changes: 8 additions & 5 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from optparse import Values
from typing import List

from pip._internal.req.req_install import InstallRequirement

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -137,7 +138,7 @@ def run(self, options, args):
req_tracker=req_tracker,
session=session,
finder=finder,
wheel_download_dir=options.wheel_dir,
download_dir=options.wheel_dir,
use_user_site=False,
)

Expand All @@ -156,10 +157,12 @@ def run(self, options, args):
reqs, check_supported_wheels=True
)

reqs_to_build = [
r for r in requirement_set.requirements.values()
if should_build_for_wheel_command(r)
]
reqs_to_build = [] # type: List[InstallRequirement]
for req in requirement_set.requirements.values():
if req.is_wheel:
preparer.save_linked_requirement(req)
elif should_build_for_wheel_command(req):
reqs_to_build.append(req)

# build wheels
build_successes, build_failures = build(
Expand Down
90 changes: 33 additions & 57 deletions src/pip/_internal/operations/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ def __init__(
build_dir, # type: str
download_dir, # type: Optional[str]
src_dir, # type: str
wheel_download_dir, # type: Optional[str]
build_isolation, # type: bool
req_tracker, # type: RequirementTracker
session, # type: PipSession
Expand All @@ -325,16 +324,6 @@ def __init__(
# not saved, and are deleted immediately after unpacking.
self.download_dir = download_dir

# Where still-packed .whl files should be written to. If None, they are
# written to the download_dir parameter. Separate to download_dir to
# permit only keeping wheel archives for pip wheel.
self.wheel_download_dir = wheel_download_dir

# NOTE
# download_dir and wheel_download_dir overlap semantically and may
# be combined if we're willing to have non-wheel archives present in
# the wheelhouse output by 'pip wheel'.

# Is build isolation allowed?
self.build_isolation = build_isolation

Expand All @@ -353,20 +342,6 @@ def __init__(
# Previous "header" printed for a link-based InstallRequirement
self._previous_requirement_header = ("", "")

@property
def _download_should_save(self):
# type: () -> bool
if not self.download_dir:
return False

if os.path.exists(self.download_dir):
return True

logger.critical('Could not find download directory')
raise InstallationError(
"Could not find or access download directory '{}'"
.format(self.download_dir))

def _log_preparing_link(self, req):
# type: (InstallRequirement) -> None
"""Provide context for the requirement being prepared."""
Expand All @@ -385,15 +360,8 @@ def _log_preparing_link(self, req):
with indent_log():
logger.info("Using cached %s", req.link.filename)

def _get_download_dir(self, link):
# type: (Link) -> Optional[str]
if link.is_wheel and self.wheel_download_dir:
# Download wheels to a dedicated dir when doing `pip wheel`.
return self.wheel_download_dir
return self.download_dir

def _ensure_link_req_src_dir(self, req, download_dir, parallel_builds):
# type: (InstallRequirement, Optional[str], bool) -> None
def _ensure_link_req_src_dir(self, req, parallel_builds):
# type: (InstallRequirement, bool) -> None
"""Ensure source_dir of a linked InstallRequirement."""
# Since source_dir is only set for editable requirements.
if req.link.is_wheel:
Expand Down Expand Up @@ -494,10 +462,9 @@ def prepare_linked_requirement(self, req, parallel_builds=False):
# Check if the relevant file is already available
# in the download directory
file_path = None
download_dir = self._get_download_dir(req.link)
if download_dir is not None and link.is_wheel:
if self.download_dir is not None and link.is_wheel:
hashes = self._get_linked_req_hashes(req)
file_path = _check_download_dir(req.link, download_dir, hashes)
file_path = _check_download_dir(req.link, self.download_dir, hashes)

if file_path is not None:
# The file is already available, so mark it as downloaded
Expand Down Expand Up @@ -528,15 +495,14 @@ def _prepare_linked_requirement(self, req, parallel_builds):
# type: (InstallRequirement, bool) -> Distribution
assert req.link
link = req.link
download_dir = self._get_download_dir(link)

self._ensure_link_req_src_dir(req, download_dir, parallel_builds)
self._ensure_link_req_src_dir(req, parallel_builds)
hashes = self._get_linked_req_hashes(req)
if link.url not in self._downloaded:
try:
local_file = unpack_url(
link, req.source_dir, self._download,
download_dir, hashes,
self.download_dir, hashes,
)
except NetworkConnectionError as exc:
raise InstallationError(
Expand All @@ -557,22 +523,33 @@ def _prepare_linked_requirement(self, req, parallel_builds):
dist = _get_prepared_distribution(
req, self.req_tracker, self.finder, self.build_isolation,
)
return dist

if download_dir:
if link.is_existing_dir():
logger.info('Link is a directory, ignoring download_dir')
elif local_file:
download_location = os.path.join(download_dir, link.filename)
if not os.path.exists(download_location):
shutil.copy(local_file.path, download_location)
download_path = display_path(download_location)
logger.info('Saved %s', download_path)

if self._download_should_save:
def save_linked_requirement(self, req):
# type: (InstallRequirement) -> None
assert self.download_dir is not None
assert req.link is not None
link = req.link
if link.is_vcs:
# Make a .zip of the source_dir we already created.
if link.is_vcs:
req.archive(self.download_dir)
return dist
req.archive(self.download_dir)
return

if link.is_existing_dir():
logger.debug(
'Not copying link to destination directory '
'since it is a directory: %s', link,
)
return
if req.local_file_path is None:
# No distribution was downloaded for this requirement.
return

download_location = os.path.join(self.download_dir, link.filename)
if not os.path.exists(download_location):
shutil.copy(req.local_file_path, download_location)
download_path = display_path(download_location)
logger.info('Saved %s', download_path)

def prepare_editable_requirement(
self,
Expand All @@ -593,14 +570,13 @@ def prepare_editable_requirement(
'hash.'.format(req)
)
req.ensure_has_source_dir(self.src_dir)
req.update_editable(not self._download_should_save)
req.update_editable(self.download_dir is None)

dist = _get_prepared_distribution(
req, self.req_tracker, self.finder, self.build_isolation,
)

if self._download_should_save:
req.archive(self.download_dir)
req.archive(self.download_dir)
Comment on lines -602 to +579
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. Is this covered by the build_dir changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is covered by the change to req.archive, since more than one call to it check for the same condition: ceaddcc

req.check_if_exists(self.use_user_site)

return dist
Expand Down
4 changes: 3 additions & 1 deletion src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,12 +700,14 @@ def _clean_zip_name(name, prefix):
return self.name + '/' + name

def archive(self, build_dir):
# type: (str) -> None
# type: (Optional[str]) -> None
"""Saves archive to provided build_dir.

Used for saving downloaded VCS requirements as part of `pip download`.
"""
assert self.source_dir
if build_dir is None:
return

create_archive = True
archive_name = '{}-{}.zip'.format(self.name, self.metadata["version"])
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_req.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def _basic_resolver(self, finder, require_hashes=False):
build_dir=os.path.join(self.tempdir, 'build'),
src_dir=os.path.join(self.tempdir, 'src'),
download_dir=None,
wheel_download_dir=None,
build_isolation=True,
req_tracker=tracker,
session=session,
Expand Down