Python C extensión embalaje DLL junto con pyd

Mi proyecto expone una biblioteca estática static.lib) a CPython (3.8) intérprete. Se compone de una biblioteca estática que a su vez depende de un controlador DLL FTDI. Después de leer este hilo parece que la solución óptima para proporcionar DLL de terceros es agruparlos junto con un paquete Python - para asegurarse de que DLL se encuentra en el mismo directorio que .pyd binario.

Los problemas que estoy teniendo es que después de correr pip install . para mi paquete, el DLL requerido (llamarlo required.dll) se coloca en site-packages/package/required.dll y la biblioteca de extensión C real (llamarlo package.pyd) se coloca en site-packages/package.pyd.

Ya que no está en el mismo directorio cuando intento utilizar la biblioteca en Python I get

ImportError: DLL load failed while importing package: The specified module could not be found.

Abajo está mi setup.py

setuptools.setup(
        name="package",
        version="1.0.0",
        packages=setuptools.find_packages(where="src"),
        package_dir={"": "src"},
        py_modules=[splitext(basename(path))[0] for path in glob("src/*.py")],
        use_scm_version=True,
        package_data={
            "package": [
                "_clibs/libs/required.dll",
            ],
        },
        ext_modules=[
            setuptools.Extension(
                "package",
                include_dirs=["src/package/_clibs/inc"],
                sources=[
                    "src/package/_clibs/src/api.cpp",
                    "src/package/_clibs/src/utils.cpp",
                ],
                library_dirs=[
                    "src/package/_clibs/libs",
                ],
                libraries=["static", "User32"],
                language="c++"
            ),
        ],
    )

El diseño del directorio para el proyecto es el siguiente:

/
setup.py
.tox
src/
...package/
......wrapper.py
......__init__.py
......_clibs/
.........inc/
.........src/
............api.cpp
............utils.cpp
.........libs/
............required.dll
............static.lib

También uso tox para la gestión del entorno virtual.

Las respuestas propuestas Aquí. y Aquí. esquema un muy similar setup.py y el mismo método para incluir el DLL package_data Opción. Las respuestas parecen sugerir que DLL y .pyd se colocan en el mismo nivel que no sucede para mí. No puedo ubicar lo que me falta para tener el mismo comportamiento.

python 3.8.6
setuptools 51.0.0
pip 20.3.1

TL;DR DLL está siendo colocado en un directorio diferente a .pyd binario por lo que es invisible para el cargador de Windows

Pregunta hecha hace 3 años, 5 meses, 0 días - Por pixelpioneerx


3 Respuestas:

  • Después de excavar, encontré una manera que funcionó para mí. Este hilo ha arrojado luz sobre los problemas de DLL carga en Windows y los acontecimientos más recientes (Python 3.8) sobre el problema.

    La solución con la que fui fue prestada numpy. Para empaquetar correctamente los DLLs junto con su extensión C:

    1. Crear un directorio dentro de su paquete que contendrá todos los DLLs requeridos su extensión estará utilizando
    2. Modifique su procedimiento de construcción para incluir este directorio junto con sdist y distribuciones de ruedas
    3. Una vez que el usuario importa su paquete, lo primero que hace es modificar dinámicamente caminos para donde se buscarán los DLLs (dos métodos diferentes dependiendo de si usted está en 3.8 o inferior)

    Roughly, adding package_data a su setup.py debe hacer el truco (menos los shenanigans que vienen con archivos MANIFEST y el uso package_data, leer más Aquí.)

     package_data={"your package name": ["path_to_DLLs/*"]},
    

    Para implementar #3, como opción en tu __init__.py para el paquete añadir lo siguiente (tomar 99% línea por línea desde numpy __config__.py que se genera automáticamente por su complejo sistema de construcción.

    import os
    import sys
    
    PATH_TO_DLL = "YOUR DLL DIRECTORY IN YOUR PACKAGE"
    extra_dll_dir = os.path.join(os.path.dirname(__file__), PATH_TO_DLL)
    
    if sys.version_info >= (3, 8):
        os.add_dll_directory(extra_dll_dir)
    else:
        # legacy DLL loading mechanism through PATH env variable manipulations
        os.environ.setdefault("PATH", "")
        os.environ["PATH"] += os.pathsep + extra_dll_dir
    

    Cualquier comentario es muy apreciado. El hilo vinculado a este ticket habla de la necesidad de una mejor documentación para la extensión C a bordo y no he podido encontrar ninguna. Hasta ahora, extensiones C + Windows + setuptools hecho para una experiencia increíblemente frustrante.

    Respondida el Dec 17, 2020 a las 03:58 - por algorithmadeptc6cb

    Votos positivos: 0 | Votos negativos: 0

  • Para asegurarte de que el archivo DLL requerido se encuentre en el mismo directorio que el archivo .pyd al instalar tu paquete a través de pip, puedes realizar los siguientes ajustes en tu setup.py:

    • Modifica la sección de `package_data` para incluir el archivo DLL en el mismo directorio que el archivo `.pyd`:
    python
    package_data={
        "": ["_clibs/libs/required.dll"],
    },
    
    • Asegúrate de que el archivo `.pyd` se instale en el directorio correcto también. Puedes hacerlo especificando el atributo `ext_package` para la extensión en el que especificas el nombre de la carpeta principal para tu paquete de extensión:
    python
    ext_modules=[
        setuptools.Extension(
            "package",
            ...
            ext_package="package",
            ...
        ),
    ],
    

    Con estos ajustes, al instalar tu paquete a través de pip install ., el archivo required.dll debería colocarse en el mismo directorio que el archivo package.pyd. Esto debería resolver el problema de que el cargador de Windows no pueda encontrar el archivo DLL al cargar la biblioteca desde Python. ¡Espero que esto te ayude a solucionar el error!

    Respondida el Dec 18, 2020 a las 04:02 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El problema es que estás utilizando el valor predeterminado package_dir de "" en tu setup.py. Esto le indica a setuptools que coloque todos los archivos de datos en el directorio raíz del paquete instalado, en lugar del subdirectorio donde se encuentra el módulo que los utiliza.

    Para resolver este problema, debes especificar el subdirectorio correcto en el que se debe colocar el archivo DLL. Puedes hacer esto modificando tu package_dir de la siguiente manera:

    python
    package_dir={"package": "src/package"},
    

    Esto le indicará a setuptools que coloque el archivo DLL en el subdirectorio src/package del paquete instalado. Una vez que hayas realizado este cambio, vuelve a ejecutar pip install para instalar el paquete. El archivo DLL ahora debería colocarse en el mismo directorio que el archivo .pyd y debería poder importar la biblioteca en Python sin errores.

    Respondida el Dec 18, 2020 a las 04:03 - por Gemini

    Votos positivos: 0 | Votos negativos: 0