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

ensure provider respects subdirectory when merging and imrove subdirectory support for vcs deps #5648

Merged
merged 6 commits into from
May 21, 2022
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ ignore_errors = true
module = [
'poetry.installation.executor',
'poetry.repositories.installed_repository',
'poetry.mixology.version_solver',
]
warn_unused_ignores = false

Expand Down
2 changes: 2 additions & 0 deletions src/poetry/mixology/incompatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ def _terse(self, term: Term, allow_every: bool = False) -> str:
pretty_name: str = term.dependency.pretty_name
return pretty_name

if term.dependency.source_type:
return str(term.dependency)
return f"{term.dependency.pretty_name} ({term.dependency.pretty_constraint})"

def _single_term_where(self, callable: Callable[[Term], bool]) -> Term | None:
Expand Down
16 changes: 13 additions & 3 deletions src/poetry/mixology/version_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from poetry.mixology.set_relation import SetRelation
from poetry.mixology.term import Term
from poetry.packages import DependencyPackage
from poetry.utils._compat import metadata


if TYPE_CHECKING:
Expand All @@ -42,16 +43,25 @@ class DependencyCache:
def __init__(self, provider: Provider) -> None:
self.provider = provider
self.cache: dict[
tuple[str, str | None, str | None, str | None], list[DependencyPackage]
tuple[str, str | None, str | None, str | None, str | None],
list[DependencyPackage],
] = {}

@functools.lru_cache(maxsize=128)
def search_for(self, dependency: Dependency) -> list[DependencyPackage]:
# TODO: re-enable cache when poetry-core upgrade is completed
self.search_for = functools.lru_cache(
maxsize=128
if metadata.version("poetry-core") # type: ignore[no-untyped-call]
!= "1.1.0a7"
abn marked this conversation as resolved.
Show resolved Hide resolved
else 0
)(self._search_for)

def _search_for(self, dependency: Dependency) -> list[DependencyPackage]:
key = (
dependency.complete_name,
dependency.source_type,
dependency.source_url,
dependency.source_reference,
dependency.source_subdirectory,
)

packages = self.cache.get(key)
Expand Down
29 changes: 25 additions & 4 deletions src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def _get_package_from_git(
branch: str | None = None,
tag: str | None = None,
rev: str | None = None,
subdirectory: str | None = None,
source_root: Path | None = None,
) -> Package:
source = Git.clone(
Expand All @@ -99,11 +100,16 @@ def _get_package_from_git(
)
revision = Git.get_revision(source)

package = Provider.get_package_from_directory(Path(source.path))
path = Path(source.path)
if subdirectory:
path = path.joinpath(subdirectory)

package = Provider.get_package_from_directory(path)
package._source_type = "git"
package._source_url = url
package._source_reference = rev or tag or branch or "HEAD"
package._source_resolved_reference = revision
package._source_subdirectory = subdirectory

return package

Expand Down Expand Up @@ -230,7 +236,14 @@ def search_for_vcs(self, dependency: VCSDependency) -> list[Package]:
Basically, we clone the repository in a temporary directory
and get the information we need by checking out the specified reference.
"""
if dependency in self._deferred_cache:
# TODO: remove explicit subdirectory check once poetry-core is updated
# we ensure subdirectory match here as workaround until poetry-core is updated
# to >1.1.0a7
if (
dependency in self._deferred_cache
and self._deferred_cache[dependency].source_subdirectory
abn marked this conversation as resolved.
Show resolved Hide resolved
== dependency.source_subdirectory
):
return [self._deferred_cache[dependency]]

package = self.get_package_from_vcs(
Expand All @@ -239,6 +252,7 @@ def search_for_vcs(self, dependency: VCSDependency) -> list[Package]:
branch=dependency.branch,
tag=dependency.tag,
rev=dependency.rev,
subdirectory=dependency.source_subdirectory,
source_root=self._source_root
or (self._env.path.joinpath("src") if self._env else None),
)
Expand All @@ -265,13 +279,19 @@ def get_package_from_vcs(
branch: str | None = None,
tag: str | None = None,
rev: str | None = None,
subdirectory: str | None = None,
source_root: Path | None = None,
) -> Package:
if vcs != "git":
raise ValueError(f"Unsupported VCS dependency {vcs}")

return _get_package_from_git(
url=url, branch=branch, tag=tag, rev=rev, source_root=source_root
url=url,
branch=branch,
tag=tag,
rev=rev,
subdirectory=subdirectory,
source_root=source_root,
)

def search_for_file(self, dependency: FileDependency) -> list[Package]:
Expand Down Expand Up @@ -555,14 +575,15 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage:
# of source type, reference etc. are taking into consideration when duplicates
# are identified.
duplicates: dict[
tuple[str, str | None, str | None, str | None], list[Dependency]
tuple[str, str | None, str | None, str | None, str | None], list[Dependency]
] = {}
for dep in dependencies:
key = (
dep.complete_name,
dep.source_type,
dep.source_url,
dep.source_reference,
dep.source_subdirectory,
)
if key not in duplicates:
duplicates[key] = []
Expand Down
10 changes: 9 additions & 1 deletion src/poetry/utils/dependency_specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,20 @@ def _parse_dependency_specification_git_url(
url = Git.normalize_url(requirement)

pair = {"name": parsed.name, "git": url.url}

if parsed.rev:
pair["rev"] = url.revision

if parsed.subdirectory:
pair["subdirectory"] = parsed.subdirectory

source_root = env.path.joinpath("src") if env else None
package = Provider.get_package_from_vcs(
"git", url=url.url, rev=pair.get("rev"), source_root=source_root
"git",
url=url.url,
rev=pair.get("rev"),
subdirectory=parsed.subdirectory,
source_root=source_root,
)
pair["name"] = package.name
return pair
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def auth_config_source() -> DictConfigSource:
return source


@pytest.fixture
@pytest.fixture(autouse=True)
def config(
config_source: DictConfigSource,
auth_config_source: DictConfigSource,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.poetry]
name = "one"
version = "1.0.0"
description = "Some description."
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.poetry]
name = "one"
version = "1.0.0"
description = "Some description."
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.poetry]
name = "two"
version = "2.0.0"
description = "Some description."
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.poetry]
name = "demo"
version = "1.2.3"
description = "Some description."
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.poetry]
name = "demo"
version = "1.2.3"
description = "Some description."
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
13 changes: 13 additions & 0 deletions tests/fixtures/with_conditional_path_deps/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[tool.poetry]
name = "sample"
version = "1.0.0"
description = "Sample Project"
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
demo = [
{ path = "demo_one", platform = "linux" },
{ path = "demo_two", platform = "win32" },
]
68 changes: 66 additions & 2 deletions tests/mixology/version_solver/test_dependency_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from poetry.factory import Factory
from poetry.mixology.version_solver import DependencyCache
from tests.compat import is_poetry_core_1_1_0a7_compat
from tests.mixology.helpers import add_to_repo


Expand Down Expand Up @@ -37,8 +38,9 @@ def test_solver_dependency_cache_respects_source_type(
packages_pypi = cache.search_for(dependency_pypi)
packages_git = cache.search_for(dependency_git)

assert cache.search_for.cache_info().hits == 2
assert cache.search_for.cache_info().currsize == 2
if not is_poetry_core_1_1_0a7_compat:
assert cache.search_for.cache_info().hits == 2
assert cache.search_for.cache_info().currsize == 2

assert len(packages_pypi) == len(packages_git) == 1
assert packages_pypi != packages_git
Expand All @@ -57,3 +59,65 @@ def test_solver_dependency_cache_respects_source_type(
package_git.package.source_resolved_reference
== "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
)


def test_solver_dependency_cache_respects_subdirectories(
root: ProjectPackage, provider: Provider, repo: Repository
):
dependency_one = Factory.create_dependency(
"one",
{
"git": "https://github.com/demo/subdirectories.git",
"subdirectory": "one",
"platform": "linux",
},
)
dependency_one_copy = Factory.create_dependency(
"one",
{
"git": "https://github.com/demo/subdirectories.git",
"subdirectory": "one-copy",
"platform": "win32",
},
)

root.add_dependency(dependency_one)
root.add_dependency(dependency_one_copy)

cache = DependencyCache(provider)
cache.search_for.cache_clear()

# ensure cache was never hit for both calls
cache.search_for(dependency_one)
cache.search_for(dependency_one_copy)
assert not cache.search_for.cache_info().hits

packages_one = cache.search_for(dependency_one)
packages_one_copy = cache.search_for(dependency_one_copy)

if not is_poetry_core_1_1_0a7_compat:
assert cache.search_for.cache_info().hits == 2
assert cache.search_for.cache_info().currsize == 2

assert len(packages_one) == len(packages_one_copy) == 1

package_one = packages_one[0]
package_one_copy = packages_one_copy[0]

assert package_one.package.name == package_one_copy.name
assert package_one.package.version.text == package_one_copy.package.version.text
assert package_one.package.source_type == package_one_copy.source_type == "git"
assert (
package_one.package.source_resolved_reference
== package_one_copy.source_resolved_reference
== "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
)
assert (
package_one.package.source_subdirectory != package_one_copy.source_subdirectory
)
assert package_one.package.source_subdirectory == "one"
assert package_one_copy.package.source_subdirectory == "one-copy"

assert package_one.dependency.marker.intersect(
package_one_copy.dependency.marker
).is_empty()
20 changes: 20 additions & 0 deletions tests/mixology/version_solver/test_unsolvable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from poetry.factory import Factory
Expand Down Expand Up @@ -92,6 +93,25 @@ def test_disjoint_root_constraints(
check_solver_result(root, provider, error=error)


def test_disjoint_root_constraints_path_dependencies(
root: ProjectPackage, provider: Provider, repo: Repository
):
provider.set_package_python_versions("^3.7")
fixtures = Path(__file__).parent.parent.parent / "fixtures"
project_dir = fixtures.joinpath("with_conditional_path_deps")
path1 = project_dir / "demo_one"
root.add_dependency(Factory.create_dependency("demo", {"path": path1}))
path2 = project_dir / "demo_two"
root.add_dependency(Factory.create_dependency("demo", {"path": path2}))

error = (
f"Because myapp depends on both demo (1.2.3 {path1.as_posix()}) "
f"and demo (1.2.3 {path2.as_posix()}), version solving failed."
)

check_solver_result(root, provider, error=error)


def test_no_valid_solution(root: ProjectPackage, provider: Provider, repo: Repository):
root.add_dependency(Factory.create_dependency("a", "*"))
root.add_dependency(Factory.create_dependency("b", "*"))
Expand Down
Loading