Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parameter --build-number to wheel builder #229

Merged
merged 8 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/build_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ Some build frontends such as [build] and [pdm] supports passing options from com
- `--python-tag=<tag>` Override the python implementation compatibility tag(e.g. `cp37`, `py3`, `pp3`)
- `--py-limited-api=<abi>` Python tag (`cp32`|`cp33`|`cpNN`) for abi3 wheel tag
- `--plat-name=<plat>` Override the platform name(e.g. `win_amd64`, `manylinux2010_x86_64`)
- `--build-number=<build-number>` Build number for this particular version. As specified in PEP-0427, this must start with a digit.
- `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`.

For example, you can supply these options with [build]:
Expand Down
40 changes: 34 additions & 6 deletions src/pdm/backend/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from pdm.backend._vendor.packaging import tags
from pdm.backend._vendor.packaging.specifiers import SpecifierSet
from pdm.backend._vendor.packaging.utils import canonicalize_name
from pdm.backend._vendor.packaging.utils import _build_tag_regex, canonicalize_name
from pdm.backend.base import Builder
from pdm.backend.hooks import Context
from pdm.backend.hooks.setuptools import SetuptoolsBuildHook
Expand Down Expand Up @@ -46,6 +46,8 @@
Tag: {tag}
"""

BUILD_TAG_FORMAT = "Build: {build_number}"

# Fix the date time for reproducible builds
try:
_env_date = time.gmtime(int(os.environ["SOURCE_DATE_EPOCH"]))[:6]
Expand Down Expand Up @@ -77,6 +79,7 @@ def __init__(
) -> None:
super().__init__(location, config_settings)
self.__tag: str | None = None
self.__build_number: str | None = None

def scheme_path(self, name: str, relative: str) -> str:
if name not in SCHEME_NAMES:
Expand Down Expand Up @@ -156,7 +159,11 @@ def build_artifact(
records.append(self._add_file_to_zip(zf, rel_path, full_path))
self._write_record(zf, records)

target = context.dist_dir / f"{self.name_version}-{self.tag}.whl"
name_version = self.name_version
if self.build_number:
name_version = f"{name_version}-{self.build_number}"

target = context.dist_dir / f"{name_version}-{self.tag}.whl"
if target.exists():
target.unlink()
shutil.move(temp_name, target)
Expand All @@ -168,6 +175,12 @@ def name_version(self) -> str:
version = to_filename(safe_version(self.config.metadata["version"]))
return f"{name}-{version}"

@property
def build_number(self) -> str | None:
if not self.__build_number:
self.__build_number = self._get_build_number()
return self.__build_number

@property
def dist_info_name(self) -> str:
return f"{self.name_version}.dist-info"
Expand All @@ -178,6 +191,18 @@ def tag(self) -> str:
self.__tag = self._get_tag()
return self.__tag

def _get_build_number(self) -> str | None:
cmd = "--build-number"
if cmd not in self.config_settings:
return None
build_number = self.config_settings[cmd]
if not _build_tag_regex.match(build_number):
raise ValueError(
f"Invalid build number: {build_number}, please refer to PEP 427"
)

return build_number

def _get_tag(self) -> str:
impl, abi, platform = self._get_platform_tags()
is_purelib = self.config.build_config.is_purelib
Expand Down Expand Up @@ -276,12 +301,15 @@ def _write_wheel_file(self, fp: IO[str], is_purelib: bool) -> None:
except ModuleNotFoundError:
version = "0.0.0+local"

fp.write(
WHEEL_FILE_FORMAT.format(
is_purelib=str(is_purelib).lower(), tag=self.tag, version=version
)
wheel_metadata = WHEEL_FILE_FORMAT.format(
is_purelib=str(is_purelib).lower(), tag=self.tag, version=version
)

if self.build_number:
wheel_metadata = f"{wheel_metadata}{BUILD_TAG_FORMAT.format(build_number=self.build_number)}"

fp.write(wheel_metadata)

def _write_entry_points(
self, fp: IO[str], entry_points: dict[str, dict[str, str]]
) -> None:
Expand Down
26 changes: 26 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ def test_build_single_module(dist: Path) -> None:
assert "demo_module-0.1.0.dist-info/licenses/LICENSE" in zip_names


@pytest.mark.parametrize("name", ["demo-module"])
def test_build_single_module_with_build_number(dist: Path) -> None:
build_number = "20231241"
wheel_name = api.build_wheel(
dist.as_posix(),
config_settings={"--build-number": build_number},
)
assert wheel_name == f"demo_module-0.1.0-{build_number}-py3-none-any.whl"
with zipfile.ZipFile(dist / wheel_name) as zf:
wheel_metadata = email.message_from_bytes(
zf.read("demo_module-0.1.0.dist-info/WHEEL")
)
assert wheel_metadata["Build"] == build_number


@pytest.mark.parametrize("name", ["demo-module"])
def test_build_single_module_without_build_number(dist: Path) -> None:
wheel_name = api.build_wheel(dist.as_posix())
assert wheel_name == "demo_module-0.1.0-py3-none-any.whl"
with zipfile.ZipFile(dist / wheel_name) as zf:
wheel_metadata = email.message_from_bytes(
zf.read("demo_module-0.1.0.dist-info/WHEEL")
)
assert "Build" not in wheel_metadata


@pytest.mark.parametrize("name", ["demo-package"])
def test_build_package(dist: Path) -> None:
wheel_name = api.build_wheel(dist.as_posix())
Expand Down
Loading