# -*- 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')]
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