Skip to content

Commit 713e672

Browse files
committed
installer: add option to install without re-resolving (just by evaluating locked markers) (#9427)
- introduce "installer.re-resolve" config option (default: True) - if the config option is set to False and the lock file is at least version 2.1, the installer will not re-resolve but evaluate locked markers
1 parent 6a6d6d6 commit 713e672

File tree

12 files changed

+1327
-769
lines changed

12 files changed

+1327
-769
lines changed

docs/configuration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,21 @@ Use parallel execution when using the new (`>=1.1.0`) installer.
269269
Set the maximum number of retries in an unstable network.
270270
This setting has no effect if the server does not support HTTP range requests.
271271

272+
### `installer.re-resolve`
273+
274+
**Type**: `boolean`
275+
276+
**Default**: `true`
277+
278+
**Environment Variable**: `POETRY_INSTALLER_RE_RESOLVE`
279+
280+
*Introduced in 2.0.0*
281+
282+
If the config option is _not_ set and the lock file is at least version 2.1
283+
(created by Poetry 2.0 or above), the installer will not re-resolve dependencies
284+
but evaluate the locked markers to decide which of the locked dependencies have to
285+
be installed into the target environment.
286+
272287
### `solver.lazy-wheel`
273288

274289
**Type**: `boolean`

src/poetry/config/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class Config:
123123
"max-retries": 0,
124124
},
125125
"installer": {
126+
"re-resolve": True,
126127
"parallel": True,
127128
"max-workers": None,
128129
"no-binary": None,
@@ -297,6 +298,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
297298
"virtualenvs.options.no-pip",
298299
"virtualenvs.options.system-site-packages",
299300
"virtualenvs.use-poetry-python",
301+
"installer.re-resolve",
300302
"installer.parallel",
301303
"solver.lazy-wheel",
302304
"system-git-client",

src/poetry/console/commands/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
7171
"virtualenvs.prompt": (str, str),
7272
"system-git-client": (boolean_validator, boolean_normalizer),
7373
"requests.max-retries": (lambda val: int(val) >= 0, int_normalizer),
74+
"installer.re-resolve": (boolean_validator, boolean_normalizer),
7475
"installer.parallel": (boolean_validator, boolean_normalizer),
7576
"installer.max-workers": (lambda val: int(val) > 0, int_normalizer),
7677
"installer.no-binary": (

src/poetry/installation/installer.py

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

99
from poetry.installation.executor import Executor
10+
from poetry.puzzle.transaction import Transaction
1011
from poetry.repositories import Repository
1112
from poetry.repositories import RepositoryPool
1213
from poetry.repositories.installed_repository import InstalledRepository
@@ -206,6 +207,10 @@ def _do_install(self) -> int:
206207
from poetry.puzzle.solver import Solver
207208

208209
locked_repository = Repository("poetry-locked")
210+
reresolve = self._config.get("installer.re-resolve", True)
211+
solved_packages: dict[Package, TransitivePackageInfo] = {}
212+
lockfile_repo = LockfileRepository()
213+
209214
if self._update:
210215
if not self._lock and self._locker.is_locked():
211216
locked_repository = self._locker.locked_repository()
@@ -242,7 +247,6 @@ def _do_install(self) -> int:
242247
self._write_lock_file(solved_packages)
243248
return 0
244249

245-
lockfile_repo = LockfileRepository()
246250
for package in solved_packages:
247251
if not lockfile_repo.has_package(package):
248252
lockfile_repo.add_package(package)
@@ -255,6 +259,13 @@ def _do_install(self) -> int:
255259
"pyproject.toml changed significantly since poetry.lock was last"
256260
" generated. Run `poetry lock [--no-update]` to fix the lock file."
257261
)
262+
if not (reresolve or self._locker.is_locked_groups_and_markers()):
263+
if self._io.is_verbose():
264+
self._io.write_line(
265+
"<info>Cannot install without re-resolving"
266+
" because the lock file is not at least version 2.1</>"
267+
)
268+
reresolve = True
258269

259270
locker_extras = {
260271
canonicalize_name(extra)
@@ -265,41 +276,63 @@ def _do_install(self) -> int:
265276
raise ValueError(f"Extra [{extra}] is not specified.")
266277

267278
locked_repository = self._locker.locked_repository()
268-
lockfile_repo = locked_repository
279+
if reresolve:
280+
lockfile_repo = locked_repository
281+
else:
282+
solved_packages = self._locker.locked_packages()
269283

270284
if self._io.is_verbose():
271285
self._io.write_line("")
272286
self._io.write_line(
273287
"<info>Finding the necessary packages for the current system</>"
274288
)
275289

276-
if self._groups is not None:
277-
root = self._package.with_dependency_groups(list(self._groups), only=True)
278-
else:
279-
root = self._package.without_optional_dependency_groups()
290+
if reresolve:
291+
if self._groups is not None:
292+
root = self._package.with_dependency_groups(
293+
list(self._groups), only=True
294+
)
295+
else:
296+
root = self._package.without_optional_dependency_groups()
280297

281-
# We resolve again by only using the lock file
282-
packages = lockfile_repo.packages + locked_repository.packages
283-
pool = RepositoryPool.from_packages(packages, self._config)
298+
# We resolve again by only using the lock file
299+
packages = lockfile_repo.packages + locked_repository.packages
300+
pool = RepositoryPool.from_packages(packages, self._config)
284301

285-
solver = Solver(
286-
root,
287-
pool,
288-
self._installed_repository.packages,
289-
locked_repository.packages,
290-
NullIO(),
291-
)
292-
# Everything is resolved at this point, so we no longer need
293-
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
294-
solver.provider.load_deferred(False)
295-
296-
with solver.use_environment(self._env):
297-
ops = solver.solve(use_latest=self._whitelist).calculate_operations(
298-
with_uninstalls=self._requires_synchronization or self._update,
299-
synchronize=self._requires_synchronization,
300-
skip_directory=self._skip_directory,
301-
extras=set(self._extras),
302+
solver = Solver(
303+
root,
304+
pool,
305+
self._installed_repository.packages,
306+
locked_repository.packages,
307+
NullIO(),
302308
)
309+
# Everything is resolved at this point, so we no longer need
310+
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
311+
solver.provider.load_deferred(False)
312+
313+
with solver.use_environment(self._env):
314+
transaction = solver.solve(use_latest=self._whitelist)
315+
316+
else:
317+
if self._groups is None:
318+
groups = self._package.dependency_group_names()
319+
else:
320+
groups = set(self._groups)
321+
transaction = Transaction(
322+
locked_repository.packages,
323+
solved_packages,
324+
self._installed_repository.packages,
325+
self._package,
326+
self._env.marker_env,
327+
groups,
328+
)
329+
330+
ops = transaction.calculate_operations(
331+
with_uninstalls=self._requires_synchronization or self._update,
332+
synchronize=self._requires_synchronization,
333+
skip_directory=self._skip_directory,
334+
extras=set(self._extras),
335+
)
303336

304337
# Validate the dependencies
305338
for op in ops:

0 commit comments

Comments
 (0)