Skip to content

gitversioned.plugins.setuptools_plugin

Setuptools build integration plugin for GitVersioned.

This module implements the integration layer between GitVersioned and the Setuptools build system. It exposes hook entry points that Setuptools calls during distribution configuration, allowing packaging configuration to dynamically query and apply resolved Git-based versions.

The plugin functions by registering setup keyword arguments and finalizer hooks. It extracts the package distribution options, resolves project context, extracts any pre-existing or environment-specified versions, executes the Git versioning resolution, and updates the distribution metadata version dynamically. If configured, it also injects the generated version file into the distribution's package_data or py_modules.

finalize_distribution_options(distribution)

Compute the package version and update the distribution metadata.

This is the primary entry point triggered during the Setuptools distribution finalization lifecycle. It resolves the package name, extracts any established or environment-provided version, reads configuration overrides, calculates the dynamic version using Git metadata, and applies the version to the distribution metadata. If a version file is generated, it is automatically injected into the distribution's package data or module list.

.. code-block:: python

# Hook is registered as a Setuptools entry point:
# entry_point = "gitversioned.plugins.setuptools_plugin"
# func = "finalize_distribution_options"

:param distribution: The Setuptools distribution object to finalize. :raises DistutilsSetupError: If the package name is unresolved or if version resolution encounters an unexpected failure.

Source code in src/gitversioned/plugins/setuptools_plugin.py
def finalize_distribution_options(distribution: Distribution) -> None:
    """
    Compute the package version and update the distribution metadata.

    This is the primary entry point triggered during the Setuptools distribution
    finalization lifecycle. It resolves the package name, extracts any established or
    environment-provided version, reads configuration overrides, calculates the dynamic
    version using Git metadata, and applies the version to the distribution metadata.
    If a version file is generated, it is automatically injected into the distribution's
    package data or module list.

    .. code-block:: python

        # Hook is registered as a Setuptools entry point:
        # entry_point = "gitversioned.plugins.setuptools_plugin"
        # func = "finalize_distribution_options"

    :param distribution: The Setuptools distribution object to finalize.
    :raises DistutilsSetupError: If the package name is unresolved or if version
        resolution encounters an unexpected failure.
    """
    configure_logger(
        enabled=True,
        clear_loggers=True,
        level="WARNING",
        otel_formatting="disable",
        enqueue=False,
        format="[gitversioned:setuptools] {level} - {message}\n",
    )
    logger.debug("Finalizing distribution options for GitVersioned.")

    project_root, source_root, package_name = _resolve_project_context(distribution)
    if not package_name:
        raise_distutils_setup_error("Could not determine package name.")

    # Check for an established version to avoid redundant Git resolution
    established_version = _extract_established_version(distribution, project_root)

    resolved = os.environ.get("GITVERSIONED_RESOLVED_VERSION")
    if resolved and not established_version:
        established_version = resolved

    config_overrides = getattr(distribution, "gitversioned_config", {})

    try:
        kwargs: Any = {
            "package_name": package_name,
            "project_root": project_root,
            "src_root": source_root,
            "build_is_editable": getattr(distribution, "editable", False),
        }
        kwargs.update(config_overrides)
        settings = Settings(**kwargs)

        if established_version:
            logger.info(f"Using established version: {established_version}")
            version_string = established_version
            output_path = _find_existing_version_file(settings)
        else:
            repository = GitRepository(settings.project_root)
            environment = BuildEnvironment(project_root=settings.project_root)
            output_path, _, version, _, _ = resolve_version_output_to_stream(
                settings=settings, repository=repository, environment=environment
            )
            version_string = str(version)

        # Update distribution metadata
        if hasattr(distribution, "metadata"):
            distribution.metadata.version = version_string

        if output_path and isinstance(output_path, Path):
            _inject_output_into_distribution(
                distribution=distribution,
                output_path=output_path,
                source_root=source_root,
                package_name=package_name,
            )

    except Exception as error:
        if is_distutils_setup_error(error):
            raise
        logger.exception("Unexpected failure during version resolution")
        raise_distutils_setup_error(
            f"Failed to resolve version: {error}", from_exception=error
        )

setup_keywords(distribution, attribute, value)

Validate and store the GitVersioned configuration dictionary.

Registers the gitversioned configuration dictionary on the distribution object. This configuration is subsequently retrieved during option finalization to customize the version resolution process.

.. code-block:: python

# Example usage inside setup.py:
setup(
    gitversioned={
        "version_source_file": "version.txt",
        "source_type": ["tag", "file"],
    },
)

:param distribution: The Setuptools distribution object being configured. :param attribute: The name of the setup keyword (must be "gitversioned"). :param value: The configuration settings dictionary provided in the setup call. :raises DistutilsSetupError: If the keyword attribute is invalid or the value is not a dict.

Source code in src/gitversioned/plugins/setuptools_plugin.py
def setup_keywords(distribution: Distribution, attribute: str, value: Any) -> None:
    """
    Validate and store the GitVersioned configuration dictionary.

    Registers the `gitversioned` configuration dictionary on the distribution object.
    This configuration is subsequently retrieved during option finalization to customize
    the version resolution process.

    .. code-block:: python

        # Example usage inside setup.py:
        setup(
            gitversioned={
                "version_source_file": "version.txt",
                "source_type": ["tag", "file"],
            },
        )

    :param distribution: The Setuptools distribution object being configured.
    :param attribute: The name of the setup keyword (must be "gitversioned").
    :param value: The configuration settings dictionary provided in the setup call.
    :raises DistutilsSetupError: If the keyword attribute is invalid or the
        value is not a dict.
    """
    configure_logger(
        enabled=True,
        clear_loggers=True,
        level="WARNING",
        otel_formatting="disable",
        enqueue=False,
        format="[gitversioned:setuptools] {level} - {message}\n",
    )
    logger.debug(f"setup_keywords called with attribute='{attribute}'")

    if attribute != "gitversioned":
        logger.error(f"Unknown keyword argument: {attribute}")
        raise_distutils_setup_error(f"Unknown keyword argument: {attribute}")

    if not isinstance(value, (dict, bool)):
        logger.error("gitversioned keyword argument must be a dict or bool")
        raise_distutils_setup_error("gitversioned must be a dict or bool")

    cast("Any", distribution).gitversioned_config = (
        {} if isinstance(value, bool) else value
    )