Skip to content

Commit fc854f2

Browse files
committed
win-support: Generate binary in standard directory to avoid issue on Windows
1 parent bd85768 commit fc854f2

File tree

5 files changed

+159
-65
lines changed

5 files changed

+159
-65
lines changed

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ else()
4343
endif()
4444
endif()
4545

46+
function(set_standard_output_directory target)
47+
set_target_properties(
48+
${target}
49+
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
50+
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
51+
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
52+
endfunction()
53+
4654
# Disable -Werror on Unix for now.
4755
set(CXX_DISABLE_WERROR True)
4856
set(CMAKE_VERBOSE_MAKEFILE True)
@@ -328,6 +336,7 @@ set(${PROJECT_NAME}_SOURCES
328336

329337
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES}
330338
${${PROJECT_NAME}_HEADERS})
339+
set_standard_output_directory(${PROJECT_NAME})
331340
target_include_directories(
332341
${PROJECT_NAME} SYSTEM
333342
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>

python/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ if(GENERATE_PYTHON_STUBS)
6767
endif(GENERATE_PYTHON_STUBS)
6868

6969
# --- INSTALL SCRIPTS
70-
set(PYTHON_FILES __init__.py)
70+
set(PYTHON_FILES __init__.py windows_dll_manager.py)
7171

7272
foreach(python ${PYTHON_FILES})
7373
python_build(${PROJECT_NAME} ${python})

python/eigenpy/__init__.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,29 @@
22
# Copyright (c) 2017-2021 CNRS INRIA
33
#
44

5-
from .eigenpy_pywrap import * # noqa
6-
from .eigenpy_pywrap import __raw_version__, __version__ # noqa
5+
# On Windows, if eigenpy.dll is not in the same directory than
6+
# the .pyd, it will not be loaded.
7+
# We first try to load eigenpy, then, if it fail and we are on Windows:
8+
# 1. We add all paths inside eigenpy_WINDOWS_DLL_PATH to DllDirectory
9+
# 2. If EIGENPY_WINDOWS_DLL_PATH we add the relative path from the
10+
# package directory to the bin directory to DllDirectory
11+
# This solution is inspired from:
12+
# - https://github.com/PixarAnimationStudios/OpenUSD/pull/1511/files
13+
# - https://stackoverflow.com/questions/65334494/python-c-extension-packaging-dll-along-with-pyd
14+
# More resources on https://github.com/diffpy/pyobjcryst/issues/33
15+
try:
16+
from .eigenpy_pywrap import * # noqa
17+
from .eigenpy_pywrap import __raw_version__, __version__ # noqa
18+
except ImportError:
19+
import platform
20+
21+
if platform.system() == "Windows":
22+
from .windows_dll_manager import get_dll_paths, build_directory_manager
23+
24+
with build_directory_manager() as dll_dir_manager:
25+
for p in get_dll_paths():
26+
dll_dir_manager.add_dll_directory(p)
27+
from .eigenpy_pywrap import * # noqa
28+
from .eigenpy_pywrap import __raw_version__, __version__ # noqa
29+
else:
30+
raise
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import os
2+
import sys
3+
import contextlib
4+
5+
6+
def get_dll_paths():
7+
eigenpy_paths = os.getenv("EIGENPY_WINDOWS_DLL_PATH")
8+
if eigenpy_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+
return [
15+
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH1),
16+
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH2),
17+
]
18+
else:
19+
return eigenpy_paths.split(os.pathsep)
20+
21+
22+
class PathManager(contextlib.AbstractContextManager):
23+
"""Restore PATH state after importing Python module"""
24+
25+
def add_dll_directory(self, dll_dir: str):
26+
os.environ["PATH"] += os.pathsep + dll_dir
27+
28+
def __enter__(self):
29+
self.old_path = os.environ["PATH"]
30+
return self
31+
32+
def __exit__(self, *exc_details):
33+
os.environ["PATH"] = self.old_path
34+
35+
36+
class DllDirectoryManager(contextlib.AbstractContextManager):
37+
"""Restore DllDirectory state after importing Python module"""
38+
39+
def add_dll_directory(self, dll_dir: str):
40+
# add_dll_directory can fail on relative path and non
41+
# existing path.
42+
# Since we don't know all the fail criterion we just ignore
43+
# thrown exception
44+
try:
45+
self.dll_dirs.append(os.add_dll_directory(dll_dir))
46+
except OSError:
47+
pass
48+
49+
def __enter__(self):
50+
self.dll_dirs = []
51+
return self
52+
53+
def __exit__(self, *exc_details):
54+
for d in self.dll_dirs:
55+
d.close()
56+
57+
58+
def build_directory_manager():
59+
if sys.version_info >= (3, 8):
60+
return DllDirectoryManager()
61+
else:
62+
return PathManager()

unittest/CMakeLists.txt

Lines changed: 61 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ macro(ADD_LIB_UNIT_TEST test)
1010
else(BUILD_TESTING)
1111
add_library(${test} SHARED EXCLUDE_FROM_ALL "${test}.cpp")
1212
endif(BUILD_TESTING)
13+
set_standard_output_directory(${test})
1314

1415
target_link_libraries(${test} PUBLIC ${PROJECT_NAME})
1516
set_target_properties(${test} PROPERTIES PREFIX "")
1617

1718
set_target_properties(${test} PROPERTIES SUFFIX ${PYTHON_EXT_SUFFIX})
1819

19-
add_test(NAME ${test} COMMAND ${PYTHON_EXECUTABLE} -c "import ${test}")
20+
add_test(
21+
NAME ${test}
22+
COMMAND ${PYTHON_EXECUTABLE} -c "import ${test}"
23+
WORKING_DIRECTORY $<TARGET_FILE_DIR:${test}>)
2024

2125
add_dependencies(build_tests ${test})
2226
if(NOT BUILD_TESTING)
@@ -51,6 +55,20 @@ if(CMAKE_CXX_STANDARD GREATER 14 AND CMAKE_CXX_STANDARD LESS 98)
5155
add_lib_unit_test(std_unique_ptr)
5256
endif()
5357

58+
function(add_python_lib_unit_test name source)
59+
add_python_unit_test(${name} ${source} "lib" "bin")
60+
endfunction()
61+
62+
function(add_python_eigenpy_lib_unit_test name source)
63+
add_python_unit_test(${name} ${source} "lib" "bin" "python")
64+
set_tests_properties(${name} PROPERTIES DEPENDS ${PYWRAP})
65+
endfunction()
66+
67+
function(add_python_eigenpy_unit_test name source)
68+
add_python_unit_test(${name} ${source} "python")
69+
set_tests_properties(${name} PROPERTIES DEPENDS ${PYWRAP})
70+
endfunction()
71+
5472
function(config_test test tagname opttype)
5573
set(MODNAME ${test}_${tagname})
5674
set(TEST_TYPE ${opttype})
@@ -64,7 +82,7 @@ function(config_test test tagname opttype)
6482
add_test(NAME ${PYTHON_TEST_NAME}
6583
COMMAND ${PYTHON_EXECUTABLE}
6684
"${CMAKE_CURRENT_BINARY_DIR}/python/${py_file}")
67-
compute_pythonpath(ENV_VARIABLES "unittest")
85+
compute_pythonpath(ENV_VARIABLES "lib" "bin")
6886
set_tests_properties(${PYTHON_TEST_NAME} PROPERTIES ENVIRONMENT
6987
"${ENV_VARIABLES}")
7088
endfunction()
@@ -81,106 +99,87 @@ endif()
8199

82100
add_lib_unit_test(bind_virtual_factory)
83101

84-
add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
102+
add_python_lib_unit_test("py-matrix" "unittest/python/test_matrix.py")
85103

86-
add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest")
87-
add_python_unit_test("py-geometry" "unittest/python/test_geometry.py"
88-
"unittest")
89-
add_python_unit_test("py-complex" "unittest/python/test_complex.py" "unittest")
90-
add_python_unit_test("py-return-by-ref" "unittest/python/test_return_by_ref.py"
91-
"unittest")
92-
add_python_unit_test("py-eigen-ref" "unittest/python/test_eigen_ref.py"
93-
"unittest")
104+
add_python_lib_unit_test("py-tensor" "unittest/python/test_tensor.py")
105+
add_python_lib_unit_test("py-geometry" "unittest/python/test_geometry.py")
106+
add_python_lib_unit_test("py-complex" "unittest/python/test_complex.py")
107+
add_python_lib_unit_test("py-return-by-ref"
108+
"unittest/python/test_return_by_ref.py")
109+
add_python_lib_unit_test("py-eigen-ref" "unittest/python/test_eigen_ref.py")
94110

95111
if(NOT NUMPY_WITH_BROKEN_UFUNC_SUPPORT)
96-
add_python_unit_test("py-user-type" "unittest/python/test_user_type.py"
97-
"unittest")
112+
add_python_lib_unit_test("py-user-type" "unittest/python/test_user_type.py")
98113
endif()
99114

100-
add_python_unit_test("py-dimensions" "unittest/python/test_dimensions.py"
101-
"python;unittest")
102-
set_tests_properties("py-dimensions" PROPERTIES DEPENDS ${PYWRAP})
115+
add_python_eigenpy_lib_unit_test("py-dimensions"
116+
"unittest/python/test_dimensions.py")
103117

104-
add_python_unit_test("py-version" "unittest/python/test_version.py"
105-
"python;unittest")
106-
set_tests_properties("py-version" PROPERTIES DEPENDS ${PYWRAP})
118+
add_python_eigenpy_lib_unit_test("py-version" "unittest/python/test_version.py")
107119

108-
add_python_unit_test("py-eigen-solver" "unittest/python/test_eigen_solver.py"
109-
"python;unittest")
110-
set_tests_properties("py-eigen-solver" PROPERTIES DEPENDS ${PYWRAP})
120+
add_python_eigenpy_lib_unit_test("py-eigen-solver"
121+
"unittest/python/test_eigen_solver.py")
111122

112-
add_python_unit_test(
123+
add_python_eigenpy_lib_unit_test(
113124
"py-self-adjoint-eigen-solver"
114-
"unittest/python/test_self_adjoint_eigen_solver.py" "python;unittest")
115-
set_tests_properties("py-self-adjoint-eigen-solver" PROPERTIES DEPENDS
116-
${PYWRAP})
125+
"unittest/python/test_self_adjoint_eigen_solver.py")
117126

118-
add_python_unit_test("py-LLT" "unittest/python/test_LLT.py" "python;unittest")
119-
set_tests_properties("py-LLT" PROPERTIES DEPENDS ${PYWRAP})
127+
add_python_eigenpy_lib_unit_test("py-LLT" "unittest/python/test_LLT.py")
120128

121-
add_python_unit_test("py-LDLT" "unittest/python/test_LDLT.py" "python;unittest")
122-
set_tests_properties("py-LDLT" PROPERTIES DEPENDS ${PYWRAP})
129+
add_python_eigenpy_lib_unit_test("py-LDLT" "unittest/python/test_LDLT.py")
123130

124131
if(NOT WIN32)
125-
add_python_unit_test("py-MINRES" "unittest/python/test_MINRES.py"
126-
"python;unittest")
127-
set_tests_properties("py-MINRES" PROPERTIES DEPENDS ${PYWRAP})
132+
add_python_eigenpy_lib_unit_test("py-MINRES" "unittest/python/test_MINRES.py")
128133
endif(NOT WIN32)
129134

130-
add_python_unit_test("py-std-vector" "unittest/python/test_std_vector.py"
131-
"python;unittest")
132-
set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
135+
add_python_eigenpy_lib_unit_test("py-std-vector"
136+
"unittest/python/test_std_vector.py")
133137

134-
add_python_unit_test("py-std-array" "unittest/python/test_std_array.py"
135-
"unittest")
138+
add_python_lib_unit_test("py-std-array" "unittest/python/test_std_array.py")
136139

137-
add_python_unit_test("py-std-pair" "unittest/python/test_std_pair.py"
138-
"unittest")
140+
add_python_lib_unit_test("py-std-pair" "unittest/python/test_std_pair.py")
139141

140-
add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
141-
"unittest")
142+
add_python_lib_unit_test("py-user-struct" "unittest/python/test_user_struct.py")
142143

143144
if(CMAKE_CXX_STANDARD GREATER 14 AND CMAKE_CXX_STANDARD LESS 98)
144-
add_python_unit_test("py-std-unique-ptr"
145-
"unittest/python/test_std_unique_ptr.py" "unittest")
145+
add_python_lib_unit_test("py-std-unique-ptr"
146+
"unittest/python/test_std_unique_ptr.py")
146147
endif()
147148

148-
add_python_unit_test("py-bind-virtual" "unittest/python/test_bind_virtual.py"
149-
"unittest")
149+
add_python_lib_unit_test("py-bind-virtual"
150+
"unittest/python/test_bind_virtual.py")
150151

151152
if(BUILD_TESTING_SCIPY)
152-
add_python_unit_test("py-sparse-matrix"
153-
"unittest/python/test_sparse_matrix.py" "unittest")
153+
add_python_lib_unit_test("py-sparse-matrix"
154+
"unittest/python/test_sparse_matrix.py")
154155

155-
add_python_unit_test(
156+
add_python_eigenpy_unit_test(
156157
"py-SimplicialLLT"
157-
"unittest/python/decompositions/sparse/test_SimplicialLLT.py" "python")
158-
add_python_unit_test(
158+
"unittest/python/decompositions/sparse/test_SimplicialLLT.py")
159+
add_python_eigenpy_unit_test(
159160
"py-SimplicialLDLT"
160-
"unittest/python/decompositions/sparse/test_SimplicialLDLT.py" "python")
161+
"unittest/python/decompositions/sparse/test_SimplicialLDLT.py")
161162

162163
if(BUILD_WITH_CHOLMOD_SUPPORT)
163-
164-
add_python_unit_test(
164+
add_python_eigenpy_unit_test(
165165
"py-CholmodSimplicialLLT"
166166
"unittest/python/decompositions/sparse/cholmod/test_CholmodSimplicialLLT.py"
167-
"python")
167+
)
168168

169-
add_python_unit_test(
169+
add_python_eigenpy_unit_test(
170170
"py-CholmodSimplicialLDLT"
171171
"unittest/python/decompositions/sparse/cholmod/test_CholmodSimplicialLDLT.py"
172-
"python")
172+
)
173173

174-
add_python_unit_test(
174+
add_python_eigenpy_unit_test(
175175
"py-CholmodSupernodalLLT"
176176
"unittest/python/decompositions/sparse/cholmod/test_CholmodSupernodalLLT.py"
177-
"python")
178-
177+
)
179178
endif(BUILD_WITH_CHOLMOD_SUPPORT)
180179

181180
if(BUILD_WITH_ACCELERATE_SUPPORT)
182-
add_python_unit_test(
181+
add_python_eigenpy_unit_test(
183182
"py-Accelerate"
184-
"unittest/python/decompositions/sparse/test_Accelerate.py" "python")
183+
"unittest/python/decompositions/sparse/test_Accelerate.py")
185184
endif(BUILD_WITH_ACCELERATE_SUPPORT)
186185
endif()

0 commit comments

Comments
 (0)