Source code for pyclibrary.utils

# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015-2020 by PyCLibrary Authors, see AUTHORS for more details.
#
# Distributed under the terms of the MIT/X11 license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
"""Utility functions to retrieve headers or library path and architecture.

Most of those function have been taken or adapted from the ones found in
PyVISA.

Functions
---------
find_header : Find the path to a header file.
find_library : Find the path to a shared library from its name.

"""
import os
import sys
import logging
import io
import struct
import subprocess

from .thirdparty.find_library import find_library as find_lib

logger = logging.getLogger(__name__)


HEADER_DIRS = [os.path.join(os.path.dirname(__file__), 'headers')]


[docs]def add_header_locations(dir_list): """Add directories in which to look for header files. """ dirs = [d for d in dir_list if os.path.isdir(d)] rejected = [d for d in dir_list if d not in dirs] if rejected: msg = 'The following directories are invalid: {}' logging.warning(msg.format(rejected)) HEADER_DIRS.extend(dirs)
[docs]def find_header(h_name, dirs=None): """Look for a header file. Headers are looked for in the directories specified by the user using the add_header_locations function, in the headers directory of PyCLibrary, and in the standards locations according to the operation system. Parameters ---------- h_name : unicode Name of the header to retrieve (should include the ".h") dirs : list, optional List of directory which should be searched for the header in addition to the default ones. Returns ------- path : unicode Path to the header file. Raises ------ OSError : if no matching file can be found. """ if dirs: dirs += HEADER_DIRS[::-1] else: dirs = HEADER_DIRS[::-1] if sys.platform == 'win32': pass if sys.platform == 'darwin': dirs.extend(('/usr/local/include', '/usr/include', '/System/Library/Frameworks', '/Library/Frameworks')) if sys.platform == 'linux2': dirs.extend(('/usr/local/include', '/usr/target/include', '/usr/include')) for d in dirs: path = os.path.join(d, h_name) if os.path.isfile(path): return path raise OSError("Can't find header with h_name {}".format(h_name))
LIBRARY_DIRS = []
[docs]def add_library_locations(dir_list): """Add directories in which to look for libraries. """ dirs = [d for d in dir_list if os.path.isdir(d)] rejected = [d for d in dir_list if d not in dirs] if rejected: msg = 'The following directories are invalid: {}' logging.warning(msg.format(rejected)) LIBRARY_DIRS.extend(dirs)
[docs]def find_library(name, dirs=None): """Look for a library file. Libraries are looked for in the directories specified by the user using the add_library_locations function, and using the find_library function found the thirdparty package. Parameters ---------- name : unicode Name of the library to retrieve (should include the extension) dirs : list, optional List of directory which should be searched for the library before ressorting to using thirdparty.find_library. Returns ------- path : unicode Path to the library file. Raises ------ OSError : if no matching file can be found. """ if dirs: dirs += LIBRARY_DIRS[::-1] else: dirs = LIBRARY_DIRS[::-1] for d in dirs: path = os.path.join(d, name) if os.path.isfile(path): return LibraryPath(path) path = find_lib(name) if path: return LibraryPath(path) raise OSError("Can't find library with name {}".format(name))
# --- Private API -------------------------------------------------------------
[docs]class LibraryPath(str): #: Architectural information (32, ) or (64, ) or (32, 64) _arch = None def __new__(cls, path, found_by='auto'): obj = super(LibraryPath, cls).__new__(cls, path) obj.path = path obj.found_by = found_by return obj @property def arch(self): if self._arch is None: try: self._arch = get_arch(self.path) except: self._arch = tuple() return self._arch @property def is_32bit(self): if not self.arch: return 'n/a' return 32 in self.arch @property def is_64bit(self): if not self.arch: return 'n/a' return 64 in self.arch @property def bitness(self): if not self.arch: return 'n/a' return ', '.join(str(a) for a in self.arch)
[docs]def get_arch(filename): this_platform = sys.platform if this_platform.startswith('win'): machine_type = get_shared_library_arch(filename) if machine_type == 'I386': return 32, elif machine_type in ('IA64', 'AMD64'): return 64, else: return () elif this_platform not in ('linux2', 'linux3', 'linux', 'darwin'): raise OSError('') out = check_output(["file", filename], stderr=subprocess.STDOUT) out = out.decode('ascii') ret = [] if this_platform.startswith('linux'): if '32-bit' in out: ret.append(32) if '64-bit' in out: ret.append(64) elif this_platform == 'darwin': if '(for architecture i386)' in out: ret.append(32) if '(for architecture x86_64)' in out: ret.append(64) return tuple(ret)
machine_types = { 0: 'UNKNOWN', 0x014c: 'I386', 0x0162: 'R3000', 0x0166: 'R4000', 0x0168: 'R10000', 0x0169: 'WCEMIPSV2', 0x0184: 'ALPHA', 0x01a2: 'SH3', 0x01a3: 'SH3DSP', 0x01a4: 'SH3E', 0x01a6: 'SH4', 0x01a8: 'SH5', 0x01c0: 'ARM', 0x01c2: 'THUMB', 0x01c4: 'ARMNT', 0x01d3: 'AM33', 0x01f0: 'POWERPC', 0x01f1: 'POWERPCFP', 0x0200: 'IA64', 0x0266: 'MIPS16', 0x0284: 'ALPHA64', 0x0366: 'MIPSFPU', 0x0466: 'MIPSFPU16', 0x0520: 'TRICORE', 0x0cef: 'CEF', 0x0ebc: 'EBC', 0x8664: 'AMD64', 0x9041: 'M32R', 0xc0ee: 'CEE', }
[docs]def get_shared_library_arch(filename): with io.open(filename, 'rb') as fp: dos_headers = fp.read(64) fp.read(4) magic, skip, offset = struct.unpack(str('2s58sl'), dos_headers) if magic != b'MZ': raise Exception('Not an executable') fp.seek(offset, io.SEEK_SET) pe_header = fp.read(6) sig, skip, machine = struct.unpack(str('2s2sH'), pe_header) if sig != b'PE': raise Exception('Not a PE executable') return machine_types.get(machine, 'UNKNOWN')
[docs]def check_output(*popenargs, **kwargs): """Run command with arguments and return its output as a byte string. Backported from Python 2.7 as it's implemented as pure python on stdlib. >>> check_output(['/usr/bin/python', '--version']) Python 2.6.2 """ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] error = subprocess.CalledProcessError(retcode, cmd) error.output = output raise error return output