Skip to content

Commit

Permalink
Set libgmt path with GMT_LIBRARY_PATH from environ (GenericMappingToo…
Browse files Browse the repository at this point in the history
…ls#177)

LibGMT no longer takes a pathname as input. The library path is either
just the file name, or the directory is taken from the GMT_LIBRARY_PATH
environment variable. This allows users to direct the GMT/Python to
libgmt. It's useful to test against a local build of GMT trunk or
resolving path issues without resorting to LD_LIBRARY_PATH.

Replace section on LD_LIBRARY_PATH with GMT_LIBRARY_PATH in install
docs. Make it clear that this should be a last resort.

Fixes GenericMappingTools#172 and fixes GenericMappingTools#165
  • Loading branch information
leouieda committed Apr 24, 2018
1 parent 65b9341 commit 51c76d6
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 27 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ env:
# TWINE_PASSWORD to deploy to PyPI
- secure: "md4fgPt9RC/sCoN5//5PcNHLUd9gWQGewV5hFpWW88MRTjxTng1Zfs8r7SqlF2AkEEepFfyzq0BEe9c3FMAnFbec3KmqdlQen4V8xDbLrcTlvkPlTrYGbAScUvdhhqojB//hMHoTD4KvxAv9CiUwFBO4hCMmj2buWHUbV9Ksu5WCW9mF/gkt/hIuYAU6Mbwt8PiYyMgUpzMHO1vruofcWRaVnvKwmBqHB0ae86D4/drpwn4CWjlM12WUnphT2bssiyPkw24FZtCN6kPVta6bLZKBxu0bZpw2vbXuUG+Yh19Q4mp8wNYT3XSHJf8Hl5LfujF48+cLWu+6rlCkdcelyVylhWLFc3rGOONAv4G8jWW2yNSz/bLQfJnMpd81fQEu5eySmFxB7mdB0uyKpvIG1jMJQ73LlYKakKLAPdYhMFyQAHoX9gvCE3S4QR95DBMi5gM/pZubOCcMLdjPHB5JKpJHSjxbOzyVwgmsUIEgd5Bi2vZvvYQXn1plk4xpQ3PhXc+/gi33bzY89mKcfOn0HJ2pD1vLqDXRCBsMCakoLZ0JB/6bacaz4FngbsGWuQ+I1cz20lJGL/MSi9bW1G7Uoidt3GXXWDmXrWt70vIXlLIxr8XV0Mu/rPbauGgWE+ZSYEfvdM5sP+FNF7vQ5de+Fkvzg5Z3tTfR+O1W+d7+vM4="
- TWINE_USERNAME=Leonardo.Uieda
- LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH
- COVERAGE=false
- BUILD_DOCS=false
- DEPLOY_DOCS=false
Expand Down
16 changes: 11 additions & 5 deletions doc/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,19 @@ Test your installation by running the following inside a Python interpreter::
Finding the GMT shared library
------------------------------

You might have to set the ``LD_LIBRARY_PATH``
variable so that Python can find the GMT shared library ``libgmt``.
Sometimes, GMT/Python will be unable to find the correct version of the GMT
shared library. This can happen if you have multiple versions of GMT installed.

If you installed GMT using conda, place the following in your ``~/.bashrc``
file::
You can tell GMT/Python exactly where to look for ``libgmt`` by setting the
``GMT_LIBRARY_PATH`` environment variable. This should be set to the directory
where ``libgmt.so`` (or ``.dylib``) is found. **Only use this as a last
resort**. Setting the path in this way means that GMT/Python will not be able
to easily find the correct ``libgmt`` when you're changing conda environments.

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/anaconda3/envs/gmt-python/lib
If you installed GMT using conda using the instructions above and you're using
Linux or Mac, place the following in your ``~/.bashrc`` file::

export GMT_LIBRARY_PATH=$HOME/anaconda3/envs/gmt-python/lib

You should change ``$HOME/anaconda3`` to wherever you installed Anaconda (this
is the default for Linux).
12 changes: 5 additions & 7 deletions gmt/clib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ class LibGMT(): # pylint: disable=too-many-instance-attributes
``GMTVersionError`` exception will be raised if the minimum version
requirements aren't met.
Parameters
----------
libname : str
The name of the GMT shared library **without the extension**. Can be a
full path to the library or just the library name.
By default, will look for the shared library in the directory specified by
the environment variable ``GMT_LIBRARY_PATH``. If the variable is not set,
will let ctypes try to find the library.
Raises
------
Expand Down Expand Up @@ -105,10 +103,10 @@ class LibGMT(): # pylint: disable=too-many-instance-attributes
'uint32': 'GMT_UINT',
}

def __init__(self, libname='libgmt'):
def __init__(self):
self._logfile = None
self._session_id = None
self._libgmt = load_libgmt(libname)
self._libgmt = load_libgmt()

@property
def current_session(self):
Expand Down
55 changes: 43 additions & 12 deletions gmt/clib/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Miscellaneous utilities
"""
import os
import sys
import ctypes

Expand Down Expand Up @@ -240,18 +241,19 @@ def _as_array(vector):
return np.asarray(vector)


def load_libgmt(libname='libgmt'):
def load_libgmt(env=None):
"""
Find and load ``libgmt`` as a :py:class:`ctypes.CDLL`.
If not given the full path to the library, it must be in standard places or
by discoverable by setting the environment variable ``LD_LIBRARY_PATH``.
By default, will look for the shared library in the directory specified by
the environment variable ``GMT_LIBRARY_PATH``. If it's not set, will let
ctypes try to find the library.
Parameters
----------
libname : str
The name of the GMT shared library **without the extension**. Can be a
full path to the library or just the library name.
env : dict or None
A dictionary containing the environment variables. If ``None``, will
default to ``os.environ``.
Returns
-------
Expand All @@ -265,20 +267,49 @@ def load_libgmt(libname='libgmt'):
couldn't access the functions).
"""
libpath = get_clib_path(env)
try:
libgmt = ctypes.CDLL('.'.join([libname, clib_extension()]))
libgmt = ctypes.CDLL(libpath)
check_libgmt(libgmt)
except OSError as err:
msg = ' '.join([
"Couldn't find the GMT shared library '{}'.".format(libname),
"Have you tried setting the LD_LIBRARY_PATH environment variable?",
"\nOriginal error message:",
"\n\n {}".format(str(err)),
msg = '\n'.join([
"Couldn't find the GMT shared library '{}'.".format(libpath),
"Original error message:",
"{}".format(str(err)),
])
raise GMTCLibNotFoundError(msg)
return libgmt


def get_clib_path(env):
"""
Get the path to the libgmt shared library.
Determine the file name and extension and append to the path set by
``GMT_LIBRARY_PATH``, if any.
Parameters
----------
env : dict or None
A dictionary containing the environment variables. If ``None``, will
default to ``os.environ``.
Returns
-------
libpath : str
The path to the libgmt shared library.
"""
libname = '.'.join(['libgmt', clib_extension()])
if env is None:
env = os.environ
if 'GMT_LIBRARY_PATH' in env:
libpath = os.path.join(env['GMT_LIBRARY_PATH'], libname)
else:
libpath = libname
return libpath


def clib_extension(os_name=None):
"""
Return the extension for the shared library for the current OS.
Expand Down
24 changes: 22 additions & 2 deletions gmt/tests/test_clib.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from ..clib.core import LibGMT
from ..clib.utils import clib_extension, load_libgmt, check_libgmt, \
dataarray_to_matrix
dataarray_to_matrix, get_clib_path
from ..exceptions import GMTCLibError, GMTOSError, GMTCLibNotFoundError, \
GMTCLibNoSessionError, GMTInvalidInput, GMTVersionError
from ..helpers import GMTTempFile
Expand Down Expand Up @@ -63,8 +63,28 @@ def test_load_libgmt():

def test_load_libgmt_fail():
"Test that loading fails when given a bad library path."
env = {'GMT_LIBRARY_PATH': 'not/a/real/path'}
with pytest.raises(GMTCLibNotFoundError):
load_libgmt('some/wrong/path/libgmt')
load_libgmt(env=env)


def test_get_clib_path():
"Test that the correct path is found when setting GMT_LIBRARY_PATH."
# Get the real path to the library first
with LibGMT() as lib:
libpath = lib.info['library path']
libdir = os.path.dirname(libpath)
# Assign it to the environment variable but keep a backup value to restore
# later
env = {'GMT_LIBRARY_PATH': libdir}

# Check that the path is determined correctly
path_used = get_clib_path(env=env)
assert os.path.samefile(path_used, libpath)
assert os.path.dirname(path_used) == libdir

# Check that loading libgmt works
load_libgmt(env=env)


def test_check_libgmt():
Expand Down

0 comments on commit 51c76d6

Please sign in to comment.