Skip to content

Commit ea58090

Browse files
authored
Merge pull request #7 from rgommers/netlib-deps
Add Netlib BLAS (libblas/libcblas) and LAPACK (liblapack/liblapacke) dependencies
2 parents 5db81a4 + 210ddc9 commit ea58090

File tree

2 files changed

+271
-45
lines changed

2 files changed

+271
-45
lines changed

mesonbuild/dependencies/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.
238238

239239
# From blas_lapack:
240240
'accelerate': 'blas_lapack',
241+
'blas': 'blas_lapack',
242+
'lapack': 'blas_lapack',
241243
'mkl': 'blas_lapack',
242244
'openblas': 'blas_lapack',
243245

mesonbuild/dependencies/blas_lapack.py

Lines changed: 269 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,28 @@
302302
"""
303303

304304

305+
def check_blas_machine_file(self, name: str, props: dict) -> T.Tuple[bool, T.List[str]]:
306+
# TBD: do we need to support multiple extra dirs?
307+
incdir = props.get(f'{name}_includedir')
308+
assert incdir is None or isinstance(incdir, str)
309+
libdir = props.get(f'{name}_librarydir')
310+
assert libdir is None or isinstance(libdir, str)
311+
312+
if incdir and libdir:
313+
has_dirs = True
314+
if not Path(incdir).is_absolute() or not Path(libdir).is_absolute():
315+
raise mesonlib.MesonException('Paths given for openblas_includedir and '
316+
'openblas_librarydir in machine file must be absolute')
317+
return has_dirs, [libdir, incdir]
318+
elif incdir or libdir:
319+
raise mesonlib.MesonException('Both openblas_includedir *and* openblas_librarydir '
320+
'have to be set in your machine file (one is not enough)')
321+
else:
322+
raise mesonlib.MesonBugException('issue with openblas dependency detection, should not '
323+
'be possible to reach this else clause')
324+
return (False, [])
325+
326+
305327
class BLASLAPACKMixin():
306328
def parse_modules(self, kwargs: T.Dict[str, T.Any]) -> None:
307329
modules: T.List[str] = mesonlib.extract_as_list(kwargs, 'modules')
@@ -322,14 +344,17 @@ def parse_modules(self, kwargs: T.Dict[str, T.Any]) -> None:
322344
self.needs_lapack = 'lapack' in modules
323345
self.needs_lapacke = 'lapacke' in modules
324346

325-
def check_symbols(self, compile_args, suffix=None) -> None:
347+
def check_symbols(self, compile_args, suffix=None, check_cblas=True,
348+
check_lapacke=True, lapack_only=False) -> None:
326349
# verify that we've found the right LP64/ILP64 interface
327-
symbols = ['dgemm_']
328-
if self.needs_cblas:
350+
symbols = []
351+
if not lapack_only:
352+
symbols += ['dgemm_']
353+
if check_cblas and self.needs_cblas:
329354
symbols += ['cblas_dgemm']
330355
if self.needs_lapack:
331356
symbols += ['zungqr_']
332-
if self.needs_lapacke:
357+
if check_lapacke and self.needs_lapacke:
333358
symbols += ['LAPACKE_zungqr']
334359

335360
if suffix is None:
@@ -416,13 +441,14 @@ def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[
416441

417442
for libname in libnames:
418443
link_arg = self.clib_compiler.find_library(libname, self.env, lib_dirs)
419-
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
420-
for hdr in ['openblas_config.h', 'openblas/openblas_config.h']:
421-
found_header, _ = self.clib_compiler.has_header(hdr, '', self.env, dependencies=[self],
422-
extra_args=incdir_args)
423-
if found_header:
424-
self._openblas_config_header = hdr
425-
break
444+
if link_arg:
445+
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
446+
for hdr in ['openblas_config.h', 'openblas/openblas_config.h']:
447+
found_header, _ = self.clib_compiler.has_header(hdr, '', self.env, dependencies=[self],
448+
extra_args=incdir_args)
449+
if found_header:
450+
self._openblas_config_header = hdr
451+
break
426452

427453
if link_arg and found_header:
428454
if not self.probe_symbols(link_arg):
@@ -433,25 +459,10 @@ def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[
433459
break
434460

435461
def detect_openblas_machine_file(self, props: dict) -> None:
436-
# TBD: do we need to support multiple extra dirs?
437-
incdir = props.get('openblas_includedir')
438-
assert incdir is None or isinstance(incdir, str)
439-
libdir = props.get('openblas_librarydir')
440-
assert libdir is None or isinstance(libdir, str)
441-
442-
if incdir and libdir:
443-
self.is_found = True
444-
if not Path(incdir).is_absolute() or not Path(libdir).is_absolute():
445-
raise mesonlib.MesonException('Paths given for openblas_includedir and '
446-
'openblas_librarydir in machine file must be absolute')
447-
elif incdir or libdir:
448-
raise mesonlib.MesonException('Both openblas_includedir *and* openblas_librarydir '
449-
'have to be set in your machine file (one is not enough)')
450-
else:
451-
raise mesonlib.MesonBugException('issue with openblas dependency detection, should not '
452-
'be possible to reach this else clause')
453-
454-
self.detect([libdir], [incdir])
462+
has_dirs, _dirs = check_blas_machine_file('openblas', props)
463+
if has_dirs:
464+
libdir, incdir = _dirs
465+
self.detect([libdir], [incdir])
455466

456467
def detect_openblas_version(self) -> str:
457468
v, _ = self.clib_compiler.get_define('OPENBLAS_VERSION',
@@ -476,7 +487,7 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) ->
476487

477488
super().__init__(name, env, kwargs)
478489

479-
if not self.probe_symbols(self.link_args):
490+
if self.is_found and not self.probe_symbols(self.link_args):
480491
self.is_found = False
481492

482493

@@ -489,19 +500,224 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
489500

490501
if self.interface == 'ilp64':
491502
self.is_found = False
492-
elif not self.probe_symbols(self.link_args):
503+
elif self.is_found and not self.probe_symbols(self.link_args):
493504
self.is_found = False
494505

495506

496-
class NetlibPkgConfigDependency(BLASLAPACKMixin, PkgConfigDependency):
507+
class NetlibMixin():
508+
def get_symbol_suffix(self) -> str:
509+
self._ilp64_suffix = '' # Handle `64_` suffix, or custom suffixes?
510+
return '' if self.interface == 'lp64' else self._ilp64_suffix
511+
512+
def probe_symbols(self, compile_args, check_cblas=True, check_lapacke=True,
513+
lapack_only=False) -> bool:
514+
"""Most ILP64 BLAS builds will not use a suffix, but the new standard will be _64
515+
(see Reference-LAPACK/lapack#666). Check which one we're dealing with"""
516+
if self.interface == 'lp64':
517+
return self.check_symbols(compile_args, check_cblas=check_cblas,
518+
check_lapacke=check_lapacke, lapack_only=lapack_only)
519+
520+
if self.check_symbols(compile_args, '_64', check_cblas=check_cblas,
521+
check_lapacke=check_lapacke, lapack_only=lapack_only):
522+
self._ilp64_suffix = '_64'
523+
elif self.check_symbols(compile_args, '', check_cblas=check_cblas,
524+
check_lapacke=check_lapacke, lapack_only=lapack_only):
525+
self._ilp64_suffix = ''
526+
else:
527+
return False
528+
return True
529+
530+
531+
class NetlibBLASPkgConfigDependency(BLASLAPACKMixin, NetlibMixin, PkgConfigDependency):
497532
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
498-
# TODO: add 'cblas'
499-
super().__init__('blas', env, kwargs)
533+
# TODO: add ILP64 - needs factory function like for OpenBLAS
534+
super().__init__(name, env, kwargs)
500535
self.feature_since = ('1.3.0', '')
501536
self.parse_modules(kwargs)
502537

503-
def get_symbol_suffix(self) -> str:
504-
return ''
538+
if self.is_found:
539+
if self.needs_cblas:
540+
# `name` may be 'blas' or 'blas64'; CBLAS library naming should be consistent
541+
# with BLAS library naming, so just prepend 'c' and try to detect it.
542+
try:
543+
cblas_pc = PkgConfigDependency('c'+name, env, kwargs)
544+
if cblas_pc.found():
545+
self.link_args += cblas_pc.link_args
546+
self.compile_args += cblas_pc.compile_args
547+
except DependencyException:
548+
pass
549+
550+
if not self.probe_symbols(self.link_args):
551+
self.is_found = False
552+
553+
554+
class NetlibBLASSystemDependency(BLASLAPACKMixin, NetlibMixin, SystemDependency):
555+
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
556+
super().__init__(name, environment, kwargs)
557+
self.feature_since = ('1.3.0', '')
558+
self.parse_modules(kwargs)
559+
560+
# First, look for paths specified in a machine file
561+
props = self.env.properties[self.for_machine].properties
562+
if any(x in props for x in ['blas_includedir', 'blas_librarydir']):
563+
self.detect_blas_machine_file(props)
564+
565+
# Then look in standard directories by attempting to link
566+
if not self.is_found:
567+
extra_libdirs: T.List[str] = []
568+
self.detect(extra_libdirs)
569+
570+
if self.is_found:
571+
self.version = 'unknown' # no way to derive this from standard headers
572+
573+
def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[T.List[str]] = None) -> None:
574+
if lib_dirs is None:
575+
lib_dirs = []
576+
if inc_dirs is None:
577+
inc_dirs = []
578+
579+
if self.interface == 'lp64':
580+
libnames = ['blas']
581+
cblas_headers = ['cblas.h']
582+
elif self.interface == 'ilp64':
583+
libnames = ['blas64', 'blas']
584+
cblas_headers = ['cblas_64.h', 'cblas.h']
585+
586+
for libname in libnames:
587+
link_arg = self.clib_compiler.find_library(libname, self.env, lib_dirs)
588+
if not link_arg:
589+
continue
590+
591+
# libblas may include CBLAS symbols (Debian builds it like this),
592+
# but more often than not there's a separate libcblas library. Handle both cases.
593+
if not self.probe_symbols(link_arg, check_cblas=False):
594+
continue
595+
if self.needs_cblas:
596+
cblas_in_blas = self.probe_symbols(link_arg, check_cblas=True)
597+
598+
if self.needs_cblas and not cblas_in_blas:
599+
# We found libblas and it does not contain CBLAS symbols, so we need libcblas
600+
cblas_libname = 'c' + libname
601+
link_arg_cblas = self.clib_compiler.find_library(cblas_libname, self.env, lib_dirs)
602+
if link_arg_cblas:
603+
link_arg.extend(link_arg_cblas)
604+
else:
605+
# We didn't find CBLAS
606+
continue
607+
608+
self.is_found = True
609+
self.link_args += link_arg
610+
if self.needs_cblas:
611+
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
612+
for hdr in cblas_headers:
613+
found_header, _ = self.clib_compiler.has_header(hdr, '', self.env, dependencies=[self],
614+
extra_args=incdir_args)
615+
if found_header:
616+
# If we don't get here, we found the library but not the header - this may
617+
# be okay, since projects may ship their own CBLAS header for portability)
618+
self.compile_args += incdir_args
619+
break
620+
621+
def detect_blas_machine_file(self, props: dict) -> None:
622+
has_dirs, _dirs = check_blas_machine_file('blas', props)
623+
if has_dirs:
624+
libdir, incdir = _dirs
625+
self.detect([libdir], [incdir])
626+
627+
628+
class NetlibLAPACKPkgConfigDependency(BLASLAPACKMixin, NetlibMixin, PkgConfigDependency):
629+
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
630+
# TODO: add ILP64 (needs factory function like for OpenBLAS)
631+
super().__init__(name, env, kwargs)
632+
self.feature_since = ('1.3.0', '')
633+
self.parse_modules(kwargs)
634+
635+
if self.is_found:
636+
if self.needs_lapacke:
637+
# Similar to CBLAS: there may be a separate liblapacke.so
638+
try:
639+
lapacke_pc = PkgConfigDependency(name+'e', env, kwargs)
640+
if lapacke_pc.found():
641+
self.link_args += lapacke_pc.link_args
642+
self.compile_args += lapacke_pc.compile_args
643+
except DependencyException:
644+
pass
645+
646+
if not self.probe_symbols(self.link_args, lapack_only=True):
647+
self.is_found = False
648+
649+
650+
class NetlibLAPACKSystemDependency(BLASLAPACKMixin, NetlibMixin, SystemDependency):
651+
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
652+
super().__init__(name, environment, kwargs)
653+
self.feature_since = ('1.3.0', '')
654+
self.parse_modules(kwargs)
655+
656+
# First, look for paths specified in a machine file
657+
props = self.env.properties[self.for_machine].properties
658+
if any(x in props for x in ['lapack_includedir', 'lapack_librarydir']):
659+
self.detect_lapack_machine_file(props)
660+
661+
# Then look in standard directories by attempting to link
662+
if not self.is_found:
663+
extra_libdirs: T.List[str] = []
664+
self.detect(extra_libdirs)
665+
666+
if self.is_found:
667+
self.version = 'unknown' # no way to derive this from standard headers
668+
669+
def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[T.List[str]] = None) -> None:
670+
if lib_dirs is None:
671+
lib_dirs = []
672+
if inc_dirs is None:
673+
inc_dirs = []
674+
675+
if self.interface == 'lp64':
676+
libnames = ['lapack']
677+
lapacke_headers = ['lapacke.h']
678+
elif self.interface == 'ilp64':
679+
libnames = ['lapack64', 'lapack']
680+
lapacke_headers = ['lapacke_64.h', 'lapacke.h']
681+
682+
for libname in libnames:
683+
link_arg = self.clib_compiler.find_library(libname, self.env, lib_dirs)
684+
if not link_arg:
685+
continue
686+
687+
if not self.probe_symbols(link_arg, check_lapacke=False, lapack_only=True):
688+
continue
689+
if self.needs_lapacke:
690+
lapacke_in_lapack = self.probe_symbols(link_arg, check_lapacke=True, lapack_only=True)
691+
692+
if self.needs_lapacke and not lapacke_in_lapack:
693+
# We found liblapack and it does not contain LAPACKE symbols, so we need liblapacke
694+
lapacke_libname = libname + 'e'
695+
link_arg_lapacke = self.clib_compiler.find_library(lapacke_libname, self.env, lib_dirs)
696+
if link_arg_lapacke:
697+
link_arg.extend(link_arg_lapacke)
698+
else:
699+
# We didn't find LAPACKE
700+
continue
701+
702+
self.is_found = True
703+
self.link_args += link_arg
704+
if self.needs_lapacke:
705+
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
706+
for hdr in lapacke_headers:
707+
found_header, _ = self.clib_compiler.has_header(hdr, '', self.env, dependencies=[self],
708+
extra_args=incdir_args)
709+
if found_header:
710+
# If we don't get here, we found the library but not the header - this may
711+
# be okay, since projects may ship their own LAPACKE header for portability)
712+
self.compile_args += incdir_args
713+
break
714+
715+
def detect_lapack_machine_file(self, props: dict) -> None:
716+
has_dirs, _dirs = check_blas_machine_file('lapack', props)
717+
if has_dirs:
718+
libdir, incdir = _dirs
719+
self.detect([libdir], [incdir])
720+
505721

506722

507723
class AccelerateSystemDependency(BLASLAPACKMixin, SystemDependency):
@@ -683,9 +899,10 @@ def detect_sdl(self) -> None:
683899
mlog.warning(f'MKLROOT env var set to {mklroot}, but not pointing to an MKL install')
684900

685901
link_arg = self.clib_compiler.find_library('mkl_rt', self.env, lib_dirs)
686-
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
687-
found_header, _ = self.clib_compiler.has_header('mkl_version.h', '', self.env,
688-
dependencies=[self], extra_args=incdir_args)
902+
if link_arg:
903+
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
904+
found_header, _ = self.clib_compiler.has_header('mkl_version.h', '', self.env,
905+
dependencies=[self], extra_args=incdir_args)
689906
if link_arg and found_header:
690907
self.is_found = True
691908
self.compile_args += incdir_args
@@ -734,11 +951,18 @@ def openblas_factory(env: 'Environment', for_machine: 'MachineChoice',
734951
packages['openblas'] = openblas_factory
735952

736953

737-
packages['netlib-blas'] = netlib_factory = DependencyFactory(
738-
'netlib-blas',
739-
[DependencyMethods.PKGCONFIG], #, DependencyMethods.SYSTEM],
740-
#system_class=NetlibSystemDependency,
741-
pkgconfig_class=NetlibPkgConfigDependency,
954+
packages['blas'] = netlib_factory = DependencyFactory(
955+
'blas',
956+
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
957+
pkgconfig_class=NetlibBLASPkgConfigDependency,
958+
system_class=NetlibBLASSystemDependency,
959+
)
960+
961+
packages['lapack'] = netlib_factory = DependencyFactory(
962+
'lapack',
963+
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
964+
pkgconfig_class=NetlibLAPACKPkgConfigDependency,
965+
system_class=NetlibLAPACKSystemDependency,
742966
)
743967

744968

0 commit comments

Comments
 (0)