Skip to content

Commit d7d15fb

Browse files
0xfabioofrostming
andauthored
feat: Add parameter --build-number to wheel builder (#229)
* Add build-tag parameter * Revert changes in sdist.py * Add unit tests for build-number * Update documentation * Revert test_wheel.py * Update src/pdm/backend/wheel.py Applied suggestions from review Co-authored-by: Frost Ming <mianghong@gmail.com> * Update src/pdm/backend/wheel.py Applied suggestions from review Co-authored-by: Frost Ming <mianghong@gmail.com> * Remove `:=` operator due to py3.7 support --------- Co-authored-by: Frost Ming <mianghong@gmail.com>
1 parent 8183604 commit d7d15fb

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

docs/build_config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ Some build frontends such as [build] and [pdm] supports passing options from com
328328
- `--python-tag=<tag>` Override the python implementation compatibility tag(e.g. `cp37`, `py3`, `pp3`)
329329
- `--py-limited-api=<abi>` Python tag (`cp32`|`cp33`|`cpNN`) for abi3 wheel tag
330330
- `--plat-name=<plat>` Override the platform name(e.g. `win_amd64`, `manylinux2010_x86_64`)
331+
- `--build-number=<build-number>` Build number for this particular version. As specified in PEP-0427, this must start with a digit.
331332
- `no-clean-build` Don't clean the build directory before the build starts, this can also work by setting env var `PDM_BUILD_NO_CLEAN` to `1`.
332333

333334
For example, you can supply these options with [build]:

src/pdm/backend/wheel.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from pdm.backend._vendor.packaging import tags
1919
from pdm.backend._vendor.packaging.specifiers import SpecifierSet
20-
from pdm.backend._vendor.packaging.utils import canonicalize_name
20+
from pdm.backend._vendor.packaging.utils import _build_tag_regex, canonicalize_name
2121
from pdm.backend.base import Builder
2222
from pdm.backend.hooks import Context
2323
from pdm.backend.hooks.setuptools import SetuptoolsBuildHook
@@ -46,6 +46,8 @@
4646
Tag: {tag}
4747
"""
4848

49+
BUILD_TAG_FORMAT = "Build: {build_number}"
50+
4951
# Fix the date time for reproducible builds
5052
try:
5153
_env_date = time.gmtime(int(os.environ["SOURCE_DATE_EPOCH"]))[:6]
@@ -77,6 +79,7 @@ def __init__(
7779
) -> None:
7880
super().__init__(location, config_settings)
7981
self.__tag: str | None = None
82+
self.__build_number: str | None = None
8083

8184
def scheme_path(self, name: str, relative: str) -> str:
8285
if name not in SCHEME_NAMES:
@@ -156,7 +159,11 @@ def build_artifact(
156159
records.append(self._add_file_to_zip(zf, rel_path, full_path))
157160
self._write_record(zf, records)
158161

159-
target = context.dist_dir / f"{self.name_version}-{self.tag}.whl"
162+
name_version = self.name_version
163+
if self.build_number:
164+
name_version = f"{name_version}-{self.build_number}"
165+
166+
target = context.dist_dir / f"{name_version}-{self.tag}.whl"
160167
if target.exists():
161168
target.unlink()
162169
shutil.move(temp_name, target)
@@ -168,6 +175,12 @@ def name_version(self) -> str:
168175
version = to_filename(safe_version(self.config.metadata["version"]))
169176
return f"{name}-{version}"
170177

178+
@property
179+
def build_number(self) -> str | None:
180+
if not self.__build_number:
181+
self.__build_number = self._get_build_number()
182+
return self.__build_number
183+
171184
@property
172185
def dist_info_name(self) -> str:
173186
return f"{self.name_version}.dist-info"
@@ -178,6 +191,18 @@ def tag(self) -> str:
178191
self.__tag = self._get_tag()
179192
return self.__tag
180193

194+
def _get_build_number(self) -> str | None:
195+
cmd = "--build-number"
196+
if cmd not in self.config_settings:
197+
return None
198+
build_number = self.config_settings[cmd]
199+
if not _build_tag_regex.match(build_number):
200+
raise ValueError(
201+
f"Invalid build number: {build_number}, please refer to PEP 427"
202+
)
203+
204+
return build_number
205+
181206
def _get_tag(self) -> str:
182207
impl, abi, platform = self._get_platform_tags()
183208
is_purelib = self.config.build_config.is_purelib
@@ -276,12 +301,15 @@ def _write_wheel_file(self, fp: IO[str], is_purelib: bool) -> None:
276301
except ModuleNotFoundError:
277302
version = "0.0.0+local"
278303

279-
fp.write(
280-
WHEEL_FILE_FORMAT.format(
281-
is_purelib=str(is_purelib).lower(), tag=self.tag, version=version
282-
)
304+
wheel_metadata = WHEEL_FILE_FORMAT.format(
305+
is_purelib=str(is_purelib).lower(), tag=self.tag, version=version
283306
)
284307

308+
if self.build_number:
309+
wheel_metadata = f"{wheel_metadata}{BUILD_TAG_FORMAT.format(build_number=self.build_number)}"
310+
311+
fp.write(wheel_metadata)
312+
285313
def _write_entry_points(
286314
self, fp: IO[str], entry_points: dict[str, dict[str, str]]
287315
) -> None:

tests/test_api.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,32 @@ def test_build_single_module(dist: Path) -> None:
4343
assert "demo_module-0.1.0.dist-info/licenses/LICENSE" in zip_names
4444

4545

46+
@pytest.mark.parametrize("name", ["demo-module"])
47+
def test_build_single_module_with_build_number(dist: Path) -> None:
48+
build_number = "20231241"
49+
wheel_name = api.build_wheel(
50+
dist.as_posix(),
51+
config_settings={"--build-number": build_number},
52+
)
53+
assert wheel_name == f"demo_module-0.1.0-{build_number}-py3-none-any.whl"
54+
with zipfile.ZipFile(dist / wheel_name) as zf:
55+
wheel_metadata = email.message_from_bytes(
56+
zf.read("demo_module-0.1.0.dist-info/WHEEL")
57+
)
58+
assert wheel_metadata["Build"] == build_number
59+
60+
61+
@pytest.mark.parametrize("name", ["demo-module"])
62+
def test_build_single_module_without_build_number(dist: Path) -> None:
63+
wheel_name = api.build_wheel(dist.as_posix())
64+
assert wheel_name == "demo_module-0.1.0-py3-none-any.whl"
65+
with zipfile.ZipFile(dist / wheel_name) as zf:
66+
wheel_metadata = email.message_from_bytes(
67+
zf.read("demo_module-0.1.0.dist-info/WHEEL")
68+
)
69+
assert "Build" not in wheel_metadata
70+
71+
4672
@pytest.mark.parametrize("name", ["demo-package"])
4773
def test_build_package(dist: Path) -> None:
4874
wheel_name = api.build_wheel(dist.as_posix())

0 commit comments

Comments
 (0)