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

We can't install package include relative path dependency as VCS dependency #4130

Closed
Closed
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
10 changes: 6 additions & 4 deletions poetry/console/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from .command import Command
from .env_command import EnvCommand

from poetry.utils.helpers import with_temp_directory_manager

if TYPE_CHECKING:
from poetry.repositories import Pool
Expand Down Expand Up @@ -414,9 +414,11 @@ def _parse_requirements(self, requirements: List[str]) -> List[Dict[str, str]]:
if extras:
pair["extras"] = extras

package = Provider.get_package_from_vcs(
"git", url.url, rev=pair.get("rev")
)
with with_temp_directory_manager() as m:
tmp_dir = m.build_tmp_dir_for_vcs(url.url)
package = Provider.get_package_from_vcs(
"git", url.url, tmp_dir, rev=pair.get("rev")
)
pair["name"] = package.name
result.append(pair)

Expand Down
6 changes: 4 additions & 2 deletions poetry/console/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from cleo.helpers import option

from .env_command import EnvCommand

from poetry.utils.helpers import with_temp_directory_manager

if TYPE_CHECKING:
from cleo.io.io import IO # noqa
Expand Down Expand Up @@ -395,7 +395,9 @@ def find_latest_package(
provider = Provider(self.poetry.package, self.poetry.pool, NullIO())

if dep.is_vcs():
return provider.search_for_vcs(dep)[0]
with with_temp_directory_manager() as m:
ret = provider.search_for_vcs(dep, m)[0]
return ret
if dep.is_file():
return provider.search_for_file(dep)[0]
if dep.is_directory():
Expand Down
5 changes: 3 additions & 2 deletions poetry/mixology/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import List

from .version_solver import VersionSolver

from poetry.utils.helpers import with_temp_directory_manager

if TYPE_CHECKING:
from poetry.core.packages.project_package import ProjectPackage
Expand All @@ -19,6 +19,7 @@ def resolve_version(
locked: Dict[str, "DependencyPackage"] = None,
use_latest: List[str] = None,
) -> "SolverResult":
solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
with with_temp_directory_manager() as m:
solver = VersionSolver(root, provider, m, locked=locked, use_latest=use_latest)

return solver.solve()
10 changes: 7 additions & 3 deletions poetry/mixology/version_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.puzzle.provider import TempDirManager

from .failure import SolveFailure
from .incompatibility import Incompatibility
Expand Down Expand Up @@ -43,12 +44,14 @@ def __init__(
self,
root: ProjectPackage,
provider: "Provider",
temp_dir_manager: TempDirManager,
locked: Dict[str, Package] = None,
use_latest: List[str] = None,
):
self._root = root
self._provider = provider
self._locked = locked or {}
self._temp_dir_manager = temp_dir_manager

if use_latest is None:
use_latest = []
Expand Down Expand Up @@ -355,8 +358,9 @@ def _get_min(dependency: Dependency) -> Tuple[bool, int]:
try:
return (
not dependency.marker.is_any(),
len(self._provider.search_for(dependency)),
len(self._provider.search_for(dependency, self._temp_dir_manager)),
)

except ValueError:
return not dependency.marker.is_any(), 0

Expand All @@ -368,7 +372,7 @@ def _get_min(dependency: Dependency) -> Tuple[bool, int]:
locked = self._get_locked(dependency)
if locked is None or not dependency.constraint.allows(locked.version):
try:
packages = self._provider.search_for(dependency)
packages = self._provider.search_for(dependency, self._temp_dir_manager)
except ValueError as e:
self._add_incompatibility(
Incompatibility([Term(dependency, True)], PackageNotFoundCause(e))
Expand All @@ -391,7 +395,7 @@ def _get_min(dependency: Dependency) -> Tuple[bool, int]:
else:
version = locked

version = self._provider.complete_package(version)
version = self._provider.complete_package(version, self._temp_dir_manager)

conflict = False
for incompatibility in self._provider.incompatibilities_for(version):
Expand Down
50 changes: 22 additions & 28 deletions poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from contextlib import contextmanager
from pathlib import Path
from tempfile import mkdtemp
from typing import Any
from typing import Dict
from typing import Iterator
Expand Down Expand Up @@ -40,7 +39,7 @@
from poetry.utils.helpers import download_file
from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory

from poetry.utils.helpers import TempDirManager

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -106,6 +105,7 @@ def search_for(
DirectoryDependency,
URLDependency,
],
temp_dir_manager: TempDirManager,
) -> List[DependencyPackage]:
"""
Search for the specifications that match the given dependency.
Expand Down Expand Up @@ -139,7 +139,7 @@ def search_for(
return PackageCollection(dependency, packages)

if dependency.is_vcs():
packages = self.search_for_vcs(dependency)
packages = self.search_for_vcs(dependency, temp_dir_manager)
elif dependency.is_file():
packages = self.search_for_file(dependency)
elif dependency.is_directory():
Expand All @@ -161,7 +161,7 @@ def search_for(

return PackageCollection(dependency, packages)

def search_for_vcs(self, dependency: VCSDependency) -> List[Package]:
def search_for_vcs(self, dependency: VCSDependency, temp_dir_manager: TempDirManager) -> List[Package]:
"""
Search for the specifications that match the given VCS dependency.

Expand All @@ -171,9 +171,11 @@ def search_for_vcs(self, dependency: VCSDependency) -> List[Package]:
if dependency in self._deferred_cache:
return [self._deferred_cache[dependency]]

tmp_dir = temp_dir_manager.build_tmp_dir_for_vcs(dependency.source)
package = self.get_package_from_vcs(
dependency.vcs,
dependency.source,
tmp_dir,
branch=dependency.branch,
tag=dependency.tag,
rev=dependency.rev,
Expand All @@ -193,6 +195,7 @@ def get_package_from_vcs(
cls,
vcs: str,
url: str,
tmp_dir: Path,
branch: Optional[str] = None,
tag: Optional[str] = None,
rev: Optional[str] = None,
Expand All @@ -201,30 +204,21 @@ def get_package_from_vcs(
if vcs != "git":
raise ValueError(f"Unsupported VCS dependency {vcs}")

tmp_dir = Path(
mkdtemp(prefix="pypoetry-git-{}".format(url.split("/")[-1].rstrip(".git")))
)

try:
git = Git()
git.clone(url, tmp_dir)
reference = branch or tag or rev
if reference is not None:
git.checkout(reference, tmp_dir)
else:
reference = "HEAD"
git = Git()
git.clone(url, tmp_dir)
reference = branch or tag or rev
if reference is not None:
git.checkout(reference, tmp_dir)
else:
reference = "HEAD"

revision = git.rev_parse(reference, tmp_dir).strip()
revision = git.rev_parse(reference, tmp_dir).strip()

package = cls.get_package_from_directory(tmp_dir, name=name)
package._source_type = "git"
package._source_url = url
package._source_reference = reference
package._source_resolved_reference = revision
except Exception:
raise
finally:
safe_rmtree(str(tmp_dir))
package = cls.get_package_from_directory(tmp_dir, name=name)
package._source_type = "git"
package._source_url = url
package._source_reference = reference
package._source_resolved_reference = revision

return package

Expand Down Expand Up @@ -430,7 +424,7 @@ def incompatibilities_for(
for dep in dependencies
]

def complete_package(self, package: DependencyPackage) -> DependencyPackage:
def complete_package(self, package: DependencyPackage, temp_dir_manager: TempDirManager) -> DependencyPackage:
if package.is_root():
package = package.clone()
requires = package.all_requires
Expand Down Expand Up @@ -461,7 +455,7 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage:
elif r.is_file():
self.search_for_file(r)
elif r.is_vcs():
self.search_for_vcs(r)
self.search_for_vcs(r, temp_dir_manager)
elif r.is_url():
self.search_for_url(r)

Expand Down
25 changes: 25 additions & 0 deletions poetry/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,28 @@ def is_dir_writable(path: Path, create: bool = False) -> bool:
return False
else:
return True

@contextmanager
def with_temp_directory_manager():
m = TempDirManager()
try:
yield(m)
except Exception:
raise
finally:
m.release()

class TempDirManager():
def __init__(self) -> None:
self._tmp_dirs = []

def release(self):
for d in self._tmp_dirs:
safe_rmtree(str(d))

def build_tmp_dir_for_vcs(self, url):
tmp_dir = Path(
tempfile.mkdtemp(prefix="pypoetry-git-{}".format(url.split("/")[-1].rstrip(".git")))
)
self._tmp_dirs.append(tmp_dir)
return tmp_dir
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
markers =
execute_setup_py: execute setup py by venv
11 changes: 10 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from cleo.testers.command_tester import CommandTester

from poetry.utils.helpers import with_temp_directory_manager
from poetry.config.config import Config as BaseConfig
from poetry.config.dict_config_source import DictConfigSource
from poetry.factory import Factory
Expand Down Expand Up @@ -117,7 +118,11 @@ def download_mock(mocker):


@pytest.fixture(autouse=True)
def pep517_metadata_mock(mocker):
def pep517_metadata_mock(request, mocker):
marker = request.node.get_closest_marker("execute_setup_py")
if marker:
return

@classmethod # noqa
def _pep517_metadata(cls, path):
try:
Expand Down Expand Up @@ -183,6 +188,10 @@ def tmp_dir():

shutil.rmtree(dir_)

@pytest.fixture
def temp_dir_manager():
with with_temp_directory_manager() as m:
yield m

@pytest.fixture
def mocked_open_files(mocker):
Expand Down
11 changes: 11 additions & 0 deletions tests/fixtures/git/github.com/demo/relative_dependency/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
from setuptools import setup

PKG_DIR = os.path.dirname(os.path.abspath(__file__))
setup(
name="relative_dependency",
version="0.1.0",
install_requires=[
f"subdir_package @ file://{PKG_DIR}/subdir_package"
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from setuptools import setup, find_packages

setup(
name="subdir_package",
version="0.1.0",
packages = find_packages(),
)
24 changes: 13 additions & 11 deletions tests/mixology/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from poetry.mixology.failure import SolveFailure
from poetry.mixology.version_solver import VersionSolver
from poetry.packages import DependencyPackage

from poetry.utils.helpers import with_temp_directory_manager

def add_to_repo(repository, name, version, deps=None, python=None):
package = Package(name, version)
Expand All @@ -23,20 +23,22 @@ def check_solver_result(
if locked is not None:
locked = {k: DependencyPackage(l.to_dependency(), l) for k, l in locked.items()}

solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
with with_temp_directory_manager() as m:

solver = VersionSolver(root, provider, m, locked=locked, use_latest=use_latest)

try:
solution = solver.solve()
except SolveFailure as e:
if error:
assert str(e) == error
try:
solution = solver.solve()
except SolveFailure as e:
if error:
assert str(e) == error

if tries is not None:
assert solver.solution.attempted_solutions == tries
if tries is not None:
assert solver.solution.attempted_solutions == tries

return
return

raise
raise

packages = {}
for package in solution.packages:
Expand Down
Loading