Skip to content

Commit ce100fe

Browse files
authored
Fix win compile (#14)
* Fix cmake build utf8 support * Fix windows cmake takes --paralel * Fix windows can't find dll * refractor extension name to avoid namespace collision * make source build static * make source build static * make autoconf build force static
1 parent 1082040 commit ce100fe

16 files changed

Lines changed: 87 additions & 56 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,5 @@ location.
208208
- All internal buffers (match data wrappers, JIT stack cache entries, error
209209
formatting scratch space) use the chosen allocator; CPython’s `PyMem_*`
210210
family is no longer used within the extension.
211-
- Call `pcre.cpcre2.get_allocator()` to inspect which backend is active at
211+
- Call `pcre_ext_c.get_allocator()` to inspect which backend is active at
212212
runtime.

pcre/__init__.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""High level Python bindings for PCRE2.
77
88
This package exposes a Pythonic API on top of the low-level C extension found in
9-
``pcre.cpcre2``. The wrapper keeps friction low compared to :mod:`re` while
9+
``pcre_ext_c``. The wrapper keeps friction low compared to :mod:`re` while
1010
surfacing PCRE2-specific flags and behaviours.
1111
"""
1212

@@ -16,7 +16,9 @@
1616
from enum import IntEnum, IntFlag
1717
from typing import Any
1818

19-
from . import cpcre2
19+
import pcre_ext_c as _backend
20+
21+
pcre_ext_c = _backend
2022
from .cache import get_cache_limit, set_cache_limit
2123
from .flags import PY_ONLY_FLAG_MEMBERS
2224
from .pcre import (
@@ -42,17 +44,17 @@
4244
from .threads import configure_threads
4345

4446

45-
__version__ = getattr(cpcre2, "__version__", "0.0")
47+
__version__ = getattr(_backend, "__version__", "0.0")
4648

47-
_cpu_ascii_vector_mode = getattr(cpcre2, "_cpu_ascii_vector_mode", None)
49+
_cpu_ascii_vector_mode = getattr(_backend, "_cpu_ascii_vector_mode", None)
4850

4951
_FLAG_MEMBERS: dict[str, int] = {}
5052
_ERROR_CODE_MEMBERS: dict[str, int] = {}
5153

52-
for _name in dir(cpcre2):
54+
for _name in dir(_backend):
5355
if not _name.startswith("PCRE2_"):
5456
continue
55-
_value = getattr(cpcre2, _name)
57+
_value = getattr(_backend, _name)
5658
if not isinstance(_value, int):
5759
continue
5860

@@ -94,9 +96,9 @@ def _error_code_property(self) -> PcreErrorCode | None:
9496

9597

9698
_EXPORTED_ERROR_CLASSES: list[str] = []
97-
for _name in dir(cpcre2):
99+
for _name in dir(_backend):
98100
if _name.startswith("PcreError") and _name != "PcreError":
99-
globals()[_name] = getattr(cpcre2, _name)
101+
globals()[_name] = getattr(_backend, _name)
100102
_EXPORTED_ERROR_CLASSES.append(_name)
101103

102104

pcre/cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from threading import RLock
1212
from typing import Any, Callable, Tuple, TypeVar
1313

14-
from . import cpcre2 as _pcre2
14+
import pcre_ext_c as _pcre2
1515

1616

1717
T = TypeVar("T")

pcre/flags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from typing import Dict
1111

12-
from . import cpcre2 as _pcre2
12+
import pcre_ext_c as _pcre2
1313

1414

1515
def _collect_native_flag_values() -> list[int]:

pcre/pcre.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from re import _parser
1313
from typing import Any, List
1414

15-
from . import cpcre2 as _pcre2
15+
import pcre_ext_c as _pcre2
1616
from .cache import cached_compile
1717
from .cache import clear_cache as _clear_cache
1818
from .flags import (
@@ -167,7 +167,7 @@ def _call_with_optional_end(method, subject: Any, pos: int, endpos: int | None,
167167

168168

169169
class Pattern:
170-
"""High-level wrapper around the C-backed :class:`cpcre2.Pattern`."""
170+
"""High-level wrapper around the C-backed :class:`pcre_ext_c.Pattern`."""
171171

172172
__slots__ = ("_pattern", "_groups_hint", "_thread_mode")
173173

pcre/re_compat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from re import _parser
1313
from typing import Any, List
1414

15-
from . import cpcre2 as _pcre2
15+
import pcre_ext_c as _pcre2
1616

1717

1818
_CRawMatch = _pcre2.Match

pcre_ext/pcre2.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,7 +2031,7 @@ static PyMethodDef module_methods[] = {
20312031

20322032
static struct PyModuleDef moduledef = {
20332033
PyModuleDef_HEAD_INIT,
2034-
.m_name = "pcre.cpcre2",
2034+
.m_name = "pcre_ext_c",
20352035
.m_doc = "Low-level bindings to the PCRE2 regular expression engine.",
20362036
.m_size = -1,
20372037
.m_methods = module_methods,
@@ -2096,7 +2096,7 @@ detect_offset_limit_support(void)
20962096
}
20972097

20982098
PyMODINIT_FUNC
2099-
PyInit_cpcre2(void)
2099+
PyInit_pcre_ext_c(void)
21002100
{
21012101
if (PyType_Ready(&PatternType) < 0) {
21022102
return NULL;

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pathlib import Path
1010

1111
from setuptools import Extension, setup
12+
from setuptools.command.build_ext import build_ext
1213

1314
ROOT_DIR = Path(__file__).resolve().parent
1415
if str(ROOT_DIR) not in sys.path:
@@ -18,9 +19,9 @@
1819

1920

2021
EXTENSION = Extension(
21-
name="pcre.cpcre2",
22+
name="pcre_ext_c",
2223
sources=MODULE_SOURCES,
2324
**collect_build_config(),
2425
)
2526

26-
setup(ext_modules=[EXTENSION])
27+
setup(ext_modules=[EXTENSION], cmdclass={"build_ext": build_ext})

setup_utils.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@
1515
from collections.abc import Callable
1616
from pathlib import Path
1717

18-
try:
19-
from setuptools._distutils.ccompiler import CCompiler, new_compiler
20-
from setuptools._distutils.errors import CCompilerError, DistutilsExecError
21-
from setuptools._distutils.sysconfig import customize_compiler
22-
except ImportError: # pragma: no cover - fallback for older Python environments
23-
from distutils.ccompiler import CCompiler, new_compiler # type: ignore
24-
from distutils.errors import CCompilerError, DistutilsExecError # type: ignore
25-
from distutils.sysconfig import customize_compiler # type: ignore
18+
19+
from setuptools._distutils.ccompiler import CCompiler, new_compiler
20+
from setuptools._distutils.errors import CCompilerError, DistutilsExecError
21+
from setuptools._distutils.sysconfig import customize_compiler
2622

2723

2824
ROOT_DIR = Path(__file__).resolve().parent
@@ -51,7 +47,9 @@
5147

5248
LIBRARY_BASENAME = "libpcre2-8"
5349

54-
__all__ = ["MODULE_SOURCES", "collect_build_config"]
50+
RUNTIME_LIBRARY_FILES: list[str] = []
51+
52+
__all__ = ["MODULE_SOURCES", "collect_build_config", "RUNTIME_LIBRARY_FILES"]
5553

5654

5755
def _run_pkg_config(*args: str) -> list[str]:
@@ -221,9 +219,10 @@ def _has_built_library() -> bool:
221219
"-B",
222220
str(build_dir),
223221
"-DPCRE2_SUPPORT_JIT=ON",
224-
"-DPCRE2_BUILD_PCRE2_16=ON",
222+
"-DPCRE2_BUILD_PCRE2_8=ON",
225223
"-DPCRE2_BUILD_TESTS=OFF",
226-
"-DBUILD_SHARED_LIBS=ON",
224+
"-DBUILD_SHARED_LIBS=OFF", # don't build DLLs
225+
"-DPCRE2_STATIC=ON", # ensure static linking symbols are used
227226
]
228227
if not _is_windows_platform():
229228
cmake_args.append("-DCMAKE_BUILD_TYPE=Release")
@@ -235,8 +234,9 @@ def _has_built_library() -> bool:
235234
str(build_dir),
236235
]
237236
if _is_windows_platform():
238-
build_command.extend(["--config", "Release"])
239-
build_command.extend(["--", "-j4"])
237+
build_command.extend(["--config", "Release", "--parallel", "4"])
238+
else:
239+
build_command.extend(["--", "-j4"])
240240
subprocess.run(build_command, cwd=destination, env=env, check=True)
241241
except (FileNotFoundError, subprocess.CalledProcessError) as exc:
242242
cmake_error = exc
@@ -255,6 +255,8 @@ def _has_built_library() -> bool:
255255
"--enable-jit",
256256
"--enable-pcre2-8",
257257
"--disable-tests",
258+
"--enable-static",
259+
"--disable-shared",
258260
]
259261
subprocess.run(configure_command, cwd=build_dir, env=env, check=True)
260262
subprocess.run(["make", "-j4"], cwd=build_dir, env=env, check=True)
@@ -452,6 +454,9 @@ def _augment_compile_flags(flags: list[str]) -> None:
452454
if _is_truthy_env("PCRE2_DISABLE_OPT_FLAGS"):
453455
return
454456

457+
if _is_windows_platform():
458+
return
459+
455460
disable_native = _is_truthy_env("PCRE2_DISABLE_NATIVE_FLAGS")
456461
compiler = _get_test_compiler()
457462
if not disable_native and _should_disable_native_flags_for_macos(compiler):
@@ -789,7 +794,7 @@ def collect_build_config() -> dict[str, list[str] | list[tuple[str, str | None]]
789794
if env_ldflags:
790795
extra_link_args.extend(shlex.split(env_ldflags))
791796

792-
if not any(flag.startswith("-std=") for flag in extra_compile_args):
797+
if not _is_windows_platform() and not any(flag.startswith("-std=") for flag in extra_compile_args):
793798
extra_compile_args.append("-std=c99")
794799

795800
if not _has_header(include_dirs):
@@ -798,7 +803,15 @@ def collect_build_config() -> dict[str, list[str] | list[tuple[str, str | None]]
798803
if not _has_library(library_dirs):
799804
library_dirs.extend(_discover_library_dirs())
800805

806+
global RUNTIME_LIBRARY_FILES
807+
runtime_libraries: list[str] = []
808+
801809
if library_files:
810+
for runtime_path in library_files:
811+
lower_name = runtime_path.lower()
812+
if lower_name.endswith(".dll") or lower_name.endswith(".dylib") or ".so" in Path(runtime_path).name:
813+
_extend_unique(runtime_libraries, runtime_path)
814+
802815
linkable_files: list[str] = []
803816
for path in library_files:
804817
suffix = Path(path).suffix.lower()
@@ -821,8 +834,18 @@ def collect_build_config() -> dict[str, list[str] | list[tuple[str, str | None]]
821834
if sys.platform.startswith("linux") and "dl" not in libraries:
822835
libraries.append("dl")
823836

837+
if _is_windows_platform():
838+
has_runtime_dll = any(path.lower().endswith(".dll") for path in runtime_libraries)
839+
force_static_env = _is_truthy_env("PCRE2_FORCE_STATIC")
840+
if (force_static_env or (library_files and not has_runtime_dll)) and not any(
841+
macro[0] == "PCRE2_STATIC" for macro in define_macros
842+
):
843+
define_macros.append(("PCRE2_STATIC", "1"))
844+
824845
_augment_compile_flags(extra_compile_args)
825846

847+
RUNTIME_LIBRARY_FILES = runtime_libraries
848+
826849
return {
827850
"include_dirs": include_dirs,
828851
"library_dirs": library_dirs,

tests/test_api_parity.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
from enum import IntFlag
88

99
import pcre
10+
import pcre_ext_c
1011
import pytest
1112
from pcre import Flag
1213

1314

14-
BACKEND = getattr(pcre, "cpcre2", getattr(pcre, "_pcre2", None))
15+
BACKEND = pcre_ext_c
1516

1617

1718
def test_purge_aliases_clear_cache():

0 commit comments

Comments
 (0)