Skip to content

Commit 70bf3fd

Browse files
committed
Refactor Windows DLL loading in Python packages
The existing space1d/space2d/space3d package initializers each carried the same Windows DLL setup code before importing their pybind extension. That logic only added the current package directory plus entries already present on PATH via os.add_dll_directory(). In practice this was fragile for wheel installs because the extension import could still fail if dependent DLLs were bundled in package-local locations that were not already on PATH. Centralize the DLL search setup in amrex._dll and call it from each spaceNd package before importing amrex_*d_pybind. The new helper prefers package-local locations first: the current spaceNd directory, spaceNd/.libs, the parent amrex package directory, and amrex/.libs. It then falls back to PATH for source and developer installs. Keep the os.add_dll_directory() handles alive for the lifetime of the process and deduplicate discovered paths. This keeps the Windows import workaround in one place and makes it compatible with vendored wheel layouts as well as traditional shared- library installs.
1 parent 1ef54dc commit 70bf3fd

4 files changed

Lines changed: 72 additions & 51 deletions

File tree

src/amrex/_dll.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
from pathlib import Path
5+
6+
_DLL_DIRECTORY_HANDLES = {}
7+
8+
9+
def _iter_windows_dll_directories(package_file):
10+
package_dir = Path(package_file).resolve().parent
11+
package_root = package_dir.parent
12+
seen = set()
13+
14+
candidates = (
15+
package_dir,
16+
package_dir / ".libs",
17+
package_root,
18+
package_root / ".libs",
19+
)
20+
21+
for candidate in candidates:
22+
if candidate.is_dir():
23+
candidate_str = str(candidate)
24+
if candidate_str not in seen:
25+
seen.add(candidate_str)
26+
yield candidate_str
27+
28+
for entry in os.environ.get("PATH", "").split(os.pathsep):
29+
if not entry:
30+
continue
31+
32+
path = Path(os.path.expandvars(os.path.expanduser(entry)))
33+
try:
34+
resolved = path.resolve()
35+
except OSError:
36+
continue
37+
38+
if not resolved.exists():
39+
continue
40+
41+
resolved_str = str(resolved)
42+
if resolved_str not in seen:
43+
seen.add(resolved_str)
44+
yield resolved_str
45+
46+
47+
def add_windows_dll_directories(package_file):
48+
"""Keep DLL directories registered for the lifetime of the process."""
49+
if os.name != "nt" or not hasattr(os, "add_dll_directory"):
50+
return
51+
52+
package_dir = str(Path(package_file).resolve().parent)
53+
if package_dir in _DLL_DIRECTORY_HANDLES:
54+
return
55+
56+
handles = []
57+
for dll_dir in _iter_windows_dll_directories(package_file):
58+
try:
59+
handles.append(os.add_dll_directory(dll_dir))
60+
except OSError:
61+
continue
62+
63+
_DLL_DIRECTORY_HANDLES[package_dir] = handles

src/amrex/space1d/__init__.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
# -*- coding: utf-8 -*-
22

3-
import os
4-
5-
# Python 3.8+ on Windows: DLL search paths for dependent
6-
# shared libraries
7-
# Refs.:
8-
# - https://github.com/python/cpython/issues/80266
9-
# - https://docs.python.org/3.8/library/os.html#os.add_dll_directory
10-
if os.name == "nt":
11-
# add anything in the current directory
12-
pwd = __file__.rsplit(os.sep, 1)[0] + os.sep
13-
os.add_dll_directory(pwd)
14-
# add anything in PATH
15-
paths = os.environ.get("PATH", "")
16-
for p in paths.split(";"):
17-
p_abs = os.path.abspath(os.path.expanduser(os.path.expandvars(p)))
18-
if os.path.exists(p_abs):
19-
os.add_dll_directory(p_abs)
3+
from .._dll import add_windows_dll_directories
4+
5+
add_windows_dll_directories(__file__)
206

217
# import core bindings to C++
228
from . import amrex_1d_pybind

src/amrex/space2d/__init__.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
# -*- coding: utf-8 -*-
22

3-
import os
4-
5-
# Python 3.8+ on Windows: DLL search paths for dependent
6-
# shared libraries
7-
# Refs.:
8-
# - https://github.com/python/cpython/issues/80266
9-
# - https://docs.python.org/3.8/library/os.html#os.add_dll_directory
10-
if os.name == "nt":
11-
# add anything in the current directory
12-
pwd = __file__.rsplit(os.sep, 1)[0] + os.sep
13-
os.add_dll_directory(pwd)
14-
# add anything in PATH
15-
paths = os.environ.get("PATH", "")
16-
for p in paths.split(";"):
17-
p_abs = os.path.abspath(os.path.expanduser(os.path.expandvars(p)))
18-
if os.path.exists(p_abs):
19-
os.add_dll_directory(p_abs)
3+
from .._dll import add_windows_dll_directories
4+
5+
add_windows_dll_directories(__file__)
206

217
# import core bindings to C++
228
from . import amrex_2d_pybind

src/amrex/space3d/__init__.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
# -*- coding: utf-8 -*-
22

3-
import os
4-
5-
# Python 3.8+ on Windows: DLL search paths for dependent
6-
# shared libraries
7-
# Refs.:
8-
# - https://github.com/python/cpython/issues/80266
9-
# - https://docs.python.org/3.8/library/os.html#os.add_dll_directory
10-
if os.name == "nt":
11-
# add anything in the current directory
12-
pwd = __file__.rsplit(os.sep, 1)[0] + os.sep
13-
os.add_dll_directory(pwd)
14-
# add anything in PATH
15-
paths = os.environ.get("PATH", "")
16-
for p in paths.split(";"):
17-
p_abs = os.path.abspath(os.path.expanduser(os.path.expandvars(p)))
18-
if os.path.exists(p_abs):
19-
os.add_dll_directory(p_abs)
3+
from .._dll import add_windows_dll_directories
4+
5+
add_windows_dll_directories(__file__)
206

217
# import core bindings to C++
228
from . import amrex_3d_pybind

0 commit comments

Comments
 (0)