22
33from os import environ , system
44from pathlib import Path
5+ from re import match
56from shutil import which
67from sys import executable , platform as sys_platform
78from sysconfig import get_path
8- from typing import List , Literal , Optional
9+ from typing import Any , List , Literal , Optional
910
10- from pydantic import BaseModel , Field
11+ from pydantic import AliasChoices , BaseModel , Field , field_validator , model_validator
1112
1213__all__ = (
1314 "HatchCppBuildConfig" ,
2829}
2930
3031
31- class HatchCppLibrary (BaseModel ):
32+ class HatchCppLibrary (BaseModel , validate_assignment = True ):
3233 """A C++ library."""
3334
3435 name : str
@@ -38,29 +39,47 @@ class HatchCppLibrary(BaseModel):
3839 binding : Binding = "cpython"
3940 std : Optional [str ] = None
4041
41- include_dirs : List [str ] = Field (default_factory = list , alias = " include-dirs" )
42- library_dirs : List [str ] = Field (default_factory = list , alias = " library-dirs" )
42+ include_dirs : List [str ] = Field (default_factory = list , alias = AliasChoices ( "include_dirs" , " include-dirs") )
43+ library_dirs : List [str ] = Field (default_factory = list , alias = AliasChoices ( "library_dirs" , " library-dirs") )
4344 libraries : List [str ] = Field (default_factory = list )
4445
45- extra_compile_args : List [str ] = Field (default_factory = list , alias = " extra-compile-args" )
46- extra_link_args : List [str ] = Field (default_factory = list , alias = " extra-link-args" )
47- extra_objects : List [str ] = Field (default_factory = list , alias = " extra-objects" )
46+ extra_compile_args : List [str ] = Field (default_factory = list , alias = AliasChoices ( "extra_compile_args" , " extra-compile-args") )
47+ extra_link_args : List [str ] = Field (default_factory = list , alias = AliasChoices ( "extra_link_args" , " extra-link-args") )
48+ extra_objects : List [str ] = Field (default_factory = list , alias = AliasChoices ( "extra_objects" , " extra-objects") )
4849
49- define_macros : List [str ] = Field (default_factory = list , alias = " define-macros" )
50- undef_macros : List [str ] = Field (default_factory = list , alias = " undef-macros" )
50+ define_macros : List [str ] = Field (default_factory = list , alias = AliasChoices ( "define_macros" , " define-macros") )
51+ undef_macros : List [str ] = Field (default_factory = list , alias = AliasChoices ( "undef_macros" , " undef-macros") )
5152
52- export_symbols : List [str ] = Field (default_factory = list , alias = " export-symbols" )
53+ export_symbols : List [str ] = Field (default_factory = list , alias = AliasChoices ( "export_symbols" , " export-symbols") )
5354 depends : List [str ] = Field (default_factory = list )
5455
56+ py_limited_api : Optional [str ] = Field (default = "" , alias = AliasChoices ("py_limited_api" , "py-limited-api" ))
57+
58+ @field_validator ("py_limited_api" , mode = "before" )
59+ @classmethod
60+ def check_py_limited_api (cls , value : Any ) -> Any :
61+ if value :
62+ if not match (r"cp3\d" , value ):
63+ raise ValueError ("py-limited-api must be in the form of cp3X" )
64+ return value
65+
5566 def get_qualified_name (self , platform ):
5667 if platform == "win32" :
5768 suffix = "dll" if self .binding == "none" else "pyd"
5869 elif platform == "darwin" and self .binding == "none" :
5970 suffix = "dylib"
6071 else :
6172 suffix = "so"
73+ if self .py_limited_api :
74+ return f"{ self .name } .abi3.{ suffix } "
6275 return f"{ self .name } .{ suffix } "
6376
77+ @model_validator (mode = "after" )
78+ def check_binding_and_py_limited_api (self ):
79+ if self .binding == "pybind11" and self .py_limited_api :
80+ raise ValueError ("pybind11 does not support Py_LIMITED_API" )
81+ return self
82+
6483
6584class HatchCppPlatform (BaseModel ):
6685 cc : str
@@ -117,6 +136,12 @@ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "r
117136 library .sources .append (str (Path (nanobind .include_dir ()).parent / "src" / "nb_combined.cpp" ))
118137 library .include_dirs .append (str ((Path (nanobind .include_dir ()).parent / "ext" / "robin_map" / "include" )))
119138
139+ if library .py_limited_api :
140+ if library .binding == "pybind11" :
141+ raise ValueError ("pybind11 does not support Py_LIMITED_API" )
142+ library .define_macros .append (f"Py_LIMITED_API=0x0{ library .py_limited_api [2 ]} 0{ hex (int (library .py_limited_api [3 :]))[2 :]} 00f0" )
143+
144+ # Toolchain-specific flags
120145 if self .toolchain == "gcc" :
121146 flags += " " + " " .join (f"-I{ d } " for d in library .include_dirs )
122147 flags += " -fPIC"
@@ -156,7 +181,7 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele
156181 flags += " " + " " .join (library .extra_objects )
157182 flags += " " + " " .join (f"-l{ lib } " for lib in library .libraries )
158183 flags += " " + " " .join (f"-L{ lib } " for lib in library .library_dirs )
159- flags += f" -o { library .name } .so "
184+ flags += f" -o { library .get_qualified_name ( self ) } "
160185 if self .platform == "darwin" :
161186 flags += " -undefined dynamic_lookup"
162187 if "mold" in self .ld :
@@ -169,7 +194,7 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele
169194 flags += " " + " " .join (library .extra_objects )
170195 flags += " " + " " .join (f"-l{ lib } " for lib in library .libraries )
171196 flags += " " + " " .join (f"-L{ lib } " for lib in library .library_dirs )
172- flags += f" -o { library .name } .so "
197+ flags += f" -o { library .get_qualified_name ( self ) } "
173198 if self .platform == "darwin" :
174199 flags += " -undefined dynamic_lookup"
175200 if "mold" in self .ld :
@@ -180,7 +205,7 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele
180205 flags += " " + " " .join (library .extra_link_args )
181206 flags += " " + " " .join (library .extra_objects )
182207 flags += " /LD"
183- flags += f" /Fe:{ library .name } .pyd "
208+ flags += f" /Fe:{ library .get_qualified_name ( self ) } "
184209 flags += " /link /DLL"
185210 if (Path (executable ).parent / "libs" ).exists ():
186211 flags += f" /LIBPATH:{ str (Path (executable ).parent / 'libs' )} "
0 commit comments