Skip to content

Commit 1e0300b

Browse files
committed
installer: add option to install without re-resolving (just by evaluating locked markers) (python-poetry#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 a6deee1 commit 1e0300b

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
@@ -285,6 +285,21 @@ Use parallel execution when using the new (`>=1.1.0`) installer.
285285
Set the maximum number of retries in an unstable network.
286286
This setting has no effect if the server does not support HTTP range requests.
287287

288+
### `installer.re-resolve`
289+
290+
**Type**: `boolean`
291+
292+
**Default**: `true`
293+
294+
**Environment Variable**: `POETRY_INSTALLER_RE_RESOLVE`
295+
296+
*Introduced in 2.0.0*
297+
298+
If the config option is _not_ set and the lock file is at least version 2.1
299+
(created by Poetry 2.0 or above), the installer will not re-resolve dependencies
300+
but evaluate the locked markers to decide which of the locked dependencies have to
301+
be installed into the target environment.
302+
288303
### `solver.lazy-wheel`
289304

290305
**Type**: `boolean`

src/poetry/config/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class Config:
126126
"max-retries": 0,
127127
},
128128
"installer": {
129+
"re-resolve": True,
129130
"parallel": True,
130131
"max-workers": None,
131132
"no-binary": None,
@@ -300,6 +301,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
300301
"virtualenvs.options.system-site-packages",
301302
"virtualenvs.options.prefer-active-python",
302303
"experimental.system-git-client",
304+
"installer.re-resolve",
303305
"installer.parallel",
304306
"solver.lazy-wheel",
305307
"keyring.enabled",

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
"experimental.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()
@@ -241,7 +246,6 @@ def _do_install(self) -> int:
241246
self._write_lock_file(solved_packages)
242247
return 0
243248

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

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

266277
locked_repository = self._locker.locked_repository()
267-
lockfile_repo = locked_repository
278+
if reresolve:
279+
lockfile_repo = locked_repository
280+
else:
281+
solved_packages = self._locker.locked_packages()
268282

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

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

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

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

303336
# Validate the dependencies
304337
for op in ops:

0 commit comments

Comments
 (0)