def do_lock(
    project: Project,
    strategy: str = "all",
    tracked_names: Iterable[str] | None = None,
    requirements: list[Requirement] | None = None,
    dry_run: bool = False,
    refresh: bool = False,
    groups: list[str] | None = None,
    strategy_change: list[str] | None = None,
    hooks: HookManager | None = None,
    env_spec: EnvSpec | None = None,
    append: bool = False,
) -> dict[str, list[Candidate]]:
    """Performs the locking process and update lockfile."""
    hooks = hooks or HookManager(project)
    check_project_file(project)
    lock_strategy = project.lockfile.apply_strategy_change(strategy_change or [])
    if FLAG_CROSS_PLATFORM in lock_strategy:  # pragma: no cover
        project.core.ui.deprecated(
            "`cross_platform` strategy is deprecated in favor of the new lock targets.\n"
            "See docs: http://pdm-project.org/en/latest/usage/lock-targets/"
        )
    if strategy_change and append:
        raise PdmUsageError("Not allowed to change lock strategy when --append is used.")
    locked_repo = project.get_locked_repository()
    if refresh:
        if env_spec is not None:
            raise PdmUsageError("Cannot pass --python/--platform/--implementation with --refresh")
        repo = project.get_repository()
        with project.core.ui.open_spinner("Re-calculating hashes..."):
            with project.core.ui.logging("lock"):
                candidates = [entry.candidate for entry in locked_repo.packages.values()]
                for c in candidates:
                    c.hashes.clear()
                repo.fetch_hashes(candidates)
            project.lockfile.format_lockfile(locked_repo, groups=project.lockfile.groups, strategy=lock_strategy)
        project.write_lockfile()
        return locked_repo.all_candidates

    if groups is None:
        groups = list(project.iter_groups())
    if not requirements:
        all_deps = project._resolve_dependencies(groups)
        requirements = [r for group in groups for r in project.get_dependencies(group, all_deps)]
    ui = project.core.ui
    supports_env_spec = "env_spec" in inspect.signature(project.get_provider).parameters
    # The repository to store the lock result
    if locked_repo.packages:
        result_repo = LockedRepository({}, sources=project.sources, environment=project.environment)
    else:
        # Use the same repository if the lock is empty.
        result_repo = locked_repo
    if not supports_env_spec:  # pragma: no cover
        ui.warn("Lock targets are not supported by the current provider")

    if append:
        if env_spec is None:
            raise PdmUsageError("Cannot use `--append` without --python/--platform/--implementation")
        if env_spec in locked_repo.targets:
            ui.echo(f"{termui.Emoji.LOCK} Lock target {env_spec} already exists, skip locking.")
            return locked_repo.all_candidates
        targets = [env_spec]
        result_repo = locked_repo  # Append to the same lockfile
    else:
        targets = [env_spec] if env_spec else (locked_repo.targets[:] or project.lock_targets)
    # Restrict the target python to within the project's requires-python
    global_requires_python = project.environment.python_requires
    for i, target in enumerate(targets):
        if (merged := global_requires_python & target.requires_python).is_empty():
            raise PdmUsageError(
                f"The target requires Python {target.requires_python} which is not compatible with "
                f"the project's requires-python {global_requires_python}"
            )
        targets[i] = target.replace(requires_python=merged)
    resolve_max_rounds = int(project.config["strategy.resolve_max_rounds"])
    hooks.try_emit("pre_lock", requirements=requirements, dry_run=dry_run)
    with ui.logging("lock"):
        # The context managers are nested to ensure the spinner is stopped before
        # any message is thrown to the output.
        resolver_class = project.get_resolver()
        with RichLockReporter(requirements, ui) as reporter:
            collected_groups: set[str] = set()
            try:
                for target in targets:
                    resolver = resolver_class(
                        environment=project.environment,
                        requirements=[r for r in requirements if not r.marker or r.marker.matches(target)],
                        update_strategy=strategy,
                        strategies=lock_strategy,
                        target=target,
                        tracked_names=list(tracked_names or ()),
                        locked_repository=locked_repo,
                        reporter=reporter,
                    )
                    reporter.update(f"Resolve for environment {target}")
                    resolved, new_groups = resolver.resolve()
                    collected_groups.update(new_groups)
                    locked_repo.merge_result(target, resolved)
                    if result_repo is not locked_repo:
                        result_repo.merge_result(target, resolved)
            except ResolutionTooDeep:
                reporter.update(f"{termui.Emoji.LOCK} Lock failed.", info="", completed=1)
                ui.echo(
                    "The dependency resolution exceeds the maximum loop depth of "
                    f"{resolve_max_rounds}, there may be some circular dependencies "
                    "in your project. Try to solve them or increase the "
                    f"[success]`strategy.resolve_max_rounds`[/] config.",
                    err=True,
                )
                raise ResolutionError(
                    f"The dependency resolution exceeds the maximum loop depth of {resolve_max_rounds}"
                ) from None
            except ResolutionImpossible as err:
                reporter.update(f"{termui.Emoji.LOCK} Lock failed.", info="", completed=1)
                ui.error(format_resolution_impossible(err))
                raise ResolutionError("Unable to find a resolution") from None
            else:
                groups = list(set(groups) | collected_groups)
                if project.enable_write_lockfile:
                    reporter.update(f"{termui.Emoji.LOCK} Lock successful.", info="", completed=1)
                project.lockfile.format_lockfile(result_repo, groups=groups, strategy=lock_strategy)
                project.write_lockfile(write=not dry_run)
                hooks.try_emit("post_lock", resolution=result_repo.all_candidates, dry_run=dry_run)

    return result_repo.all_candidates
Performs the locking process and update lockfile.
src/pdm/cli/actions.py