From f18b5de805982abd5057337f8d69b65f0cf4be88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Randy=20D=C3=B6ring?=
<30527984+radoering@users.noreply.github.com>
Date: Fri, 31 Mar 2023 21:39:33 +0200
Subject: [PATCH] installer: do not fail on invalid wheels, print only a
warning (#7694)
(cherry picked from commit c2a7a8d6ea412d021a65068551fa814f0925dc40)
---
src/poetry/installation/executor.py | 8 ++
src/poetry/installation/wheel_installer.py | 8 +-
..._invalid_record-0.1.0-py2.py3-none-any.whl | Bin 0 -> 1169 bytes
...invalid_record2-0.1.0-py2.py3-none-any.whl | Bin 0 -> 1307 bytes
tests/installation/test_chef.py | 4 +-
tests/installation/test_executor.py | 72 ++++++++++++++++--
6 files changed, 84 insertions(+), 8 deletions(-)
create mode 100644 tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl
create mode 100644 tests/fixtures/distributions/demo_invalid_record2-0.1.0-py2.py3-none-any.whl
diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py
index 15ec0c0c47e..ac25a0f7a0d 100644
--- a/src/poetry/installation/executor.py
+++ b/src/poetry/installation/executor.py
@@ -205,6 +205,14 @@ def execute(self, operations: list[Operation]) -> int:
for warning in self._yanked_warnings:
self._io.write_error_line(f"Warning: {warning}")
+ for path, issues in self._wheel_installer.invalid_wheels.items():
+ formatted_issues = "\n".join(issues)
+ warning = (
+ f"Validation of the RECORD file of {path.name} failed."
+ " Please report to the maintainers of that package so they can fix"
+ f" their build process. Details:\n{formatted_issues}\n"
+ )
+ self._io.write_error_line(f"Warning: {warning}")
return 1 if self._shutdown else 0
diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py
index ab2e0a82f3e..ad4f0fec2e7 100644
--- a/src/poetry/installation/wheel_installer.py
+++ b/src/poetry/installation/wheel_installer.py
@@ -10,6 +10,7 @@
from installer import install
from installer.destinations import SchemeDictionaryDestination
from installer.sources import WheelFile
+from installer.sources import _WheelFileValidationError
from poetry.__version__ import __version__
from poetry.utils._compat import WINDOWS
@@ -93,12 +94,17 @@ def __init__(self, env: Env) -> None:
schemes, interpreter=self._env.python, script_kind=script_kind
)
+ self.invalid_wheels: dict[Path, list[str]] = {}
+
def enable_bytecode_compilation(self, enable: bool = True) -> None:
self._destination.bytecode_optimization_levels = (-1,) if enable else ()
def install(self, wheel: Path) -> None:
with WheelFile.open(wheel) as source:
- source.validate_record()
+ try:
+ source.validate_record()
+ except _WheelFileValidationError as e:
+ self.invalid_wheels[wheel] = e.issues
install(
source=source,
destination=self._destination.for_source(source),
diff --git a/tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl b/tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl
new file mode 100644
index 0000000000000000000000000000000000000000..184475aa2766a660ce6fac5b681ee128845088bc
GIT binary patch
literal 1169
zcmWIWW@Zs#U|`??Vnv1~uELiRK$aK~3jlFSYHq%Me0*kJW=VX!UO{DdzrMGw*10q1
zyZgg8ga(*jFur0O@SKSu0B(wFRY_Q8KW*zWEPj`X6B{k>-)NfIJ!87I8L2-*YA*nKCvZ^rdwT?E?Jk@w2SX!n!v^XGj|0>M$hAqHv5%ZX_s-=
zBKxjI{#}Vp9&1xX=NtX|AjW#;;=j*gY40n|FI6(8IzMB|nCv{ySzT$abDHNaF5iFx
zn^VDd51J=CACkVh$8*2E#=G|$@)4oUuw%Zv!&Dyja9}7$0I?>Kp&jnw>gvOL%8{!<
zL4ftb-sK;dl+!L8;&$2Hv104N+V?o^~j+GN6ywDT*uN}61HICp+#jtS4{+B
zEh1ePEV}{&CA4&&N=*g@8_H?22Ro0(0BCtqkR=Mx;G-GEx9|dQ1;L4
zjZDk##~YW#O==4)kn_If#;G}_m%;t^pDr`Y>20k!#M(owTKPxa1U2kAKp`r
zTn!2WtQYn!|G=c2cHt1W%kGX9TMsU8nbGMfesABsUvI?=F0<8cF4!{na~g|tuwd^u
zL6O{RyY+S))KO|(HvQSE9iIhrFLG^O_PJko$MUa74lOuxwg%z6FFhq;8y?JFbrt9=
zYhaSlCf0dDuFn2JF1=^(mN^?RFg*Be=e?pxKz#QF&MuwDOP20F`BKOv`DEK)yT8}E
z?|o<8=f7or)!7*#MTuu>elvdlnX>!dUB3N1Gb~PD-}y(@o-gFEdcKq0tKNk7>}x!V
z&OG0BV*6pi8{b|X`lCF5c_5eB))@aoW;(^mQOhQLPV%_=($vkQ$4M%#|8daUiML+u
zJDJ&CFm0!unn`Deie(Vje(ry=DZw#(0zbA@M+YtE5WTK=_d?vS%qw3O2UoT0txJDs
za$c`TIL}7u^eZbpxiZ$3rVSsqEpYn2q>CZqFJpi=Ba;X-?o0;^A~5*h2%^xlAV{}8
z14K7wW>bM^Wl#XpZcyDsXFV>kHU RepositoryPool:
@pytest.fixture()
-def mock_file_downloads(http: type[httpretty.httpretty]) -> None:
+def mock_file_downloads(
+ http: type[httpretty.httpretty], fixture_dir: FixtureDirGetter
+) -> None:
def callback(
request: HTTPrettyRequest, uri: str, headers: dict[str, Any]
) -> list[int | dict[str, Any] | str]:
@@ -140,11 +142,9 @@ def callback(
)
if not fixture.exists():
- if name == "demo-0.1.0.tar.gz":
- fixture = Path(__file__).parent.parent.joinpath(
- "fixtures/distributions/demo-0.1.0.tar.gz"
- )
- else:
+ fixture = fixture_dir("distributions") / name
+
+ if not fixture.exists():
fixture = Path(__file__).parent.parent.joinpath(
"fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"
)
@@ -342,6 +342,66 @@ def test_execute_prints_warning_for_yanked_package(
assert error.count("yanked") == 0
+def test_execute_prints_warning_for_invalid_wheels(
+ config: Config,
+ pool: RepositoryPool,
+ io: BufferedIO,
+ tmp_dir: str,
+ mock_file_downloads: None,
+ env: MockEnv,
+):
+ config.merge({"cache-dir": tmp_dir})
+
+ executor = Executor(env, pool, config, io)
+
+ base_url = "https://files.pythonhosted.org/"
+ wheel1 = "demo_invalid_record-0.1.0-py2.py3-none-any.whl"
+ wheel2 = "demo_invalid_record2-0.1.0-py2.py3-none-any.whl"
+ return_code = executor.execute(
+ [
+ Install(
+ Package(
+ "demo-invalid-record",
+ "0.1.0",
+ source_type="url",
+ source_url=f"{base_url}/{wheel1}",
+ )
+ ),
+ Install(
+ Package(
+ "demo-invalid-record2",
+ "0.1.0",
+ source_type="url",
+ source_url=f"{base_url}/{wheel2}",
+ )
+ ),
+ ]
+ )
+
+ warning1 = f"""\
+Warning: Validation of the RECORD file of {wheel1} failed.\
+ Please report to the maintainers of that package so they can fix their build process.\
+ Details:
+In .*?{wheel1}, demo/__init__.py is not mentioned in RECORD
+In .*?{wheel1}, demo_invalid_record-0.1.0.dist-info/WHEEL is not mentioned in RECORD
+"""
+
+ warning2 = f"""\
+Warning: Validation of the RECORD file of {wheel2} failed.\
+ Please report to the maintainers of that package so they can fix their build process.\
+ Details:
+In .*?{wheel2}, hash / size of demo_invalid_record2-0.1.0.dist-info/METADATA didn't\
+ match RECORD
+"""
+
+ output = io.fetch_output()
+ error = io.fetch_error()
+ assert return_code == 0, f"\noutput: {output}\nerror: {error}\n"
+ assert re.match(f"{warning1}\n{warning2}", error) or re.match(
+ f"{warning2}\n{warning1}", error
+ ), error
+
+
def test_execute_shows_skipped_operations_if_verbose(
config: Config,
pool: RepositoryPool,