# Copyright (C) 2014 Josh Willis
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
This module provides a simple interface for loading a shared library via ctypes,
allowing it to be specified in an OS-independent way and searched for preferentially
according to the paths that pkg-config specifies.
"""
from __future__ import print_function
import os, fnmatch, ctypes, sys, subprocess
from ctypes.util import find_library
from collections import deque
try:
from subprocess import getoutput
except ImportError:
from commands import getoutput
[docs]def pkg_config(pkg_libraries):
"""Use pkg-config to query for the location of libraries, library directories,
and header directories
Arguments:
pkg_libries(list): A list of packages as strings
Returns:
libraries(list), library_dirs(list), include_dirs(list)
"""
libraries=[]
library_dirs=[]
include_dirs=[]
# Check that we have the packages
for pkg in pkg_libraries:
if os.system('pkg-config --exists %s 2>/dev/null' % pkg) == 0:
pass
else:
print("Could not find library {0}".format(pkg))
sys.exit(1)
# Get the pck-config flags
if len(pkg_libraries)>0 :
# PKG_CONFIG_ALLOW_SYSTEM_CFLAGS explicitly lists system paths.
# On system-wide LAL installs, this is needed for swig to find lalswig.i
for token in getoutput("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags %s" % ' '.join(pkg_libraries)).split():
if token.startswith("-l"):
libraries.append(token[2:])
elif token.startswith("-L"):
library_dirs.append(token[2:])
elif token.startswith("-I"):
include_dirs.append(token[2:])
return libraries, library_dirs, include_dirs
[docs]def pkg_config_check_exists(package):
return (os.system('pkg-config --exists {0} 2>/dev/null'.format(package)) == 0)
[docs]def pkg_config_libdirs(packages):
"""
Returns a list of all library paths that pkg-config says should be included when
linking against the list of packages given as 'packages'. An empty return list means
that the package may be found in the standard system locations, irrespective of
pkg-config.
"""
# don't try calling pkg-config if NO_PKGCONFIG is set in environment
if os.environ.get("NO_PKGCONFIG", None):
return []
# if calling pkg-config failes, don't continue and don't try again.
try:
FNULL = open(os.devnull, 'w')
subprocess.check_call(["pkg-config", "--version"], stdout=FNULL, close_fds=True)
except:
print("PyCBC.libutils: pkg-config call failed, setting NO_PKGCONFIG=1",
file=sys.stderr)
os.environ['NO_PKGCONFIG'] = "1"
return []
# First, check that we can call pkg-config on each package in the list
for pkg in packages:
if not pkg_config_check_exists(pkg):
raise ValueError("Package {0} cannot be found on the pkg-config search path".format(pkg))
libdirs = []
for token in getoutput("PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs-only-L {0}".format(' '.join(packages))).split():
if token.startswith("-L"):
libdirs.append(token[2:])
return libdirs
[docs]def get_libpath_from_dirlist(libname, dirs):
"""
This function tries to find the architecture-independent library given by libname in the first
available directory in the list dirs. 'Architecture-independent' means omitting any prefix such
as 'lib' or suffix such as 'so' or 'dylib' or version number. Within the first directory in which
a matching pattern can be found, the lexicographically first such file is returned, as a string
giving the full path name. The only supported OSes at the moment are posix and mac, and this
function does not attempt to determine which is being run. So if for some reason your directory
has both '.so' and '.dylib' libraries, who knows what will happen. If the library cannot be found,
None is returned.
"""
dirqueue = deque(dirs)
while (len(dirqueue) > 0):
nextdir = dirqueue.popleft()
possible = []
# Our directory might be no good, so try/except
try:
for libfile in os.listdir(nextdir):
if fnmatch.fnmatch(libfile,'lib'+libname+'.so*') or \
fnmatch.fnmatch(libfile,'lib'+libname+'.dylib*') or \
fnmatch.fnmatch(libfile,libname+'.dll') or \
fnmatch.fnmatch(libfile,'cyg'+libname+'-*.dll'):
possible.append(libfile)
except OSError:
pass
# There might be more than one library found, we want the highest-numbered
if (len(possible) > 0):
possible.sort()
return os.path.join(nextdir,possible[-1])
# If we get here, we didn't find it...
return None
[docs]def get_ctypes_library(libname, packages, mode=None):
"""
This function takes a library name, specified in architecture-independent fashion (i.e.
omitting any prefix such as 'lib' or suffix such as 'so' or 'dylib' or version number) and
a list of packages that may provide that library, and according first to LD_LIBRARY_PATH,
then the results of pkg-config, and falling back to the system search path, will try to
return a CDLL ctypes object. If 'mode' is given it will be used when loading the library.
"""
libdirs = []
# First try to get from LD_LIBRARY_PATH
if "LD_LIBRARY_PATH" in os.environ:
libdirs += os.environ["LD_LIBRARY_PATH"].split(":")
# Next try to append via pkg_config
try:
libdirs += pkg_config_libdirs(packages)
except ValueError:
pass
# Next, if we are in a virtual environment, search inside its '/lib'
if "VIRTUAL_ENV" in os.environ:
libdirs.append(os.path.join(os.environ["VIRTUAL_ENV"], "lib"))
# Note that the function below can accept an empty list for libdirs, in which case
# it will return None
fullpath = get_libpath_from_dirlist(libname,libdirs)
if fullpath is None:
# This won't actually return a full-path, but it should be something
# that can be found by CDLL
fullpath = find_library(libname)
if fullpath is None:
# We got nothin'
return None
else:
if mode is None:
return ctypes.CDLL(fullpath)
else:
return ctypes.CDLL(fullpath,mode=mode)