Skip to content
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ dynamic = ["dependencies"]
main = [...]
other = [...]

[tool.hatch.metadata.hooks.multi]
[tool.hatch.metadata.hooks.hatch-multi]
primary = "main"

# Required for split sdists to retain their package name when installed.
[tool.hatch.build.targets.sdist.hooks.hatch-multi]
```

```bash
Expand Down
4 changes: 2 additions & 2 deletions hatch_multi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__version__ = "1.0.0"

from .hooks import hatch_register_metadata_hook
from .plugin import HatchMultiMetadataHook
from .hooks import hatch_register_build_hook, hatch_register_metadata_hook
from .plugin import HatchMultiBuildHook, HatchMultiMetadataHook
from .structs import *
7 changes: 6 additions & 1 deletion hatch_multi/hooks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from hatchling.plugin import hookimpl

from .plugin import HatchMultiMetadataHook
from .plugin import HatchMultiBuildHook, HatchMultiMetadataHook


@hookimpl
def hatch_register_build_hook() -> type[HatchMultiBuildHook]:
return HatchMultiBuildHook


@hookimpl
Expand Down
50 changes: 48 additions & 2 deletions hatch_multi/plugin.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
from __future__ import annotations

from json import dumps
from logging import getLogger
from os import getenv
from pathlib import Path
from tempfile import NamedTemporaryFile

from hatchling.builders.hooks.plugin.interface import BuildHookInterface
from hatchling.metadata.plugin.interface import MetadataHookInterface

from .structs import HatchMultiConfig

__all__ = ("HatchMultiMetadataHook",)
__all__ = ("HatchMultiBuildHook", "HatchMultiMetadataHook")


class HatchMultiMetadataHook(MetadataHookInterface):
class HatchMultiBuildHook(BuildHookInterface):
"""The hatch-multi build hook."""

PLUGIN_NAME = "hatch-multi"
_temporary_project_file: Path | None = None

def initialize(self, version: str, build_data: dict) -> None:
if self.target_name != "sdist":
return

configured_name = self.metadata.config["project"]["name"]
package_name = self.metadata.core.raw_name
if package_name == configured_name:
return

project_file = Path(self.root, "pyproject.toml")
lines = project_file.read_text(encoding="utf-8").splitlines(keepends=True)
in_project_table = False
for index, line in enumerate(lines):
stripped_line = line.strip()
if stripped_line.startswith("[") and stripped_line.endswith("]"):
in_project_table = stripped_line == "[project]"
elif in_project_table:
key, separator, _value = line.partition("=")
if separator and key.strip() == "name":
newline = "\n" if line.endswith("\n") else ""
lines[index] = f"{key}= {dumps(package_name)}{newline}"
break

with NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".toml", delete=False) as temporary_project_file:
temporary_project_file.writelines(lines)
self._temporary_project_file = Path(temporary_project_file.name)

build_data["force_include"].pop(str(project_file), None)
build_data["force_include"][str(self._temporary_project_file)] = "pyproject.toml"

def finalize(self, version: str, build_data: dict, artifact_path: str) -> None:
if self._temporary_project_file is not None:
self._temporary_project_file.unlink()
self._temporary_project_file = None


class HatchMultiMetadataHook(MetadataHookInterface):
"""The hatch-multi metadata hook."""

PLUGIN_NAME = "hatch-multi"
_logger = getLogger(__name__)

Expand Down
2 changes: 2 additions & 0 deletions hatch_multi/tests/test_project_basic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ packages = ["project"]

[tool.hatch.metadata.hooks.hatch-multi]
primary = "main"

[tool.hatch.build.targets.sdist.hooks.hatch-multi]
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ packages = ["project"]

[tool.hatch.metadata.hooks.hatch-multi]
default = ["main", "other"]

[tool.hatch.build.targets.sdist.hooks.hatch-multi]
46 changes: 46 additions & 0 deletions hatch_multi/tests/test_projects_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from shutil import rmtree
from subprocess import check_call
from sys import executable
from tarfile import TarFile
from zipfile import ZipFile


Expand Down Expand Up @@ -105,3 +106,48 @@ def test_basic():
"""
)
rmtree(f"hatch_multi/tests/{project}/dist")


def test_sdist_preserves_extra():
project = "test_project_basic"
project_root = Path(f"hatch_multi/tests/{project}")
rmtree(project_root / "dist", ignore_errors=True)

check_call(
[
executable,
"-m",
"build",
"-n",
"-s",
],
cwd=project_root,
env={"HATCH_MULTI_BUILD": "other"},
)

sdist_name = "hatch_cpp_test_project_basic_other-0.1.0.tar.gz"
assert listdir(project_root / "dist") == [sdist_name]
with TarFile.open(project_root / "dist" / sdist_name, "r:gz") as tar_file:
tar_file.extractall(project_root / "dist" / "extracted", filter="data")

source_root = project_root / "dist" / "extracted" / "hatch_cpp_test_project_basic_other-0.1.0"
check_call(
[
executable,
"-m",
"build",
"-n",
"-w",
],
cwd=source_root,
env={},
)

wheel_name = "hatch_cpp_test_project_basic_other-0.1.0-py3-none-any.whl"
assert listdir(source_root / "dist") == [wheel_name]
with ZipFile(source_root / "dist" / wheel_name) as zip_file:
metadata = zip_file.read("hatch_cpp_test_project_basic_other-0.1.0.dist-info/METADATA").decode()

assert "Name: hatch-cpp-test-project-basic-other\n" in metadata
assert "Requires-Dist: organizeit2\n" in metadata
rmtree(project_root / "dist")