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 support for other spawners and linkers #11

Merged
merged 1 commit into from
Jan 10, 2025
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
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 @@

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 HatchCppPlatform(BaseModel):
cc: str
cxx: str
ld: str
platform: Platform
toolchain: CompilerToolchain

Expand All @@ -54,6 +58,7 @@
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 @@
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}"

Check warning on line 74 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L73-L74

Added lines #L73 - L74 were not covered by tests

# 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"

Check warning on line 95 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L95

Added line #L95 was not covered by tests
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 @@
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"

Check warning on line 123 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L123

Added line #L123 was not covered by tests
if "mold" in self.ld:
flags += f" -fuse-ld={self.ld}"

Check warning on line 125 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L125

Added line #L125 was not covered by tests
elif "lld" in self.ld:
flags += " -fuse-ld=lld"

Check warning on line 127 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L127

Added line #L127 was not covered by tests
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"

Check warning on line 140 in hatch_cpp/structs.py

View check run for this annotation

Codecov / codecov/patch

hatch_cpp/structs.py#L129-L140

Added lines #L129 - L140 were not covered by tests
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 @@
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 @@
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
Loading