Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve how PyGMT finds the GMT library #702

Merged
merged 23 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2cc5a0d
Improve how PyGMT find the GMT library
seisman Oct 27, 2020
50dae17
Check library names for FreeBSD
seisman Nov 29, 2020
cbc4eb5
loading works even though the given library path is invalid
seisman Nov 29, 2020
c9e142e
Fix a lint issue
seisman Nov 29, 2020
b1c9a6a
Merge branch 'master' into gmt-library
seisman Nov 30, 2020
c43f9bb
Merge branch 'master' into gmt-library
seisman Dec 1, 2020
131676a
Merge branch 'master' into gmt-library
seisman Dec 16, 2020
5ad4f56
Merge branch 'master' into gmt-library
seisman Dec 23, 2020
7712bcc
Merge branch 'master' into gmt-library
seisman Dec 23, 2020
5e28f31
Merge branch 'master' into gmt-library
seisman Dec 29, 2020
671db53
Merge branch 'master' into gmt-library
seisman Dec 29, 2020
54f9664
Merge branch 'master' into gmt-library
seisman Jan 2, 2021
7108301
Merge branch 'master' into gmt-library
seisman Jan 8, 2021
5f17f1e
Use generator yield in clib_full_names function
weiji14 Jan 15, 2021
11c2c9b
Merge branch 'master' into gmt-library
weiji14 Jan 20, 2021
20728f9
Refactor test_load_libgmt_with_a_bad_library_path to use monkeypatch
weiji14 Jan 20, 2021
243ad13
Monkeypatch test to check that GMTCLibNotFoundError is raised properly
weiji14 Jan 20, 2021
ba5a715
Merge branch 'master' into gmt-library
seisman Feb 2, 2021
c27b565
Merge branch 'master' into gmt-library
seisman Feb 12, 2021
dbfd7e2
Check if the GMT shared library exists in GMT_LIBRARY_PATH
seisman Feb 12, 2021
6769dc3
Format codes
seisman Feb 12, 2021
c3887ce
Change Returns to Yields
seisman Feb 12, 2021
fbad831
Merge branch 'master' into gmt-library
weiji14 Feb 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions pygmt/clib/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
import ctypes
import os
import subprocess as sp
import sys
from ctypes.util import find_library

Expand All @@ -32,9 +33,10 @@ def load_libgmt():
couldn't access the functions).

"""
lib_fullnames = clib_full_names()
lib_fullnames = []
error = True
for libname in lib_fullnames:
for libname in clib_full_names():
lib_fullnames.append(libname)
try:
libgmt = ctypes.CDLL(libname)
check_libgmt(libgmt)
Expand Down Expand Up @@ -75,7 +77,7 @@ def clib_names(os_name):
elif os_name.startswith("freebsd"): # FreeBSD
libnames = ["libgmt.so"]
else:
raise GMTOSError(f'Operating system "{sys.platform}" not supported.')
raise GMTOSError(f'Operating system "{os_name}" not supported.')
return libnames


Expand All @@ -97,17 +99,36 @@ def clib_full_names(env=None):
"""
if env is None:
env = os.environ

libnames = clib_names(os_name=sys.platform) # e.g. libgmt.so, libgmt.dylib, gmt.dll
libpath = env.get("GMT_LIBRARY_PATH", "") # e.g. $HOME/miniconda/envs/pygmt/lib

lib_fullnames = [os.path.join(libpath, libname) for libname in libnames]
# Search for DLLs in PATH if GMT_LIBRARY_PATH is not defined [Windows only]
if not libpath and sys.platform == "win32":
# list of libraries paths to search, sort by priority from high to low
# Search for libraries in GMT_LIBRARY_PATH if defined.
if libpath:
for libname in libnames:
yield os.path.join(libpath, libname)

# Search for the library returned by command "gmt --show-library"
try:
lib_fullpath = sp.check_output(
["gmt", "--show-library"], encoding="utf-8"
).rstrip("\n")
assert os.path.exists(lib_fullpath)
yield lib_fullpath
except (FileNotFoundError, AssertionError): # command not found
pass

# Search for DLLs in PATH (done by calling "find_library")
if sys.platform == "win32":
for libname in libnames:
libfullpath = find_library(libname)
if libfullpath:
lib_fullnames.append(libfullpath)
return lib_fullnames
yield libfullpath

# Search for library names in the system default path [the lowest priority]
for libname in libnames:
yield libname


def check_libgmt(libgmt):
Expand All @@ -133,10 +154,5 @@ def check_libgmt(libgmt):
functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"]
for func in functions:
if not hasattr(libgmt, "GMT_" + func):
msg = " ".join(
[
"Error loading libgmt.",
"Couldn't access function GMT_{}.".format(func),
]
)
msg = f"Error loading libgmt. Couldn't access function GMT_{func}."
raise GMTCLibError(msg)
35 changes: 22 additions & 13 deletions pygmt/tests/test_clib_loading.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
Test the functions that load libgmt
"""
import os
import subprocess
import sys

import pytest
from pygmt.clib.loading import check_libgmt, clib_names, load_libgmt
Expand All @@ -19,20 +20,26 @@ def test_load_libgmt():
check_libgmt(load_libgmt())


def test_load_libgmt_fail():
"Test that loading fails when given a bad library path."
# save the old value (if any) before setting a fake "GMT_LIBRARY_PATH"
old_gmt_library_path = os.environ.get("GMT_LIBRARY_PATH")
@pytest.mark.skipif(sys.platform == "win32", reason="run on UNIX platforms only")
def test_load_libgmt_fails(monkeypatch):
"""
Test that GMTCLibNotFoundError is raised when GMT's shared library cannot
be found.
"""
with monkeypatch.context() as mpatch:
mpatch.setattr(sys, "platform", "win32") # pretend to be on Windows
mpatch.setattr(
subprocess, "check_output", lambda cmd, encoding: "libfakegmt.so"
)
with pytest.raises(GMTCLibNotFoundError):
check_libgmt(load_libgmt())

os.environ["GMT_LIBRARY_PATH"] = "/not/a/real/path"
with pytest.raises(GMTCLibNotFoundError):
load_libgmt()

# revert back to the original status (if any)
if old_gmt_library_path:
os.environ["GMT_LIBRARY_PATH"] = old_gmt_library_path
else:
del os.environ["GMT_LIBRARY_PATH"]
def test_load_libgmt_with_a_bad_library_path(monkeypatch):
"Test that loading still works when given a bad library path."
# Set a fake "GMT_LIBRARY_PATH"
monkeypatch.setenv("GMT_LIBRARY_PATH", "/not/a/real/path")
assert check_libgmt(load_libgmt()) is None


def test_clib_names():
Expand All @@ -41,5 +48,7 @@ def test_clib_names():
assert clib_names(linux) == ["libgmt.so"]
assert clib_names("darwin") == ["libgmt.dylib"]
assert clib_names("win32") == ["gmt.dll", "gmt_w64.dll", "gmt_w32.dll"]
for freebsd in ["freebsd10", "freebsd11", "freebsd12"]:
assert clib_names(freebsd) == ["libgmt.so"]
with pytest.raises(GMTOSError):
clib_names("meh")