Skip to content
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

[1.4] installer: do not fail on invalid wheels, print only a warning #7751

Merged
merged 1 commit into from
Mar 31, 2023
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
8 changes: 8 additions & 0 deletions src/poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: {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: {warning}</warning>")

return 1 if self._shutdown else 0

Expand Down
8 changes: 7 additions & 1 deletion src/poetry/installation/wheel_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
Binary file not shown.
Binary file not shown.
4 changes: 3 additions & 1 deletion tests/installation/test_chef.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ def test_get_cached_archives_for_link(config: Config, mocker: MockerFixture):
)

assert archives
assert set(archives) == set(distributions.glob("demo-0.1.*"))
assert set(archives) == set(distributions.glob("*.whl")) | set(
distributions.glob("*.tar.gz")
)


def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path):
Expand Down
72 changes: 66 additions & 6 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ def pool() -> 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]:
Expand All @@ -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"
)
Expand Down Expand Up @@ -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>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>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,
Expand Down