Skip to content

Commit dd255c4

Browse files
committed
adds scikit core to build arrayfire binaries
1 parent f4f06fe commit dd255c4

File tree

4 files changed

+202
-39
lines changed

4 files changed

+202
-39
lines changed

CMakeLists.txt

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
project(
4+
${SKBUILD_PROJECT_NAME}
5+
LANGUAGES C CXX
6+
VERSION ${SKBUILD_PROJECT_VERSION})
7+
8+
find_package(Python COMPONENTS Interpreter Development.Module)
9+
10+
include(FetchContent)
11+
12+
#set(NO_SONAME)
13+
FetchContent_Declare(
14+
arrayfire
15+
GIT_REPOSITORY https://github.com/syurkevi/arrayfire.git
16+
GIT_TAG origin/pywrapper-main
17+
)
18+
#TODO: change package name to match repository/project name?
19+
#set(AF_INSTALL_CMAKE_DIR "${SKBUILD_PROJECT_NAME}")
20+
21+
set(AF_INSTALL_BIN_DIR "arrayfire_wrapper/binaries")
22+
set(AF_INSTALL_CMAKE_DIR "arrayfire_wrapper/binaries")
23+
set(AF_INSTALL_DOC_DIR "arrayfire_wrapper/binaries")
24+
set(AF_INSTALL_EXAMPLE_DIR "arrayfire_wrapper/binaries")
25+
set(AF_INSTALL_INC_DIR "arrayfire_wrapper/binaries")
26+
set(AF_INSTALL_LIB_DIR "arrayfire_wrapper/binaries")
27+
set(FG_INSTALL_LIB_DIR "arrayfire_wrapper/binaries")
28+
29+
FetchContent_MakeAvailable(arrayfire)
30+
31+
# Testing artifacts
32+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated.txt "Testing")
33+
install(
34+
FILES ${CMAKE_CURRENT_BINARY_DIR}/generated.txt
35+
DESTINATION arrayfire_wrapper
36+
COMPONENT Generated)
37+
38+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated_ignored.txt "Testing")
39+
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/generated_ignored.txt
40+
DESTINATION arrayfire_wrapper)

arrayfire_wrapper/_backend.py

+114-28
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from enum import Enum
1010
from pathlib import Path
1111
from typing import Iterator
12+
import sysconfig
1213

1314
from .defines import is_arch_x86
1415
from .version import ARRAYFIRE_VER_MAJOR
@@ -36,6 +37,7 @@ class _BackendPathConfig:
3637
lib_prefix: str
3738
lib_postfix: str
3839
af_path: Path
40+
af_is_user_path : bool
3941
cuda_found: bool
4042

4143
def __iter__(self) -> Iterator:
@@ -46,16 +48,20 @@ def _get_backend_path_config() -> _BackendPathConfig:
4648
platform_name = platform.system()
4749
cuda_found = False
4850

51+
# try to use user provided AF_PATH if explicitly set
4952
try:
5053
af_path = Path(os.environ["AF_PATH"])
54+
af_is_user_path = True
5155
except KeyError:
5256
af_path = None
57+
af_is_user_path = False
5358

5459
try:
5560
cuda_path = Path(os.environ["CUDA_PATH"])
5661
except KeyError:
5762
cuda_path = None
5863

64+
# try to find default arrayfire installation paths
5965
if platform_name == _SupportedPlatforms.windows.value or _SupportedPlatforms.is_cygwin(platform_name):
6066
if platform_name == _SupportedPlatforms.windows.value:
6167
# HACK Supressing crashes caused by missing dlls
@@ -64,40 +70,84 @@ def _get_backend_path_config() -> _BackendPathConfig:
6470
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002) # type: ignore[attr-defined]
6571

6672
if not af_path:
67-
af_path = _find_default_path(f"C:/Program Files/ArrayFire/v{ARRAYFIRE_VER_MAJOR}")
73+
try:
74+
af_path = _find_default_path(f"C:/Program Files/ArrayFire/v{ARRAYFIRE_VER_MAJOR}")
75+
except ValueError:
76+
af_path = None
77+
6878

6979
if cuda_path and (cuda_path / "bin").is_dir() and (cuda_path / "nvvm/bin").is_dir():
7080
cuda_found = True
7181

72-
return _BackendPathConfig("", ".dll", af_path, cuda_found)
82+
return _BackendPathConfig("", ".dll", af_path, af_is_user_path, cuda_found)
7383

7484
if platform_name == _SupportedPlatforms.darwin.value:
7585
default_cuda_path = Path("/usr/local/cuda/")
7686

7787
if not af_path:
7888
af_path = _find_default_path("/opt/arrayfire", "/usr/local")
89+
try:
90+
af_path = _find_default_path(f"C:/Program Files/ArrayFire/v{ARRAYFIRE_VER_MAJOR}",
91+
"C:/Program Files (x86)/ArrayFire/v{ARRAYFIRE_VER_MAJOR}")
92+
except ValueError:
93+
af_path = None
7994

8095
if not (cuda_path and default_cuda_path.exists()):
8196
cuda_found = (default_cuda_path / "lib").is_dir() and (default_cuda_path / "/nvvm/lib").is_dir()
8297

83-
return _BackendPathConfig("lib", f".{ARRAYFIRE_VER_MAJOR}.dylib", af_path, cuda_found)
98+
return _BackendPathConfig("lib", f".{ARRAYFIRE_VER_MAJOR}.dylib", af_path, af_is_user_path, cuda_found)
8499

85100
if platform_name == _SupportedPlatforms.linux.value:
86101
default_cuda_path = Path("/usr/local/cuda/")
87102

88103
if not af_path:
89-
af_path = _find_default_path(f"/opt/arrayfire-{ARRAYFIRE_VER_MAJOR}", "/opt/arrayfire/", "/usr/local/")
104+
try:
105+
af_path = _find_default_path(f"/opt/arrayfire-{ARRAYFIRE_VER_MAJOR}", "/opt/arrayfire/", "/usr/local/")
106+
except ValueError:
107+
af_path = None
90108

91109
if not (cuda_path and default_cuda_path.exists()):
92110
if "64" in platform.architecture()[0]: # Check either is 64 bit arch is selected
93111
cuda_found = (default_cuda_path / "lib64").is_dir() and (default_cuda_path / "nvvm/lib64").is_dir()
94112
else:
95113
cuda_found = (default_cuda_path / "lib").is_dir() and (default_cuda_path / "nvvm/lib").is_dir()
96114

97-
return _BackendPathConfig("lib", f".so.{ARRAYFIRE_VER_MAJOR}", af_path, cuda_found)
115+
return _BackendPathConfig("lib", f".so.{ARRAYFIRE_VER_MAJOR}", af_path, af_is_user_path, cuda_found)
98116

99117
raise OSError(f"{platform_name} is not supported.")
100118

119+
# finds paths to locally packaged arrayfire libraries if they exist in site
120+
def _find_site_local_path() -> Path:
121+
local_paths = ["."]
122+
123+
# module search paths
124+
af_module = __import__(__name__)
125+
module_paths = af_module.__path__ if af_module.__path__ else []
126+
for path in module_paths:
127+
local_paths.append(path)
128+
129+
# site search path
130+
purelib_path = sysconfig.get_path('purelib')
131+
platlib_path = sysconfig.get_path('platlib')
132+
local_paths.append(purelib_path)
133+
local_paths.append(platlib_path)
134+
135+
# sys search path
136+
local_paths.extend(sys.path)
137+
138+
module_name = af_module.__name__
139+
for path in local_paths:
140+
lpath = Path(path)
141+
if lpath.exists():
142+
p = lpath.glob(f"{module_name}/binaries/*")
143+
files = [x.name for x in p if x.is_file()]
144+
query_libnames = ['afcpu', 'afoneapi', 'afopencl', 'afcuda', 'af', 'forge']
145+
found_lib_in_dir = any(q in f for q in query_libnames for f in files)
146+
if found_lib_in_dir:
147+
print( lpath)
148+
print( lpath / module_name / "binaries")
149+
return lpath / module_name / "binaries"
150+
raise ValueError("No binaries detected in site path.")
101151

102152
def _find_default_path(*args: str) -> Path:
103153
for path in args:
@@ -108,26 +158,51 @@ def _find_default_path(*args: str) -> Path:
108158

109159

110160
class BackendType(enum.Enum): # TODO change name - avoid using _backend_type - e.g. type
111-
unified = 0 # NOTE It is set as Default value on Arrayfire backend
112-
cpu = 1
113161
cuda = 2
114162
opencl = 4
115163
oneapi = 8
164+
cpu = 1
165+
unified = 0 # NOTE It is set as Default value on Arrayfire backend
116166

117167
def __iter__(self) -> Iterator:
118168
# NOTE cpu comes last because we want to keep this order priorty during backend initialization
119-
return iter((self.unified, self.cuda, self.oneapi, self.opencl, self.cpu))
169+
return iter((self.unified, self.cuda, self.opencl, self.oneapi, self.cpu))
120170

121171

122172
class Backend:
123173
_backend_type: BackendType
124-
_clib: ctypes.CDLL
174+
_clibs: dict[BackendType, ctypes.CDLL]
125175

126176
def __init__(self) -> None:
127177
self._backend_path_config = _get_backend_path_config()
128178

129-
self._load_forge_lib()
179+
self._backend_type = None
180+
self._clibs = {}
130181
self._load_backend_libs()
182+
self._load_forge_lib()
183+
184+
def set_backend(self, backend_type : BackendType) -> None:
185+
# if unified is available, do dynamic module loading through libaf
186+
if self._backend_type == BackendType.unified:
187+
import pdb;pdb.set_trace()
188+
from arrayfire_wrapper.lib.unified_api_functions import set_backend as unified_set_backend
189+
try:
190+
unified_set_backend(backend_type)
191+
except RuntimeError:
192+
if VERBOSE_LOADS:
193+
print(f"Unable to change backend using unified loader")
194+
raise RuntimeError
195+
# if unified not available
196+
else:
197+
if backend_type in self._clibs:
198+
self._backend_type = backend_type
199+
else:
200+
self._backend_path_config = _get_backend_path_config()
201+
202+
self._backend_type = None
203+
#self._clib = None
204+
self._load_backend_libs(backend_type)
205+
#self._load_forge_lib() needed to reload?
131206

132207
def _load_forge_lib(self) -> None:
133208
for lib_name in self._lib_names("forge", _LibPrefixes.forge):
@@ -141,16 +216,18 @@ def _load_forge_lib(self) -> None:
141216
print(f"Unable to load {lib_name}")
142217
pass
143218

144-
def _load_backend_libs(self) -> None:
145-
for backend_type in BackendType:
219+
def _load_backend_libs(self, specific_backend : BackendType | None = None) -> None:
220+
available_backends = [specific_backend] if specific_backend else list(BackendType)
221+
for backend_type in available_backends:
222+
print(backend_type)
146223
self._load_backend_lib(backend_type)
147224

148225
if self._backend_type:
149226
if VERBOSE_LOADS:
150227
print(f"Setting {backend_type.name} as backend.")
151228
break
152229

153-
if not self._backend_type and not self._clib:
230+
if not self._backend_type and not self._clibs:
154231
raise RuntimeError(
155232
"Could not load any ArrayFire libraries.\n"
156233
"Please look at https://github.com/arrayfire/arrayfire-python/wiki for more information."
@@ -164,7 +241,7 @@ def _load_backend_lib(self, _backend_type: BackendType) -> None:
164241
try:
165242
ctypes.cdll.LoadLibrary(str(lib_name))
166243
self._backend_type = _backend_type
167-
self._clib = ctypes.CDLL(str(lib_name))
244+
self._clibs[_backend_type] = ctypes.CDLL(str(lib_name))
168245

169246
if _backend_type == BackendType.cuda:
170247
self._load_nvrtc_builtins_lib(lib_name.parent)
@@ -191,22 +268,22 @@ def _lib_names(self, name: str, lib: _LibPrefixes, ver_major: str | None = None)
191268
post = self._backend_path_config.lib_postfix if ver_major is None else ver_major
192269
lib_name = self._backend_path_config.lib_prefix + lib.value + name + post
193270

194-
lib64_path = self._backend_path_config.af_path / "lib64"
195-
search_path = lib64_path if lib64_path.is_dir() else self._backend_path_config.af_path / "lib"
196-
197-
site_path = Path(sys.prefix) / "lib64" if not is_arch_x86() else Path(sys.prefix) / "lib"
198-
199-
# prefer locally packaged arrayfire libraries if they exist
200-
af_module = __import__(__name__)
201-
local_path = Path(af_module.__path__[0]) if af_module.__path__ else Path("")
271+
lib_paths = [Path("", lib_name)]
202272

203-
lib_paths = [Path("", lib_name), site_path / lib_name, local_path / lib_name]
273+
# use local or site packaged arrayfire libraries if they exist
274+
local_path = _find_site_local_path()
275+
lib_paths.append(local_path / lib_name)
204276

205277
if self._backend_path_config.af_path: # prefer specified AF_PATH if exists
206-
return [search_path / lib_name] + lib_paths
207-
else:
208-
lib_paths.insert(2, Path(str(search_path), lib_name))
209-
return lib_paths
278+
lib64_path = self._backend_path_config.af_path / "lib64"
279+
search_path = lib64_path if lib64_path.is_dir() else self._backend_path_config.af_path / "lib"
280+
# prefer path explicitly set by user through AF_PATH
281+
if self._backend_path_config.af_is_user_path:
282+
return [search_path / lib_name] + lib_paths
283+
# otherwise, prefer to use site-packaged or local path
284+
return lib_paths + [search_path / lib_name]
285+
286+
return lib_paths
210287

211288
def _find_nvrtc_builtins_lib_name(self, search_path: Path) -> str | None:
212289
for f in search_path.iterdir():
@@ -220,7 +297,7 @@ def backend_type(self) -> BackendType:
220297

221298
@property
222299
def clib(self) -> ctypes.CDLL:
223-
return self._clib
300+
return self._clibs[self._backend_type]
224301

225302

226303
# Initialize the backend
@@ -238,3 +315,12 @@ def get_backend() -> Backend:
238315
"""
239316

240317
return __backend
318+
319+
def set_backend(backend_type : BackendType) -> None:
320+
321+
try:
322+
backend = get_backend()
323+
backend.set_backend(backend_type)
324+
except RuntimeError:
325+
print(f"Requested backend {backend_type.name} could not be found")
326+

dev-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ codecov>=2.1.12
2323

2424
# Package building
2525
build>=1.0.3
26+
scikit-build-core>=0.8.0
2627

2728
# Package publishing
2829
twine>=4.0.2
30+

0 commit comments

Comments
 (0)