From 483ef745e470f41c82f955aa77f3d459f294b337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 4 Jan 2026 22:23:23 +0100 Subject: [PATCH] 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. --- CMakeLists.txt | 33 +++++++++++++++++++++++++++++++ doc/python/pages/building.rst | 12 +++++++++++ src/python/corrade/__init__.py.in | 24 +++++++++++++--------- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d13d17..111482f 100644 --- a/CMakeLists.txt +++ b/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 diff --git a/doc/python/pages/building.rst b/doc/python/pages/building.rst index 363a0b0..7474f7a 100644 --- a/doc/python/pages/building.rst +++ b/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`_ --------------------- diff --git a/src/python/corrade/__init__.py.in b/src/python/corrade/__init__.py.in index 2b4dd8b..1e26939 100644 --- a/src/python/corrade/__init__.py.in +++ b/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 *