From d6689bdf236196e7750bf730674e3c8b8df04f69 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Tue, 13 Mar 2018 04:55:02 -0400 Subject: [PATCH 1/3] api: Cygwin compatibility This change adds Cygwin compatibility by generalizing some Windows and Linux functionalities. In particular: * Some tests for Windows are changed into one that consider and accept Cygwin/MSYS2 runtime layers. * Registry values no longer change magick_home; they now influence PATH instead. ctypes on Windows search based on PATH. * msvcrt-specific parts are not changed. * *nix libc loading now default to the BSD "let ctypes find libc" loader, technically correct for every *nix system including Darwin. The hardcoded libc6 value is preserved as a fallback. * *nix libmagick handling now accepts a separate library as found on Cygwin and MinGW. --- wand/api.py | 84 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/wand/api.py b/wand/api.py index d19c1a31..ce98a259 100644 --- a/wand/api.py +++ b/wand/api.py @@ -14,11 +14,38 @@ import platform import sys import traceback -if platform.system() == "Windows": - try: - import winreg - except ImportError: - import _winreg as winreg + +def _is_windows(): + """Python on Windows hatch in different nests. Some are more Unix + than others. + + :returns: A pair of ``>`` type. The first element + indicates Windows-ness; the second gives a string for + replacement "lib" prefix when the platform is Unix-y. + :rtype: :class:`tuple` + """ + system = platform.system() + if system == "Windows": + return (True, False) + if system.startswith('CYGWIN_NT-'): + return (True, "cyg") + if system.startswith('MSYS_NT-'): + return (True, "msys-") + else: + return (False, False) + +is_windows = _is_windows() + +if is_windows[0]: + for winregmod in ['winreg', '_winreg', 'cygwinreg']: + try: + winreg = __import__(winregmod) + except ImportError: + pass + else: + winreg = None +else: + winreg = None __all__ = ('MagickPixelPacket', 'PointInfo', 'AffineMatrix', 'c_magick_char_p', 'library', 'libc', 'libmagick', 'load_library') @@ -60,26 +87,30 @@ def library_paths(): system = platform.system() magick_home = os.environ.get('MAGICK_HOME') - if system == 'Windows': + if winreg: # ImageMagick installers normally install coder and filter DLLs in # subfolders, we need to add those folders to PATH, otherwise loading # the DLL later will fail. + # + # We prepend those paths to PATH so that ImageMagick will be sure to + # use those DLLs. Since ctype looks in PATH, we can save magick_home + # for the actual environment variable. try: with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ImageMagick\Current") as reg_key: libPath = winreg.QueryValueEx(reg_key, "LibPath") coderPath = winreg.QueryValueEx(reg_key, "CoderModulesPath") filterPath = winreg.QueryValueEx(reg_key, "FilterModulesPath") - magick_home = libPath[0] - os.environ['PATH'] += (';' + libPath[0] + ";" + - coderPath[0] + ";" + filterPath[0]) + os.environ['PATH'] += (libPath[0] + ";" + + coderPath[0] + ";" + filterPath[0] + ';') except OSError: - # otherwise use MAGICK_HOME, and we assume the coder and + # otherwise do nothing; we assume the coder and # filter DLLs are in the same directory pass def magick_path(path): return os.path.join(magick_home, *path) + combinations = itertools.product(versions, options) suffixes = (version + option for version, option in combinations) if magick_home: @@ -92,9 +123,20 @@ def magick_path(path): libwand = 'CORE_RL_wand_{0}.dll'.format(suffix), libmagick = 'CORE_RL_magick_{0}.dll'.format(suffix), yield magick_path(libwand), magick_path(libmagick) - libwand = 'libMagickWand{0}.dll'.format(suffix), - libmagick = 'libMagickCore{0}.dll'.format(suffix), - yield magick_path(libwand), magick_path(libmagick) + + # Prioritize the Unix-y instance + if is_windows[1] != False: + prefixes = (is_windows[1], 'lib') + else: + prefixes = ('lib',) + + for prefix in prefixes: + libwand = '{1}MagickWand{0}.dll'.format(suffix, prefix), + libmagick = '{1}MagickCore{0}.dll'.format(suffix, prefix), + # "lib" (or other prefixes) are used for MinGW/Cygwin/MSYS + # builds, which may have a "bin" hierarchy over the DLLs. Add twice. + yield magick_path(libwand), magick_path(libmagick) + yield magick_path('bin', libwand), magick_path('bin', libmagick) elif system == 'Darwin': libwand = 'lib', 'libMagickWand{0}.dylib'.format(suffix), yield magick_path(libwand), magick_path(libwand) @@ -107,11 +149,14 @@ def magick_path(path): libmagick = ctypes.util.find_library('CORE_RL_magick_' + suffix) yield libwand, libmagick libwand = ctypes.util.find_library('libMagickWand' + suffix) - libmagick = ctypes.util.find_library('libMagickCore' + suffix) + libmagick = ctypes.util.find_library('libMagickCore' + suffix) or libwand yield libwand, libmagick + # On Cygwin-like systems, find_library function normally with the prefixes. + # But libmagick is split (totally legit), so let's do a "or" fallback instead: else: libwand = ctypes.util.find_library('MagickWand' + suffix) - yield libwand, libwand + libmagick = ctypes.util.find_library('MagickCore' + suffix) or libwand + yield libwand, libmagick def load_library(): @@ -1422,10 +1467,13 @@ class AffineMatrix(ctypes.Structure): except OSError: # In case of El Capitan SIP libc = ctypes.cdll.LoadLibrary('/usr/lib/libc.dylib') - elif sys.platform.startswith(('dragonfly', 'freebsd')): - libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) else: - libc = ctypes.cdll.LoadLibrary('libc.so.6') + try: + # Theoretically correct for all *nix, including Darwin + libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) + except OSError: + # Hardcode a Linux case + libc = ctypes.cdll.LoadLibrary('libc.so.6') libc.fdopen.argtypes = [ctypes.c_int, ctypes.c_char_p] libc.fdopen.restype = ctypes.c_void_p libc.fflush.argtypes = [ctypes.c_void_p] From 9a86b036a8689e7b86e3b22aa7658703215d838c Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Tue, 13 Mar 2018 05:02:55 -0400 Subject: [PATCH 2/3] api: (minor) amend PATH assembly A previous version forgot to add the original PATH back for prepending. This version fixes the problem and corrects path separater for Unixized Windows environments. --- wand/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wand/api.py b/wand/api.py index ce98a259..ae5c8a65 100644 --- a/wand/api.py +++ b/wand/api.py @@ -101,8 +101,9 @@ def library_paths(): libPath = winreg.QueryValueEx(reg_key, "LibPath") coderPath = winreg.QueryValueEx(reg_key, "CoderModulesPath") filterPath = winreg.QueryValueEx(reg_key, "FilterModulesPath") - os.environ['PATH'] += (libPath[0] + ";" + - coderPath[0] + ";" + filterPath[0] + ';') + pathSep = ';' if is_windows[1] == False else ':' + os.environ['PATH'] = (pathSep.join(libPath[0], coderPath[0], filterPath[0]) + + os.environ['PATH']) except OSError: # otherwise do nothing; we assume the coder and # filter DLLs are in the same directory From 062648382f2917b25a32de6281ea70d4f7172ca0 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Tue, 13 Mar 2018 05:06:53 -0400 Subject: [PATCH 3/3] api: (minor minor) wrap a tuple for join Please squash this. This is embarassing. --- wand/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wand/api.py b/wand/api.py index ae5c8a65..9bffcb17 100644 --- a/wand/api.py +++ b/wand/api.py @@ -102,7 +102,7 @@ def library_paths(): coderPath = winreg.QueryValueEx(reg_key, "CoderModulesPath") filterPath = winreg.QueryValueEx(reg_key, "FilterModulesPath") pathSep = ';' if is_windows[1] == False else ':' - os.environ['PATH'] = (pathSep.join(libPath[0], coderPath[0], filterPath[0]) + + os.environ['PATH'] = (pathSep.join((libPath[0], coderPath[0], filterPath[0])) + os.environ['PATH']) except OSError: # otherwise do nothing; we assume the coder and