Skip to content

Commit

Permalink
installer: add option to install without re-resolving (just by evalua…
Browse files Browse the repository at this point in the history
…ting 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
  • Loading branch information
radoering committed Oct 4, 2024
1 parent 31db080 commit 5db16dd
Show file tree
Hide file tree
Showing 12 changed files with 1,327 additions and 769 deletions.
15 changes: 15 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,21 @@ Use parallel execution when using the new (`>=1.1.0`) installer.
Set the maximum number of retries in an unstable network.
This setting has no effect if the server does not support HTTP range requests.

### `installer.re-resolve`

**Type**: `boolean`

**Default**: `true`

**Environment Variable**: `POETRY_INSTALLER_RE_RESOLVE`

*Introduced in 2.0.0*

If the config option is _not_ set and the lock file is at least version 2.1
(created by Poetry 2.0 or above), the installer will not re-resolve dependencies
but evaluate the locked markers to decide which of the locked dependencies have to
be installed into the target environment.

### `solver.lazy-wheel`

**Type**: `boolean`
Expand Down
2 changes: 2 additions & 0 deletions src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class Config:
"max-retries": 0,
},
"installer": {
"re-resolve": True,
"parallel": True,
"max-workers": None,
"no-binary": None,
Expand Down Expand Up @@ -303,6 +304,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
"virtualenvs.options.system-site-packages",
"virtualenvs.options.prefer-active-python",
"experimental.system-git-client",
"installer.re-resolve",
"installer.parallel",
"solver.lazy-wheel",
"warnings.export",
Expand Down
1 change: 1 addition & 0 deletions src/poetry/console/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
"virtualenvs.prompt": (str, str),
"experimental.system-git-client": (boolean_validator, boolean_normalizer),
"requests.max-retries": (lambda val: int(val) >= 0, int_normalizer),
"installer.re-resolve": (boolean_validator, boolean_normalizer),
"installer.parallel": (boolean_validator, boolean_normalizer),
"installer.max-workers": (lambda val: int(val) > 0, int_normalizer),
"installer.no-binary": (
Expand Down
85 changes: 59 additions & 26 deletions src/poetry/installation/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from packaging.utils import canonicalize_name

from poetry.installation.executor import Executor
from poetry.puzzle.transaction import Transaction
from poetry.repositories import Repository
from poetry.repositories import RepositoryPool
from poetry.repositories.installed_repository import InstalledRepository
Expand Down Expand Up @@ -206,6 +207,10 @@ def _do_install(self) -> int:
from poetry.puzzle.solver import Solver

locked_repository = Repository("poetry-locked")
reresolve = self._config.get("installer.re-resolve", True)
solved_packages: dict[Package, TransitivePackageInfo] = {}
lockfile_repo = LockfileRepository()

if self._update:
if not self._lock and self._locker.is_locked():
locked_repository = self._locker.locked_repository()
Expand Down Expand Up @@ -241,7 +246,6 @@ def _do_install(self) -> int:
self._write_lock_file(solved_packages)
return 0

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

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

locked_repository = self._locker.locked_repository()
lockfile_repo = locked_repository
if reresolve:
lockfile_repo = locked_repository
else:
solved_packages = self._locker.locked_packages()

if self._io.is_verbose():
self._io.write_line("")
self._io.write_line(
"<info>Finding the necessary packages for the current system</>"
)

if self._groups is not None:
root = self._package.with_dependency_groups(list(self._groups), only=True)
else:
root = self._package.without_optional_dependency_groups()
if reresolve:
if self._groups is not None:
root = self._package.with_dependency_groups(
list(self._groups), only=True
)
else:
root = self._package.without_optional_dependency_groups()

# We resolve again by only using the lock file
packages = lockfile_repo.packages + locked_repository.packages
pool = RepositoryPool.from_packages(packages, self._config)
# We resolve again by only using the lock file
packages = lockfile_repo.packages + locked_repository.packages
pool = RepositoryPool.from_packages(packages, self._config)

solver = Solver(
root,
pool,
self._installed_repository.packages,
locked_repository.packages,
NullIO(),
)
# Everything is resolved at this point, so we no longer need
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
solver.provider.load_deferred(False)

with solver.use_environment(self._env):
ops = solver.solve(use_latest=self._whitelist).calculate_operations(
with_uninstalls=self._requires_synchronization or self._update,
synchronize=self._requires_synchronization,
skip_directory=self._skip_directory,
extras=set(self._extras),
solver = Solver(
root,
pool,
self._installed_repository.packages,
locked_repository.packages,
NullIO(),
)
# Everything is resolved at this point, so we no longer need
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
solver.provider.load_deferred(False)

with solver.use_environment(self._env):
transaction = solver.solve(use_latest=self._whitelist)

else:
if self._groups is None:
groups = self._package.dependency_group_names()
else:
groups = set(self._groups)
transaction = Transaction(
locked_repository.packages,
solved_packages,
self._installed_repository.packages,
self._package,
self._env.marker_env,
groups,
)

ops = transaction.calculate_operations(
with_uninstalls=self._requires_synchronization or self._update,
synchronize=self._requires_synchronization,
skip_directory=self._skip_directory,
extras=set(self._extras),
)

# Validate the dependencies
for op in ops:
Expand Down
Loading

0 comments on commit 5db16dd

Please sign in to comment.