Skip to content

Commit 4ae2a78

Browse files
authored
Add extra_metadata build data to the wheel target (#621)
1 parent 46e924c commit 4ae2a78

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

backend/src/hatchling/builders/wheel.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def write_metadata(
510510
self.add_licenses(archive, records)
511511

512512
# extra_metadata/ - write last
513-
self.add_extra_metadata(archive, records)
513+
self.add_extra_metadata(archive, records, build_data)
514514

515515
def write_archive_metadata(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:
516516
from packaging.tags import parse_tag
@@ -548,8 +548,11 @@ def add_licenses(self, archive: WheelArchive, records: RecordFile) -> None:
548548
record = archive.write_metadata(f'licenses/{relative_path}', f.read())
549549
records.write(record)
550550

551-
def add_extra_metadata(self, archive: WheelArchive, records: RecordFile) -> None:
552-
for extra_metadata_file in self.recurse_explicit_files(self.config.extra_metadata):
551+
def add_extra_metadata(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None:
552+
extra_metadata = dict(self.config.extra_metadata)
553+
extra_metadata.update(normalize_inclusion_map(build_data['extra_metadata'], self.root))
554+
555+
for extra_metadata_file in self.recurse_explicit_files(extra_metadata):
553556
record = archive.add_extra_metadata_file(extra_metadata_file)
554557
records.write(record)
555558

@@ -586,7 +589,13 @@ def get_default_tag(self) -> str:
586589
return f'{".".join(supported_python_versions)}-none-any'
587590

588591
def get_default_build_data(self) -> dict[str, Any]:
589-
return {'infer_tag': False, 'pure_python': True, 'dependencies': [], 'force_include_editable': {}}
592+
return {
593+
'infer_tag': False,
594+
'pure_python': True,
595+
'dependencies': [],
596+
'force_include_editable': {},
597+
'extra_metadata': {},
598+
}
590599

591600
def get_forced_inclusion_map(self, build_data: dict[str, Any]) -> dict[str, str]:
592601
if not build_data['force_include_editable']:

docs/history/hatchling.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
***Added:***
1212

13+
- Add `extra_metadata` build data to the `wheel` target
1314
- Add more type hints
1415
- Store Hatchling's metadata in `pyproject.toml`
1516

docs/plugins/builder/wheel.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ This is data that can be modified by [build hooks](../build-hook/reference.md).
6060
| `infer_tag` | `#!python False` | When `tag` is not set, this may be enabled to use the one most specific to the platform, Python interpreter, and ABI |
6161
| `pure_python` | `#!python True` | Whether or not to write metadata indicating that the package does not contain any platform-specific files |
6262
| `dependencies` | | Extra [project dependencies](../../config/metadata.md#required) |
63+
| `extra_metadata` | | Additional [`extra-metadata`](#options) entries, which take precedence in case of conflicts |
6364
| `force_include_editable` | | Similar to the [`force_include` option](../build-hook/reference.md#build-data) but specifically for the `editable` [version](#versions) and takes precedence |

tests/backend/builders/test_wheel.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,79 @@ def test_default_extra_metadata(self, hatch, helpers, temp_dir, config_file):
16421642
)
16431643
helpers.assert_files(extraction_directory, expected_files)
16441644

1645+
def test_default_extra_metadata_build_data(self, hatch, helpers, temp_dir, config_file):
1646+
config_file.model.template.plugins['default']['src-layout'] = False
1647+
config_file.save()
1648+
1649+
project_name = 'My.App'
1650+
1651+
with temp_dir.as_cwd():
1652+
result = hatch('new', project_name)
1653+
1654+
assert result.exit_code == 0, result.output
1655+
1656+
project_path = temp_dir / 'my-app'
1657+
1658+
extra_metadata_path = temp_dir / 'data'
1659+
extra_metadata_path.ensure_dir_exists()
1660+
(extra_metadata_path / 'foo.txt').touch()
1661+
nested_data_path = extra_metadata_path / 'nested'
1662+
nested_data_path.ensure_dir_exists()
1663+
(nested_data_path / 'bar.txt').touch()
1664+
1665+
build_script = project_path / DEFAULT_BUILD_SCRIPT
1666+
build_script.write_text(
1667+
helpers.dedent(
1668+
"""
1669+
import pathlib
1670+
1671+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
1672+
1673+
class CustomHook(BuildHookInterface):
1674+
def initialize(self, version, build_data):
1675+
build_data['extra_metadata']['../data'] = '/'
1676+
"""
1677+
)
1678+
)
1679+
1680+
config = {
1681+
'project': {'name': project_name, 'requires-python': '>3', 'dynamic': ['version']},
1682+
'tool': {
1683+
'hatch': {
1684+
'version': {'path': 'my_app/__about__.py'},
1685+
'build': {'targets': {'wheel': {'versions': ['standard'], 'hooks': {'custom': {}}}}},
1686+
},
1687+
},
1688+
}
1689+
builder = WheelBuilder(str(project_path), config=config)
1690+
1691+
build_path = project_path / 'dist'
1692+
build_path.mkdir()
1693+
1694+
with project_path.as_cwd():
1695+
artifacts = list(builder.build(str(build_path)))
1696+
1697+
assert len(artifacts) == 1
1698+
expected_artifact = artifacts[0]
1699+
1700+
build_artifacts = list(build_path.iterdir())
1701+
assert len(build_artifacts) == 1
1702+
assert expected_artifact == str(build_artifacts[0])
1703+
1704+
extraction_directory = temp_dir / '_archive'
1705+
extraction_directory.mkdir()
1706+
1707+
with zipfile.ZipFile(str(expected_artifact), 'r') as zip_archive:
1708+
zip_archive.extractall(str(extraction_directory))
1709+
1710+
metadata_directory = f'{builder.project_id}.dist-info'
1711+
expected_files = helpers.get_template_files(
1712+
'wheel.standard_default_extra_metadata',
1713+
project_name,
1714+
metadata_directory=metadata_directory,
1715+
)
1716+
helpers.assert_files(extraction_directory, expected_files)
1717+
16451718
@pytest.mark.requires_unix
16461719
def test_default_symlink(self, hatch, helpers, temp_dir, config_file):
16471720
config_file.model.template.plugins['default']['src-layout'] = False

0 commit comments

Comments
 (0)