Использование GitHub Action для создания Python Wheel Package для Dynamsoft C++ Barcode SDK

Реализовать расширение 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:

  1. Задать аргументы сборки. rpath имеет решающее значение для поиска зависимых общих библиотек в Linux и macOS.
  2. Задайте каталоги заголовочных файлов и библиотек.
  3. Собрать модуль расширения.
  4. Подключите внешние динамические библиотеки.
  5. Скопируйте модуль Python и зависимые библиотеки в папку пакета barcodeQrSDK.

Файл __init__.py также должен находиться в папке пакета barcodeQrSDK.

from .barcodeQrSDK import * 
__version__ = version
Вход в полноэкранный режим Выход из полноэкранного режима

Выполните команду python setup.py develop для тестирования модуля расширения. Если ошибок нет, можно создавать пакет wheel:

pip wheel .
Войти в полноэкранный режим Выйти из полноэкранного режима

После успешного создания пакета wheel его структура папок будет выглядеть следующим образом:

Создание нескольких колесных пакетов с помощью GitHub Actions

Как мы уже говорили выше, создание нескольких колесных пакетов для каждой версии Python и разных платформ сводит нас с ума. К счастью, GitHub Actions может избавить нас от этой работы.

Вот шаги для создания и публикации нескольких колесных пакетов:

  1. Перейдите на главную страницу репозитория и нажмите Actions, чтобы создать новый рабочий процесс.
  2. Нажмите Создать рабочий процесс самостоятельно, чтобы создать пользовательский рабочий процесс. Мы можем обратиться к примерам, предоставленным 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"
    
  3. Перейдите в Settings > Secrets > Actions, чтобы создать секрет репозитория для публикации пакетов wheel на pypi.org.

  4. После завершения рабочих процессов мы можем скачать артефакт, содержащий сгенерированные пакеты wheel.

    Кроме того, пакеты wheel доступны для загрузки на pypi.org.

Исходный код

https://github.com/yushulx/python-barcode-qrcode-sdk

Оцените статью
Procodings.ru
Добавить комментарий