Skip to content

Commit

Permalink
Merge pull request #11 from python-project-templates/tkp/pyd
Browse files Browse the repository at this point in the history
Add support for other spawners and linkers
  • Loading branch information
timkpaine authored Jan 10, 2025
2 parents 6312893 + 8d0af69 commit 25c7895
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 38 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,27 @@ Hatch plugin for C++ builds

## Overview

A simple, extensible C++ build plugin for [hatch](https://hatch.pypa.io/latest/).

```toml
[tool.hatch.build.hooks.hatch-cpp]
libraries = [
{name = "basic_project/extension", sources = ["cpp/basic-project/basic.cpp"], include-dirs = ["cpp"]}
]
```

For more complete systems, see:
- [scikit-build-core](https://github.com/scikit-build/scikit-build-core)
- [setuptools](https://setuptools.pypa.io/en/latest/userguide/ext_modules.html)

## Environment Variables
| Name | Default | Description |
|:-----|:--------|:------------|
|`CC`| | |
|`CXX`| | |
|`LD`| | |
|`HATCH_CPP_PLATFORM`| | |
|`HATCH_CPP_DISABLE_CCACHE`| | |

> [!NOTE]
> This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).
2 changes: 1 addition & 1 deletion hatch_cpp/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None:
# Log commands if in verbose mode
if config.verbose:
for command in build_plan.commands:
self._logger.info(command)
self._logger.warning(command)

# Execute build plan
build_plan.execute()
Expand Down
102 changes: 69 additions & 33 deletions hatch_cpp/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from os import environ, system
from pathlib import Path
from shutil import which
from sys import executable, platform as sys_platform
from sysconfig import get_path
from typing import List, Literal, Optional
Expand All @@ -15,12 +16,14 @@
"HatchCppBuildPlan",
)

Platform = Literal["linux", "darwin", "win32"]
BuildType = Literal["debug", "release"]
CompilerToolchain = Literal["gcc", "clang", "msvc"]
Language = Literal["c", "c++"]
Platform = Literal["linux", "darwin", "win32"]
PlatformDefaults = {
"linux": {"CC": "gcc", "CXX": "g++"},
"darwin": {"CC": "clang", "CXX": "clang++"},
"win32": {"CC": "cl", "CXX": "cl"},
"linux": {"CC": "gcc", "CXX": "g++", "LD": "ld"},
"darwin": {"CC": "clang", "CXX": "clang++", "LD": "ld"},
"win32": {"CC": "cl", "CXX": "cl", "LD": "link"},
}


Expand All @@ -29,7 +32,7 @@ class HatchCppLibrary(BaseModel):

name: str
sources: List[str]

language: Language = "c++"
include_dirs: List[str] = Field(default_factory=list, alias="include-dirs")
library_dirs: List[str] = Field(default_factory=list, alias="library-dirs")
libraries: List[str] = Field(default_factory=list)
Expand All @@ -46,6 +49,7 @@ class HatchCppLibrary(BaseModel):
class HatchCppPlatform(BaseModel):
cc: str
cxx: str
ld: str
platform: Platform
toolchain: CompilerToolchain

Expand All @@ -54,6 +58,7 @@ def default() -> HatchCppPlatform:
platform = environ.get("HATCH_CPP_PLATFORM", sys_platform)
CC = environ.get("CC", PlatformDefaults[platform]["CC"])
CXX = environ.get("CXX", PlatformDefaults[platform]["CXX"])
LD = environ.get("LD", PlatformDefaults[platform]["LD"])
if "gcc" in CC and "g++" in CXX:
toolchain = "gcc"
elif "clang" in CC and "clang++" in CXX:
Expand All @@ -62,34 +67,35 @@ def default() -> HatchCppPlatform:
toolchain = "msvc"
else:
raise Exception(f"Unrecognized toolchain: {CC}, {CXX}")
return HatchCppPlatform(cc=CC, cxx=CXX, platform=platform, toolchain=toolchain)

def get_compile_flags(self, library: HatchCppLibrary) -> str:
# Customizations
if which("ccache") and not environ.get("HATCH_CPP_DISABLE_CCACHE"):
CC = f"ccache {CC}"
CXX = f"ccache {CXX}"

# https://github.com/rui314/mold/issues/647
# if which("ld.mold"):
# LD = which("ld.mold")
# elif which("ld.lld"):
# LD = which("ld.lld")
return HatchCppPlatform(cc=CC, cxx=CXX, ld=LD, platform=platform, toolchain=toolchain)

def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str:
flags = ""
if self.toolchain == "gcc":
flags = f"-I{get_path('include')}"
flags += " " + " ".join(f"-I{d}" for d in library.include_dirs)
flags += " -fPIC -shared"
flags += " -fPIC"
flags += " " + " ".join(library.extra_compile_args)
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
flags += f" -o {library.name}.so"
elif self.toolchain == "clang":
flags = f"-I{get_path('include')} "
flags += " ".join(f"-I{d}" for d in library.include_dirs)
flags += " -undefined dynamic_lookup -fPIC -shared"
flags += " -fPIC"
flags += " " + " ".join(library.extra_compile_args)
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
flags += f" -o {library.name}.so"
elif self.toolchain == "msvc":
flags = f"/I{get_path('include')} "
flags += " ".join(f"/I{d}" for d in library.include_dirs)
Expand All @@ -98,7 +104,44 @@ def get_compile_flags(self, library: HatchCppLibrary) -> str:
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros)
flags += " /EHsc /DWIN32 /LD"
flags += " /EHsc /DWIN32"
# clean
while flags.count(" "):
flags = flags.replace(" ", " ")
return flags

def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str:
flags = ""
if self.toolchain == "gcc":
flags += " -shared"
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
flags += f" -o {library.name}.so"
if self.platform == "darwin":
flags += " -undefined dynamic_lookup"
if "mold" in self.ld:
flags += f" -fuse-ld={self.ld}"
elif "lld" in self.ld:
flags += " -fuse-ld=lld"
elif self.toolchain == "clang":
flags += " -shared"
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
flags += f" -o {library.name}.so"
if self.platform == "darwin":
flags += " -undefined dynamic_lookup"
if "mold" in self.ld:
flags += f" -fuse-ld={self.ld}"
elif "lld" in self.ld:
flags += " -fuse-ld=lld"
elif self.toolchain == "msvc":
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " /LD"
flags += f" /Fo:{library.name}.obj"
flags += f" /Fe:{library.name}.pyd"
flags += " /link /DLL"
Expand All @@ -111,22 +154,21 @@ def get_compile_flags(self, library: HatchCppLibrary) -> str:
flags = flags.replace(" ", " ")
return flags

def get_link_flags(self, library: HatchCppLibrary) -> str:
# TODO
flags = ""
return flags


class HatchCppBuildPlan(BaseModel):
build_type: BuildType = "release"
libraries: List[HatchCppLibrary] = Field(default_factory=list)
platform: HatchCppPlatform = Field(default_factory=HatchCppPlatform.default)
commands: List[str] = Field(default_factory=list)

def generate(self):
self.commands = []
for library in self.libraries:
flags = self.platform.get_compile_flags(library)
self.commands.append(f"{self.platform.cc} {' '.join(library.sources)} {flags}")
compile_flags = self.platform.get_compile_flags(library, self.build_type)
link_flags = self.platform.get_link_flags(library, self.build_type)
self.commands.append(
f"{self.platform.cc if library.language == 'c' else self.platform.cxx} {' '.join(library.sources)} {compile_flags} {link_flags}"
)
return self.commands

def execute(self):
Expand All @@ -148,9 +190,3 @@ class HatchCppBuildConfig(BaseModel):
verbose: Optional[bool] = Field(default=False)
libraries: List[HatchCppLibrary] = Field(default_factory=list)
platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default)

# build_function: str | None = None
# build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
# editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
# ensured_targets: list[str] = field(default_factory=list)
# skip_if_exists: list[str] = field(default_factory=list)
6 changes: 3 additions & 3 deletions hatch_cpp/tests/test_projects.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from os import listdir
from pathlib import Path
from shutil import rmtree
from subprocess import check_output
from subprocess import check_call
from sys import path, platform


class TestProject:
def test_basic(self):
rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.so", ignore_errors=True)
rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.pyd", ignore_errors=True)
check_output(
check_call(
[
"hatchling",
"build",
Expand All @@ -30,7 +30,7 @@ def test_basic(self):
def test_override_classes(self):
rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.so", ignore_errors=True)
rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.pyd", ignore_errors=True)
check_output(
check_call(
[
"hatchling",
"build",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ exclude_also = [
"@(abc\\.)?abstractmethod",
]
ignore_errors = true
fail_under = 75
fail_under = 70

[tool.hatch.build]
artifacts = []
Expand Down

0 comments on commit 25c7895

Please sign in to comment.