Source code for bob.extension.utils

#!/usr/bin/env python
# encoding: utf-8
# Andre Anjos <andre.dos.anjos@gmail.com>
# Fri 21 Mar 2014 10:37:40 CET

'''General utilities for building extensions'''

import os
import re
import sys
import glob
import platform
import pkg_resources
from . import DEFAULT_PREFIXES


[docs]def construct_search_paths(prefixes=None, subpaths=None, suffix=None): """Constructs a list of candidate paths to search for. The list of paths is constructed using the following order of priority: 1. ``BOB_PREFIX_PATH`` environment variable, if set. ``BOB_PREFIX_PATH`` can contain several paths divided by :any:`os.pathsep`. 2. The paths provided with the ``prefixes`` parameter. 3. The current python executable prefix. 4. The ``CONDA_PREFIX`` environment variable, if set. 5. :any:`DEFAULT_PREFIXES`. Parameters ---------- prefixes : [:obj:`str`], optional The list of paths to be added to the results. subpaths : [:obj:`str`], optional A list of subpaths to be appended to each path at the end. For example, if you specify ``['foo', 'bar']`` for this parameter, then ``os.path.join(paths[0], 'foo')``, ``os.path.join(paths[0], 'bar')``, and so on are added to the returned paths. Globs are accepted in this list and resolved using the function :py:func:`glob.glob`. suffix : :obj:`str`, optional ``suffix`` will be appended to all paths except ``prefixes``. Returns ------- paths : [str] A list of unique and existing paths to be used in your search. """ search = [] suffix = suffix or '' # Priority 1: the environment if 'BOB_PREFIX_PATH' in os.environ: paths = os.environ['BOB_PREFIX_PATH'].split(os.pathsep) search += [p + suffix for p in paths] # Priority 2: user passed paths if prefixes: search += prefixes # Priority 3: the current system executable search.append(os.path.dirname(os.path.dirname(sys.executable)) + suffix) # Priority 4: the conda prefix conda_prefix = os.environ.get('CONDA_PREFIX') if conda_prefix: search.append(conda_prefix + suffix) # Priority 5: the default search prefixes search += [p + suffix for p in DEFAULT_PREFIXES] # Make unique to avoid searching twice search = uniq_paths(search) # Exhaustive combination of paths and subpaths if subpaths: subsearch = [] for s in search: for p in subpaths: subsearch.append(os.path.join(s, p)) subsearch.append(s) search = subsearch # Before we do a file-system check, filter out the un-existing paths tmp = [] for k in search: tmp += glob.glob(k) search = tmp return search
[docs]def find_file(name, subpaths=None, prefixes=None): """Finds a generic file on the file system. Returns all occurrences. This method will find all occurrences of a given name on the file system and will return them to the user. It uses :any:`construct_search_paths` to construct the candidate folders that file may exist in. Parameters ---------- name : str The name of the file. For example, ``gcc``. subpaths : [:obj:`str`], optional See :any:`construct_search_paths` subpaths : :obj:`str`, optional See :any:`construct_search_paths` Returns ------- [str] A list of filenames that exist on the filesystem, matching your description. """ search = construct_search_paths(prefixes=prefixes, subpaths=subpaths) retval = [] for path in search: candidate = os.path.join(path, name) if os.path.exists(candidate): retval.append(candidate) return retval
[docs]def find_header(name, subpaths=None, prefixes=None): """Finds a header file on the file system. Returns all candidates. This method will find all occurrences of a given name on the file system and will return them to the user. It uses :any:`construct_search_paths` to construct the candidate folders that header may exist in accounting automatically for typical header folder names. Parameters ---------- name : str The name of the header file. subpaths : [:obj:`str`], optional See :any:`construct_search_paths` subpaths : :obj:`str`, optional See :any:`construct_search_paths` Returns ------- [str] A list of filenames that exist on the filesystem, matching your description. """ headerpaths = [] # arm-based system (e.g. raspberry pi 32 or 64-bit) if platform.machine().startswith('arm'): headerpaths += [os.path.join('include', 'arm-linux-gnueabihf')] # else, consider it intel compatible elif platform.architecture()[0] == '32bit': headerpaths += [os.path.join('include', 'i386-linux-gnu')] else: headerpaths += [os.path.join('include', 'x86_64-linux-gnu')] # Raspberry PI search directory (arch independent) + normal include headerpaths += ['include'] # Exhaustive combination of paths and subpaths if subpaths: my_subpaths = [] for hp in headerpaths: my_subpaths += [os.path.join(hp, k) for k in subpaths] else: my_subpaths = headerpaths return find_file(name, my_subpaths, prefixes)
[docs]def find_library(name, version=None, subpaths=None, prefixes=None, only_static=False): """Finds a library file on the file system. Returns all candidates. This method will find all occurrences of a given name on the file system and will return them to the user. It uses :any:`construct_search_paths` to construct the candidate folders that the library may exist in accounting automatically for typical library folder names. Parameters ---------- name : str The name of the module to be found. If you'd like to find libz.so, for example, specify ``"z"``. For libmath.so, specify ``"math"``. version : :obj:`str`, optional The version of the library we are searching for. If not specified, then look only for the default names, such as ``libz.so`` and the such. subpaths : [:obj:`str`], optional See :any:`construct_search_paths` subpaths : :obj:`str`, optional See :any:`construct_search_paths` only_static : :obj:`bool`, optional A boolean, indicating if we should try only to search for static versions of the libraries. If not set, any would do. Returns ------- [str] A list of filenames that exist on the filesystem, matching your description. """ libpaths = [] # arm-based system (e.g. raspberry pi 32 or 64-bit) if platform.machine().startswith('arm'): libpaths += [os.path.join('lib', 'arm-linux-gnueabihf')] # else, consider it intel compatible elif platform.architecture()[0] == '32bit': libpaths += [ os.path.join('lib', 'i386-linux-gnu'), os.path.join('lib32'), ] else: libpaths += [ os.path.join('lib', 'x86_64-linux-gnu'), os.path.join('lib64'), ] libpaths += ['lib'] # Exhaustive combination of paths and subpaths if subpaths: my_subpaths = [] for lp in libpaths: my_subpaths += [os.path.join(lp, k) for k in subpaths] else: my_subpaths = libpaths # Extensions to consider if only_static: extensions = ['.a'] else: if sys.platform == 'darwin': extensions = ['.dylib', '.a'] elif sys.platform == 'win32': extensions = ['.dll', '.a'] else: # linux like extensions = ['.so', '.a'] # The module names can be set with or without version number retval = [] if version: for ext in extensions: if sys.platform == 'darwin': # version in the middle libname = 'lib' + name + '.' + version + ext else: # version at the end libname = 'lib' + name + ext + '.' + version retval += find_file(libname, my_subpaths, prefixes) for ext in extensions: libname = 'lib' + name + ext retval += find_file(libname, my_subpaths, prefixes) return retval
[docs]def find_executable(name, subpaths=None, prefixes=None): """Finds an executable on the file system. Returns all candidates. This method will find all occurrences of a given name on the file system and will return them to the user. It uses :any:`construct_search_paths` to construct the candidate folders that the executable may exist in accounting automatically for typical executable folder names. Parameters ---------- name : str The name of the file. For example, ``gcc``. subpaths : [:obj:`str`], optional See :any:`construct_search_paths` prefixes : :obj:`str`, optional See :any:`construct_search_paths` Returns ------- [str] A list of filenames that exist on the filesystem, matching your description. """ binpaths = [] # arm-based system (e.g. raspberry pi 32 or 64-bit) if platform.machine().startswith('arm'): binpaths += [os.path.join('bin', 'arm-linux-gnueabihf')] # else, consider it intel compatible elif platform.architecture()[0] == '32bit': binpaths += [ os.path.join('bin', 'i386-linux-gnu'), os.path.join('bin32'), ] else: binpaths += [ os.path.join('bin', 'x86_64-linux-gnu'), os.path.join('bin64'), ] binpaths += ['bin'] # Exhaustive combination of paths and subpaths if subpaths: my_subpaths = [] for lp in binpaths: my_subpaths += [os.path.join(lp, k) for k in subpaths] else: my_subpaths = binpaths # if conda-build's BUILD_PREFIX is set, use it as it may contain build tools # which are not available on the host environment prefixes = prefixes if prefixes is not None else [] if 'BUILD_PREFIX' in os.environ: prefixes += [os.environ['BUILD_PREFIX']] # The module names can be set with or without version number return find_file(name, my_subpaths, prefixes)
[docs]def uniq(seq, idfun=None): """Very fast, order preserving uniq function""" # order preserving if idfun is None: def idfun(x): return x seen = {} result = [] for item in seq: marker = idfun(item) # in old Python versions: # if seen.has_key(marker) # but in new ones: if marker in seen: continue seen[marker] = 1 result.append(item) return result
[docs]def uniq_paths(seq): """Uniq'fy a list of paths taking into consideration their real paths""" return uniq([os.path.realpath(k) for k in seq if os.path.exists(k)])
[docs]def egrep(filename, expression): """Runs grep for a given expression on each line of the file Parameters: filename, str The name of the file to grep for the expression expression A regular expression, that will be initialized using :py:func:`re.compile`. Returns a list of re matches. """ retval = [] with open(filename, 'rt') as f: rexp = re.compile(expression) for line in f: p = rexp.match(line) if p: retval.append(p) return retval
[docs]def load_requirements(f=None): """Loads the contents of requirements.txt on the given path. Defaults to "./requirements.txt" """ def readlines(f): retval = [str(k.strip()) for k in f] return [k for k in retval if k and k[0] not in ('#', '-')] # if f is None, use the default ('requirements.txt') if f is None: f = 'requirements.txt' if isinstance(f, str): f = open(f, 'rt') # read the contents return readlines(f)
[docs]def find_packages(directories=['bob']): """This function replaces the ``find_packages`` command from ``setuptools`` to search for packages only in the given directories. Using this function will increase the building speed, especially when you have (links to) deep non-code-related directory structures inside your package directory. The given ``directories`` should be a list of top-level sub-directories of your package, where package code can be found. By default, it uses ``'bob'`` as the only directory to search. """ from setuptools import find_packages as _original if isinstance(directories, str): directories = [directories] packages = [] for d in directories: packages += [d] packages += ["%s.%s" % (d, p) for p in _original(d)] return packages