From 047939738e3c84533b06dc0540fa2f5c1042b069 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Wed, 20 Jul 2022 19:29:02 -0700 Subject: [PATCH] Added LAPACK library detection mechanisms to compile and test library success. Unified @pkgs list into @libsets in find_libs() to group libraries by dependency. Compatibility with the 0.33 release is important across multiple UNIX deployments. The first section discusses how the new code is equivalent to the old code in terms of functionality, while extending the detection to make actual FORTRAN calls to test that the libraries work. In addition, libraries with multiple dependencies are tested together instead _and_ independently, instead of _only_ independently as previously designed. This commit is documented in sections 1. Overview of the current implementaiton 2. Design goals for the new implementation 3. Devel::CheckLib limitations 4. Implementation details 5. Validation ORIGINAL IMPLEMENTATION ======================= The previous code was as follows. The indented lines are original code, unindented lines are commentary. There are 3 major parts to the original library detection mechanism: # Part 1: This section tries to find LAPACK by searching # for -llapack and -lblas. If neither PkgConfig->find(), # ExtUtils::PkgConfig->libs, nor `pkg-config` can find the # libraries, then '-L/usr/lib/atlas -llapack -lblas -latlas' # is forced as a default: my @pkgs = qw(lapack blas); our $libs0 = ( eval {require PkgConfig; join ' ', map PkgConfig->find($_)->get_ldflags, @pkgs} || eval {require ExtUtils::PkgConfig; join ' ', map ExtUtils::PkgConfig->libs($_), @pkgs} || `pkg-config @pkgs --libs` || '-L/usr/lib/atlas -llapack -lblas -latlas' # Finally, any FORTRAN libraries are added to $lib0: ) . ' ' . ExtUtils::F77->runtime; # The next anonymous code block is in two parts: our $inc = '-DF77_USCORE='. (ExtUtils::F77->trail_ ? '_' : ''); { # Part 2a: use check_lib() to strip any libraries added above # (lapack, blas) that fail to link as tested by check_lib(). # They may fail to link for one of two reasons. Either (a) they # do not exist on the host, or (b) that fail to link independently # (ie, lapack may depend on blas or vice-versa): # work around Devel::CheckLib not doing Text::ParseWords::shellwords use Text::ParseWords qw(shellwords); my @libpath = grep -d, map {my $r=$_;$r=~s/^-L//?$r:()} my @sw = shellwords $libs0; my @lib = grep check_lib(lib=>$_), map {my $r=$_;$r=~s/^-l//?$r:()} @sw; $libs0 = join ' ', (map qq{-L"$_"}, @libpath), (map "-l$_", @lib); # Part 2b: Emit a warning and `exit 0` if compilation fails. # This is a sticky issue, because Devel::CheckLib has some very old # known bugs. Notably, RT75803: (10 years ago): "Allow function # option to be used without lib option". Simply stated, if 'lib=>' # is not passed to check_lib_or_exit() then it will do nothing, # and return success. PDL::LinearAlgebra commit 1d25c1b8 _does_ # have a 'lib=>' option, but _it was removed_ in e1b65760, at # which point the following code may as well have been deleted: my $f77_uscore = (ExtUtils::F77->trail_ ? '_' : ''); check_lib_or_exit( ldflags => $libs0, header => [($^O =~ /MSWin/ ? 'float.h' : ()), qw(stdio.h math.h)], function => ... ) } So we can distill the above code down to two items: 1. Add the FORTRAN libs determined by ExtUtils::F77 2. Add -llapack, -lblas, and -latlas if `pkg-config` cannot find them. NEW DESIGN ========== The new design intends to satisfy the following: 1. Define an array of reasonable library combinations that are compatible with existing UNIX deployments 2. By default, the first successful library is selected. - A library is "successful" if it can compile and link FORTRAN functions. 3. Allow users who have multiple LAPACK/BLAS implementations to select a specific implementation if they wish. 4. Provide adequate debug output for troubleshooting test reports. 5. Linux and other UNIXen that support compling and linking with `-l` and `-L` linker options are treated the same. While there may be special cases for libraries (openblas vs atlas), there is no special-case for UNIX-like operating systems. 6. Library detection/selection for Windows will not be modified. 7. Safety: Use shell_quote/shellwords to make sure library paths are clean. BUGS IN Devel::CheckLib ======================= (They are commented in the code as well, so pasted here for good visibility) In addition, Devel::CheckLib fails to provide `argc` to main() before about v1.11, so a Devel::CheckLib version requirement was added. IMPLEMENTATION ============== We have written a wrapper `new_check_lib()` around Devel::CheckLib::check_lib() to work around the issues above. This is the comment in the code: Configurability with environment variables: 1. PDL_LA_LDFLAGS='-L -l...': The user can set this to specify their own linker flags (for example, to link with the Intel MKL library). 2. PDL_LA_DEBUG=1: Pass `debug=>1` to Devel::CheckLib::check_lib() 3. PDL_LA_LIB_INDEX=: Select a LAPACK/BLAS library. When Makefile.PL runs it emits output like the following. The user can specify PDL_LA_LIB_INDEX= to choose the implementation that succeeded. This defaults to 0 if not specified: [0] found (openblaso): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -lopenblaso missing (openblas_openmp) [1] found (tatlas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -L/usr/lib64/atlas/ -lgfortran -lm -ltatlas [2] found (openblasp): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -lopenblasp missing (openblas_pthreads) [3] found (openblas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -lopenblas missing (openblas_serial) [4] found (satlas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -L/usr/lib64/atlas/ -lgfortran -lm -lsatlas [5] found (atlas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -L/usr/lib64/atlas/ -lgfortran -lm -lsatlas missing (lapack_atlas) [6] found (lapack): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -llapack missing (blas) [7] found (lapack blas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -llapack -lblas [8] found (lapack blas atlas): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -L/usr/lib64/atlas/ -lgfortran -lm -llapack -lblas -lsatlas [9] found (mkl_rt): -Llibgfortran.a -L/usr/lib -L/opt/intel/compilers_and_libraries_2020.4.304/linux/mkl/lib/intel64_lin/ -lgfortran -lm -lmkl_rt Safety: - -L flags are passed before -l flags so the linker can know the paths before hunting for the libraries. - Any linker options that match neither `-l` nor `-L` are passed verbatim. - Options are handled as lists except when generating an LDFLAGS-style output, in which case `shell_quote` and `shellwords` are used to properly parse and escape the input and output. The library detection code at the top of `Makefile.PL` is now a single line: our $libs0 = find_libs(ExtUtils::F77->runtime, $ENV{PDL_LA_LDFLAGS}); The list of available library combinations are in `@libsets` atop of `find_libs()`: my @libsets = ( # Empty case if PDL_LA_LDFLAGS satisfies the build [], # Threaded OpenBLAS/ATLAS ['openblaso'], ['tatlas'], # [ ... many others ...] # And finally the options from the original implementation if none of the above options match: ['blas'], ['lapack', 'blas'], ['lapack', 'blas', 'atlas'], # Experimental Intel Math Kernel Library support: ['mkl_rt'], ); Notable changes: FORTRAN check function: Added library code to test `dgebal_` existence Added note about compile problems when missing a leading empty line Added 'return 0' to the end Calls to ExtUtils::F77->trail_ ? '_' : '' were replaced with $f77_uscore Notes: The windows library detection was not modified in this patch. The 'shellwords' work-around was not changed, but the diff looks that way VALIDATION ========== The following library combinations pass `make test` on my Oracle Linux 8 deployment: for i in {0..8}; do export i; echo === $i; (PDL_LA_LIB_INDEX=$i perl Makefile.PL && make -j100 && make test) 2>&1 | grep -P 'will use|ok$' -A1; done 1. -Llibgfortran.a -L/usr/lib -lgfortran -lm -lopenblaso 2. -Llibgfortran.a -L/usr/lib -L/usr/lib64/atlas/ -lgfortran -lm -ltatlas 3. -Llibgfortran.a -L/usr/lib -lgfortran -lm -lopenblasp 4. -Llibgfortran.a -L/usr/lib -lgfortran -lm -lopenblas 5. -Llibgfortran.a -L/usr/lib -L/usr/lib64/atlas/ -lgfortran -lm -lsatlas 6. -Llibgfortran.a -L/usr/lib -L/usr/lib64/atlas/ -lgfortran -lm -lsatlas 7. -Llibgfortran.a -L/usr/lib -lgfortran -lm -llapack 8. -Llibgfortran.a -L/usr/lib -lgfortran -lm -llapack -lblas 9. -Llibgfortran.a -L/usr/lib -L/usr/lib64/atlas/ -lgfortran -lm -llapack -lblas -lsatlas t/1.t ....... ok t/cgtsv.t ... ok t/gtsv.t .... ok t/legacy.t .. ok All tests successful. In addition, the following Ubuntu/Debian distributions have been tested, all you need to install is `libopenblas-dev` to build LAPACK+BLAS successfully: bionic bionic-i386 bullseye bullseye-i386 buster buster-i386 focal xenial xenial-i386 --- Makefile.PL | 418 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 379 insertions(+), 39 deletions(-) diff --git a/Makefile.PL b/Makefile.PL index 77708ca..1050863 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -32,26 +32,14 @@ EOF our %ldloadlibs = ($^O =~ /MSWin/ && $Config{cc} eq 'cl') ? (LDLOADLIBS => 'oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib netapi32.lib uuid.lib wsock32.lib mpr.lib winmm.lib version.lib odbc32.lib odbccp32.lib msvcrt.lib ../lapack/libacml.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\lib\msvcrt.lib" ') : (); -my @pkgs = qw(lapack blas); -our $libs0 = ( - eval {require PkgConfig; join ' ', map PkgConfig->find($_)->get_ldflags, @pkgs} || - eval {require ExtUtils::PkgConfig; join ' ', map ExtUtils::PkgConfig->libs($_), @pkgs} || - find_macos_libs() || - find_libs() || - `pkg-config @pkgs --libs` || - '-L/usr/lib/atlas -llapack -lblas -latlas' -) . ' ' . ExtUtils::F77->runtime; -{ - # work around Devel::CheckLib not doing Text::ParseWords::shellwords - my @libpath = grep -d, map {my $r=$_;$r=~s/^-L//?$r:()} my @sw = shellwords $libs0; - my @lib = grep check_lib(lib=>$_), map {my $r=$_;$r=~s/^-l//?$r:()} @sw; - $libs0 = join ' ', (map qq{-L"$_"}, @libpath), (map "-l$_", @lib); - check_lib_or_exit( - ldflags => $libs0, - header => [($^O =~ /MSWin/ ? 'float.h' : ()), qw(stdio.h math.h)], - function => $fortran_checklib, - ); -} +sub _shell_quote { join ' ', map /\s/ ? qq{"$_"} : $_, @_ } + +our $libs0 = find_libs(ExtUtils::F77->runtime, grep defined, $ENV{PDL_LA_LDFLAGS}); + +# Prevent makemaker from complaining about LIBS arguments that are +# neither -L nor -l. We (ab)use LDFLAGS for this purpose: +$ldloadlibs{LDFLAGS} .= ' ' . _shell_quote(grep { m!^/! } shellwords($libs0)); +$libs0 = _shell_quote(grep { !m!^/! } shellwords($libs0)); my $preop = '$(PERLRUNINST) -MPDL::Core::Dev -e pdlpp_mkgen $(DISTVNAME)'; my $package_name = "PDL::LinearAlgebra"; @@ -96,31 +84,383 @@ WriteMakefile( ); sub find_libs { - return if $^O !~ /linux/i; - # in performance order based on libraries I've used + my @args = @_; + my $existing_ldflags = _shell_quote(map { shellwords($_) } @args); + + # Test each library that was passed and make sure it compiles: + $existing_ldflags = filter_libs($existing_ldflags); + + return $existing_ldflags if $^O !~ /linux/i; + + my @existing_dirs = _L($existing_ldflags); + my @existing_libs = _l($existing_ldflags); + my @existing_ld_other = _ld_other($existing_ldflags); + + # In performance order based on libraries I've used # for xnec2c in Ubuntu, Debian, SuSE, CentOS, etc. # See comments here for detail: # https://github.com/KJ7LNW/xnec2c/blob/master/src/mathlib.c#L29 - my @libs = qw/ - openblaso - openblas_openmp - openblasp - openblas_pthreads - openblas - openblas_serial - mkl_rt/; - for my $l (@libs) { - return "-l$l" if (check_lib(lib => $l)); + my @libsets = ( + # Empty case if PDL_LA_LDFLAGS satisfies the build + [], + + # Threaded OpenBLAS/ATLAS + ['openblaso'], + ['openblas_openmp'], + ['tatlas'], + ['openblasp'], + ['openblas_pthreads'], + + # Serial OpenBLAS/ATLAS + ['openblas'], + ['openblas_serial'], + ['satlas'], + ['atlas'], + + # Remaining Guesses + ['lapack'], + ['blas'], + ['lapack', 'blas'], + ['lapack', 'blas', 'atlas'], + ['lapack_atlas'], + + # Intel Math Kernel Library + # + # Set one of these environment variables to select a different threading model: + # MKL_THREADING_LAYER=SEQUENTIAL + # MKL_THREADING_LAYER=INTEL + # MKL_THREADING_LAYER=GNU + # MKL_THREADING_LAYER=PGI + # (see page 30: + # https://www.intel.com/content/dam/develop/external/us/en/documents/onemkl-developerguide-linux.pdf ) + # + # Setting this variable at configure time isn't critical, but you will want + # to choose your desired threading model at runtime in your + # PDL::LinearAlgebra application: + # + # ~] export MKL_THREADING_LAYER=INTEL ; ./my-super-awesome-benchmark.pl + # + ['mkl_rt'], + ); + + printf STDERR "Checking LAPACK libraries...\n"; + + my @found; + my $found_idx = -1; + for my $ls (@libsets) { + + if ($ENV{PDL_LA_DEBUG}) + { + my $sep = '=' x 80; + print STDERR "\n$sep\n"; + my $name = "@$ls" || '(default)'; + print STDERR "== Testing library set: $name\n"; + print STDERR "$sep\n"; + } + + # Run pkgconfig on each lib individually so if pkgconfig returns + # an empty list then -l will be used: + my (@libs, @dirs, @ld_other); + foreach my $l (@$ls) { + my ($pc, @pclibs); + + # Threaded/serial ATLAS on RHEL is special: + if ($l =~ /[st]atlas/) { + $pc = pkgconfig('atlas'); + @pclibs = ($l); + } + else { + $pc = pkgconfig($l); + + @pclibs = _l($pc); + + # If pkgconfig does not return any libs, then specify the -l itself: + push @pclibs, $l if !@pclibs; + } + + push @libs, @pclibs; + push @dirs, _L($pc); + push @ld_other, _ld_other($pc); + } + + # Existing libs must go at the end of the list or CheckDir will put the + # LAPACK libs we are testing before the .c file in the compile string and + # libraries will be missing: + push @libs, @existing_libs; + push @dirs, @existing_dirs; + push @ld_other, @existing_ld_other; + + die "BUG: \@libs is empty?" if !@libs; + + # Try the -l version: + my $ldflags = _merge_ld_flags(\@dirs, \@libs, \@ld_other); + + # Also try to find lib.so.X files in the -L search path; this works + # for older debian/ubuntu builds: + my $ldflags_so = _merge_ld_flags(\@dirs, find_so(\@dirs, \@libs), + \@ld_other); + + my %common_args = ( + debug => $ENV{PDL_LA_DEBUG}, + header => [ ( $^O =~ /MSWin/ ? 'float.h' : () ), qw(stdio.h math.h) ], + function => $fortran_checklib); + + if (new_check_lib(%common_args, ldflags => $ldflags)) { + $found_idx++; + print STDERR " [$found_idx] found (@$ls): $ldflags\n"; + + push @found, $ldflags; + } + elsif (new_check_lib(%common_args, ldflags => $ldflags_so)) { + $found_idx++; + print STDERR " [$found_idx] found (@$ls): $ldflags_so\n"; + + push @found, $ldflags_so; + } + else + { + print STDERR " missing (@$ls)\n" if @$ls; + } + } + + die "ERROR: Cannot find LAPACK library that links without error!\n" if !@found; + + my $selected_lib_idx = $ENV{PDL_LA_LIB_INDEX} || 0; + + my $selected_ldflags = $found[$selected_lib_idx]; + + print STDERR qq{ + +If you prefer a different LAPACK implementation you can select it by: + +1. Specifying PDL_LA_LDFLAGS that are sufficient to run FORTRAN LAPACK functions: + ~# PDL_LA_LDFLAGS='-lsomething -L/somewhere -lelse' perl $0 +2. Specifying the index shown above like '[2] found (...)' as follows: + ~# PDL_LA_LIB_INDEX=2 perl $0 + +Distributions like Debian and Ubuntu may be configured using `alternatives`: + ~# update-alternatives --config libblas.so.3-x86_64-linux-gnu + ~# update-alternatives --config liblapack.so.3-x86_64-linux-gnu +You may need to re-run `perl $0` after changing your LAPACK selection with +alternatives. + +You may also specify PDL_LA_DEBUG=1 to enable library detection debugging, +however this is very noisy. + +PDL::LinearAlgebra will use these LAPACK libraries (index $selected_lib_idx): + $selected_ldflags +}; + + print STDERR "\nNOTICE: Intel MKL Library support is experimental!\n\n" if ($selected_ldflags =~ /mkl_/); + + return $selected_ldflags; +} + +sub pkgconfig { + my @pkgs = @_; + my $ret = + eval {require PkgConfig; join ' ', map PkgConfig->find($_)->get_ldflags, @pkgs} || + eval {require ExtUtils::PkgConfig; join ' ', map ExtUtils::PkgConfig->libs($_), @pkgs} || + sub { my $p = `pkg-config --libs @pkgs 2>/dev/null` ; chomp $p; return $p }->(); + + print STDERR "pkg-config found libs for '@pkgs': $ret\n" if $ENV{PDL_LA_DEBUG} && $ret; + + return $ret; +} + +# Strip -l, return the libs as an array. Takes an array or scalar: +sub _l { + return map { s/^-l//; $_ } grep { /^-l/ } map { shellwords($_) } @_; +} + +# Strip -L, return the dirs as an array. Takes an array or scalar: +sub _L { + return grep { -d $_ } map { s/^-L//; $_ } grep { /^-L/ } map { shellwords($_) } @_; +} + +# Return any ldflags that are neither -L nor -l as an array. +# Takes an array or scalar: +sub _ld_other { + return grep { !/^-[lL]/ } map { shellwords($_) } @_; +} + +# Take 3 array refs: +# - lib search paths +# - lib names +# - linker args that are neither -L nor -l +# Return a string of "-L ... -Xfoo ... -l ..." +sub _merge_ld_flags { + my ($dirs, $libs, $ld_other) = @_; + + $dirs //= []; + $libs //= []; + $ld_other //= []; + + $dirs = _shell_quote(map {"-L$_"} @$dirs); + + # If the libs start with a / then put them in ld_other, otherwise add -l in front: + my @libfiles = grep { m!^/! } @$libs; + my @libnames = grep { !m!^/! } @$libs; + + $libs = _shell_quote(map { "-l$_" } @libnames); + $ld_other = _shell_quote(@$ld_other, @libfiles); + + # Join the ldflags only if they have content: + my $ldflags = join (' ', grep { $_ } ($dirs, $ld_other, $libs)); + + # RT#109028 work-around, however join+_shell_quote probably did the right thing: + $ldflags =~ s/^\s+|\s+$//g; + + return $ldflags; +} + +# The original Devel::CheckLib::check_lib() function tests libraries, but there +# are several noteworthy deficiencies (bugs on RT) to be aware of: +# +# RT#60176: (12 years ago) no way to test with dependent libraries +# RT#75803: (10 years ago) Allow function option to be used without lib option +# RT#109028: (7 years ago) Problems with trailing space in ldflags +# +# Given the age of these bugs, I don't expect a fix any time soon. +# +# Our new_check_lib() attempts to work around these problems as follows. Maybe +# a new CPAN module should be released for this, but that sounds like a bit too +# much maintenance...anyone up for that? Ahem. Anyway: +# +# To address #60176: No way to test with dependent libraries +# The issue here is that check_lib() always tests libraries one at a time +# instead of trying to test them together. To solve this, we only pass the +# first library to `lib=>`; all remaining libs are passed as ldflags. +# This is necessary because all dependent libs must be on the cmdline to +# work together (like blas+lapack). Note that `lib=>` must be defined or +# check_lib() will silently _NOT_ run the `function=>$f` argument! +# +# In this implementation we assume all libs are dependent, so call +# new_check_lib iteratively if you want to test one at a time. +# +# To address 109028#: Problems with trailing space in ldflags +# $ldflags =~ s/^\s+|\s+$//g; +# +# To address 75803#: Allow function option to be used without lib option +# This would be nice to fix, but we don't really need it for the +# PDL::LinearAlgebra module. In our case we _always_ have a library to +# test or we wouldn't call the function. +# +# However, should someone ever need to evaluate a C `function=>` without +# trying a library link, then one could pass `lib=>'c'` to check_lib() +# hoping that everyone has a libc library. (This probably doesn't work in +# windows, so maybe it should pass kernel32.dll?) +# +# In addition to those above: +# We have found that check_lib()'s `libpath=>` option will (sometimes?) +# place the -L options _after_ the -l options, possibly causing the +# compiler to fail to find the -l. To address this we pass -L options +# on the ldflags line instead of `libpath=>`. +# +# Even in the midst of these deficiencies, Devel::CheckLib does a good job +# with the intracacies of different compilers, so no sense re-inventing it. +# The original check_lib() funciton is the core of this wrapper. +# +# Note that new_check_lib() parses the 'lib', 'libpath', and 'ldflags' +# arguments so 'ldflags' is the only necessary argument. If all or any of +# these are supplied then they will be merged. +sub new_check_lib { + my (%args) = @_; + + my (@libs, @dirs, @ld_other); + + # Take a single lib or a arrayref of _dependent_ libs: + @libs = @{ $args{lib} } if defined($args{lib}) && ref($args{lib}) eq 'ARRAY'; + push @libs, $args{lib} if defined($args{lib}) && !ref($args{lib}); + + # Separate libs, dirs, and other options: + if (defined($args{ldflags})) { + my $ldflags = delete $args{ldflags}; + push @libs, _l($ldflags); + push @dirs, _L($ldflags); + push @ld_other, _ld_other($ldflags); + } + + # work around possible libpath -L re-ordering by moving libpath + # to @dirs: + if (defined($args{libpath})) { + push @dirs, @{ delete $args{libpath} } } - return; + + # Split out dependent libs in to first and other (RT#60176 workaround): + my ($first_lib, @other_libs) = @libs; + $args{lib} = $first_lib; + + warn "No lib passed and check_lib will return a (possibly) false success" if !$args{lib}; + + # rebuild $ldflags placing @dirs in front of @other_libs. + $args{ldflags} = _merge_ld_flags(\@dirs, \@other_libs, \@ld_other); + + print STDERR "check_lib($args{lib}): $args{ldflags}\n" if $ENV{PDL_LA_DEBUG}; + + return check_lib(%args); +} + +# Filter $ldflags and return $ldflags only with libs that compile successfully. +# This only filters -l libraries, so directories and other flags are +# passed verbatim. +sub filter_libs { + my $ldflags = shift; + + my @libs = _l($ldflags); + my @dirs = _L($ldflags); + my @ld_other = _ld_other($ldflags); + + my @goodlibs; + foreach my $l (@libs) + { + if (!new_check_lib(ldflags => _merge_ld_flags(\@dirs, [ $l ], \@ld_other))) + { + warn "filter_libs: libary -l$l was not found, excluding" + } + else + { + push @goodlibs, $l + } + } + + return _merge_ld_flags(\@dirs, \@goodlibs, \@ld_other); } -sub find_macos_libs { - return if $^O ne 'darwin'; - my $pref = `brew --prefix lapack`; - return if !$pref; - chomp $pref; - qq{-L"$pref/lib" -llapack -lblas}; +sub find_so { + my ($dirs, $libs) = @_; + + my @found; + + my %files; + foreach my $d (@$dirs) + { + opendir(my $dh, $d) || die "opendir: $d: $!"; + $files{$d} = [ sort grep { -f "$d/$_" && /\.so\.\d+$/ } readdir($dh) ]; + } + + foreach my $l (@$libs) + { + my $found_lib; + foreach my $d (@$dirs) + { + my @possible_libs = map { "$d/$_" } grep { /^lib\Q$l\E\.so/ } @{ $files{$d} }; + + next if (!@possible_libs); + + print STDERR "possible $l path: @possible_libs\n" if $ENV{PDL_LA_DEBUG}; + + # Keep the first occurance: + push @found, $possible_libs[0]; + $found_lib = 1; + last; + } + + # Keep the original if we didn't find a .so: + push @found, $l if (!$found_lib); + } + + return \@found; } my @pd_srcs;