Skip to content

Commit fef9bd4

Browse files
authored
Merge pull request #12197 from cosmicexplorer/fix-metadata-sdist-download
2 parents 0778c1c + 39da6e0 commit fef9bd4

File tree

5 files changed

+430
-294
lines changed

5 files changed

+430
-294
lines changed

news/12191.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent downloading sdists twice when PEP 658 metadata is present.

src/pip/_internal/operations/prepare.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import mimetypes
88
import os
99
import shutil
10+
from pathlib import Path
1011
from typing import Dict, Iterable, List, Optional
1112

1213
from pip._vendor.packaging.utils import canonicalize_name
@@ -20,7 +21,6 @@
2021
InstallationError,
2122
MetadataInconsistent,
2223
NetworkConnectionError,
23-
PreviousBuildDirError,
2424
VcsHashUnsupported,
2525
)
2626
from pip._internal.index.package_finder import PackageFinder
@@ -47,7 +47,6 @@
4747
display_path,
4848
hash_file,
4949
hide_url,
50-
is_installable_dir,
5150
)
5251
from pip._internal.utils.temp_dir import TempDirectory
5352
from pip._internal.utils.unpacking import unpack_file
@@ -319,21 +318,7 @@ def _ensure_link_req_src_dir(
319318
autodelete=True,
320319
parallel_builds=parallel_builds,
321320
)
322-
323-
# If a checkout exists, it's unwise to keep going. version
324-
# inconsistencies are logged later, but do not fail the
325-
# installation.
326-
# FIXME: this won't upgrade when there's an existing
327-
# package unpacked in `req.source_dir`
328-
# TODO: this check is now probably dead code
329-
if is_installable_dir(req.source_dir):
330-
raise PreviousBuildDirError(
331-
"pip can't proceed with requirements '{}' due to a"
332-
"pre-existing build directory ({}). This is likely "
333-
"due to a previous installation that failed . pip is "
334-
"being responsible and not assuming it can delete this. "
335-
"Please delete it and try again.".format(req, req.source_dir)
336-
)
321+
req.ensure_pristine_source_checkout()
337322

338323
def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
339324
# By the time this is called, the requirement's link should have
@@ -481,20 +466,19 @@ def _complete_partial_requirements(
481466
for link, (filepath, _) in batch_download:
482467
logger.debug("Downloading link %s to %s", link, filepath)
483468
req = links_to_fully_download[link]
469+
# Record the downloaded file path so wheel reqs can extract a Distribution
470+
# in .get_dist().
484471
req.local_file_path = filepath
485-
# TODO: This needs fixing for sdists
486-
# This is an emergency fix for #11847, which reports that
487-
# distributions get downloaded twice when metadata is loaded
488-
# from a PEP 658 standalone metadata file. Setting _downloaded
489-
# fixes this for wheels, but breaks the sdist case (tests
490-
# test_download_metadata). As PyPI is currently only serving
491-
# metadata for wheels, this is not an immediate issue.
492-
# Fixing the problem properly looks like it will require a
493-
# complete refactoring of the `prepare_linked_requirements_more`
494-
# logic, and I haven't a clue where to start on that, so for now
495-
# I have fixed the issue *just* for wheels.
496-
if req.is_wheel:
497-
self._downloaded[req.link.url] = filepath
472+
# Record that the file is downloaded so we don't do it again in
473+
# _prepare_linked_requirement().
474+
self._downloaded[req.link.url] = filepath
475+
476+
# If this is an sdist, we need to unpack it after downloading, but the
477+
# .source_dir won't be set up until we are in _prepare_linked_requirement().
478+
# Add the downloaded archive to the install requirement to unpack after
479+
# preparing the source dir.
480+
if not req.is_wheel:
481+
req.needs_unpacked_archive(Path(filepath))
498482

499483
# This step is necessary to ensure all lazy wheels are processed
500484
# successfully by the 'download', 'wheel', and 'install' commands.

src/pip/_internal/req/req_install.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import uuid
77
import zipfile
88
from optparse import Values
9+
from pathlib import Path
910
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
1011

1112
from pip._vendor.packaging.markers import Marker
@@ -17,7 +18,7 @@
1718
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
1819

1920
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
20-
from pip._internal.exceptions import InstallationError
21+
from pip._internal.exceptions import InstallationError, PreviousBuildDirError
2122
from pip._internal.locations import get_scheme
2223
from pip._internal.metadata import (
2324
BaseDistribution,
@@ -47,11 +48,13 @@
4748
backup_dir,
4849
display_path,
4950
hide_url,
51+
is_installable_dir,
5052
redact_auth_from_url,
5153
)
5254
from pip._internal.utils.packaging import safe_extra
5355
from pip._internal.utils.subprocess import runner_with_spinner_message
5456
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
57+
from pip._internal.utils.unpacking import unpack_file
5558
from pip._internal.utils.virtualenv import running_under_virtualenv
5659
from pip._internal.vcs import vcs
5760

@@ -180,6 +183,9 @@ def __init__(
180183
# This requirement needs more preparation before it can be built
181184
self.needs_more_preparation = False
182185

186+
# This requirement needs to be unpacked before it can be installed.
187+
self._archive_source: Optional[Path] = None
188+
183189
def __str__(self) -> str:
184190
if self.req:
185191
s = str(self.req)
@@ -645,6 +651,27 @@ def ensure_has_source_dir(
645651
parallel_builds=parallel_builds,
646652
)
647653

654+
def needs_unpacked_archive(self, archive_source: Path) -> None:
655+
assert self._archive_source is None
656+
self._archive_source = archive_source
657+
658+
def ensure_pristine_source_checkout(self) -> None:
659+
"""Ensure the source directory has not yet been built in."""
660+
assert self.source_dir is not None
661+
if self._archive_source is not None:
662+
unpack_file(str(self._archive_source), self.source_dir)
663+
elif is_installable_dir(self.source_dir):
664+
# If a checkout exists, it's unwise to keep going.
665+
# version inconsistencies are logged later, but do not fail
666+
# the installation.
667+
raise PreviousBuildDirError(
668+
f"pip can't proceed with requirements '{self}' due to a "
669+
f"pre-existing build directory ({self.source_dir}). This is likely "
670+
"due to a previous installation that failed . pip is "
671+
"being responsible and not assuming it can delete this. "
672+
"Please delete it and try again."
673+
)
674+
648675
# For editable installations
649676
def update_editable(self) -> None:
650677
if not self.link:

0 commit comments

Comments
 (0)