diff --git a/.github/workflows/ci.yaml b/.github/workflows/library.yaml similarity index 93% rename from .github/workflows/ci.yaml rename to .github/workflows/library.yaml index ea97eac..182873b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/library.yaml @@ -1,4 +1,4 @@ -name: CI +name: Library on: pull_request: @@ -13,13 +13,13 @@ env: jobs: build: - name: Build library + name: Build runs-on: ubuntu-latest strategy: matrix: cc: [gcc, clang] container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: @@ -27,7 +27,8 @@ jobs: password: ${{ secrets.DOCKERHUB_PASS }} steps: - uses: actions/checkout@v2 - - name: Build library + + - name: Build run: | cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -S . -B build cmake --build build @@ -36,7 +37,7 @@ jobs: name: Build samples runs-on: ubuntu-latest container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: @@ -44,6 +45,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PASS }} steps: - uses: actions/checkout@v2 + - name: Build samples run: | cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SAMPLES=ON -S . -B build @@ -54,7 +56,7 @@ jobs: runs-on: ubuntu-latest needs: build container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: @@ -92,7 +94,7 @@ jobs: runs-on: ubuntu-latest needs: unit_tests container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: @@ -113,7 +115,7 @@ jobs: name: Check documentation runs-on: ubuntu-latest container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: @@ -121,6 +123,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PASS }} steps: - uses: actions/checkout@v2 + - name: Build library documentation run: | cmake -DBUILD_DOCUMENTATION=ON -S . -B build @@ -131,7 +134,7 @@ jobs: runs-on: ubuntu-latest needs: unit_tests container: - image: andrelcmoreira/pool-day:v2 + image: andrelcmoreira/pool-day:v3 volumes: - ${{ github.workspace }}:/pool-day credentials: diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml new file mode 100644 index 0000000..7e97bbb --- /dev/null +++ b/.github/workflows/python.yaml @@ -0,0 +1,45 @@ +name: Python bindings + +on: + pull_request: + push: + branches: + - develop + +env: + PYLINT_MINIMUM_RATE: 9 + +jobs: + build: + name: Build python package + runs-on: ubuntu-latest + container: + image: andrelcmoreira/pool-day:v3 + volumes: + - ${{ github.workspace }}:/pool-day + credentials: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PASS }} + steps: + - uses: actions/checkout@v2 + + - name: Build package + run: | + cd python + pip install . --break-system-packages + + lint: + name: Lint the python bindings + runs-on: ubuntu-latest + container: + image: andrelcmoreira/pool-day:v3 + volumes: + - ${{ github.workspace }}:/pool-day + credentials: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PASS }} + steps: + - uses: actions/checkout@v2 + + - name: Run pylint + run: pylint --rcfile python/pylintrc python/pool_day --fail-under ${{ env.PYLINT_MINIMUM_RATE }} diff --git a/CMakeLists.txt b/CMakeLists.txt index fa10940..29fc9b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ option(BUILD_UNIT_TESTS "build the unit tests of the library" OFF) option(BUILD_DOCUMENTATION "build the documentation of the library" OFF) option(BUILD_COVERAGE "build the coverage data" OFF) option(ENABLE_LOGGING "enable library logs" OFF) +option(BUILD_PYTHON_BINDINGS "build the library's python bindings" OFF) set(PROJECT_NAME "pool-day") set(SOURCES src/task.c src/queue.c src/pool_day.c) @@ -45,6 +46,10 @@ if(ENABLE_LOGGING) list(APPEND SOURCES "src/logger.c") endif(ENABLE_LOGGING) +if(BUILD_PYTHON_BINDINGS) + add_subdirectory(python) +endif(BUILD_PYTHON_BINDINGS) + add_library(${PROJECT_NAME} SHARED ${SOURCES}) target_link_libraries(${PROJECT_NAME} pthread) diff --git a/Dockerfile b/Dockerfile index 33648c2..6f3d426 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt install cmake gcc g++ clang -y # building tools RUN apt install doxygen python3 python3-pip -y # utils RUN apt install cppcheck gcovr valgrind -y # qa tools RUN apt install libgmock-dev libgtest-dev -y # frameworks -RUN pip3 install --break-system-packages cpplint # python packages +RUN pip3 install --break-system-packages cpplint pylint # python packages WORKDIR /pool-day diff --git a/README.md b/README.md index 98f0d0d..dabc7d1 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,11 @@ $ sudo cmake --install build Additional flags can be supplied as parameter to cmake according to the table below: -| Flag | Description | -|----------------|--------------------------------------| -| BUILD_SAMPLES | Build the library's samples | -| ENABLE_LOGGING | Enable the library's logging feature | +| Flag | Description | +|---------------------|----------------------------------------| +| BUILD_SAMPLES | Build the library's samples | +| ENABLE_LOGGING | Enable the library's logging feature | +|BUILD_PYTHON_BINDINGS| Build the library's python bindings | #### Support diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..c128320 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,4 @@ +#cmake_minimum_required(VERSION 3.18) +# +#find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +# TODO diff --git a/python/pool_day/__init__.py b/python/pool_day/__init__.py new file mode 100644 index 0000000..36ba6c3 --- /dev/null +++ b/python/pool_day/__init__.py @@ -0,0 +1,6 @@ +from pool_day.c_defs import pool_day_callback +from .pool_day import create_pool, create_task, PoolDay, TaskResult + + +__all__ = ['PoolDay', 'TaskResult', 'create_pool', 'create_task', + 'pool_day_callback'] diff --git a/python/pool_day/c_defs.py b/python/pool_day/c_defs.py new file mode 100644 index 0000000..50930e9 --- /dev/null +++ b/python/pool_day/c_defs.py @@ -0,0 +1,86 @@ +from ctypes import CDLL as cdll +from ctypes import POINTER as c_pointer +from ctypes import CFUNCTYPE as c_func_type +from ctypes import cast +from ctypes import byref +from ctypes import ( + Structure, + c_int, + c_uint32, + c_void_p, + c_int, + c_size_t, +) +from dataclasses import dataclass +from typing import Any + + +class CPoolDay(Structure): + pass + + +class CTask(Structure): + pass + + +@dataclass +class TaskResult: + result: Any + + +def pool_day_callback(cb): + + @c_func_type(c_void_p, c_void_p) + def _cb(param): + ret = cb(param) + + print('pool_day_callback ret:', ret) + print('pool_day_callback ret type:', type(ret)) + print('pool_day_callback param value:', param) + print('pool_day_callback param type:', type(param)) + + return ret + + return _cb + + +def to_result(ptr: c_void_p) -> TaskResult: + return TaskResult(result=ptr) + + +_pd_handle = cdll('/usr/lib/libpool-day.so') + +# create_pool +_pd_handle.create_pool.argtypes = [c_uint32] +_pd_handle.create_pool.restype = c_pointer(CPoolDay) + +# destroy_pool +_pd_handle.destroy_pool.argtypes = [c_void_p] +_pd_handle.destroy_pool.restype = c_int + +# abort_tasks +_pd_handle.abort_tasks.argtypes = [c_pointer(CPoolDay)] +_pd_handle.abort_tasks.restype = c_int + +# enqueue_task +_pd_handle.enqueue_task.argtypes = [c_pointer(CPoolDay), c_pointer(CTask)] +_pd_handle.enqueue_task.restype = c_int + +# queued_tasks +_pd_handle.queued_tasks.argtypes = [c_pointer(CPoolDay)] +_pd_handle.queued_tasks.restype = c_uint32 + +# create_sync_task +_pd_handle.create_sync_task.argtypes = [c_uint32, c_void_p, c_void_p, c_size_t] +_pd_handle.create_sync_task.restype = c_pointer(CTask) + +# create_async_task +_pd_handle.create_async_task.argtypes = [c_uint32, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p] +_pd_handle.create_async_task.restype = c_pointer(CTask) + +# wait_task_finish +_pd_handle.wait_task_finish.argtypes = [c_pointer(CTask)] +_pd_handle.wait_task_finish.restype = c_void_p + +# get_task_result +_pd_handle.get_task_result.argtypes = [c_pointer(CTask)] diff --git a/python/pool_day/pool_day.py b/python/pool_day/pool_day.py new file mode 100644 index 0000000..ce2fb49 --- /dev/null +++ b/python/pool_day/pool_day.py @@ -0,0 +1,99 @@ +from contextlib import contextmanager +from ctypes import byref + +from pool_day.c_defs import _pd_handle, CTask, CPoolDay, TaskResult, to_result + + +# TODO: task return + + +class PoolDay: + + """ + PoolDay instance. + + Implements a wrapper around the pool-day C library. + """ + + def __init__(self, pool: CPoolDay): + """ + Initialize the PoolDay instance. + + :pool: The pool structure instance. + """ + self._pool = pool + + def enqueue_task(self, task: CTask) -> int: + """ + Enqueue a task to the pool. + + :task: The task instance. + :return: 0 on success, the suitable error code on failure. + """ + return _pd_handle.enqueue_task(self._pool, task) + + def abort_tasks(self) -> int: + """ + Abort all pending tasks in the pool. + + :return: 0 on success, the suitable error code on failure. + """ + return _pd_handle.abort_tasks(self._pool) + + def queued_tasks(self) -> int: + """ + Get the number of queued tasks in the pool. + + :return: Number of queued tasks. + """ + return _pd_handle.queued_tasks(self._pool) + + def wait_task_finish(self, task: CTask) -> None: + """ + Wait for a task to finish. + + :task: The task instance. + """ + _pd_handle.wait_task_finish(self._pool, task) + + def get_task_result(self, task: CTask) -> TaskResult: + """ + Get a task result. + + :task: The task instance. + :return: The return value of the task's callback function. + """ + ret = _pd_handle.get_task_result(self._pool, task) + + print(f'get_task_result ret: {ret}') + + return to_result(ret) + + +def create_sync_task(cb, param) -> CTask: + """ + Create a new sync task instance. + + :cb: The callback function. + :param: The parameter passed to the callback function. + + :return: A new task instance. + """ + return _pd_handle.create_sync_task(cb, param) + + +# TODO: create_async_task + + +@contextmanager +def create_pool(size: int): + """ + Create a new pool instance. + + :size: Number of threads in the pool. + + :return: A pool-day instance. + """ + pool = _pd_handle.create_pool(size) + yield PoolDay(pool) + _pd_handle.destroy_pool(byref(pool)) diff --git a/python/pylintrc b/python/pylintrc new file mode 100644 index 0000000..0a644ec --- /dev/null +++ b/python/pylintrc @@ -0,0 +1,2 @@ +[MESSAGES CONTROL] +disable=too-few-public-methods diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..9ae22c1 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling >= 1.26"] +build-backend = "hatchling.build" + +[project] +name = "pool-day" +version = "0.1.1" +requires-python = ">=3.7" +authors = [{ name = "André L. C. Moreira", email = "andrelcmoreira@proton.me" }] +description = "Python bindings to pool-day library" +license = "LGPL-3.0-only" +license-files = ["LICEN[CS]E.*"] +readme = { file = "../README.md", content-type = "text/markdown" } + +[project.urls] +Repository = "https://github.com/andrelcmoreira/pool-day.git" + +# TODO: check dependencies for libpoolday.so diff --git a/samples/python/sync-sample.py b/samples/python/sync-sample.py new file mode 100644 index 0000000..ff57a74 --- /dev/null +++ b/samples/python/sync-sample.py @@ -0,0 +1,34 @@ +from time import sleep + +from pool_day import create_pool, create_sync_task, pool_day_callback + + +@pool_day_callback +def thread_cb(param): + for i in range(1, 10): + print(f'thread {param}: hello from python callback, i: {i}') + sleep(param) + + #return f'task {param} done' + return param + + +def main(): + with create_pool(2) as pool: + t1 = create_sync_task(thread_cb, 1) + t2 = create_sync_task(thread_cb, 2) + t3 = create_sync_task(thread_cb, 3) + + print('task 1 enqueued, ret =', pool.enqueue_task(t1)) + print('task 2 enqueued, ret =', pool.enqueue_task(t2)) + print('task 3 enqueued, ret =', pool.enqueue_task(t3)) + print('number of queued tasks =', pool.queued_tasks()) + + ret = pool.get_task_result(t3) + + print(type(ret)) + print(ret) + + +if __name__ == "__main__": + main()