Skip to content
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
33 changes: 32 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,35 @@ jobs:
run: |
docker run --rm liblpm-go:ci

# Lua bindings test
test-lua-bindings:
name: Test Lua Bindings
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
submodules: recursive

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Lua container
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile.lua
push: false
load: true
tags: liblpm-lua:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Lua tests
run: |
docker run --rm liblpm-lua:ci

# Perl bindings test
test-perl-bindings:
name: Test Perl Bindings
Expand Down Expand Up @@ -281,7 +310,7 @@ jobs:
ci-summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-perl-bindings, test-php-bindings, test-python-bindings, code-quality]
needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-lua-bindings, test-perl-bindings, test-php-bindings, test-python-bindings, code-quality]
if: always()

steps:
Expand All @@ -291,6 +320,7 @@ jobs:
echo "Build and test: ${{ needs.build-and-test.result }}"
echo "C++ bindings: ${{ needs.test-cpp-bindings.result }}"
echo "Go bindings: ${{ needs.test-go-bindings.result }}"
echo "Lua bindings: ${{ needs.test-lua-bindings.result }}"
echo "Perl bindings: ${{ needs.test-perl-bindings.result }}"
echo "PHP bindings: ${{ needs.test-php-bindings.result }}"
echo "Python bindings: ${{ needs.test-python-bindings.result }}"
Expand All @@ -300,6 +330,7 @@ jobs:
if [[ "${{ needs.build-and-test.result }}" == "failure" ]] || \
[[ "${{ needs.test-cpp-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-go-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-lua-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-perl-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-php-bindings.result }}" == "failure" ]] || \
[[ "${{ needs.test-python-bindings.result }}" == "failure" ]]; then
Expand Down
29 changes: 29 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark" OFF)
option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries" OFF)
option(BUILD_GO_WRAPPER "Build Go wrapper and bindings" OFF)
option(BUILD_CPP_WRAPPER "Build C++ wrapper and bindings" OFF)
option(BUILD_LUA_WRAPPER "Build Lua wrapper and bindings" OFF)
option(BUILD_PERL_WRAPPER "Build Perl wrapper and bindings" OFF)
option(BUILD_PHP_WRAPPER "Build PHP wrapper and bindings" OFF)
option(BUILD_PYTHON_WRAPPER "Build Python wrapper and bindings" OFF)
Expand Down Expand Up @@ -420,6 +421,11 @@ if(BUILD_GO_WRAPPER)
endif()
endif()

# Lua wrapper
if(BUILD_LUA_WRAPPER)
add_subdirectory(bindings/lua)
endif()

# Perl wrapper
if(BUILD_PERL_WRAPPER)
find_program(PERL_EXECUTABLE perl)
Expand Down Expand Up @@ -507,6 +513,24 @@ if(BUILD_PHP_WRAPPER)
endif()
endif()

# Python wrapper
if(BUILD_PYTHON_WRAPPER)
find_package(Python COMPONENTS Interpreter Development.Module)
find_program(CYTHON_EXECUTABLE NAMES cython cython3)
if(Python_FOUND AND CYTHON_EXECUTABLE)
message(STATUS "Found Python: ${Python_EXECUTABLE} (${Python_VERSION})")
message(STATUS "Found Cython: ${CYTHON_EXECUTABLE}")
add_subdirectory(bindings/python)
else()
if(NOT Python_FOUND)
message(WARNING "Python not found, skipping Python bindings")
endif()
if(NOT CYTHON_EXECUTABLE)
message(WARNING "Cython not found, skipping Python bindings. Install with: pip install cython")
endif()
endif()
endif()

# Print configuration summary
message(STATUS "liblpm configuration:")
message(STATUS " Version: ${PROJECT_VERSION}")
Expand Down Expand Up @@ -552,6 +576,11 @@ if(BUILD_CPP_WRAPPER)
else()
message(STATUS " Build C++ wrapper: OFF")
endif()
if(BUILD_LUA_WRAPPER)
message(STATUS " Build Lua wrapper: ON")
else()
message(STATUS " Build Lua wrapper: OFF")
endif()
if(BUILD_PERL_WRAPPER AND PERL_EXECUTABLE)
message(STATUS " Build Perl wrapper: ON")
else()
Expand Down
208 changes: 208 additions & 0 deletions bindings/lua/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# ============================================================================
# CMakeLists.txt for liblpm Lua bindings
# ============================================================================
#
# Supports:
# - Lua 5.3, 5.4, and LuaJIT 2.1+
# - CMake 3.16+
#
# Build options:
# cmake -DBUILD_LUA_WRAPPER=ON ..
#
# Targets:
# lpm_lua - Lua C module (shared library)
# lua_test - Run Lua tests
# lua_example - Run basic example
#
# ============================================================================

cmake_minimum_required(VERSION 3.16)

# ============================================================================
# Find Lua
# ============================================================================

# Try to find Lua using CMake's FindLua module
# This will search for Lua 5.4, 5.3, 5.2, 5.1 in order
find_package(Lua)

if(NOT LUA_FOUND)
# Try pkg-config as fallback
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
# Try specific versions
pkg_check_modules(LUA QUIET lua5.4)
if(NOT LUA_FOUND)
pkg_check_modules(LUA QUIET lua5.3)
endif()
if(NOT LUA_FOUND)
pkg_check_modules(LUA QUIET luajit)
endif()
if(NOT LUA_FOUND)
pkg_check_modules(LUA QUIET lua)
endif()
endif()
endif()

if(NOT LUA_FOUND)
message(WARNING "Lua not found. Lua bindings will not be built.")
message(WARNING "Install Lua development files:")
message(WARNING " Ubuntu/Debian: sudo apt install liblua5.4-dev")
message(WARNING " Fedora: sudo dnf install lua-devel")
message(WARNING " macOS: brew install lua")
return()
endif()

# Check Lua version (require 5.3+)
if(LUA_VERSION_STRING VERSION_LESS "5.3")
message(WARNING "Lua ${LUA_VERSION_STRING} found, but 5.3+ is required.")
message(WARNING "Lua bindings will not be built.")
return()
endif()

message(STATUS "Found Lua: ${LUA_VERSION_STRING}")
message(STATUS " Include: ${LUA_INCLUDE_DIR}")
message(STATUS " Library: ${LUA_LIBRARIES}")

# ============================================================================
# Lua C Module
# ============================================================================

# Source files
set(LPM_LUA_SOURCES
src/liblpm.c
src/liblpm_utils.c
)

# Create shared library module
add_library(lpm_lua MODULE ${LPM_LUA_SOURCES})

# Set output name to match Lua's require() expectations
# The module will be named "liblpm.so" (or .dll on Windows)
set_target_properties(lpm_lua PROPERTIES
OUTPUT_NAME "liblpm"
PREFIX ""
SUFFIX ".so"
)

# Platform-specific suffix
if(APPLE)
set_target_properties(lpm_lua PROPERTIES SUFFIX ".so")
elseif(WIN32)
set_target_properties(lpm_lua PROPERTIES SUFFIX ".dll")
endif()

# Include directories
target_include_directories(lpm_lua PRIVATE
${LUA_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/include
)

# Link against liblpm shared library
# Note: The liblpm.so must be loadable (in LD_LIBRARY_PATH or installed)
# before the Lua module is loaded, due to ifunc resolver requirements
target_link_libraries(lpm_lua PRIVATE
lpm
${LUA_LIBRARIES}
)

# Set RPATH to find liblpm.so in the build directory and install location
set_target_properties(lpm_lua PROPERTIES
BUILD_RPATH "${CMAKE_BINARY_DIR}"
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"
)

# C standard
set_target_properties(lpm_lua PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
)

# Compiler flags
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(lpm_lua PRIVATE
-Wall -Wextra -Wpedantic
-Wno-unused-parameter
)
endif()

# ============================================================================
# Installation
# ============================================================================

# Determine Lua install path
if(DEFINED LUA_CPATH)
set(LUA_INSTALL_CPATH ${LUA_CPATH})
else()
# Default to standard Lua cpath
# This can be overridden with -DLUA_CPATH=/path/to/lua/cpath
set(LUA_INSTALL_CPATH "${CMAKE_INSTALL_LIBDIR}/lua/${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}")
endif()

install(TARGETS lpm_lua
LIBRARY DESTINATION ${LUA_INSTALL_CPATH}
RUNTIME DESTINATION ${LUA_INSTALL_CPATH}
)

# ============================================================================
# Test Target
# ============================================================================

# Find Lua interpreter
find_program(LUA_EXECUTABLE
NAMES lua${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR} lua
HINTS ${LUA_INCLUDE_DIR}/../bin
)

if(LUA_EXECUTABLE)
message(STATUS "Lua interpreter: ${LUA_EXECUTABLE}")

# Test target
# Note: LD_PRELOAD is required to ensure liblpm.so's ifunc resolvers are
# executed before the Lua module is loaded via dlopen
add_custom_target(lua_test
COMMAND ${CMAKE_COMMAND} -E env
"LD_PRELOAD=${CMAKE_BINARY_DIR}/liblpm.so"
"LUA_CPATH=${CMAKE_CURRENT_BINARY_DIR}/?.so"
${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_lpm.lua
DEPENDS lpm_lua lpm
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Lua tests"
)

# Example target
add_custom_target(lua_example
COMMAND ${CMAKE_COMMAND} -E env
"LD_PRELOAD=${CMAKE_BINARY_DIR}/liblpm.so"
"LUA_CPATH=${CMAKE_CURRENT_BINARY_DIR}/?.so"
${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/examples/basic_example.lua
DEPENDS lpm_lua lpm
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Lua basic example"
)

# Add to CTest
if(BUILD_TESTS)
add_test(NAME lua_wrapper_test
COMMAND ${CMAKE_COMMAND} -E env
"LD_PRELOAD=${CMAKE_BINARY_DIR}/liblpm.so"
"LUA_CPATH=${CMAKE_CURRENT_BINARY_DIR}/?.so"
${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_lpm.lua
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endif()
else()
message(WARNING "Lua interpreter not found. Test targets will not be available.")
endif()

# ============================================================================
# Summary
# ============================================================================

message(STATUS "Lua bindings configuration:")
message(STATUS " Lua version: ${LUA_VERSION_STRING}")
message(STATUS " Install path: ${LUA_INSTALL_CPATH}")
if(LUA_EXECUTABLE)
message(STATUS " Test command: make lua_test")
message(STATUS " Example command: make lua_example")
endif()
Loading
Loading