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,