Skip to content

Commit 6a6d6d6

Browse files
committed
locker: lock transitive marker and groups for each package (#9427)
1 parent 3b7ef12 commit 6a6d6d6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1244
-247
lines changed

src/poetry/installation/installer.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from packaging.utils import canonicalize_name
88

99
from poetry.installation.executor import Executor
10-
from poetry.installation.operations import Uninstall
11-
from poetry.installation.operations import Update
1210
from poetry.repositories import Repository
1311
from poetry.repositories import RepositoryPool
1412
from poetry.repositories.installed_repository import InstalledRepository
@@ -20,12 +18,14 @@
2018

2119
from cleo.io.io import IO
2220
from packaging.utils import NormalizedName
21+
from poetry.core.packages.package import Package
2322
from poetry.core.packages.path_dependency import PathDependency
2423
from poetry.core.packages.project_package import ProjectPackage
2524

2625
from poetry.config.config import Config
2726
from poetry.installation.operations.operation import Operation
2827
from poetry.packages import Locker
28+
from poetry.packages.transitive_package_info import TransitivePackageInfo
2929
from poetry.utils.env import Env
3030

3131

@@ -196,12 +196,9 @@ def _do_refresh(self) -> int:
196196
with solver.provider.use_source_root(
197197
source_root=self._env.path.joinpath("src")
198198
):
199-
ops = solver.solve(use_latest=use_latest).calculate_operations()
199+
solved_packages = solver.solve(use_latest=use_latest).get_solved_packages()
200200

201-
lockfile_repo = LockfileRepository()
202-
self._populate_lockfile_repo(lockfile_repo, ops)
203-
204-
self._write_lock_file(lockfile_repo, force=True)
201+
self._write_lock_file(solved_packages, force=True)
205202

206203
return 0
207204

@@ -236,10 +233,19 @@ def _do_install(self) -> int:
236233
with solver.provider.use_source_root(
237234
source_root=self._env.path.joinpath("src")
238235
):
239-
ops = solver.solve(use_latest=self._whitelist).calculate_operations()
236+
solved_packages = solver.solve(
237+
use_latest=self._whitelist
238+
).get_solved_packages()
239+
240+
if not self.executor.enabled:
241+
# If we are only in lock mode, no need to go any further
242+
self._write_lock_file(solved_packages)
243+
return 0
240244

241245
lockfile_repo = LockfileRepository()
242-
self._populate_lockfile_repo(lockfile_repo, ops)
246+
for package in solved_packages:
247+
if not lockfile_repo.has_package(package):
248+
lockfile_repo.add_package(package)
243249

244250
else:
245251
self._io.write_line("<info>Installing dependencies from lock file</>")
@@ -261,11 +267,6 @@ def _do_install(self) -> int:
261267
locked_repository = self._locker.locked_repository()
262268
lockfile_repo = locked_repository
263269

264-
if not self.executor.enabled:
265-
# If we are only in lock mode, no need to go any further
266-
self._write_lock_file(lockfile_repo)
267-
return 0
268-
269270
if self._io.is_verbose():
270271
self._io.write_line("")
271272
self._io.write_line(
@@ -312,13 +313,17 @@ def _do_install(self) -> int:
312313

313314
if status == 0 and self._update:
314315
# Only write lock file when installation is success
315-
self._write_lock_file(lockfile_repo)
316+
self._write_lock_file(solved_packages)
316317

317318
return status
318319

319-
def _write_lock_file(self, repo: LockfileRepository, force: bool = False) -> None:
320+
def _write_lock_file(
321+
self,
322+
packages: dict[Package, TransitivePackageInfo],
323+
force: bool = False,
324+
) -> None:
320325
if not self.is_dry_run() and (force or self._update):
321-
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
326+
updated_lock = self._locker.set_lock_data(self._package, packages)
322327

323328
if updated_lock:
324329
self._io.write_line("")
@@ -327,16 +332,5 @@ def _write_lock_file(self, repo: LockfileRepository, force: bool = False) -> Non
327332
def _execute(self, operations: list[Operation]) -> int:
328333
return self._executor.execute(operations)
329334

330-
def _populate_lockfile_repo(
331-
self, repo: LockfileRepository, ops: Iterable[Operation]
332-
) -> None:
333-
for op in ops:
334-
if isinstance(op, Uninstall):
335-
continue
336-
337-
package = op.target_package if isinstance(op, Update) else op.package
338-
if not repo.has_package(package):
339-
repo.add_package(package)
340-
341335
def _get_installed(self) -> InstalledRepository:
342336
return InstalledRepository.load(self._env)

src/poetry/packages/locker.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from poetry.core.packages.vcs_dependency import VCSDependency
3939
from tomlkit.toml_document import TOMLDocument
4040

41+
from poetry.packages.transitive_package_info import TransitivePackageInfo
4142
from poetry.repositories.lockfile_repository import LockfileRepository
4243

4344
logger = logging.getLogger(__name__)
@@ -49,7 +50,7 @@
4950

5051

5152
class Locker:
52-
_VERSION = "2.0"
53+
_VERSION = "2.1"
5354
_READ_VERSION_RANGE = ">=1,<3"
5455

5556
_legacy_keys: ClassVar[list[str]] = [
@@ -236,7 +237,9 @@ def locked_repository(self) -> LockfileRepository:
236237

237238
return repository
238239

239-
def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
240+
def set_lock_data(
241+
self, root: Package, packages: dict[Package, TransitivePackageInfo]
242+
) -> bool:
240243
"""Store lock data and eventually persist to the lock file"""
241244
lock = self._compute_lock_data(root, packages)
242245

@@ -247,7 +250,7 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
247250
return False
248251

249252
def _compute_lock_data(
250-
self, root: Package, packages: list[Package]
253+
self, root: Package, packages: dict[Package, TransitivePackageInfo]
251254
) -> TOMLDocument:
252255
package_specs = self._lock_packages(packages)
253256
# Retrieving hashes
@@ -407,7 +410,9 @@ def _get_lock_data(self) -> dict[str, Any]:
407410

408411
return lock_data
409412

410-
def _lock_packages(self, packages: list[Package]) -> list[dict[str, Any]]:
413+
def _lock_packages(
414+
self, packages: dict[Package, TransitivePackageInfo]
415+
) -> list[dict[str, Any]]:
411416
locked = []
412417

413418
for package in sorted(
@@ -422,13 +427,15 @@ def _lock_packages(self, packages: list[Package]) -> list[dict[str, Any]]:
422427
x.source_resolved_reference or "",
423428
),
424429
):
425-
spec = self._dump_package(package)
430+
spec = self._dump_package(package, packages[package])
426431

427432
locked.append(spec)
428433

429434
return locked
430435

431-
def _dump_package(self, package: Package) -> dict[str, Any]:
436+
def _dump_package(
437+
self, package: Package, transitive_info: TransitivePackageInfo
438+
) -> dict[str, Any]:
432439
dependencies: dict[str, list[Any]] = {}
433440
for dependency in sorted(
434441
package.requires,
@@ -498,8 +505,21 @@ def _dump_package(self, package: Package) -> dict[str, Any]:
498505
"description": package.description or "",
499506
"optional": package.optional,
500507
"python-versions": package.python_versions,
501-
"files": sorted(package.files, key=lambda x: x["file"]),
508+
"groups": sorted(transitive_info.groups, key=lambda x: (x != "main", x)),
502509
}
510+
if transitive_info.markers:
511+
if len(markers := set(transitive_info.markers.values())) == 1:
512+
if not (marker := next(iter(markers))).is_any():
513+
data["markers"] = str(marker)
514+
else:
515+
data["markers"] = inline_table()
516+
for k, v in sorted(
517+
transitive_info.markers.items(),
518+
key=lambda x: (x[0] != "main", x[0]),
519+
):
520+
if not v.is_any():
521+
data["markers"][k] = str(v)
522+
data["files"] = sorted(package.files, key=lambda x: x["file"])
503523

504524
if dependencies:
505525
data["dependencies"] = table()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import TYPE_CHECKING
5+
6+
7+
if TYPE_CHECKING:
8+
from poetry.core.version.markers import BaseMarker
9+
10+
11+
@dataclass
12+
class TransitivePackageInfo:
13+
depth: int # max depth in the dependency tree
14+
groups: set[str]
15+
markers: dict[str, BaseMarker] # group -> marker

src/poetry/puzzle/provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ def complete_package(
525525
package = dependency_package.package
526526
dependency = dependency_package.dependency
527527
new_dependency = package.without_features().to_dependency()
528+
new_dependency.marker = AnyMarker()
528529

529530
# When adding dependency foo[extra] -> foo, preserve foo's source, if it's
530531
# specified. This prevents us from trying to get foo from PyPI

0 commit comments

Comments
 (0)