Commit 315f9e4d by Ambrose Chua Committed by GitHub

fix: Broken cache on Windows (#4531)

Closes #4479

The previous implementation would fail to install packages on Windows
because it creates a `Path` starting with a slash. Such a `Path` is
invalid on Windows. Instead, use `Link` and `url_to_path`.
parent 51a112f1
...@@ -19,6 +19,7 @@ from cleo.io.null_io import NullIO ...@@ -19,6 +19,7 @@ from cleo.io.null_io import NullIO
from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import url_to_path
from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.pyproject.toml import PyProjectTOML
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
...@@ -119,7 +120,7 @@ class Executor: ...@@ -119,7 +120,7 @@ class Executor:
return self return self
def pip_install( def pip_install(
self, req: Union[Path, str], upgrade: bool = False, editable: bool = False self, req: Union[Path, Link], upgrade: bool = False, editable: bool = False
) -> int: ) -> int:
func = pip_install func = pip_install
if editable: if editable:
...@@ -504,7 +505,7 @@ class Executor: ...@@ -504,7 +505,7 @@ class Executor:
) )
) )
self._write(operation, message) self._write(operation, message)
return self.pip_install(str(archive), upgrade=operation.job_type == "update") return self.pip_install(archive, upgrade=operation.job_type == "update")
def _update(self, operation: Union[Install, Update]) -> int: def _update(self, operation: Union[Install, Update]) -> int:
return self._install(operation) return self._install(operation)
...@@ -678,17 +679,20 @@ class Executor: ...@@ -678,17 +679,20 @@ class Executor:
return archive return archive
@staticmethod @staticmethod
def _validate_archive_hash(archive: Path, package: Package) -> str: def _validate_archive_hash(archive: Union[Path, Link], package: Package) -> str:
archive_path = (
url_to_path(archive.url) if isinstance(archive, Link) else archive
)
file_dep = FileDependency( file_dep = FileDependency(
package.name, package.name,
Path(archive.path) if isinstance(archive, Link) else archive, archive_path,
) )
archive_hash = "sha256:" + file_dep.hash() archive_hash = "sha256:" + file_dep.hash()
known_hashes = {f["hash"] for f in package.files} known_hashes = {f["hash"] for f in package.files}
if archive_hash not in known_hashes: if archive_hash not in known_hashes:
raise RuntimeError( raise RuntimeError(
f"Hash for {package} from archive {archive.name} not found in known hashes (was: {archive_hash})" f"Hash for {package} from archive {archive_path.name} not found in known hashes (was: {archive_hash})"
) )
return archive_hash return archive_hash
......
...@@ -4,6 +4,8 @@ import sys ...@@ -4,6 +4,8 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union
from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import url_to_path
from poetry.exceptions import PoetryException from poetry.exceptions import PoetryException
from poetry.utils.env import Env from poetry.utils.env import Env
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
...@@ -11,13 +13,13 @@ from poetry.utils.env import ephemeral_environment ...@@ -11,13 +13,13 @@ from poetry.utils.env import ephemeral_environment
def pip_install( def pip_install(
path: Union[Path, str], path: Union[Path, Link],
environment: Env, environment: Env,
editable: bool = False, editable: bool = False,
deps: bool = False, deps: bool = False,
upgrade: bool = False, upgrade: bool = False,
) -> Union[int, str]: ) -> Union[int, str]:
path = Path(path) if isinstance(path, str) else path path = url_to_path(path.url) if isinstance(path, Link) else path
is_wheel = path.suffix == ".whl" is_wheel = path.suffix == ".whl"
# We disable version check here as we are already pinning to version available in either the # We disable version check here as we are already pinning to version available in either the
...@@ -60,7 +62,9 @@ def pip_install( ...@@ -60,7 +62,9 @@ def pip_install(
raise PoetryException(f"Failed to install {path.as_posix()}") from e raise PoetryException(f"Failed to install {path.as_posix()}") from e
def pip_editable_install(directory: Path, environment: Env) -> Union[int, str]: def pip_editable_install(
directory: Union[Path, Link], environment: Env
) -> Union[int, str]:
return pip_install( return pip_install(
path=directory, environment=environment, editable=True, deps=False, upgrade=True path=directory, environment=environment, editable=True, deps=False, upgrade=True
) )
...@@ -11,6 +11,7 @@ from cleo.io.buffered_io import BufferedIO ...@@ -11,6 +11,7 @@ from cleo.io.buffered_io import BufferedIO
from poetry.config.config import Config from poetry.config.config import Config
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.core.utils._compat import PY36 from poetry.core.utils._compat import PY36
from poetry.installation.executor import Executor from poetry.installation.executor import Executor
from poetry.installation.operations import Install from poetry.installation.operations import Install
...@@ -462,3 +463,34 @@ def test_executor_should_write_pep610_url_references_for_git( ...@@ -462,3 +463,34 @@ def test_executor_should_write_pep610_url_references_for_git(
"url": package.source_url, "url": package.source_url,
}, },
) )
def test_executor_should_use_cached_link_and_hash(
tmp_venv, pool, config, io, mocker, fixture_dir
):
# Produce a file:/// URI that is a valid link
link_cached = Link(
fixture_dir("distributions")
.joinpath("demo-0.1.0-py2.py3-none-any.whl")
.as_uri()
)
mocker.patch(
"poetry.installation.chef.Chef.get_cached_archive_for_link",
return_value=link_cached,
)
package = Package("demo", "0.1.0")
# Set package.files so the executor will attempt to hash the package
package.files = [
{
"file": "demo-0.1.0-py2.py3-none-any.whl",
"hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a",
}
]
executor = Executor(tmp_venv, pool, config, io)
archive = executor._download_link(
Install(package),
Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"),
)
assert archive == link_cached
...@@ -2,6 +2,8 @@ import subprocess ...@@ -2,6 +2,8 @@ import subprocess
import pytest import pytest
from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import path_to_url
from poetry.utils.pip import pip_install from poetry.utils.pip import pip_install
...@@ -12,6 +14,15 @@ def test_pip_install_successful(tmp_dir, tmp_venv, fixture_dir): ...@@ -12,6 +14,15 @@ def test_pip_install_successful(tmp_dir, tmp_venv, fixture_dir):
assert "Successfully installed demo-0.1.0" in result assert "Successfully installed demo-0.1.0" in result
def test_pip_install_link(tmp_dir, tmp_venv, fixture_dir):
file_path = Link(
path_to_url(fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl"))
)
result = pip_install(file_path, tmp_venv)
assert "Successfully installed demo-0.1.0" in result
def test_pip_install_with_keyboard_interrupt(tmp_dir, tmp_venv, fixture_dir, mocker): def test_pip_install_with_keyboard_interrupt(tmp_dir, tmp_venv, fixture_dir, mocker):
file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl")
mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) mocker.patch("subprocess.run", side_effect=KeyboardInterrupt())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment