Skip to content

Commit 474bf7c

Browse files
committed
python: Fix Windows pyd directory and manage new python 3.8 rule to import a library on Windows
1 parent 5c284f6 commit 474bf7c

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

python/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ SET_TARGET_PROPERTIES(${PYWRAP}
3636
SUFFIX ${PYTHON_EXT_SUFFIX}
3737
OUTPUT_NAME "${PYWRAP}"
3838
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
39+
# On Windows, shared library are treated as binary
40+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
3941
)
4042

4143
IF(UNIX AND NOT APPLE)
@@ -47,6 +49,7 @@ INSTALL(TARGETS ${PYWRAP} DESTINATION ${${PYWRAP}_INSTALL_DIR})
4749
# --- INSTALL SCRIPTS
4850
SET(PYTHON_FILES
4951
__init__.py
52+
windows_dll_manager.py
5053
)
5154

5255
FOREACH(python ${PYTHON_FILES})

python/pycppad/__init__.py

+27
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,30 @@
44

55
from .pycppad_pywrap import *
66
from .pycppad_pywrap import __version__, __raw_version__
7+
8+
# On Windows, if pycppad.dll is not in the same directory than
9+
# the .pyd, it will not be loaded.
10+
# We first try to load pycppad, then, if it fail and we are on Windows:
11+
# 1. We add all paths inside PYCPPAD_WINDOWS_DLL_PATH to DllDirectory
12+
# 2. If PYCPPAD_WINDOWS_DLL_PATH we add the relative path from the
13+
# package directory to the bin directory to DllDirectory
14+
# This solution is inspired from:
15+
# - https://github.com/PixarAnimationStudios/OpenUSD/pull/1511/files
16+
# - https://stackoverflow.com/questions/65334494/python-c-extension-packaging-dll-along-with-pyd
17+
# More resources on https://github.com/diffpy/pyobjcryst/issues/33
18+
try:
19+
from .pycppad_pywrap import *
20+
from .pycppad_pywrap import __version__, __raw_version__
21+
except ImportError:
22+
import platform
23+
24+
if platform.system() == "Windows":
25+
from .windows_dll_manager import get_dll_paths, build_directory_manager
26+
27+
with build_directory_manager() as dll_dir_manager:
28+
for p in get_dll_paths():
29+
dll_dir_manager.add_dll_directory(p)
30+
from .pycppad_pywrap import *
31+
from .pycppad_pywrap import __version__, __raw_version__
32+
else:
33+
raise

python/pycppad/windows_dll_manager.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import os
2+
import sys
3+
import contextlib
4+
5+
6+
def get_dll_paths():
7+
pycppad_paths = os.getenv("PYCPPAD_WINDOWS_DLL_PATH")
8+
if pycppad_paths is None:
9+
# From https://peps.python.org/pep-0250/#implementation
10+
# lib/python-version/site-packages/package
11+
RELATIVE_DLL_PATH1 = "..\\..\\..\\..\\bin"
12+
# lib/site-packages/package
13+
RELATIVE_DLL_PATH2 = "..\\..\\..\\bin"
14+
# For unit test
15+
RELATIVE_DLL_PATH3 = "..\\..\\bin"
16+
return [
17+
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH1),
18+
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH2),
19+
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH3),
20+
]
21+
else:
22+
return pycppad_paths.split(os.pathsep)
23+
24+
25+
class PathManager(contextlib.AbstractContextManager):
26+
"""Restore PATH state after importing Python module"""
27+
28+
def add_dll_directory(self, dll_dir: str):
29+
os.environ["PATH"] += os.pathsep + dll_dir
30+
31+
def __enter__(self):
32+
self.old_path = os.environ["PATH"]
33+
return self
34+
35+
def __exit__(self, *exc_details):
36+
os.environ["PATH"] = self.old_path
37+
38+
39+
class DllDirectoryManager(contextlib.AbstractContextManager):
40+
"""Restore DllDirectory state after importing Python module"""
41+
42+
def add_dll_directory(self, dll_dir: str):
43+
# add_dll_directory can fail on relative path and non
44+
# existing path.
45+
# Since we don't know all the fail criterion we just ignore
46+
# thrown exception
47+
try:
48+
self.dll_dirs.append(os.add_dll_directory(dll_dir))
49+
except OSError:
50+
pass
51+
52+
def __enter__(self):
53+
self.dll_dirs = []
54+
return self
55+
56+
def __exit__(self, *exc_details):
57+
for d in self.dll_dirs:
58+
d.close()
59+
60+
61+
def build_directory_manager():
62+
if sys.version_info >= (3, 8):
63+
return DllDirectoryManager()
64+
else:
65+
return PathManager()

0 commit comments

Comments
 (0)