ããã¸ã§ã¯ããCã½ã¼ã¹ã³ã¼ããPythonããã±ã¼ã¸ã§ä½¿ãã
- 0. æ¦è¦
- 1. C language project å®è£
- 2. CMake å®è£
- 3. Python ç¨ C è¨èª Wrapper å®è£
- 4. Python/C Wrapper ã® CMake å
- 5. Docker 㨠setup.py ã®æ´å
- 6. pyproject.toml ã®æ´å
- github.com
ï¼ï¼ï¼Python/C Wrapper ã® CMake å
ãã¦ãããã§ã¯ã CMake 㨠Python/C Wrapper ã®ä¸¡æ¹ãå¦ãã ã¨ããã§ã CMake 㧠Python/C Wrapper ã³ã¼ãããã«ããã¦ãããã
- ããã¸ã§ã¯ããCã½ã¼ã¹ã³ã¼ããPythonããã±ã¼ã¸ã§ä½¿ãã
- ï¼ï¼ï¼Python/C Wrapper ã® CMake å
- 1. 調æ»
- 2. ãã£ã¬ã¯ããªæ§æ
- 3. test å·¥ç¨
- 4. CMake for Python/C Wrapper
- 5. build å·¥ç¨
- 6. test å®è¡
- 7. ã¾ã¨ã
1. 調æ»
- CTEST_OUTPUT_ON_FAILURE -- Runebook.dev, 2023/08
- CMake.org, FindPython -- 2023
- CMake.org, find_package -- 2023
- CMake.org, set_tests_properties -- 2023
2. ãã£ã¬ã¯ããªæ§æ
2-camke_project ãå¼ãç¶ãã¤ã¤ã 3-c_wrapper_for_python ã®ãã¡ã¤ã«ãçµã¿è¾¼ãã§ããã 追å ãããã¡ã¤ã«ã¯ãã£ã2ã¤ã® CMakeLists.txt ãããªãã æ´æ°ç¹ã¯ããã¡ã¤ã«æ«å°¾ã« (*) ã¨ã¤ãã¦å¼·èª¿ãã¦ããã
4-cmake_for_python_c_wrapper âââ util.bash âââ build.bash âââ CMakeLists.txt âââ src â  âââ myapp â  â  âââ CMakeLists.txt â  â  âââ main.c â  âââ mypkg â  â  âââ CMakeLists.txt â  â  âââ include â  â  â  âââ mypkg.h â  â  âââ mypkg.c â  âââ mypkg_wrap â  âââ CMakeLists.txt (*) â  âââ include â  â  âââ mypkg_wrap.h â  âââ mypkg_wrap.c âââ src-python â  âââ mypkg â  âââ __init__.py â  âââ CMakeLists.txt (*) â  âââ core.py âââ tests   âââ CMakeLists.txt   âââ cmake_support.bash   âââ run_test.base.c   âââ run_test.base.h   âââ test_mypkg.c   âââ test_mypkg.py
3. test å·¥ç¨
å®è£ ã¯ã2-camke_project ãå¼ãç¶ãã¤ã¤ã 3-c_wrapper_for_python ã®ãã¡ã¤ã«ãçµã¿è¾¼ãã§ããæ¹åã§è¡ã£ã¦ããã
ã¾ãã¯TDD ã«å¾ãã tests/test_mypkg.py ãå¼ãç¶ãã
ããã¦ã make test
ã§ãã¹ããå®è¡ã§ããããã«ã
add_test
ã追å ããã
add_test(NAME test_with_pytest COMMAND pytest
WORKING_DIRECTORY .)
ãã¹ãåã¯é©å½ã«ã¤ãã¦ãã³ãã³ã㯠pytest ãç¨ããã å¾ã§ PYTHONPATH ãéã£ã¦ããªãã¨è¨ãããããªäºæããããããã®ã¾ã¾ã«ãã¦ãããã
ã²ã¨ã¾ãããã§ãã¹ããå®è¡ãã¦ã¿ãã
$ make test Running tests... Test project /tmp/4-cmake_for_python_c_wrapper Start 1: test_with_run_test 1/2 Test #1: test_with_run_test ............... Passed 0.00 sec Start 2: test_with_pytest 2/2 Test #2: test_with_pytest .................***Failed 0.22 sec 50% tests passed, 1 tests failed out of 2 Total Test time (real) = 0.22 sec The following tests FAILED: 2 - test_with_pytest (Failed) Errors while running CTest make: *** [Makefile:84: test] Error 8
ã¨ã©ã¼ãåºã¦ãããããã¯ãä¸èº«ãè¦ããªãã
調æ»ä¸ã«å¤æããã®ã ããã©ãã ctest ãç´æ¥å®è¡ããã¨ãã ç°å¢å¤æ°ãè¨å®ããã°ã失ææã«ãã°ãè¦ãã¦ãããããã ã
å
·ä½çã«ã¯ã export CTEST_OUTPUT_ON_FAILURE="1"
ã¨ããã°è¯ãã
ãã®å ´åã®ãã°ã¯ä»¥ä¸ã®ããã«ãªãã
$ export CTEST_OUTPUT_ON_FAILURE="1" $ make test Running tests... Test project /tmp/4-cmake_for_python_c_wrapper Start 1: test_with_run_test 1/2 Test #1: test_with_run_test ............... Passed 0.00 sec Start 2: test_with_pytest 2/2 Test #2: test_with_pytest .................***Failed 0.22 sec ============================= test session starts ============================== platform linux -- Python 3.11.4, pytest-7.3.1, pluggy-1.0.0 rootdir: /tmp/4-cmake_for_python_c_wrapper plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collected 0 items / 1 error ==================================== ERRORS ==================================== _____________________ ERROR collecting tests/test_mypkg.py _____________________ ImportError while importing test module '/tmp/4-cmake_for_python_c_wrapper/tests/test_mypkg.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: /usr/lib/python3.11/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) tests/test_mypkg.py:4: in <module> import mypkg E ModuleNotFoundError: No module named 'mypkg' =========================== short test summary info ============================ ERROR tests/test_mypkg.py !!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!! =============================== 1 error in 0.04s =============================== 50% tests passed, 1 tests failed out of 2 Total Test time (real) = 0.22 sec The following tests FAILED: 2 - test_with_pytest (Failed) Errors while running CTest make: *** [Makefile:84: test] Error 8
ã¨ããããã§ãæ³å®éã No module named 'mypkg'
ãè¦ãããã
ããããã®ç®æ¨ã¯ã Python ã¢ã¸ã¥ã¼ã« mypkg ãä½ã£ã¦ãããã¨ã«ãªããã
åç« ã¨ã¯ç°ãªã CMake ã§å®çµãããã¨ãç®æãã
4. CMake for Python/C Wrapper
4.1. ä»ã¾ã§ã®åå©ç¨ã¨å¾©ç¿
CMakeLists.txt ãä½ã£ã¦ããåã«ã ã¾ããæ¢ã«å®æã ãã¯ãã¦ãã mypkg_warp ã®ã³ã¼ããæã£ã¦ãããã 3ç« ã® 3-c_wapper_for_python ãã以ä¸ã®ãã¡ã¤ã«ãæã£ã¦ããã
. âââ src â  âââ mypkg_wrap â  âââ include â  â  âââ mypkg_wrap.h â  âââ mypkg_wrap.c âââ src-python ã  âââ mypkg ã  âââ __init__.py ã  âââ core.py
ã¾ãã mypkg_wrap ã«å¯¾ã㦠CMakeLists.txt ã追å ãããã
ããããã£ã¬ã¯ããªã® CMakeLists.txt ã« add_subcirectory(src/mypkg_wrap)
ã
追å ãããã¨ãå¿ããªãããã«ã
ã¨ãããããä»ã¾ã§ã®ç¥èãç·åå¡ããã¨ã ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªã¨ã©ã¤ãã©ãªã¸ã®ãªã³ã¯ãããã°è¯ãããã ã
add_library(mypkg_wrap SHARED mypkg_wrap.c) target_include_directories(mypkg_wrap PUBLIC include) target_link_libraries(mypkg_wrap PRIVATE mypkg) set_property(TARGET mypkg_wrap PROPERTY POSITION_INDEPENDENT_CODE ON)
ã¡ãã£ã¨æ¹è¡ãå ¥ã£ãããã¦è¦ãç®ãå¤ãã£ã¦ãããããããªãããæ¬è³ªã¯ mypkg + myapp ã®æ¹å¤ã ã æ¬å½ã«ã³ãããã人ã¯ãç¹ã«ã¿ã¼ã²ããåãå¤ãå¿ãã¦ããªãã確ãããã (2æ)ã
4.2. find_package(Python ...)
ããããæããã«è¶³ããªããã®ãããã <Python.h>
ã®ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªã§ããã
åç« ã§ã¯ python3 ã®ã³ãã³ãã使ã£ã¦ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªãåã£ã¦ãããã
CMake ã«ã¯ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªã解決ãã¦ãããã³ãã³ããããã
ããã find_package ã§ããã
ããã¯ååã®éããããã±ã¼ã¸ã®è©³ç´°æ å ±ãè²ã ã¨è¦ã¤ãã¦ãã¦ã å¤æ°ã«æ å ±ãèªåçã«ç»é²ãã¦ããã CMake ã®ã³ãã³ãã§ããã Python ã®å ´åã¯ãä¾ãã°ä»¥ä¸ã®ããã«æ¸ãã
find_package(Python REQUIRED COMPONENTS Interpreter Development)
ã©ããã¼ãæ¸ãã ããªãã Python ã®å¾ã¯ REQUIRED COMPONENTS Interpreter Development
ã¨æ¸ãã¦ããã°ååã§ããã
ã¤ã³ã¿ã¼ããªã¿ç¨ãéçºç¨ã®ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªãã©ã¤ãã©ãªãæ¢ãã¦ãã¦ãããã
詳細ã¯èª¿æ»æç®ãåç
§ã®ãã¨ã
REQUIRED ã ã説æãã¦ããã¨ãããã¯è¦ã¯ããã®ããã±ã¼ã¸ã¯å¿ é ã§ããã¨ãããã¼ã¯ã§ããã 調æ»æç®ä¸ã® 以ä¸ã®æã«æ¸ããã¦ããéããããã±ã¼ã¸ãè¦ã¤ãããªãå ´åã¯ã¨ã©ã¼ã¨ãã¦å¦çãçµäºããã
The REQUIRED option stops processing with an error message if the package cannot be found.
ããã§è¨å®ãããã¿ã¼ã²ãããå¤æ°ã®ä¸è¦§ã¯ã
ããã§è¦ããã
ããã§ä½¿ãã®ã¯ Python::Python
ã¿ã¼ã²ããã ã
3ç« ã§è¦ã /usr/include/python3.11
ã®ãããªãã¹ãããã«å«ã¾ããã
ããã§ã¯ãããã PRIVATE ã¨ãã¦ãªã³ã¯ã©ã¤ãã©ãªã«è¿½å ããã
target_link_libraries(mypkg_wrap PRIVATE Python::Python mypkg)
ããã§ã³ã³ãã¤ã«ãéãã¯ãã ã
4.3. Python ã¢ã¸ã¥ã¼ã« mypkg ç¨æ´å
libmypkg_wrap.so ãä½æããæºåãæ´ã£ãã®ã§ã 次ã¯ããã Python ã®ã½ã¼ã¹ã³ã¼ãç¨ãã£ã¬ã¯ããªã§ãã src-python ã« libmypkg.so ã¨ãã¦ã³ãã¼ããå¿ è¦ããã (mypkg ä¸ã« libmypkg.so ãã§ãã¦ãã¾ã£ã¦ããã®ã§ååã被ããã Python ç¨ã¯ src-python ä¸ã«åããã®ã§è¡çªã¯ããªããæ··ä¹±ãæãã®ã§ã¿ããªã¯é¿ããã)ã
ããã¯ãæ¢ã«ä½æããã©ã¤ãã©ãªãã³ãã¼ããã ãã§ããã
ããããçæãã¡ã¤ã«ã¨ã㦠libmypkg.so ã¯ãããã®ã®ã
ä»ã®ã©ã¤ãã©ãªã®ãã«ãã«ããã使ããã¨ã¯ãªãã
ãªã®ã§ã add_custom_command
ã§ã¯ãªãã
ã¿ã¼ã²ããã追å ãã add_custom_target
ã使ããã
src-python/mypkg/CMakeLists.txt
ãä½æãã¦ã以ä¸ã®å
容ãæ¸ãè¾¼ããã
add_subdirectory ã追å ããã®ãå¿ãã¦ã¯ãããªãã
add_custom_target(py_libmypkg ALL COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mypkg_wrap> libmypkg.so DEPENDS mypkg_wrap )
ã¿ã¼ã²ããåã¯é©å½ã§è¯ããã¨ããã®ãã ALL
ã§ãããã©ã«ãã¿ã¼ã²ããã«è¿½å ã§ããããã ã
éã« ALL
ãæå®ããªãå ´åã make py_libmypkg
ã¨æå®ããå¿
è¦ãããã
COMMAND ${CMAKE_COMMAND} -E copy
ã¯ç°å¢è²»ä¾åã®ã³ãã¼ã³ãã³ãã§ã
cmake ãå®è¡ããã¦ããç°å¢ã§ããã°ã³ãã¼ãå®è¡ãããã¨ãã§ããã
大ä½ã®ãã©ãããã©ã¼ã 㧠cp ã³ãã³ãã使ãããããªæ°ããããã
ãã®ããã«æ¸ãã°ããã®ãã¡ã¤ã«ãèªã¿è¾¼ã㧠cmake ã§ããç°å¢ã§ããã°ç°å¢ä¾åãæ°ã«ããªãã¦ãè¯ããªãã
ããã使ã£ã¦ãã¡ã¤ã«ãã³ãã¼ãã訳ã ãã
ã¿ã¼ã²ããã®ææç©ã¯$<TARGET_FILE:mypkg_wrap>
ã®ãããªå½¢ã§æå®ã§ããã
ã¾ããã³ãã¼ããã¨ãã« libmypkg_wrap.so ãã§ãã¦ããªãã¨ãããªãã®ã§ã
DEPENDS mypkg_wrap
ãæå®ããã
core.py ã微修æ£ããã
from . import libmypkg as _mypkg def message(res): res = _mypkg.message(res) return res
import libmypkg
ã®ããã«ç°å¢ã«ãã¹ãéã£ã¦ãã libmypkg ãã¤ã³ãã¼ãããã®ã§ã¯ãªãã
from . import libmypkg
ã®ããã«ã
èªèº«ã®ããã±ã¼ã¸ã«å«ã¾ããå
±æã©ã¤ãã©ãªãã¤ã³ãã¼ãããå½¢ã«å¤æ´ããã
as _mypkg
ã¨ãã¦ååãå¤æ´ãããã¯å¥½ã¿ã ãã
å¤ã«æ··åããããã¨ãé²ãããã«å¥åã«ãã¦ããã
5. build å·¥ç¨
CMakeLists.txt ã®ã¿ãæ¸ãæãã¦ãããããã㧠cmake ãéãã ãããã éä¸ã§è²ã æ¸ãæããããããã£ã¬ã¯ããªã® CMakeLists.txt ã ãåæ²ãã¦ãããã
cmake_minimum_required(VERSION 3.16) project(my_package) find_package(Python REQUIRED COMPONENTS Interpreter Development) add_subdirectory(src/mypkg) add_subdirectory(src/myapp) add_subdirectory(src/mypkg_wrap) add_subdirectory(src-python/mypkg) add_subdirectory(tests) enable_testing() add_test(NAME test_with_run_test COMMAND run_test WORKING_DIRECTORY tests) add_test(NAME test_with_pytest COMMAND pytest WORKING_DIRECTORY .)
ããã§ã¯ã cmake ããã£ã¦ã¿ããã
$ cmake . -- The C compiler identification is GNU 9.4.0 -- The CXX compiler identification is GNU 9.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Found Python: /usr/bin/python3.8 (found version "3.8.10") found components: Interpreter Development -- Configuring done -- Generating done -- Build files have been written to: /tmp/4-tmp
åé¡ãªãããã ãããã§ã¯ãç¶ã㦠make ããã¦ãããã
$ make Scanning dependencies of target mypkg [ 10%] Building C object src/mypkg/CMakeFiles/mypkg.dir/mypkg.c.o [ 20%] Linking C shared library libmypkg.so [ 20%] Built target mypkg Scanning dependencies of target myapp [ 30%] Building C object src/myapp/CMakeFiles/myapp.dir/main.c.o [ 40%] Linking C executable myapp [ 40%] Built target myapp Scanning dependencies of target mypkg_wrap [ 50%] Building C object src/mypkg_wrap/CMakeFiles/mypkg_wrap.dir/mypkg_wrap.c.o [ 60%] Linking C shared library libmypkg_wrap.so [ 60%] Built target mypkg_wrap Scanning dependencies of target py_libmypkg [ 60%] Built target py_libmypkg [ 70%] Generating run_test.c, run_test.h Scanning dependencies of target run_test [ 80%] Building C object tests/CMakeFiles/run_test.dir/test_mypkg.c.o [ 90%] Building C object tests/CMakeFiles/run_test.dir/run_test.c.o [100%] Linking C executable run_test [100%] Built target run_test
åé¡ãªã make ãéãã
6. test å®è¡
ã§ã¯ãæå¾ã« make test ãéãã確èªãããã
$ export CTEST_OUTPUT_ON_FAILURE="1" $ make test Running tests... Test project /tmp/4-tmp Start 1: test_with_run_test 1/2 Test #1: test_with_run_test ............... Passed 0.00 sec Start 2: test_with_pytest 2/2 Test #2: test_with_pytest .................***Failed 0.22 sec ============================= test session starts ============================== platform linux -- Python 3.11.4, pytest-7.3.1, pluggy-1.0.0 rootdir: /tmp/4-tmp plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collected 0 items / 1 error ==================================== ERRORS ==================================== _____________________ ERROR collecting tests/test_mypkg.py _____________________ ImportError while importing test module '/tmp/4-tmp/tests/test_mypkg.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: /usr/lib/python3.11/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) tests/test_mypkg.py:4: in <module> import mypkg E ModuleNotFoundError: No module named 'mypkg' =========================== short test summary info ============================ ERROR tests/test_mypkg.py !!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!! =============================== 1 error in 0.04s =============================== 50% tests passed, 1 tests failed out of 2 Total Test time (real) = 0.22 sec The following tests FAILED: 2 - test_with_pytest (Failed) Errors while running CTest make: *** [Makefile:84: test] Error 8
ãã£ã¨ã mypkg ãè¦ã¤ãããªãã¨æããã¦ãã¾ã£ãã PYTHONPATH ãéã£ã¦ããªãã£ããã以åã¯ç°å¢å¤æ°ãéãããã ããã CMakeLists.txt ã§ããã«ã¯ä»¥ä¸ã®ããã«æ¸ãã°ããã
set_tests_properties(test_with_pytest PROPERTIES ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/src-python:$ENV{PYTHONPATH}")
ããã§ããä¸åº¦ make test
ãããã
$ make test Running tests... Test project /tmp/4-cmake_for_python_c_wrapper Start 1: test_with_run_test 1/2 Test #1: test_with_run_test ............... Passed 0.00 sec Start 2: test_with_pytest 2/2 Test #2: test_with_pytest ................. Passed 0.20 sec 100% tests passed, 0 tests failed out of 2 Total Test time (real) = 0.20 sec
ããã§ãã¹ããéã£ãã
7. ã¾ã¨ã
以ä¸ã®ããã«ããã¹ããéããã¨ã確èªã§ããã
ããã¾ã§ã®å·¥ç¨ã§ã Python/C wrapper ã¨ã㦠mypkg ã¢ã¸ã¥ã¼ã«ãä½æãããã¨ãã§ããã ããããã¯ããã®ã¢ã¸ã¥ã¼ã«ãã¤ã³ã¹ãã¼ã«ã§ããããã«ã setup.py ã§ããã±ã¼ã¸ã³ã°ããã¨ããã¾ã§æã£ã¦ãããã¨ãèãããã