diff --git a/COVERAGE.md b/COVERAGE.md
index b289b6e..5af6cf2 100644
--- a/COVERAGE.md
+++ b/COVERAGE.md
@@ -1,10 +1,18 @@
-| Name | Stmts | Miss | Branch | BrPart | Cover |
-|---------------------------------- | -------: | -------: | -------: | -------: | ------: |
-| src/hatch\_cython/\_\_init\_\_.py | 2 | 0 | 0 | 0 | 100% |
-| src/hatch\_cython/config.py | 282 | 19 | 124 | 12 | 92% |
-| src/hatch\_cython/devel.py | 5 | 0 | 0 | 0 | 100% |
-| src/hatch\_cython/hooks.py | 5 | 1 | 2 | 0 | 86% |
-| src/hatch\_cython/plugin.py | 194 | 9 | 102 | 8 | 94% |
-| src/hatch\_cython/types.py | 19 | 5 | 2 | 1 | 71% |
-| src/hatch\_cython/utils.py | 10 | 0 | 2 | 0 | 100% |
-| **TOTAL** | **517** | **34** | **232** | **21** | **92%** |
+| Name | Stmts | Miss | Branch | BrPart | Cover |
+|----------------------------------------- | -------: | -------: | -------: | -------: | ------: |
+| src/hatch\_cython/\_\_init\_\_.py | 2 | 0 | 0 | 0 | 100% |
+| src/hatch\_cython/config/\_\_init\_\_.py | 2 | 0 | 0 | 0 | 100% |
+| src/hatch\_cython/config/autoimport.py | 9 | 0 | 4 | 0 | 100% |
+| src/hatch\_cython/config/config.py | 145 | 16 | 70 | 10 | 87% |
+| src/hatch\_cython/config/defaults.py | 6 | 0 | 0 | 0 | 100% |
+| src/hatch\_cython/config/files.py | 22 | 1 | 12 | 2 | 91% |
+| src/hatch\_cython/config/flags.py | 53 | 1 | 16 | 0 | 99% |
+| src/hatch\_cython/config/platform.py | 71 | 2 | 28 | 2 | 96% |
+| src/hatch\_cython/constants.py | 11 | 0 | 0 | 0 | 100% |
+| src/hatch\_cython/devel.py | 5 | 0 | 0 | 0 | 100% |
+| src/hatch\_cython/hooks.py | 5 | 1 | 2 | 0 | 86% |
+| src/hatch\_cython/plugin.py | 189 | 9 | 142 | 8 | 95% |
+| src/hatch\_cython/temp.py | 12 | 0 | 2 | 0 | 100% |
+| src/hatch\_cython/types.py | 19 | 4 | 2 | 1 | 76% |
+| src/hatch\_cython/utils.py | 22 | 0 | 8 | 0 | 100% |
+| **TOTAL** | **573** | **34** | **286** | **23** | **93%** |
diff --git a/README.md b/README.md
index dd21ef5..1024c2d 100644
--- a/README.md
+++ b/README.md
@@ -87,11 +87,12 @@ include_somelib = { pkg = "pyarrow", include="get_include", libraries="get_libra
| extra_link_args | str or `{ platforms = ["*"] \| "*", arg = str }` |
| env | `{ env = "VAR1", arg = "VALUE", platforms = ["*"], arch = ["*"] }`
if flag is one of:
- ARFLAGS
- LDSHARED
- LDFLAGS
- CPPFLAGS
- CFLAGS
- CCSHARED
the current env vars will be merged with the value (provided platform & arch applies), separated by a space. This can be enabled by adding `{ env = "MYVAR" ... , merges = true }` to the definition. |
| includes | list str |
-| includes\_{package} | `{ pkg = str, include = str, libraries = str\| None, library_dirs = str \| None , required_call = str \| None }`
where all fields, but `pkg`, are attributes of `pkg` in the type of `callable() -> list[str] \| str` \| `list[str] \| str`. `pkg` is a module, or loadable module object, which may be imported through `import x.y.z`. |
-| includes_numpy \| includes_pyarrow | bool
3rd party named imports. must have the respective opt in `dependencies` |
-| retain_intermediate_artifacts | bool = False
whether to keep `.c` \| `.cpp` files |
-| parallel | bool = False
if parallel, add openmp headers
important: if using macos, you need the *homebrew* llvm vs _apple's_ llvm in order to pass `-fopenmp` to clang compiler |
-| compiler | compiler used at build-time. if `msvc` (Microsoft Visual Studio), `/openmp` is used as argument to compile instead of `-fopenmp` when `parallel = true` |
+| includes\_{package} | `{ pkg = str, include = str, libraries = str\| None, library_dirs = str \| None , required_call = str \| None }`
where all fields, but `pkg`, are attributes of `pkg` in the type of `callable() -> list[str] \| str` \| `list[str] \| str`. `pkg` is a module, or loadable module object, which may be imported through `import x.y.z`. |
+| includes_numpy \| includes_pyarrow | bool
3rd party named imports. must have the respective opt in `dependencies` |
+| retain_intermediate_artifacts | bool = False
whether to keep `.c` \| `.cpp` files |
+| parallel | bool = False
if parallel, add openmp headers
important: if using macos, you need the *homebrew* llvm vs _apple's_ llvm in order to pass `-fopenmp` to clang compiler |
+| compiler | compiler used at build-time. if `msvc` (Microsoft Visual Studio), `/openmp` is used as argument to compile instead of `-fopenmp` when `parallel = true`. `default = false` |
+| compile_py | whether to include `.py` files when building cython exts. note, this can be enabled & you can do per file / matched file ignores as below. `default = true` |
| \*\* kwargs | keyword = value pair arguments to pass to the extension module when building |
### Files
@@ -100,12 +101,15 @@ include_somelib = { pkg = "pyarrow", include="get_include", libraries="get_libra
[build.targets.wheel.hooks.cython.options.files]
exclude = [
# anything matching no_compile is ignored by cython
- "*/no_compile/*"
+ "*/no_compile/*",
+ # note - anything "*" is escaped to "([^\s]*)" (non whitespace).
+ # if you need an actual * for python regex, use as below:
+ # this excludes all pyd or pytempl extensions
+ "([^.]\\*).(pyd$|pytempl$)"
]
+aliases = {"abclib._filewithoutsuffix" = "abclib.importalias"}
```
-
-
## License
`hatch-cython` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
diff --git a/example/hatch.toml b/example/hatch.toml
index d1e92db..b8ccf7c 100644
--- a/example/hatch.toml
+++ b/example/hatch.toml
@@ -24,7 +24,6 @@ src = "example_lib"
parallel = true
include_numpy = true
retain_intermediate_artifacts = true
-
directives = { language_level = 3, boundscheck = false }
compile_args = [
{ arg = "-v" },
@@ -51,6 +50,7 @@ cythonize_kwargs = { annotate = true, nthreads = 4 }
exclude = [
"*/no_compile/*"
]
+aliases = {"example_lib._alias" = "example_lib.aliased"}
[[envs.all.matrix]]
python = ["3.8", "3.9", "3.10", "3.11"]
diff --git a/example/src/example_lib/_alias.pyx b/example/src/example_lib/_alias.pyx
new file mode 100644
index 0000000..34392e6
--- /dev/null
+++ b/example/src/example_lib/_alias.pyx
@@ -0,0 +1,4 @@
+# distutils: language=c++
+
+cpdef str some_aliased(str name):
+ return name
diff --git a/example/src/example_lib/mod_a/deep_nest/creates.pyx b/example/src/example_lib/mod_a/deep_nest/creates.pyx
new file mode 100644
index 0000000..cb880a5
--- /dev/null
+++ b/example/src/example_lib/mod_a/deep_nest/creates.pyx
@@ -0,0 +1,9 @@
+cdef class MyClass:
+ def __cinit__(self):
+ pass
+
+ cpdef str do(self):
+ return "abc"
+
+cpdef MyClass fast_create():
+ return MyClass.__new__(MyClass)
diff --git a/example/tests/test_aliased.py b/example/tests/test_aliased.py
new file mode 100644
index 0000000..0205da7
--- /dev/null
+++ b/example/tests/test_aliased.py
@@ -0,0 +1,4 @@
+def test_aliased():
+ from example_lib.aliased import some_aliased
+
+ assert some_aliased("abc") == "abc"
diff --git a/example/tests/test_submodule.py b/example/tests/test_submodule.py
index c530a89..1a24be4 100644
--- a/example/tests/test_submodule.py
+++ b/example/tests/test_submodule.py
@@ -1,14 +1,21 @@
-from example_lib.mod_a.adds import fmul, imul
-from example_lib.mod_a.some_defn import ValueDefn
-
-
def test_muls():
+ from example_lib.mod_a.adds import fmul, imul
+
assert fmul(5.5, 5.5) == 30.25
assert imul(21, 2) == 42
def test_vals():
+ from example_lib.mod_a.some_defn import ValueDefn
+
v = ValueDefn(10)
assert v.value == 10
v.set(5)
assert v.value == 5
+
+
+def test_deep_nesting():
+ from example_lib.mod_a.deep_nest.creates import fast_create
+
+ o = fast_create()
+ assert o.do() == "abc"
diff --git a/src/hatch_cython/__about__.py b/src/hatch_cython/__about__.py
index 664c19b..af7d6ac 100644
--- a/src/hatch_cython/__about__.py
+++ b/src/hatch_cython/__about__.py
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-present joshua-auchincloss
#
# SPDX-License-Identifier: MIT
-__version__ = "0.2.3"
+__version__ = "0.2.4"
diff --git a/src/hatch_cython/config/__init__.py b/src/hatch_cython/config/__init__.py
new file mode 100644
index 0000000..0875259
--- /dev/null
+++ b/src/hatch_cython/config/__init__.py
@@ -0,0 +1,2 @@
+from hatch_cython.config.config import Config, parse_from_dict
+from hatch_cython.config.platform import PlatformArgs
diff --git a/src/hatch_cython/config/autoimport.py b/src/hatch_cython/config/autoimport.py
new file mode 100644
index 0000000..8321c8c
--- /dev/null
+++ b/src/hatch_cython/config/autoimport.py
@@ -0,0 +1,26 @@
+from dataclasses import dataclass, field
+
+
+@dataclass
+class Autoimport:
+ pkg: str
+
+ include: str
+ libraries: str = field(default=None)
+ library_dirs: str = field(default=None)
+ required_call: str = field(default=None)
+
+
+__packages__ = {
+ a.pkg: a
+ for a in (
+ Autoimport("numpy", "get_include"),
+ Autoimport(
+ "pyarrow",
+ include="get_include",
+ libraries="get_libraries",
+ library_dirs="get_library_dirs",
+ required_call="create_library_symlinks",
+ ),
+ )
+}
diff --git a/src/hatch_cython/config.py b/src/hatch_cython/config/config.py
similarity index 52%
rename from src/hatch_cython/config.py
rename to src/hatch_cython/config/config.py
index 45cab67..0b41a4a 100644
--- a/src/hatch_cython/config.py
+++ b/src/hatch_cython/config/config.py
@@ -1,46 +1,26 @@
-import platform
-from collections.abc import Generator, Hashable
+from collections.abc import Generator
from dataclasses import asdict, dataclass, field
from importlib import import_module
-from os import environ, path
-from typing import ClassVar, Optional
+from os import path
+from typing import Optional
from hatch.utils.ci import running_in_ci
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
-from packaging.markers import Marker
-
-from hatch_cython.types import CorePlatforms, ListStr, callable_t, dict_t, list_t, union_t
-from hatch_cython.utils import memo
-
-EXIST_TRIM = 2
-
-ANON = "anon"
-INCLUDE = "include_"
-OPTIMIZE = "-O2"
-DIRECTIVES = {
- "binding": True,
- "language_level": 3,
-}
-LTPY311 = "python_version < '3.11'"
-MUST_UNIQUE = ["-O", "-arch", "-march"]
-POSIX_CORE: list_t[CorePlatforms] = ["darwin", "linux"]
-
-
-@memo
-def plat():
- return platform.system().lower()
-
-
-@memo
-def aarch():
- return platform.machine().lower()
+from hatch_cython.config.autoimport import Autoimport, __packages__
+from hatch_cython.config.defaults import get_default_compile, get_default_link
+from hatch_cython.config.files import FileArgs
+from hatch_cython.config.flags import EnvFlag, EnvFlags, parse_env_args
+from hatch_cython.config.platform import ListedArgs, PlatformArgs, parse_platform_args
+from hatch_cython.constants import DIRECTIVES, EXIST_TRIM, INCLUDE, LTPY311, MUST_UNIQUE
+from hatch_cython.types import CallableT, ListStr
# fields tracked by this plugin
__known__ = (
"src",
"env",
"files",
+ "compile_py",
"includes",
"libraries",
"library_dirs",
@@ -52,224 +32,6 @@ def aarch():
)
-@dataclass
-class Autoimport:
- pkg: str
-
- include: str
- libraries: str = field(default=None)
- library_dirs: str = field(default=None)
- required_call: str = field(default=None)
-
-
-__packages__ = {
- a.pkg: a
- for a in (
- Autoimport("numpy", "get_include"),
- Autoimport(
- "pyarrow",
- include="get_include",
- libraries="get_libraries",
- library_dirs="get_library_dirs",
- required_call="create_library_symlinks",
- ),
- )
-}
-
-
-@dataclass
-class PlatformBase(Hashable):
- platforms: union_t[ListStr, str] = "*"
- arch: union_t[ListStr, str] = "*"
- depends_path: bool = False
- marker: str = None
- apply_to_marker: callable_t[[], bool] = None
-
- def __post_init__(self):
- self.do_rewrite("platforms")
- self.do_rewrite("arch")
-
- def do_rewrite(self, attr: str):
- att = getattr(self, attr)
- if isinstance(att, list):
- setattr(self, attr, [p.lower() for p in att])
- elif isinstance(att, str):
- setattr(self, attr, att.lower())
-
- def check_marker(self):
- do = True
- if self.apply_to_marker:
- do = self.apply_to_marker()
- if do:
- marker = Marker(self.marker)
- return marker.evaluate()
- return False
-
- def _applies_impl(self, attr: str, defn: str):
- if self.marker:
- ok = self.check_marker()
- if not ok:
- return False
-
- att = getattr(self, attr)
- if isinstance(att, list):
- # https://docs.python.org/3/library/platform.html#platform.machine
- # "" is a possible value so we have to add conditions for anon
- _anon = ANON in att and defn == ""
- return defn in att or "*" in att or _anon
- _anon = ANON == att and defn == ""
- return (att in (defn, "*")) or _anon
-
- def applies(self):
- _isplatform = self._applies_impl("platforms", plat())
- _isarch = self._applies_impl("arch", aarch())
- return _isplatform and _isarch
-
- def is_exist(self, trim: int = 0):
- if self.depends_path:
- return path.exists(self.arg[trim:])
- return True
-
-
-@dataclass
-class PlatformArgs(PlatformBase):
- arg: str = None
-
- def __hash__(self) -> int:
- return hash(self.arg)
-
-
-@dataclass
-class EnvFlag(PlatformArgs):
- env: str = field(default="")
- merges: bool = field(default=False)
-
- def __hash__(self) -> int:
- return hash(self.field)
-
-
-__flags__ = (
- EnvFlag(env="CC", merges=False),
- EnvFlag(env="CPP", merges=False),
- EnvFlag(env="CXX", merges=False),
- EnvFlag(env="CFLAGS", merges=True),
- EnvFlag(env="CCSHARED", merges=True),
- EnvFlag(env="CPPFLAGS", merges=True),
- EnvFlag(env="LDFLAGS", merges=True),
- EnvFlag(env="LDSHARED", merges=True),
- EnvFlag(env="SHLIB_SUFFIX", merges=False),
- EnvFlag(env="AR", merges=False),
- EnvFlag(env="ARFLAGS", merges=True),
-)
-
-
-@dataclass
-class EnvFlags:
- CC: PlatformArgs = None
- CPP: PlatformArgs = None
- CXX: PlatformArgs = None
-
- CFLAGS: PlatformArgs = None
- CCSHARED: PlatformArgs = None
-
- CPPFLAGS: PlatformArgs = None
-
- LDFLAGS: PlatformArgs = None
- LDSHARED: PlatformArgs = None
-
- SHLIB_SUFFIX: PlatformArgs = None
-
- AR: PlatformArgs = None
- ARFLAGS: PlatformArgs = None
-
- custom: dict_t[str, PlatformArgs] = field(default_factory=dict)
- env: dict = field(default_factory=environ.copy)
-
- __known__: ClassVar[dict_t[str, EnvFlag]] = {e.env: e for e in __flags__}
-
- def __post_init__(self):
- for flag in __flags__:
- self.merge_to_env(flag, self.get_from_self)
- for flag in self.custom.values():
- self.merge_to_env(flag, self.get_from_custom)
-
- def merge_to_env(self, flag: EnvFlag, get: callable_t[[str], EnvFlag]):
- var = environ.get(flag.env)
- override: EnvFlag = get(flag.env)
- if override and flag.merges:
- add = var + " " if var else ""
- self.env[flag.env] = add + override.arg
- elif override:
- self.env[flag.env] = override.arg
-
- def get_from_self(self, attr):
- return getattr(self, attr)
-
- def get_from_custom(self, attr):
- return self.custom.get(attr)
-
-
-ListedArgs = list_t[union_t[PlatformArgs, str]]
-"""
-List[str | PlatformArgs]
-"""
-
-
-@dataclass
-class FileArgs:
- exclude: ListStr = field(default_factory=list)
-
-
-def get_default_link():
- return [
- PlatformArgs(arg="-L/opt/homebrew/lib", platforms=POSIX_CORE, depends_path=True),
- PlatformArgs(arg="-L/usr/local/lib", platforms=POSIX_CORE, depends_path=True),
- PlatformArgs(arg="-L/usr/local/opt", platforms=POSIX_CORE, depends_path=True),
- ]
-
-
-def get_default_compile():
- return [
- PlatformArgs(arg="-O2"),
- PlatformArgs(arg="-I/opt/homebrew/include", platforms=POSIX_CORE, depends_path=True),
- PlatformArgs(arg="-I/usr/local/include", platforms=POSIX_CORE, depends_path=True),
- ]
-
-
-def parse_to_plat(cls, arg, args: union_t[list, dict], key: union_t[int, str], require_argform: bool, **kwargs):
- if isinstance(arg, dict):
- args[key] = cls(**arg, **kwargs)
- elif require_argform:
- msg = f"arg {key} is invalid. must be of type ({{ flag = ... , platform = '*' }}) given {arg} ({type(arg)})"
- raise ValueError(msg)
-
-
-def parse_platform_args(
- kwargs: dict,
- name: str,
- default: callable_t[[], list_t[PlatformArgs]],
-) -> list_t[union_t[str, PlatformArgs]]:
- try:
- args = [*default(), *kwargs.pop(name)]
- for i, arg in enumerate(args):
- parse_to_plat(PlatformArgs, arg, args, i, require_argform=False)
- except KeyError:
- args = default()
- return args
-
-
-def parse_env_args(
- kwargs: dict,
-):
- try:
- args: list = kwargs.pop("env")
- for i, arg in enumerate(args):
- parse_to_plat(EnvFlag, arg, args, i, require_argform=True)
- except KeyError:
- args = []
- return args
-
-
def parse_from_dict(cls: BuildHookInterface):
given = cls.config.get("options", {})
passed = given.copy()
@@ -359,6 +121,7 @@ class Config:
extra_link_args: ListedArgs = field(default_factory=get_default_link)
retain_intermediate_artifacts: bool = field(default=False)
envflags: EnvFlags = field(default_factory=EnvFlags)
+ compile_py: bool = field(default=True)
def __post_init__(self):
self.directives = {**DIRECTIVES, **self.directives}
@@ -377,8 +140,8 @@ def _post_import_attr(
im: Autoimport,
att: str,
mod: any,
- extend: callable_t[[ListStr], None],
- append: callable_t[[str], None],
+ extend: CallableT[[ListStr], None],
+ append: CallableT[[str], None],
):
attr = getattr(im, att)
if attr is not None:
diff --git a/src/hatch_cython/config/defaults.py b/src/hatch_cython/config/defaults.py
new file mode 100644
index 0000000..b455ea4
--- /dev/null
+++ b/src/hatch_cython/config/defaults.py
@@ -0,0 +1,18 @@
+from hatch_cython.config.platform import PlatformArgs
+from hatch_cython.constants import POSIX_CORE
+
+
+def get_default_link():
+ return [
+ PlatformArgs(arg="-L/opt/homebrew/lib", platforms=POSIX_CORE, depends_path=True),
+ PlatformArgs(arg="-L/usr/local/lib", platforms=POSIX_CORE, depends_path=True),
+ PlatformArgs(arg="-L/usr/local/opt", platforms=POSIX_CORE, depends_path=True),
+ ]
+
+
+def get_default_compile():
+ return [
+ PlatformArgs(arg="-O2"),
+ PlatformArgs(arg="-I/opt/homebrew/include", platforms=POSIX_CORE, depends_path=True),
+ PlatformArgs(arg="-I/usr/local/include", platforms=POSIX_CORE, depends_path=True),
+ ]
diff --git a/src/hatch_cython/config/files.py b/src/hatch_cython/config/files.py
new file mode 100644
index 0000000..2bddca8
--- /dev/null
+++ b/src/hatch_cython/config/files.py
@@ -0,0 +1,27 @@
+import re
+from dataclasses import dataclass, field
+
+from hatch_cython.types import ListStr, UnionT, dict_t
+from hatch_cython.utils import parse_user_glob
+
+
+@dataclass
+class FileArgs:
+ exclude: ListStr = field(default_factory=list)
+ aliases: dict_t[str, str] = field(default_factory=dict)
+
+ def __post_init__(self):
+ rep = {}
+ for k, v in self.aliases.items():
+ rep[parse_user_glob(k)] = v
+ self.aliases = rep
+
+ def matches_alias(self, other: str) -> UnionT[str, None]:
+ matched = [re.match(v, other) for v in self.aliases.keys()]
+ if any(matched):
+ first = 0
+ for ok in matched:
+ if ok:
+ break
+ first += 1
+ return self.aliases[list(self.aliases.keys())[first]]
diff --git a/src/hatch_cython/config/flags.py b/src/hatch_cython/config/flags.py
new file mode 100644
index 0000000..d5a1541
--- /dev/null
+++ b/src/hatch_cython/config/flags.py
@@ -0,0 +1,88 @@
+from dataclasses import dataclass, field
+from os import environ
+from typing import ClassVar
+
+from hatch_cython.config.platform import PlatformArgs, parse_to_plat
+from hatch_cython.types import CallableT, dict_t
+
+
+@dataclass
+class EnvFlag(PlatformArgs):
+ env: str = field(default="")
+ merges: bool = field(default=False)
+
+ def __hash__(self) -> int:
+ return hash(self.field)
+
+
+__flags__ = (
+ EnvFlag(env="CC", merges=False),
+ EnvFlag(env="CPP", merges=False),
+ EnvFlag(env="CXX", merges=False),
+ EnvFlag(env="CFLAGS", merges=True),
+ EnvFlag(env="CCSHARED", merges=True),
+ EnvFlag(env="CPPFLAGS", merges=True),
+ EnvFlag(env="LDFLAGS", merges=True),
+ EnvFlag(env="LDSHARED", merges=True),
+ EnvFlag(env="SHLIB_SUFFIX", merges=False),
+ EnvFlag(env="AR", merges=False),
+ EnvFlag(env="ARFLAGS", merges=True),
+)
+
+
+@dataclass
+class EnvFlags:
+ CC: PlatformArgs = None
+ CPP: PlatformArgs = None
+ CXX: PlatformArgs = None
+
+ CFLAGS: PlatformArgs = None
+ CCSHARED: PlatformArgs = None
+
+ CPPFLAGS: PlatformArgs = None
+
+ LDFLAGS: PlatformArgs = None
+ LDSHARED: PlatformArgs = None
+
+ SHLIB_SUFFIX: PlatformArgs = None
+
+ AR: PlatformArgs = None
+ ARFLAGS: PlatformArgs = None
+
+ custom: dict_t[str, PlatformArgs] = field(default_factory=dict)
+ env: dict = field(default_factory=environ.copy)
+
+ __known__: ClassVar[dict_t[str, EnvFlag]] = {e.env: e for e in __flags__}
+
+ def __post_init__(self):
+ for flag in __flags__:
+ self.merge_to_env(flag, self.get_from_self)
+ for flag in self.custom.values():
+ self.merge_to_env(flag, self.get_from_custom)
+
+ def merge_to_env(self, flag: EnvFlag, get: CallableT[[str], EnvFlag]):
+ var = environ.get(flag.env)
+ override: EnvFlag = get(flag.env)
+ if override and flag.merges:
+ add = var + " " if var else ""
+ self.env[flag.env] = add + override.arg
+ elif override:
+ self.env[flag.env] = override.arg
+
+ def get_from_self(self, attr):
+ return getattr(self, attr)
+
+ def get_from_custom(self, attr):
+ return self.custom.get(attr)
+
+
+def parse_env_args(
+ kwargs: dict,
+):
+ try:
+ args: list = kwargs.pop("env")
+ for i, arg in enumerate(args):
+ parse_to_plat(EnvFlag, arg, args, i, require_argform=True)
+ except KeyError:
+ args = []
+ return args
diff --git a/src/hatch_cython/config/platform.py b/src/hatch_cython/config/platform.py
new file mode 100644
index 0000000..b011c1f
--- /dev/null
+++ b/src/hatch_cython/config/platform.py
@@ -0,0 +1,99 @@
+from collections.abc import Hashable
+from dataclasses import dataclass
+from os import path
+
+from packaging.markers import Marker
+
+from hatch_cython.constants import ANON
+from hatch_cython.types import CallableT, ListStr, UnionT, list_t
+from hatch_cython.utils import aarch, plat
+
+
+@dataclass
+class PlatformBase(Hashable):
+ platforms: UnionT[ListStr, str] = "*"
+ arch: UnionT[ListStr, str] = "*"
+ depends_path: bool = False
+ marker: str = None
+ apply_to_marker: CallableT[[], bool] = None
+
+ def __post_init__(self):
+ self.do_rewrite("platforms")
+ self.do_rewrite("arch")
+
+ def do_rewrite(self, attr: str):
+ att = getattr(self, attr)
+ if isinstance(att, list):
+ setattr(self, attr, [p.lower() for p in att])
+ elif isinstance(att, str):
+ setattr(self, attr, att.lower())
+
+ def check_marker(self):
+ do = True
+ if self.apply_to_marker:
+ do = self.apply_to_marker()
+ if do:
+ marker = Marker(self.marker)
+ return marker.evaluate()
+ return False
+
+ def _applies_impl(self, attr: str, defn: str):
+ if self.marker:
+ ok = self.check_marker()
+ if not ok:
+ return False
+
+ att = getattr(self, attr)
+ if isinstance(att, list):
+ # https://docs.python.org/3/library/platform.html#platform.machine
+ # "" is a possible value so we have to add conditions for anon
+ _anon = ANON in att and defn == ""
+ return defn in att or "*" in att or _anon
+ _anon = ANON == att and defn == ""
+ return (att in (defn, "*")) or _anon
+
+ def applies(self):
+ _isplatform = self._applies_impl("platforms", plat())
+ _isarch = self._applies_impl("arch", aarch())
+ return _isplatform and _isarch
+
+ def is_exist(self, trim: int = 0):
+ if self.depends_path:
+ return path.exists(self.arg[trim:])
+ return True
+
+
+@dataclass
+class PlatformArgs(PlatformBase):
+ arg: str = None
+
+ def __hash__(self) -> int:
+ return hash(self.arg)
+
+
+def parse_to_plat(cls, arg, args: UnionT[list, dict], key: UnionT[int, str], require_argform: bool, **kwargs):
+ if isinstance(arg, dict):
+ args[key] = cls(**arg, **kwargs)
+ elif require_argform:
+ msg = f"arg {key} is invalid. must be of type ({{ flag = ... , platform = '*' }}) given {arg} ({type(arg)})"
+ raise ValueError(msg)
+
+
+def parse_platform_args(
+ kwargs: dict,
+ name: str,
+ default: CallableT[[], list_t[PlatformArgs]],
+) -> list_t[UnionT[str, PlatformArgs]]:
+ try:
+ args = [*default(), *kwargs.pop(name)]
+ for i, arg in enumerate(args):
+ parse_to_plat(PlatformArgs, arg, args, i, require_argform=False)
+ except KeyError:
+ args = default()
+ return args
+
+
+ListedArgs = list_t[UnionT[PlatformArgs, str]]
+"""
+List[str | PlatformArgs]
+"""
diff --git a/src/hatch_cython/constants.py b/src/hatch_cython/constants.py
new file mode 100644
index 0000000..8ca8430
--- /dev/null
+++ b/src/hatch_cython/constants.py
@@ -0,0 +1,15 @@
+from hatch_cython.types import CorePlatforms, list_t
+
+NORM_GLOB = r"([^\s]*)"
+UAST = "${U_AST}"
+EXIST_TRIM = 2
+ANON = "anon"
+INCLUDE = "include_"
+OPTIMIZE = "-O2"
+DIRECTIVES = {
+ "binding": True,
+ "language_level": 3,
+}
+LTPY311 = "python_version < '3.11'"
+MUST_UNIQUE = ["-O", "-arch", "-march"]
+POSIX_CORE: list_t[CorePlatforms] = ["darwin", "linux"]
diff --git a/src/hatch_cython/plugin.py b/src/hatch_cython/plugin.py
index 540f7d1..8100f80 100644
--- a/src/hatch_cython/plugin.py
+++ b/src/hatch_cython/plugin.py
@@ -9,71 +9,16 @@
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
-from hatch_cython.config import Config, parse_from_dict, plat
+from hatch_cython.config import parse_from_dict
+from hatch_cython.temp import ExtensionArg, setup_py
from hatch_cython.types import ListStr, P, list_t
-
-
-def options_kws(kwds: dict):
- return ",\n\t".join((f"{k}={v!r}" for k, v in kwds.items()))
-
-
-def setup_py(
- *files: list_t[ListStr],
- options: Config,
-):
- code = """
-from setuptools import Extension, setup
-from Cython.Build import cythonize
-
-COMPILEARGS = {compile_args}
-DIRECTIVES = {directives}
-INCLUDES = {includes}
-LIBRARIES = {libs}
-LIBRARY_DIRS = {lib_dirs}
-EXTENSIONS = [{ext_files}]
-LINKARGS = {extra_link_args}
-
-if __name__ == "__main__":
- exts = [
- Extension("*",
- ex,
- extra_compile_args=COMPILEARGS,
- extra_link_args=LINKARGS,
- include_dirs=INCLUDES,
- libraries=LIBRARIES,
- library_dirs=LIBRARY_DIRS,
- {keywords}
- ) for ex in EXTENSIONS
- ]
- ext_modules = cythonize(
- exts,
- compiler_directives=DIRECTIVES,
- include_path=INCLUDES,
- {cython}
- )
- setup(ext_modules=ext_modules)
-"""
- ext_files = ",".join([repr(lf) for lf in files])
- kwds = options_kws(options.compile_kwargs)
- cython = options_kws(options.cythonize_kwargs)
- return code.format(
- compile_args=repr(options.compile_args_for_platform),
- extra_link_args=repr(options.compile_links_for_platform),
- directives=repr(options.directives),
- ext_files=ext_files,
- keywords=kwds,
- cython=cython,
- includes=repr(options.includes),
- libs=repr(options.libraries),
- lib_dirs=repr(options.library_dirs),
- ).strip()
+from hatch_cython.utils import memo, parse_user_glob, plat
class CythonBuildHook(BuildHookInterface):
PLUGIN_NAME = "cython"
precompiled_extension: ClassVar[list] = [
- ".py",
".pyx",
".pxd",
]
@@ -89,27 +34,11 @@ class CythonBuildHook(BuildHookInterface):
".pyd",
]
- _config: Config
- _dir: str
- _included: ListStr
- _artifact_patterns: ListStr
- _artifact_globs: ListStr
- _norm_included_files: ListStr
- _norm_artifact_patterns: ListStr
- _grouped_norm: list_t[ListStr]
-
def __init__(self, *args: P.args, **kwargs: P.kwargs):
super().__init__(*args, **kwargs)
- self._included = None
- self._norm_included_files = None
- self._artifact_patterns = None
- self._artifact_globs = None
- self._norm_artifact_patterns = None
- self._config = None
- self._dir = None
- self._grouped_norm = None
@property
+ @memo
def is_src(self):
return os.path.exists(os.path.join(self.root, "src"))
@@ -126,100 +55,110 @@ def normalize_glob(self, pattern: str):
return pattern.replace("\\", "/")
@property
+ @memo
def dir_name(self):
return self.options.src if self.options.src is not None else self.metadata.name
@property
+ @memo
def project_dir(self):
- if self._dir is None:
- if self.is_src:
- src = f"./src/{self.dir_name}"
- else:
- src = f"./{self.dir_name}"
- self._dir = src
- return self._dir
+ if self.is_src:
+ src = f"./src/{self.dir_name}"
+ else:
+ src = f"./{self.dir_name}"
+ return src
@property
def precompiled_globs(self):
_globs = []
for ex in self.precompiled_extension:
_globs.extend((f"{self.project_dir}/*{ex}", f"{self.project_dir}/**/*{ex}"))
- return _globs
+ return list(set(_globs))
@property
+ @memo
+ def options_exclude(self):
+ return [parse_user_glob(e) for e in self.options.files.exclude]
+
+ def filter_ensure_wanted(self, tgts: ListStr):
+ matched = list(
+ filter(
+ lambda s: not any(re.match(e, self.normalize_glob(s), re.IGNORECASE) for e in self.options_exclude),
+ tgts,
+ )
+ )
+ return matched
+
+ @property
+ @memo
def included_files(self):
- if self._included is None:
- self._included = []
- for patt in self.precompiled_globs:
- globbed = glob(patt)
- matched = list(
- filter(
- lambda s: not any(
- re.match(
- self.normalize_glob(e).replace("*", r"([^\s]*)"), self.normalize_glob(s), re.IGNORECASE
- )
- for e in self.options.files.exclude
- ),
- globbed,
- )
- )
- self._included.extend(matched)
- return self._included
+ included = []
+ _normu = [self.normalize_glob(parse_user_glob(e)) for e in self.options.files.exclude]
+ self.app.display_debug("user globs")
+ self.app.display_debug(_normu)
+ for patt in self.precompiled_globs:
+ globbed = glob(patt, recursive=True)
+ if len(globbed) == 0:
+ continue
+ matched = self.filter_ensure_wanted(globbed)
+ included.extend(matched)
+ return included
@property
+ @memo
def normalized_included_files(self):
"""
Produces files in posix format
"""
- if self._norm_included_files is None:
- self._norm_included_files = [self.normalize_glob(f) for f in self.included_files]
- return self._norm_included_files
+ return [self.normalize_glob(f) for f in self.included_files]
@property
- def grouped_included_files(self) -> list_t[ListStr]:
- if self._grouped_norm is None:
- grouped = {}
- for norm in self.normalized_included_files:
- root, ext = os.path.splitext(norm)
- ok = True
- if ext == ".pxd":
- pyfile = norm.replace(".pxd", ".py")
- if os.path.exists(pyfile):
- norm = pyfile # noqa: PLW2901
- else:
- ok = False
- self.app.display_warning(f"attempted to use .pxd file without .py file ({norm})")
- if grouped.get(root) and ok:
- grouped[root].append(norm)
- elif ok:
- grouped[root] = [norm]
- self._grouped_norm = list(grouped.values())
- return self._grouped_norm
+ @memo
+ def grouped_included_files(self) -> list_t[ExtensionArg]:
+ grouped = {}
+ for norm in self.normalized_included_files:
+ root, ext = os.path.splitext(norm)
+ ok = True
+ if ext == ".pxd":
+ pyfile = norm.replace(".pxd", ".py")
+ if os.path.exists(pyfile):
+ norm = pyfile # noqa: PLW2901
+ else:
+ ok = False
+ self.app.display_warning(f"attempted to use .pxd file without .py file ({norm})")
+ if self.is_src:
+ root = root.replace("./src/", "")
+ root = root.replace("/", ".")
+ alias = self.options.files.matches_alias(root)
+ if alias:
+ root = alias
+ if grouped.get(root) and ok:
+ grouped[root].append(norm)
+ elif ok:
+ grouped[root] = [norm]
+ return [ExtensionArg(name=key, files=files) for key, files in grouped.items()]
@property
+ @memo
def artifact_globs(self):
- if self._artifact_globs is None:
- artifact_globs = []
- for included_file in self.normalized_included_files:
- root, _ = os.path.splitext(included_file)
- artifact_globs.extend(f"{root}.*{ext}" for ext in self.precompiled_extension)
- self._artifact_globs = artifact_globs
- return self._artifact_globs
+ artifact_globs = []
+ for included_file in self.normalized_included_files:
+ root, _ = os.path.splitext(included_file)
+ artifact_globs.extend(f"{root}.*{ext}" for ext in self.precompiled_extension)
+ return artifact_globs
@property
+ @memo
def normalized_artifact_globs(self):
"""
Produces files in platform native format (e.g. a/b vs a\\b)
"""
- if self._norm_artifact_patterns is None:
- self._norm_artifact_patterns = [self.normalize_glob(f) for f in self.artifact_globs]
- return self._norm_artifact_patterns
+ return [self.normalize_glob(f) for f in self.artifact_globs]
@property
+ @memo
def artifact_patterns(self):
- if self._artifact_patterns is None:
- self._artifact_patterns = [f"/{artifact_glob}" for artifact_glob in self.normalized_artifact_globs]
- return self._artifact_patterns
+ return [f"/{artifact_glob}" for artifact_glob in self.normalized_artifact_globs]
@contextmanager
def get_build_dirs(self):
@@ -233,7 +172,7 @@ def _globs(self, exts: ListStr):
]
globbed = []
for g in globs:
- globbed += [self.normalize_path(f) for f in glob(g)]
+ globbed += [self.normalize_path(f) for f in glob(g, recursive=True)]
return list(set(globbed))
@property
@@ -266,10 +205,12 @@ def clean(self, _versions: ListStr):
self.clean_compiled()
@property
+ @memo
def options(self):
- if self._config is None:
- self._config = parse_from_dict(self)
- return self._config
+ config = parse_from_dict(self)
+ if config.compile_py:
+ self.precompiled_extension.append(".py")
+ return config
def initialize(self, version: str, build_data: dict):
self.app.display_mini_header(self.PLUGIN_NAME)
@@ -321,7 +262,7 @@ def initialize(self, version: str, build_data: dict):
self.app.display_info(process.stdout.decode("utf-8"))
self.app.display_success("Post-build artifacts")
- self.app.display_info(glob(f"{self.project_dir}/*/**"))
+ self.app.display_info(glob(f"{self.project_dir}/*/**", recursive=True))
if not self.options.retain_intermediate_artifacts:
self.clean_intermediate()
diff --git a/src/hatch_cython/temp.py b/src/hatch_cython/temp.py
new file mode 100644
index 0000000..09f6944
--- /dev/null
+++ b/src/hatch_cython/temp.py
@@ -0,0 +1,62 @@
+from typing import TypedDict
+
+from hatch_cython.config import Config
+from hatch_cython.types import ListStr, list_t
+from hatch_cython.utils import options_kws
+
+
+class ExtensionArg(TypedDict):
+ name: str
+ files: ListStr
+
+
+def setup_py(
+ *files: list_t[ListStr],
+ options: Config,
+):
+ code = """
+from setuptools import Extension, setup
+from Cython.Build import cythonize
+
+COMPILEARGS = {compile_args}
+DIRECTIVES = {directives}
+INCLUDES = {includes}
+LIBRARIES = {libs}
+LIBRARY_DIRS = {lib_dirs}
+EXTENSIONS = [{ext_files}]
+LINKARGS = {extra_link_args}
+
+if __name__ == "__main__":
+ exts = [
+ Extension( ex.get("name"),
+ ex.get("files"),
+ extra_compile_args=COMPILEARGS,
+ extra_link_args=LINKARGS,
+ include_dirs=INCLUDES,
+ libraries=LIBRARIES,
+ library_dirs=LIBRARY_DIRS,
+ {keywords}
+ ) for ex in EXTENSIONS
+ ]
+ ext_modules = cythonize(
+ exts,
+ compiler_directives=DIRECTIVES,
+ include_path=INCLUDES,
+ {cython}
+ )
+ setup(ext_modules=ext_modules)
+"""
+ ext_files = ",".join([repr(lf) for lf in files])
+ kwds = options_kws(options.compile_kwargs)
+ cython = options_kws(options.cythonize_kwargs)
+ return code.format(
+ compile_args=repr(options.compile_args_for_platform),
+ extra_link_args=repr(options.compile_links_for_platform),
+ directives=repr(options.directives),
+ ext_files=ext_files,
+ keywords=kwds,
+ cython=cython,
+ includes=repr(options.includes),
+ libs=repr(options.libraries),
+ lib_dirs=repr(options.library_dirs),
+ ).strip()
diff --git a/src/hatch_cython/types.py b/src/hatch_cython/types.py
index 62ed357..0e09367 100644
--- a/src/hatch_cython/types.py
+++ b/src/hatch_cython/types.py
@@ -18,14 +18,14 @@
dict_t = Dict # noqa: UP006
list_t = List # noqa: UP006
- ListStr = List[str] # noqa: UP006
P = ParamSpec("P")
-union_t = Union
+ListStr = list_t[str]
+UnionT = Union
CorePlatforms = Literal[
"darwin",
"linux",
"windows",
]
-callable_t = Callable
+CallableT = Callable
diff --git a/src/hatch_cython/utils.py b/src/hatch_cython/utils.py
index b833f70..386b180 100644
--- a/src/hatch_cython/utils.py
+++ b/src/hatch_cython/utils.py
@@ -1,11 +1,14 @@
-from hatch_cython.types import P, T, callable_t
+import platform
+from hatch_cython.constants import NORM_GLOB, UAST
+from hatch_cython.types import CallableT, P, T
-def memo(func: callable_t[P, T]) -> T:
+
+def memo(func: CallableT[P, T]) -> CallableT[P, T]:
value = None
ran = False
- def wrapped(*args, **kwargs):
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
nonlocal value, ran
if not ran:
value = func(*args, **kwargs)
@@ -13,3 +16,21 @@ def wrapped(*args, **kwargs):
return value
return wrapped
+
+
+@memo
+def plat():
+ return platform.system().lower()
+
+
+@memo
+def aarch():
+ return platform.machine().lower()
+
+
+def options_kws(kwds: dict):
+ return ",\n\t".join((f"{k}={v!r}" for k, v in kwds.items()))
+
+
+def parse_user_glob(uglob: str):
+ return uglob.replace("\\*", UAST).replace("*", NORM_GLOB).replace(UAST, "*")
diff --git a/taskfile.yaml b/taskfile.yaml
index 4e497e4..6c6934b 100644
--- a/taskfile.yaml
+++ b/taskfile.yaml
@@ -18,10 +18,12 @@ tasks:
- rm -rf .coverag*
- rm -rf __pycache__
- rm -rf **/__pycache__
- - rm -f **/*.c
- - rm -f **/**/*.so
- - rm -f **/**/*.cpp
- - rm -f **/*.so
+ - rm -rf ./example/src/**/**/*.c
+ - rm -rf ./example/src/**/**/*.so
+ - rm -rf ./example/src/**/**/*.cpp
+ - rm -rf ./example/src/**/**/*.html
+ - rm -rf ./**/*.so
+ - rm -rf ./**/*.html
- rm -rf .ruff_cache
- rm -rf **/.ruff_cache
- rm -rf .pytest_cache
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index cdb627f..5504e83 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -73,58 +73,95 @@ def test_build_hook(new_proj):
with override_dir(new_proj):
assert sorted(hook.normalized_included_files) == sorted(
[
- "./src/example_lib/normal.py",
- "./src/example_lib/__init__.py",
"./src/example_lib/__about__.py",
+ "./src/example_lib/__about__.py",
+ "./src/example_lib/__init__.py",
+ "./src/example_lib/__init__.py",
+ "./src/example_lib/_alias.pyx",
+ "./src/example_lib/_alias.pyx",
"./src/example_lib/mod_a/__init__.py",
- "./src/example_lib/mod_a/some_defn.py",
- "./src/example_lib/test.pyx",
"./src/example_lib/mod_a/adds.pyx",
+ "./src/example_lib/mod_a/deep_nest/creates.pyx",
"./src/example_lib/mod_a/some_defn.pxd",
+ "./src/example_lib/mod_a/some_defn.py",
+ "./src/example_lib/normal.py",
+ "./src/example_lib/normal.py",
+ "./src/example_lib/test.pyx",
+ "./src/example_lib/test.pyx",
]
)
- assert sorted([sorted(ls) for ls in hook.grouped_included_files]) == sorted(
- [
- sorted(ls)
- for ls in [
- ["./src/example_lib/normal.py"],
- ["./src/example_lib/__init__.py"],
- ["./src/example_lib/__about__.py"],
- ["./src/example_lib/mod_a/__init__.py"],
- ["./src/example_lib/mod_a/some_defn.py", "./src/example_lib/mod_a/some_defn.py"],
- ["./src/example_lib/test.pyx"],
- ["./src/example_lib/mod_a/adds.pyx"],
- ]
- ]
- )
+ assert sorted(
+ [{**ls, "files": sorted(ls.get("files"))} for ls in hook.grouped_included_files],
+ key=lambda x: x.get("name"),
+ ) == [
+ {
+ "name": "example_lib.__about__",
+ "files": ["./src/example_lib/__about__.py", "./src/example_lib/__about__.py"],
+ },
+ {
+ "name": "example_lib.__init__",
+ "files": ["./src/example_lib/__init__.py", "./src/example_lib/__init__.py"],
+ },
+ {"name": "example_lib.aliased", "files": ["./src/example_lib/_alias.pyx", "./src/example_lib/_alias.pyx"]},
+ {"name": "example_lib.mod_a.__init__", "files": ["./src/example_lib/mod_a/__init__.py"]},
+ {"name": "example_lib.mod_a.adds", "files": ["./src/example_lib/mod_a/adds.pyx"]},
+ {"name": "example_lib.mod_a.deep_nest.creates", "files": ["./src/example_lib/mod_a/deep_nest/creates.pyx"]},
+ {
+ "name": "example_lib.mod_a.some_defn",
+ "files": ["./src/example_lib/mod_a/some_defn.py", "./src/example_lib/mod_a/some_defn.py"],
+ },
+ {"name": "example_lib.normal", "files": ["./src/example_lib/normal.py", "./src/example_lib/normal.py"]},
+ {"name": "example_lib.test", "files": ["./src/example_lib/test.pyx", "./src/example_lib/test.pyx"]},
+ ]
rf = sorted(
[
- "./src/example_lib/normal.*.py",
- "./src/example_lib/normal.*.pyx",
- "./src/example_lib/normal.*.pxd",
- "./src/example_lib/__init__.*.py",
- "./src/example_lib/__init__.*.pyx",
- "./src/example_lib/__init__.*.pxd",
+ "./src/example_lib/__about__.*.pxd",
+ "./src/example_lib/__about__.*.pxd",
+ "./src/example_lib/__about__.*.py",
"./src/example_lib/__about__.*.py",
"./src/example_lib/__about__.*.pyx",
- "./src/example_lib/__about__.*.pxd",
+ "./src/example_lib/__about__.*.pyx",
+ "./src/example_lib/__init__.*.pxd",
+ "./src/example_lib/__init__.*.pxd",
+ "./src/example_lib/__init__.*.py",
+ "./src/example_lib/__init__.*.py",
+ "./src/example_lib/__init__.*.pyx",
+ "./src/example_lib/__init__.*.pyx",
+ "./src/example_lib/_alias.*.pxd",
+ "./src/example_lib/_alias.*.pxd",
+ "./src/example_lib/_alias.*.py",
+ "./src/example_lib/_alias.*.py",
+ "./src/example_lib/_alias.*.pyx",
+ "./src/example_lib/_alias.*.pyx",
+ "./src/example_lib/mod_a/__init__.*.pxd",
"./src/example_lib/mod_a/__init__.*.py",
"./src/example_lib/mod_a/__init__.*.pyx",
- "./src/example_lib/mod_a/__init__.*.pxd",
- "./src/example_lib/mod_a/some_defn.*.py",
- "./src/example_lib/mod_a/some_defn.*.pyx",
- "./src/example_lib/mod_a/some_defn.*.pxd",
- "./src/example_lib/test.*.py",
- "./src/example_lib/test.*.pyx",
- "./src/example_lib/test.*.pxd",
+ "./src/example_lib/mod_a/adds.*.pxd",
"./src/example_lib/mod_a/adds.*.py",
"./src/example_lib/mod_a/adds.*.pyx",
- "./src/example_lib/mod_a/adds.*.pxd",
+ "./src/example_lib/mod_a/deep_nest/creates.*.pxd",
+ "./src/example_lib/mod_a/deep_nest/creates.*.py",
+ "./src/example_lib/mod_a/deep_nest/creates.*.pyx",
+ "./src/example_lib/mod_a/some_defn.*.pxd",
+ "./src/example_lib/mod_a/some_defn.*.pxd",
+ "./src/example_lib/mod_a/some_defn.*.py",
"./src/example_lib/mod_a/some_defn.*.py",
"./src/example_lib/mod_a/some_defn.*.pyx",
- "./src/example_lib/mod_a/some_defn.*.pxd",
+ "./src/example_lib/mod_a/some_defn.*.pyx",
+ "./src/example_lib/normal.*.pxd",
+ "./src/example_lib/normal.*.pxd",
+ "./src/example_lib/normal.*.py",
+ "./src/example_lib/normal.*.py",
+ "./src/example_lib/normal.*.pyx",
+ "./src/example_lib/normal.*.pyx",
+ "./src/example_lib/test.*.pxd",
+ "./src/example_lib/test.*.pxd",
+ "./src/example_lib/test.*.py",
+ "./src/example_lib/test.*.py",
+ "./src/example_lib/test.*.pyx",
+ "./src/example_lib/test.*.pyx",
]
)
assert sorted(hook.normalized_artifact_globs) == rf
@@ -142,4 +179,4 @@ def test_build_hook(new_proj):
assert build_data.get("infer_tag")
assert not build_data.get("pure_python")
assert sorted(build_data.get("artifacts")) == sorted([f"/{f}" for f in rf])
- assert len(build_data.get("force_include")) == 7
+ assert len(build_data.get("force_include")) == 9
diff --git a/tests/test_setuppy.py b/tests/test_setuppy.py
index 246a824..b874418 100644
--- a/tests/test_setuppy.py
+++ b/tests/test_setuppy.py
@@ -20,13 +20,13 @@ def clean(s: str):
INCLUDES = ['/123']
LIBRARIES = ['/abc']
LIBRARY_DIRS = ['/def']
-EXTENSIONS = [['./abc/def.pyx'],['./abc/depb.py']]
+EXTENSIONS = [{'name': 'abc.def', 'files': ['./abc/def.pyx']},{'name': 'abc.depb', 'files': ['./abc/depb.py']}]
LINKARGS = ['-I/etc/abc/linka.h']
if __name__ == "__main__":
exts = [
- Extension("*",
- ex,
+ Extension( ex.get("name"),
+ ex.get("files"),
extra_compile_args=COMPILEARGS,
extra_link_args=LINKARGS,
include_dirs=INCLUDES,
@@ -53,11 +53,11 @@ def test_setup_py():
cythonize_kwargs={"abc": "def"},
extra_link_args=[PlatformArgs(arg="-I/etc/abc/linka.h")],
)
- with patch("hatch_cython.config.path.exists", true_if_eq()):
+ with patch("hatch_cython.config.config.path.exists", true_if_eq()):
with arch_platform("x86_64", ""):
setup = setup_py(
- ["./abc/def.pyx"],
- ["./abc/depb.py"],
+ {"name": "abc.def", "files": ["./abc/def.pyx"]},
+ {"name": "abc.depb", "files": ["./abc/depb.py"]},
options=cfg,
)
assert clean(setup) == clean(EXPECT)
@@ -71,10 +71,10 @@ def test_solo_ext_type_validations():
cythonize_kwargs={"abc": "def"},
extra_link_args=[PlatformArgs(arg="-I/etc/abc/linka.h")],
)
- with patch("hatch_cython.config.path.exists", true_if_eq()):
+ with patch("hatch_cython.config.config.path.exists", true_if_eq()):
with arch_platform("x86_64", ""):
setup = setup_py(
- ["./abc/def.pyx"],
+ {"name": "abc.def", "files": ["./abc/def.pyx"]},
options=cfg,
)
tested = False
@@ -85,8 +85,9 @@ def test_solo_ext_type_validations():
ext = ast.literal_eval(ln.replace(exteq, "").strip())
assert isinstance(ext, list)
for ex in ext:
- ok = [isinstance(node, str) for node in ex]
- assert all(ok)
+ assert isinstance(ex, dict)
+ assert isinstance(ex.get("name"), str)
+ assert isinstance(ex.get("files"), list)
if not tested:
raise ValueError(setup, tested, "missed test")
diff --git a/tests/utils.py b/tests/utils.py
index 82d8295..c1e7f8b 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -29,7 +29,7 @@ def patch_path(arch: str, *extra: str):
def wrap(path):
return h(path, *extra)
- with patch("hatch_cython.config.path.exists", wrap):
+ with patch("hatch_cython.config.config.path.exists", wrap):
yield
@@ -42,10 +42,13 @@ def platformgetter():
return platform
try:
- with patch("hatch_cython.config.aarch", aarchgetter):
- with patch("hatch_cython.config.plat", platformgetter):
- with patch("hatch_cython.plugin.plat", platformgetter):
- yield
+ with patch("hatch_cython.utils.plat", platformgetter):
+ with patch("hatch_cython.utils.plat", platformgetter):
+ with patch("hatch_cython.config.platform.plat", platformgetter):
+ with patch("hatch_cython.plugin.plat", platformgetter):
+ with patch("hatch_cython.utils.aarch", aarchgetter):
+ with patch("hatch_cython.config.platform.aarch", aarchgetter):
+ yield
finally:
print(f"Clean {arch}-{platform}") # noqa: T201
del aarchgetter, platformgetter
@@ -74,7 +77,7 @@ def get_import(name: str):
try:
with patch(
- "hatch_cython.config.import_module",
+ "hatch_cython.config.config.import_module",
get_import,
):
yield
@@ -101,7 +104,11 @@ def override_env(d: dict):
for k, v in d.items():
new[k] = v
os.environ.update(new)
- yield
+ with patch(
+ "hatch_cython.config.flags.environ",
+ new,
+ ):
+ yield
finally:
for k, v in current.items():
os.environ[k] = v