Browse Source

python: add an option to provide DLL path on Windows.

After much suffering and hunting irrelevant issues I discovered that
with Python 3.8 the PATH is no longer taken into account when looking
for DLLs on Windows, and thus all my module imports fail with the most
extremely anger-inducing nondescript trash message ever:

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

Fortunately, I have been already doing a very nasty hardcoding operation
for this very problem, for the prebuilt tools, so all that was needed
was turning that into something nicer the user can have control over.
next
Vladimír Vondruš 4 months ago
parent
commit
483ef745e4
  1. 33
      CMakeLists.txt
  2. 12
      doc/python/pages/building.rst
  3. 24
      src/python/corrade/__init__.py.in

33
CMakeLists.txt

@ -105,6 +105,39 @@ if(MAGNUM_WITH_PYTHON AND MAGNUM_BUILD_STATIC AND UNIX)
option(MAGNUM_BUILD_PYTHON_BINDINGS_RTLD_GLOBAL "Build Python bindings linking to static libraries with RTLD_GLOBAL enabled for loading" ON)
endif()
# Python since version 3.8 doesn't take dependency DLLs implicitly from PATH
# anymore, and one has to either place them right next to the Python modules,
# into system locations, or add extra paths via os.add_dll_directory().
#
# Be nice to the user and implicitly populate that with Corrade and Magnum DLL
# directories, if found, on the first run. Anything else (such as SDL or GLFW
# DLL path) has to be specified by the user.
if(MAGNUM_WITH_PYTHON AND WIN32)
# Do this lookup only on the first ever run. But add the option always, so
# in case it's passed via command line in the initial CMake run, it
# actually becomes a cached option with a help text and not just a loose
# variable.
if(NOT MAGNUM_PYTHON_BINDINGS_DLL_PATH)
set(dll_path )
foreach(dll_variable CORRADE_UTILITY_DLL_DEBUG CORRADE_UTILITY_DLL_RELEASE MAGNUM_DLL_DEBUG MAGNUM_DLL_RELEASE)
if(${dll_variable})
get_filename_component(dll_directory ${${dll_variable}} DIRECTORY)
list(APPEND dll_path ${dll_directory})
endif()
endforeach()
list(REMOVE_DUPLICATES dll_path)
# If no DLLs were found, it means Corrade and Magnum is static and thus no
# DLL directories may need to be passed
if(dll_path)
message(STATUS "Autodetected ${dll_path} as directories to pass to Python for finding Corrade, Magnum and other dependency DLLs. Update the MAGNUM_PYTHON_BINDINGS_DLL_PATH variable if needed.")
endif()
endif()
# This variable is then used in src/python/corrade/__init__.py.in
set(MAGNUM_PYTHON_BINDINGS_DLL_PATH "${dll_path}" CACHE STRING "Semicolon-separated directories where to look for Corrade, Magnum and other dependency DLLs")
endif()
# Backwards compatibility for unprefixed CMake options. If the user isn't
# explicitly using prefixed options in the first run already, accept the
# unprefixed options, and remember this decision for subsequent runs

12
doc/python/pages/building.rst

@ -29,6 +29,8 @@ Downloading and building
.. role:: sh(code)
:language: sh
.. role:: bat(code)
:language: bat
:summary: Installation guide for the Python bindings.
:ref-prefix:
@ -176,6 +178,16 @@ depending on Magnum loaded later. See also
:dox:`MAGNUM_BUILD_STATIC_UNIQUE_GLOBALS` in Corrade and Magnum for more
information.
On Windows, Python since version 3.8 no longer loads dependency DLLs from
:bat:`%PATH%`. Instead, it requires relevant DLL directories to be explicitly
passed to :ref:`os.add_dll_directory()`. CMake by default adds the (absolute)
path to Corrade and Magnum DLLs there, you can edit the path or add additional
directories for other dependencies (such as SDL or GLFW) using the
``MAGNUM_PYTHON_BINDINGS_DLL_PATH`` CMake option. Non-absolute paths are
interpreted as relative to the package root --- for example, if the Corrade
module is in ``tools/python/corrade/``, specifying ``bin/`` as the DLL path
will resolve to ``tools/bin/``.
`Running unit tests`_
---------------------

24
src/python/corrade/__init__.py.in

@ -39,16 +39,22 @@ ${_MAGNUM_BUILD_PYTHON_BINDINGS_RTLD_GLOBAL}sys.setdlopenflags(sys.getdlopenflag
import platform
if platform.system() == 'Windows':
import os
# os.add_dll_directory() is only since Python 3.8, in earlier versions
# PATH is used and that alone may be sufficient
if hasattr(os, 'add_dll_directory'):
# The variable might be empty, or might contain empty parts, so skip
# all empty items. There's no splitWithoutEmptyParts() like in Corrade,
# sigh. Using """ to prevent accidental syntax errors if the path
# itself would contain quotes.
for directory in """${MAGNUM_PYTHON_BINDINGS_DLL_PATH}""".split(';'):
if not directory:
continue
for directory in [
# Prebuilt binaries from the magnum-ci repo have this file in
# python/corrade/ and DLLs in bin/
'../../bin'
]:
bin_path = os.path.join(os.path.dirname(__file__), directory)
if os.path.exists(bin_path):
os.add_dll_directory(bin_path)
break
# If the directory is relative, interpret is as relative to parent
# dir. Also add it only if it exists.
bin_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), directory)
if os.path.exists(bin_path):
os.add_dll_directory(bin_path)
from _corrade import *

Loading…
Cancel
Save