Реализовать расширение Python на C или C++ легко, но собрать родной модуль в Python wheel package для разных операционных систем и разных версий Python – это кошмар. Пакет CPython wheel зависит от версии Python, поэтому, чтобы модуль CPython работал универсально для Python 3.x, необходимо собрать wheel для Python 3.6, Python 3.7, Python 3.8, Python 3.9 и Python 3.10 для каждой операционной системы. Максимальное количество пакетов равно Версии Python (3.6, 3.7, 3.8, 3.9, 3.10) * операционные системы (Windows, Linux, macOS) * архитектуры CPU (x64, aarch64)
. Чтобы упростить процесс, мы можем использовать GitHub Actions. В этой статье мы рассмотрим в качестве примера Dynamsoft C/C++ Barcode SDK. Вы увидите, как создать расширение CPython, которое ссылается на внешние библиотеки C/C++ (*.dll, *.so, *.dylib
), и как автоматизировать процесс создания и публикации пакета Python wheel с помощью GitHub Actions.
Требования
Dynamsoft C/C++ Barcode SDK v9.0
Сборка проекта расширения CPython с помощью Scikit-build
Если вы читали руководство по разработке Python, вы можете знать, что distutils.core.Extension является наиболее широко используемым конструктором расширений Python. Однако distutils
не может последовательно собрать расширение и упаковать сгенерированную библиотеку в папку package при выполнении команды pip wheel
для создания пакета wheel.
Scikit-build является альтернативой distutils
. Он расширяет функции distutils
с помощью сборки CMake. Чтобы начать работу с scikit-build, мы можем посетить scikit-build-sample-projects.
Для нашего проекта расширения штрих-кода и QR-кода на Python файл setup.py
выглядит следующим образом:
from skbuild import setup
import io
packages = ['barcodeQrSDK']
setup (name = 'barcode-qr-code-sdk',
version = '9.0.3',
description = 'Barcode and QR code scanning SDK for Python',
packages=packages,
include_package_data=False,
)
Как видите, файл setup.py
довольно прост по сравнению с файлом setup.py
, используемым в disutils
. Он содержит только папку пакета. Чтобы запустить сборку расширения, мы создаем CMakeLists.txt
вместе с файлом setup.py
.
cmake_minimum_required(VERSION 3.4...3.22)
project(barcodeQrSDK)
find_package(PythonExtensions REQUIRED)
if(CMAKE_HOST_UNIX)
if(CMAKE_HOST_APPLE)
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath,@loader_path")
SET(CMAKE_INSTALL_RPATH "@loader_path")
else()
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
SET(CMAKE_INSTALL_RPATH "$ORIGIN")
endif()
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()
MESSAGE( STATUS "CPU architecture ${CMAKE_SYSTEM_PROCESSOR}" )
if(CMAKE_HOST_WIN32)
link_directories("${PROJECT_SOURCE_DIR}/lib/win/")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/lib/linux/" )
link_directories("${PROJECT_SOURCE_DIR}/lib/linux/")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l)
MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/lib/arm32/" )
link_directories("${PROJECT_SOURCE_DIR}/lib/arm32/")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/lib/aarch64/" )
link_directories("${PROJECT_SOURCE_DIR}/lib/aarch64/")
endif()
elseif(CMAKE_HOST_APPLE)
MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/lib/macos/" )
link_directories("${PROJECT_SOURCE_DIR}/lib/macos/")
endif()
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/")
add_library(${PROJECT_NAME} MODULE src/barcodeQrSDK.cpp)
if(CMAKE_HOST_WIN32)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_libraries (${PROJECT_NAME} "DynamsoftBarcodeReaderx64")
else()
target_link_libraries (${PROJECT_NAME} "DBRx64")
endif()
else()
target_link_libraries (${PROJECT_NAME} "DynamsoftBarcodeReader" pthread)
endif()
if(CMAKE_HOST_WIN32)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${PROJECT_SOURCE_DIR}/lib/win/"
$<TARGET_FILE_DIR:${PROJECT_NAME}>)
endif()
python_extension_module(barcodeQrSDK)
install(TARGETS barcodeQrSDK LIBRARY DESTINATION barcodeQrSDK)
if(CMAKE_HOST_WIN32)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/win/" DESTINATION barcodeQrSDK)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/linux/" DESTINATION barcodeQrSDK)
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l OR ARM32_BUILD)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/arm32/" DESTINATION barcodeQrSDK)
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/aarch64/" DESTINATION barcodeQrSDK)
endif()
elseif(CMAKE_HOST_APPLE)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/macos/" DESTINATION barcodeQrSDK)
endif()
Что мы делаем в файле CMakeLists.txt
:
- Задать аргументы сборки.
rpath
имеет решающее значение для поиска зависимых общих библиотек в Linux и macOS. - Задайте каталоги заголовочных файлов и библиотек.
- Собрать модуль расширения.
- Подключите внешние динамические библиотеки.
- Скопируйте модуль Python и зависимые библиотеки в папку пакета
barcodeQrSDK
.
Файл __init__.py
также должен находиться в папке пакета barcodeQrSDK
.
from .barcodeQrSDK import *
__version__ = version
Выполните команду python setup.py develop
для тестирования модуля расширения. Если ошибок нет, можно создавать пакет wheel:
pip wheel .
После успешного создания пакета wheel его структура папок будет выглядеть следующим образом:
Создание нескольких колесных пакетов с помощью GitHub Actions
Как мы уже говорили выше, создание нескольких колесных пакетов для каждой версии Python и разных платформ сводит нас с ума. К счастью, GitHub Actions может избавить нас от этой работы.
Вот шаги для создания и публикации нескольких колесных пакетов:
- Перейдите на главную страницу репозитория и нажмите
Actions
, чтобы создать новый рабочий процесс. -
Нажмите
Создать рабочий процесс самостоятельно
, чтобы создать пользовательский рабочий процесс. Мы можем обратиться к примерам, предоставленным cibuildwheel.name: Build and upload to PyPI on: [push, pull_request] jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-2019, macos-10.15] steps: - uses: actions/checkout@v2 - name: Build wheels uses: pypa/cibuildwheel@v2.5.0 env: CIBW_ARCHS_WINDOWS: AMD64 CIBW_ARCHS_MACOS: x86_64 CIBW_ARCHS_LINUX: "x86_64 aarch64" CIBW_SKIP: "pp* *-win32 *-manylinux_i686" - uses: actions/upload-artifact@v2 with: path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build sdist run: pipx run build --sdist - uses: actions/upload-artifact@v2 with: path: dist/*.tar.gz upload_pypi: needs: [build_wheels, build_sdist] runs-on: ubuntu-latest # upload to PyPI on every tag starting with 'v' if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # alternatively, to publish when a GitHub Release is created, use the following rule: # if: github.event_name == 'release' && github.event.action == 'published' steps: - uses: actions/download-artifact@v2 with: name: artifact path: dist - uses: pypa/gh-action-pypi-publish@v1.4.2 with: user: __token__ password: ${{ secrets.pypi_password }} skip_existing: true
Поскольку наше расширение только 64-битное, мы можем пропустить 32-битные сборки, установив
CIBW_SKIP: "pp* *-win32 *-manylinux_i686"
. Мы также можем указать архитектуру ОС:CIBW_ARCHS_WINDOWS: AMD64 CIBW_ARCHS_MACOS: x86_64 CIBW_ARCHS_LINUX: "x86_64 aarch64"
-
Перейдите в
Settings > Secrets > Actions
, чтобы создать секрет репозитория для публикации пакетов wheel на pypi.org. -
После завершения рабочих процессов мы можем скачать артефакт, содержащий сгенерированные пакеты wheel.
Кроме того, пакеты wheel доступны для загрузки на pypi.org.
Исходный код
https://github.com/yushulx/python-barcode-qrcode-sdk