Skip to content

Commit 8520a2f

Browse files
add python bindings with cython and pypi packaging support (#27)
* add python bindings with cython and pypi packaging support * add php extension with pecl packaging support (#26) * add php extension with pecl packaging support * fixing php binding test ci * add python bindings with cython and pypi packaging support * Fix Python bindings Cython import issue Remove incorrect self-import in _liblpm.pyx that was causing compilation failure. Cython automatically imports the corresponding .pxd file, so explicit cimport _liblpm was creating a circular dependency. All references updated to use types/functions directly. * adapting to ts and dl versions of libdynemit to run the python binding
1 parent 1a69093 commit 8520a2f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5183
-187
lines changed

.github/workflows/ci.yml

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -115,34 +115,61 @@ jobs:
115115
run: |
116116
docker run --rm liblpm-go:ci
117117
118-
# PHP bindings test
119-
test-php-bindings:
120-
name: Test PHP Bindings
118+
# Python bindings test
119+
test-python-bindings:
120+
name: Test Python Bindings
121121
runs-on: ubuntu-latest
122+
strategy:
123+
fail-fast: false
124+
matrix:
125+
python-version: ['3.10', '3.11', '3.12']
122126

123127
steps:
124128
- name: Checkout code
125129
uses: actions/checkout@v6
126130
with:
127131
submodules: recursive
128132

129-
- name: Set up Docker Buildx
130-
uses: docker/setup-buildx-action@v3
131-
132-
- name: Build PHP container
133-
uses: docker/build-push-action@v6
133+
- name: Set up Python ${{ matrix.python-version }}
134+
uses: actions/setup-python@v5
134135
with:
135-
context: .
136-
file: docker/Dockerfile.php
137-
push: false
138-
load: true
139-
tags: liblpm-php:ci
140-
cache-from: type=gha
141-
cache-to: type=gha,mode=max
136+
python-version: ${{ matrix.python-version }}
142137

143-
- name: Run PHP tests
138+
- name: Install dependencies
139+
run: |
140+
sudo apt-get update
141+
sudo apt-get install -y build-essential cmake
142+
python -m pip install --upgrade pip
143+
python -m pip install cython pytest pytest-cov scikit-build-core
144+
145+
- name: Build liblpm
146+
run: |
147+
mkdir -p build && cd build
148+
cmake .. -DCMAKE_BUILD_TYPE=Release -DLPM_TS_RESOLVERS=ON
149+
make -j$(nproc)
150+
sudo make install
151+
sudo ldconfig
152+
153+
- name: Build Python bindings
154+
working-directory: bindings/python
144155
run: |
145-
docker run --rm liblpm-php:ci
156+
pip install -e ".[dev]"
157+
158+
- name: Run Python tests
159+
working-directory: bindings/python
160+
env:
161+
# Workaround for IFUNC/dlopen incompatibility
162+
LD_PRELOAD: /usr/local/lib/liblpm.so.1
163+
run: |
164+
pytest tests/ -v --cov=liblpm --cov-report=xml
165+
166+
- name: Upload coverage
167+
if: matrix.python-version == '3.11'
168+
uses: codecov/codecov-action@v4
169+
with:
170+
file: bindings/python/coverage.xml
171+
flags: python
172+
fail_ci_if_error: false
146173

147174
# Code quality checks
148175
code-quality:
@@ -196,7 +223,7 @@ jobs:
196223
ci-summary:
197224
name: CI Summary
198225
runs-on: ubuntu-latest
199-
needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-php-bindings, code-quality]
226+
needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-python-bindings, code-quality]
200227
if: always()
201228

202229
steps:
@@ -206,14 +233,14 @@ jobs:
206233
echo "Build and test: ${{ needs.build-and-test.result }}"
207234
echo "C++ bindings: ${{ needs.test-cpp-bindings.result }}"
208235
echo "Go bindings: ${{ needs.test-go-bindings.result }}"
209-
echo "PHP bindings: ${{ needs.test-php-bindings.result }}"
236+
echo "Python bindings: ${{ needs.test-python-bindings.result }}"
210237
echo "Code quality: ${{ needs.code-quality.result }}"
211238
212239
# Fail if any required job failed
213240
if [[ "${{ needs.build-and-test.result }}" == "failure" ]] || \
214241
[[ "${{ needs.test-cpp-bindings.result }}" == "failure" ]] || \
215242
[[ "${{ needs.test-go-bindings.result }}" == "failure" ]] || \
216-
[[ "${{ needs.test-php-bindings.result }}" == "failure" ]]; then
243+
[[ "${{ needs.test-python-bindings.result }}" == "failure" ]]; then
217244
echo "One or more required jobs failed"
218245
exit 1
219246
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ build_clang/
6666
build_gcc/
6767
build_*/
6868
build_afl
69+
build-*
6970
run_fuzz.sh
7071
fuzz_input
7172
fuzz_output

CMakeLists.txt

Lines changed: 34 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,22 @@ endif()
5757
option(BUILD_TESTS "Build test programs" ON)
5858
option(BUILD_BENCHMARKS "Build benchmark programs" OFF)
5959
option(ENABLE_NATIVE_ARCH "Enable native architecture optimizations" OFF)
60-
option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark (requires DPDK)" OFF)
61-
option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries (Docker only)" OFF)
60+
option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark" OFF)
61+
option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries" OFF)
6262
option(BUILD_GO_WRAPPER "Build Go wrapper and bindings" OFF)
6363
option(BUILD_CPP_WRAPPER "Build C++ wrapper and bindings" OFF)
64-
option(BUILD_PHP_WRAPPER "Build PHP extension and bindings" OFF)
64+
option(BUILD_PYTHON_WRAPPER "Build Python wrapper and bindings" OFF)
65+
option(LPM_TS_RESOLVERS "Enable thread-safe resolvers (for dlopen contexts)" OFF)
6566

66-
# External LPM libraries directory (set by Docker builds)
67+
# Thread-safe resolver configuration
68+
if(LPM_TS_RESOLVERS)
69+
add_compile_definitions(LPM_TS_RESOLVERS=1)
70+
message(STATUS "Thread-safe resolvers: ENABLED (for dlopen contexts)")
71+
else()
72+
message(STATUS "Thread-safe resolvers: DISABLED (standard C library)")
73+
endif()
74+
75+
# External LPM libraries directory
6776
set(EXTERNAL_LPM_DIR "" CACHE PATH "Directory containing external LPM libraries")
6877

6978
# Check for external LPM libraries if requested
@@ -316,6 +325,24 @@ if(BUILD_CPP_WRAPPER)
316325
add_subdirectory(bindings/cpp)
317326
endif()
318327

328+
# Python wrapper
329+
if(BUILD_PYTHON_WRAPPER)
330+
find_package(Python COMPONENTS Interpreter Development.Module)
331+
find_program(CYTHON_EXECUTABLE NAMES cython cython3)
332+
if(Python_FOUND AND CYTHON_EXECUTABLE)
333+
message(STATUS "Found Python: ${Python_EXECUTABLE} (${Python_VERSION})")
334+
message(STATUS "Found Cython: ${CYTHON_EXECUTABLE}")
335+
add_subdirectory(bindings/python)
336+
else()
337+
if(NOT Python_FOUND)
338+
message(WARNING "Python not found, skipping Python bindings")
339+
endif()
340+
if(NOT CYTHON_EXECUTABLE)
341+
message(WARNING "Cython not found, skipping Python bindings. Install with: pip install cython")
342+
endif()
343+
endif()
344+
endif()
345+
319346
# Package configuration for find_package(liblpm)
320347
include(CMakePackageConfigHelpers)
321348

@@ -391,88 +418,6 @@ if(BUILD_GO_WRAPPER)
391418
endif()
392419
endif()
393420

394-
# PHP extension
395-
if(BUILD_PHP_WRAPPER)
396-
find_program(PHP_CONFIG_EXECUTABLE NAMES php-config php-config8.3 php-config8.2 php-config8.1)
397-
if(PHP_CONFIG_EXECUTABLE)
398-
# Get PHP version
399-
execute_process(
400-
COMMAND ${PHP_CONFIG_EXECUTABLE} --version
401-
OUTPUT_VARIABLE PHP_VERSION
402-
OUTPUT_STRIP_TRAILING_WHITESPACE
403-
)
404-
message(STATUS "Found PHP: ${PHP_VERSION}")
405-
406-
# Get PHP include path
407-
execute_process(
408-
COMMAND ${PHP_CONFIG_EXECUTABLE} --includes
409-
OUTPUT_VARIABLE PHP_INCLUDES
410-
OUTPUT_STRIP_TRAILING_WHITESPACE
411-
)
412-
413-
# Get PHP extension directory
414-
execute_process(
415-
COMMAND ${PHP_CONFIG_EXECUTABLE} --extension-dir
416-
OUTPUT_VARIABLE PHP_EXTENSION_DIR
417-
OUTPUT_STRIP_TRAILING_WHITESPACE
418-
)
419-
420-
# Get PHP binary
421-
execute_process(
422-
COMMAND ${PHP_CONFIG_EXECUTABLE} --php-binary
423-
OUTPUT_VARIABLE PHP_BINARY
424-
OUTPUT_STRIP_TRAILING_WHITESPACE
425-
)
426-
427-
message(STATUS " PHP binary: ${PHP_BINARY}")
428-
message(STATUS " PHP extension dir: ${PHP_EXTENSION_DIR}")
429-
430-
# Custom target to configure PHP extension
431-
add_custom_target(php_configure
432-
COMMAND phpize
433-
COMMAND ${CMAKE_COMMAND} -E env "LDFLAGS=-L${CMAKE_BINARY_DIR}"
434-
./configure --with-liblpm=${CMAKE_SOURCE_DIR}
435-
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/php
436-
DEPENDS lpm
437-
COMMENT "Configuring PHP extension"
438-
)
439-
440-
# Custom target to build PHP extension
441-
add_custom_target(php_wrapper ALL
442-
COMMAND make
443-
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/php
444-
DEPENDS php_configure
445-
COMMENT "Building PHP extension"
446-
)
447-
448-
# Custom target to test PHP extension
449-
add_custom_target(php_test
450-
COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}:$ENV{LD_LIBRARY_PATH}"
451-
${PHP_BINARY} run-tests.php -q tests/
452-
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/php
453-
DEPENDS php_wrapper
454-
COMMENT "Testing PHP extension"
455-
)
456-
457-
# Custom target to install PHP extension
458-
add_custom_target(php_install
459-
COMMAND ${CMAKE_COMMAND} -E copy_if_different
460-
${CMAKE_CURRENT_SOURCE_DIR}/bindings/php/modules/liblpm.so
461-
${PHP_EXTENSION_DIR}/liblpm.so
462-
DEPENDS php_wrapper
463-
COMMENT "Installing PHP extension to ${PHP_EXTENSION_DIR}"
464-
)
465-
466-
message(STATUS "PHP extension targets added:")
467-
message(STATUS " make php_wrapper - Build PHP extension")
468-
message(STATUS " make php_test - Run PHP tests")
469-
message(STATUS " make php_install - Install PHP extension")
470-
else()
471-
message(WARNING "php-config not found. PHP extension will not be built.")
472-
message(WARNING "Install PHP development files: sudo apt install php-dev")
473-
endif()
474-
endif()
475-
476421
# Print configuration summary
477422
message(STATUS "liblpm configuration:")
478423
message(STATUS " Version: ${PROJECT_VERSION}")
@@ -518,10 +463,10 @@ if(BUILD_CPP_WRAPPER)
518463
else()
519464
message(STATUS " Build C++ wrapper: OFF")
520465
endif()
521-
if(BUILD_PHP_WRAPPER AND PHP_CONFIG_EXECUTABLE)
522-
message(STATUS " Build PHP extension: ON")
466+
if(BUILD_PYTHON_WRAPPER AND Python_FOUND AND CYTHON_EXECUTABLE)
467+
message(STATUS " Build Python wrapper: ON")
523468
else()
524-
message(STATUS " Build PHP extension: OFF")
469+
message(STATUS " Build Python wrapper: OFF")
525470
endif()
526471

527472
# ============================================================================

TODO.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,45 @@ Comprehensive man pages for the library API, accessible via `man liblpm`.
254254

255255
**Location:** `docs/man/man3/` (English), `docs/man/<lang>/man3/` (translations)
256256

257+
## Thread-Safe Resolver Improvements
258+
259+
**Status:** Implemented (January 2026)
260+
261+
**Description:**
262+
Optional thread-safe resolver support for dlopen() contexts via CMake configuration.
263+
264+
**Implemented:**
265+
- [x] CMake option `LPM_TS_RESOLVERS` (default OFF)
266+
- [x] Conditional compilation macro `LPM_DETECT_SIMD()`
267+
- [x] Automatic enablement for Python bindings
268+
- [x] Documentation in README and Python IFUNC_WORKAROUND.md
269+
270+
**Future Enhancements:**
271+
- [ ] Performance benchmarks comparing TS vs non-TS overhead
272+
- [ ] Thread-safety stress tests for concurrent initialization
273+
- [ ] Testing with different dynamic loaders (musl, bionic)
274+
- [ ] Investigate IFUNC alternatives for better portability
275+
- [ ] Go bindings: evaluate if thread-safe mode needed
276+
- [ ] C++ bindings: evaluate if thread-safe mode needed
277+
- [ ] Add runtime detection of dlopen() context (if feasible)
278+
279+
**Testing Needs:**
280+
- [ ] Concurrent initialization stress test (pthread_create during IFUNC resolve)
281+
- [ ] Memory ordering verification for atomic caching
282+
- [ ] Python multiprocessing edge cases
283+
- [ ] Plugin loader compatibility tests
284+
- [ ] Valgrind/Helgrind analysis of atomic operations
285+
286+
**Documentation:**
287+
- [ ] Add architecture diagram showing resolver flow in both modes
288+
- [ ] Performance comparison table (TS vs non-TS)
289+
- [ ] Best practices guide for binding authors
290+
291+
**Benefits:**
292+
- Zero overhead for regular C programs (default mode)
293+
- Safe for Python and other dlopen() contexts (opt-in)
294+
- Flexible configuration per binding type
295+
257296
## Related Documentation
258297

259298
- [docs/DOCKER.md](docs/DOCKER.md) - Container usage guide

0 commit comments

Comments
 (0)