Source code for dwas.predefined._package

from __future__ import annotations

import logging
import shutil
from contextlib import suppress
from typing import Any

# XXX: All imports here should be done from the top level. If we need it,
#      users might need it
from .. import (
    Step,
    StepRunner,
    StepWithDependentSetup,
    parametrize,
    set_defaults,
)

LOGGER = logging.getLogger(__name__)


@set_defaults({"isolate": True})
class Package(StepWithDependentSetup):
    def __init__(self) -> None:
        self.__name__ = "package"

    def gather_artifacts(self, step: StepRunner) -> dict[str, list[Any]]:
        artifacts = {}
        sdists = [str(p) for p in step.cache_path.glob("*.tar.gz")]
        if sdists:
            artifacts["sdists"] = sdists

        wheels = [str(p) for p in step.cache_path.glob("*.whl")]
        if wheels:
            artifacts["wheels"] = wheels

        return artifacts

    def setup_dependent(
        self, original_step: StepRunner, current_step: StepRunner
    ) -> None:
        wheels = list(original_step.cache_path.glob("*.whl"))
        assert len(wheels) == 1

        LOGGER.debug("Installing wheel with its dependencies")
        current_step.install(str(wheels[0]))

        LOGGER.debug(
            "Forcing reinstallation of the wheel in case it had code changes"
        )
        current_step.install(
            str(wheels[0]), no_deps=True, force_reinstall=True
        )

    def __call__(self, step: StepRunner, *, isolate: bool) -> None:
        with suppress(FileNotFoundError):
            shutil.rmtree(step.cache_path)

        command = ["uv", "build", f"--out-dir={step.cache_path}"]
        if not isolate:
            command.append("--no-build-isolation")

        step.run(
            command,
            silent_on_success=step.config.verbosity < 1,
            external_command=True,
        )


[docs] def package(*, isolate: bool = True) -> Step: """ Build a python package that follows :pep:`517`, and install it in dependent venvs. .. warning:: This step is not suitable for building wheels with C extensions. If you have such requirements, please see `the manylinux project`_, or other similar initiatives. This step does not require dependencies by default. :param isolate: Whether to create a new virtual environment for building the package, or run it in the one created for the step. Setting this to :python:`False` does bring a measurable speedup, but might lead to under-declared build dependencies. Defaults to :python:`True`. :return: The step so that you can add additional parameters to it if needed. This leverages ``uv build`` in order to build a source distribution and a universal wheel (assuming there are no c-extensions). When this step is used as a requirement for another step, it will also install the wheel inside it. This allows you to only build the sdist and wheel once, and then install your package in various different other steps, speeding up your whole pipeline. It also helps ensuring that your packaging is correct, as you end up running against the same version as if you were installing your project from e.g. pypi. .. tip:: Using :python:`isolate=False` will speedup measurably this step, but reduces your confidence in not under-declaring build dependencies. :Examples: In order to generate a step ``package``: .. code-block:: # NOTE: we use just register_step as there is no need for dependencies dwas.register_step(dwas.predefined.package()) Or, if you want your step to run faster: .. code-block:: # NOTE: we use register_managed_step as we now need dependencies dwas.register_managed_step( dwas.predefined.package(isolate=False), # We could read those from pyproject.toml directly, but we'd need # to install a toml reading library, so let's live with duplication # for now. dependencies=["setuptools>=61.0.0", "wheel"], ) """ return parametrize("isolate", [isolate])(Package())