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+
305327class 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
507723class 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',
734951packages ['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