Skip to content

Commit e4cd419

Browse files
fix #1150: enable if setuptools_scm is part of the projects own build-requires
1 parent d837167 commit e4cd419

File tree

5 files changed

+311
-12
lines changed

5 files changed

+311
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- improve typing for entry_points
1616
- refactor file modification time logic into shared helper function for better maintainability
1717
- reduce complexity of HgWorkdir.get_meta method by extracting focused helper methods
18+
- fix #1150: enable setuptools-scm when we are a build requirement
1819

1920
### Fixed
2021

src/setuptools_scm/_config.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from . import _log
1616
from . import _types as _t
17+
from ._integration.pyproject_reading import PyProjectData
1718
from ._integration.pyproject_reading import (
1819
get_args_for_pyproject as _get_args_for_pyproject,
1920
)
@@ -115,17 +116,31 @@ def from_file(
115116
cls,
116117
name: str | os.PathLike[str] = "pyproject.toml",
117118
dist_name: str | None = None,
118-
_require_section: bool = True,
119+
missing_file_ok: bool = False,
119120
**kwargs: Any,
120121
) -> Configuration:
121122
"""
122-
Read Configuration from pyproject.toml (or similar).
123-
Raises exceptions when file is not found or toml is
124-
not installed or the file has invalid format or does
125-
not contain the [tool.setuptools_scm] section.
123+
Read Configuration from pyproject.toml (or similar).
124+
Raises exceptions when file is not found or toml is
125+
not installed or the file has invalid format or does
126+
not contain setuptools_scm configuration (either via
127+
_ [tool.setuptools_scm] section or build-system.requires).
126128
"""
127129

128-
pyproject_data = _read_pyproject(Path(name), require_section=_require_section)
130+
try:
131+
pyproject_data = _read_pyproject(Path(name))
132+
except FileNotFoundError:
133+
if missing_file_ok:
134+
log.warning("File %s not found, using empty configuration", name)
135+
pyproject_data = PyProjectData(
136+
path=Path(name),
137+
tool_name="setuptools_scm",
138+
project={},
139+
section={},
140+
is_required=False,
141+
)
142+
else:
143+
raise
129144
args = _get_args_for_pyproject(pyproject_data, dist_name, kwargs)
130145

131146
args.update(read_toml_overrides(args["dist_name"]))

src/setuptools_scm/_integration/pyproject_reading.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from pathlib import Path
66
from typing import NamedTuple
7+
from typing import Sequence
78

89
from .. import _log
910
from .setuptools import read_dist_name_from_setup_cfg
@@ -20,30 +21,56 @@ class PyProjectData(NamedTuple):
2021
tool_name: str
2122
project: TOML_RESULT
2223
section: TOML_RESULT
24+
is_required: bool
2325

2426
@property
2527
def project_name(self) -> str | None:
2628
return self.project.get("name")
2729

2830

31+
def has_build_package(
32+
requires: Sequence[str], build_package_names: Sequence[str]
33+
) -> bool:
34+
for requirement in requires:
35+
import re
36+
37+
# Remove extras like [toml] first
38+
clean_req = re.sub(r"\[.*?\]", "", requirement)
39+
# Split on version operators and take first part
40+
package_name = re.split(r"[><=!~]", clean_req)[0].strip().lower()
41+
if package_name in build_package_names:
42+
return True
43+
return False
44+
45+
2946
def read_pyproject(
3047
path: Path = Path("pyproject.toml"),
3148
tool_name: str = "setuptools_scm",
32-
require_section: bool = True,
49+
build_package_names: Sequence[str] = ("setuptools_scm", "setuptools-scm"),
3350
) -> PyProjectData:
34-
defn = read_toml_content(path, None if require_section else {})
51+
defn = read_toml_content(path)
52+
requires: list[str] = defn.get("build-system", {}).get("requires", [])
53+
is_required = has_build_package(requires, build_package_names)
54+
3555
try:
3656
section = defn.get("tool", {})[tool_name]
3757
except LookupError as e:
38-
error = f"{path} does not contain a tool.{tool_name} section"
39-
if require_section:
58+
if not is_required:
59+
# Enhanced error message that mentions both configuration options
60+
error = (
61+
f"{path} does not contain a tool.{tool_name} section. "
62+
f"setuptools_scm requires configuration via either:\n"
63+
f" 1. [tool.{tool_name}] section in {path}, or\n"
64+
f" 2. {tool_name} (or setuptools-scm) in [build-system] requires"
65+
)
4066
raise LookupError(error) from e
4167
else:
68+
error = f"{path} does not contain a tool.{tool_name} section"
4269
log.warning("toml section missing %r", error, exc_info=True)
4370
section = {}
4471

4572
project = defn.get("project", {})
46-
return PyProjectData(path, tool_name, project, section)
73+
return PyProjectData(path, tool_name, project, section, is_required)
4774

4875

4976
def get_args_for_pyproject(

src/setuptools_scm/_integration/setuptools.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ def _warn_on_old_setuptools(_version: str = setuptools.__version__) -> None:
4545
)
4646

4747

48+
def _extract_package_name(requirement: str) -> str:
49+
"""Extract the package name from a requirement string.
50+
51+
Examples:
52+
'setuptools_scm' -> 'setuptools_scm'
53+
'setuptools-scm>=8' -> 'setuptools-scm'
54+
'setuptools_scm[toml]>=7.0' -> 'setuptools_scm'
55+
"""
56+
# Split on common requirement operators and take the first part
57+
# This handles: >=, <=, ==, !=, >, <, ~=
58+
import re
59+
60+
# Remove extras like [toml] first
61+
requirement = re.sub(r"\[.*?\]", "", requirement)
62+
# Split on version operators
63+
package_name = re.split(r"[><=!~]", requirement)[0].strip()
64+
return package_name
65+
66+
4867
def _assign_version(
4968
dist: setuptools.Distribution, config: _config.Configuration
5069
) -> None:
@@ -97,7 +116,7 @@ def version_keyword(
97116

98117
config = _config.Configuration.from_file(
99118
dist_name=dist_name,
100-
_require_section=False,
119+
missing_file_ok=True,
101120
**overrides,
102121
)
103122
_assign_version(dist, config)
@@ -115,6 +134,7 @@ def infer_version(dist: setuptools.Distribution) -> None:
115134
return
116135
if dist_name == "setuptools-scm":
117136
return
137+
118138
try:
119139
config = _config.Configuration.from_file(dist_name=dist_name)
120140
except LookupError as e:

0 commit comments

Comments
 (0)