summaryrefslogtreecommitdiff
path: root/src/setuptools_scm/integration.py
blob: 9df4f3ef26aa973cdda56ebaa2fa42997a54816b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from __future__ import annotations

import logging
import os
import textwrap
import warnings
from pathlib import Path
from typing import Any
from typing import Callable
from typing import TYPE_CHECKING

import setuptools

from . import _get_version
from . import _types as _t
from . import _version_missing
from . import Configuration
from ._integration.setuptools import (
    read_dist_name_from_setup_cfg as _read_dist_name_from_setup_cfg,
)
from ._version_cls import _validate_version_cls

log = logging.getLogger(__name__)
if TYPE_CHECKING:
    pass


def _warn_on_old_setuptools(_version: str = setuptools.__version__) -> None:
    if int(_version.split(".")[0]) < 45:
        warnings.warn(
            RuntimeWarning(
                f"""
ERROR: setuptools=={_version} is used in combination with setuptools_scm>=6.x

Your build configuration is incomplete and previously worked by accident!
setuptools_scm requires setuptools>=45


This happens as setuptools is unable to replace itself when a activated build dependency
requires a more recent setuptools version
(it does not respect "setuptools>X" in setup_requires).


setuptools>=31 is required for setup.cfg metadata support
setuptools>=42 is required for pyproject.toml configuration support

Suggested workarounds if applicable:
 - preinstalling build dependencies like setuptools_scm before running setup.py
 - installing setuptools_scm using the system package manager to ensure consistency
 - migrating from the deprecated setup_requires mechanism to pep517/518
   and using a pyproject.toml to declare build dependencies
   which are reliably pre-installed before running the build tools
"""
            )
        )


_warn_on_old_setuptools()


def _assign_version(dist: setuptools.Distribution, config: Configuration) -> None:
    maybe_version = _get_version(config)

    if maybe_version is None:
        _version_missing(config)
    else:
        assert dist.metadata.version is None
        dist.metadata.version = maybe_version


def version_keyword(
    dist: setuptools.Distribution,
    keyword: str,
    value: bool | dict[str, Any] | Callable[[], dict[str, Any]],
) -> None:
    if not value:
        return
    elif value is True:
        value = {}
    elif callable(value):
        value = value()
    assert (
        "dist_name" not in value
    ), "dist_name may not be specified in the setup keyword "
    dist_name: str | None = dist.metadata.name
    if dist.metadata.version is not None:
        warnings.warn(f"version of {dist_name} already set")
        return
    log.debug(
        "version keyword %r",
        vars(dist.metadata),
    )
    log.debug("dist %s %s", id(dist), id(dist.metadata))

    if dist_name is None:
        dist_name = _read_dist_name_from_setup_cfg()
    version_cls = value.pop("version_cls", None)
    normalize = value.pop("normalize", True)
    final_version = _validate_version_cls(version_cls, normalize)
    config = Configuration(dist_name=dist_name, version_cls=final_version, **value)
    _assign_version(dist, config)


def infer_version(dist: setuptools.Distribution) -> None:
    log.debug(
        "finalize hook %r",
        vars(dist.metadata),
    )
    log.debug("dist %s %s", id(dist), id(dist.metadata))
    if dist.metadata.version is not None:
        return  # metadata already added by hook
    dist_name = dist.metadata.name
    if dist_name is None:
        dist_name = _read_dist_name_from_setup_cfg()
    if not os.path.isfile("pyproject.toml"):
        return
    if dist_name == "setuptools_scm":
        return
    try:
        config = Configuration.from_file(dist_name=dist_name)
    except LookupError as e:
        log.exception(e)
    else:
        _assign_version(dist, config)


def data_from_mime(path: _t.PathT) -> dict[str, str]:
    content = Path(path).read_text(encoding="utf-8")
    log.debug("mime %s content:\n%s", path, textwrap.indent(content, "    "))
    # the complex conditions come from reading pseudo-mime-messages
    data = dict(x.split(": ", 1) for x in content.splitlines() if ": " in x)

    log.debug("mime %s data:\n%s", path, data)
    return data