diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..2534a45ee555bb5ebefd37210d19399634c7a4ee
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 80
+ignore = E501,W503,E302,E402,E203
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6352dfc534c96dd6ea1c8bc7c061f4745b199881
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,27 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+  - repo: https://github.com/timothycrosley/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
+        args: [--settings-path, "pyproject.toml"]
+  - repo: https://github.com/psf/black
+    rev: 22.3.0
+    hooks:
+      - id: black
+  - repo: https://gitlab.com/pycqa/flake8
+    rev: 3.9.2
+    hooks:
+      - id: flake8
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.2.0
+    hooks:
+      - id: check-ast
+      - id: check-case-conflict
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+      - id: debug-statements
+      - id: check-added-large-files
+      - id: check-yaml
+        exclude: .*/meta.yaml
diff --git a/bob/__init__.py b/bob/__init__.py
index 2ab1e28b150f0549def9963e9e87de3fdd6b2579..edbb4090fca046b19d22d3982711084621bff3be 100644
--- a/bob/__init__.py
+++ b/bob/__init__.py
@@ -1,3 +1,4 @@
 # see https://docs.python.org/3/library/pkgutil.html
 from pkgutil import extend_path
+
 __path__ = extend_path(__path__, __name__)
diff --git a/bob/extension/__init__.py b/bob/extension/__init__.py
index 50422783c95d6277b4f3e4f92bc5f292583d7f65..16c9116c79011f1c68112a6f2dbf26301e87a2a7 100644
--- a/bob/extension/__init__.py
+++ b/bob/extension/__init__.py
@@ -1,39 +1,20 @@
 #!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 # Andre Anjos <andre.anjos@idiap.ch>
-# Mon 28 Jan 2013 16:40:27 CET
+# Amir Mohammadi <amir.mohammadi@idiap.ch>
 
 """A custom build class for Pkg-config based extensions
 """
 
 import contextlib
-import os
-import sys
-import platform
-import subprocess
+import logging
+
 import pkg_resources
-from setuptools.extension import Extension as DistutilsExtension
-from setuptools.command.build_ext import build_ext as _build_ext
-import distutils.sysconfig
 
-from pkg_resources import resource_filename
+from .rc_config import _loadrc
 
-import logging
 logger = logging.getLogger(__name__)
 
-DEFAULT_PREFIXES = [
-    "/opt/local",
-    "/usr/local",
-    "/usr",
-]
-"""The list common places to search for library-related files."""
-
-
-from .pkgconfig import pkgconfig
-from .boost import boost
-from .utils import uniq, uniq_paths, find_executable, find_library, construct_search_paths
-from .cmake import CMakeListsGenerator
-from .rc_config import _loadrc
 
 __version__ = pkg_resources.require(__name__)[0].version
 
@@ -42,859 +23,88 @@ rc = _loadrc()
 """The content of the global configuration file loaded as a dictionary.
 The value for any non-existing key is ``None``."""
 
+
 @contextlib.contextmanager
 def rc_context(dict):
-  """A context manager for bob.extension.rc.
-  You can use this context manager to temporarily change a value in
-  ``bob.extension.rc``.
-
-  Example
-  -------
-  >>> from bob.extension import rc, rc_context
-  >>> assert rc.get("non-existing-key") is None
-  >>> with rc_context({"non-existing-key": 1}):
-  ...     a = rc.get("non-existing-key")
-  >>> a
-  1
-  """
-  old_rc = rc.copy()
-  try:
-    rc.update(dict)
-    yield
-  finally:
-    rc.clear()
-    rc.update(old_rc)
-
-
-def check_packages(packages):
-  """Checks if the requirements for the given packages are satisfied.
-
-  Parameters:
-
-    :py:class:`list` of :py:class:`str`: Each representing a requirement that
-       must be statistfied. Package requirements can be set like this::
-
-         "pkg > VERSION"
-
-       In this case, the package version should be greater than the given
-       version number. Comparisons are done using
-       :py:mod:`distutils.version.LooseVersion`.  You can use other comparators
-       such as ``<``, ``<=``, ``>=`` or ``==``. If no version number is given,
-       then we only require that the package is installed.
-
-
-  Raises:
-
-    ``RuntimeError``: in case requirements are not satisfied. This means either
-      not finding a package if no version number is specified or verifying that
-      the package version does not match the required version by the builder.
-
-  """
-
-  from re import split
-
-  used = set()
-  retval = []
-
-  for requirement in uniq(packages):
-
-    splitreq = split(r'\s*(?P<cmp>[<>=]+)\s*', requirement)
-
-    if len(splitreq) not in (1, 3):
-
-      raise RuntimeError("cannot parse requirement `%s'", requirement)
-
-    p = pkgconfig(splitreq[0])
-
-    if len(splitreq) == 3: # package + version number
-
-      if splitreq[1] == '>':
-        assert p > splitreq[2], "%s version is not > `%s'" % (p.name, splitreq[2])
-      elif splitreq[1] == '>=':
-        assert p >= splitreq[2], "%s version is not >= `%s'" % (p.name, splitreq[2])
-      elif splitreq[1] == '<':
-        assert p < splitreq[2], "%s version is not < `%s'" % (p, splitreq[2])
-      elif splitreq[1] == '<=':
-        assert p <= splitreq[2], "%s version is not <= `%s'" % (p, splitreq[2])
-      elif splitreq[1] == '==':
-        assert p <= splitreq[2], "%s version is not == `%s'" % (p, splitreq[2])
-      else:
-        raise RuntimeError("cannot parse requirement `%s'", requirement)
-
-    retval.append(p)
-
-    if p.name in used:
-      raise RuntimeError("package `%s' had already been requested - cannot (currently) handle recurring requirements")
-    used.add(p.name)
-
-  return retval
-
-def generate_self_macros(extname, version):
-  """Generates standard macros with library, module names and prefix"""
-
-  if version is None:
-    return []
-
-  s = extname.rsplit('.', 1)
-
-  retval = [
-      ('BOB_EXT_MODULE_PREFIX', '"%s"' % s[0]),
-      ('BOB_EXT_MODULE_NAME', '"%s"' % s[1]),
-      ]
-
-  if sys.version_info[0] >= 3:
-    retval.append(('BOB_EXT_ENTRY_NAME', 'PyInit_%s' % s[1]))
-  else:
-    retval.append(('BOB_EXT_ENTRY_NAME', 'init%s' % s[1]))
-
-  if version: retval.append(('BOB_EXT_MODULE_VERSION', '"%s"' % version))
-
-  return retval
-
-def reorganize_isystem(args):
-  """Re-organizes the -isystem includes so that more specific paths come
-  first"""
-
-  remainder = []
-  includes = []
-
-  skip = False
-  for i in range(len(args)):
-    if skip:
-      skip = False
-      continue
-    if args[i] == '-isystem':
-      includes.append(args[i+1])
-      skip = True
-    else:
-      remainder.append(args[i])
-
-  includes = uniq_paths(includes[::-1])[::-1]
-
-  # sort includes so that the shortest system includes go last
-  # this algorithm will ensure '/usr/include' comes after other
-  # overwrites
-  includes.sort(key=lambda item: (-len(item), item))
-
-  retval = [tuple(remainder)] + [('-isystem', k) for k in includes]
-  from itertools import chain
-  return list(chain.from_iterable(retval))
-
-def normalize_requirements(requirements):
-  """Normalizes the requirements keeping only the most tight"""
-
-  from re import split
-
-  parsed = {}
-
-  for requirement in requirements:
-    splitreq = split(r'\s*(?P<cmp>[<>=]+)\s*', requirement)
-
-    if len(splitreq) not in (1, 3):
-
-      raise RuntimeError("cannot parse requirement `%s'", requirement)
-
-    if len(splitreq) == 1: # only package
-
-      parsed.setdefault(splitreq[0], [])
-
-    if len(splitreq) == 3: # package + version number
-
-      parsed.setdefault(splitreq[0], []).append(tuple(splitreq[1:]))
-
-  # at this point, all requirements are organised:
-  # requirement -> [(op, version), (op, version), ...]
-
-  leftovers = []
-
-  for key, value in parsed.items():
-    value = uniq(value)
-
-    if not value:
-      leftovers.append(key)
-      continue
-
-    for v in value:
-      leftovers.append(' '.join((key, v[0], v[1])))
-
-  return leftovers
-
-
-def get_bob_libraries(bob_packages):
-  """Returns a list of include directories, libraries and library directories
-  for the given bob libraries."""
-  includes = []
-  libraries = []
-  library_directories = []
-  macros = []
-  # iterate through the list of bob packages
-  if bob_packages is not None:
-    # TODO: need to handle versions?
-    bob_packages = normalize_requirements([k.strip().lower() for k in bob_packages])
-    for package in bob_packages:
-      import importlib
-      pkg = importlib.import_module(package)
-      location = os.path.dirname(pkg.__file__)
-      includes.append(os.path.join(location, 'include'))
-      # check if the package contains a get_include_directories() function
-      if hasattr(pkg, 'get_include_directories'):
-        includes.extend(pkg.get_include_directories())
-      if hasattr(pkg, 'get_macros'):
-        macros.extend(pkg.get_macros())
-
-      lib_name = package.replace('.', '_')
-      libs = find_library(lib_name, prefixes=[location])
-      # add the FIRST lib that we found, if any
-      if len(libs):
-        libraries.append(lib_name)
-        library_directories.append(os.path.dirname(libs[0]))
-
-  return includes, libraries, library_directories, macros
-
-
-def get_full_libname(name, path=None, version=None):
-  """Generates the name of the library from the given name, path and version."""
-  libname = 'lib' + name.replace('.', '_')
-  if sys.platform == 'darwin':
-    libname += ('.' + version if version is not None else '') + '.dylib'
-  elif sys.platform == 'win32':
-    libname += '.dll' + ('.' + version if version is not None else '')
-  else: # linux like
-    libname += '.so' + ('.' + version if version is not None else '')
-
-  if path is not None:
-    return os.path.join(path, libname)
-  else:
-    return libname
-
-
-def load_bob_library(name, _file_):
-  """Loads the bob Library for the given package name in the given version (if given).
-  The _file_ parameter is expected to be the ``__file__`` member of the main ``__init__.py`` of the package.
-  It is used to determine the directory, where the library should be loaded from.
-
-  Keyword parameters
-
-  name : string
-    The name of the bob package to load the library from, e.g. ``bob.core``
-
-  _file_ : string
-    The ``__file__`` member of the ``__init__.py`` file in which the library is loaded.
-  """
-
-  full_libname = get_full_libname(name, os.path.dirname(_file_))
-  import ctypes
-  ctypes.cdll.LoadLibrary(full_libname)
-
-
-def find_system_include_paths():
-  """Finds system include paths if the environment variable ``CC`` is set
-
-  Returns:
-
-    list: A list of include paths defined by the compiler
-
-  """
-
-  start1 = '#include "..." search starts here:\n'
-  start2 = '#include <...> search starts here:\n'
-  end = 'End of search list'
-
-  def make_list(s):
-    tmp = [k.strip() for k in s.split('\n') if k.strip()]
-    remove = ' (framework directory)'
-    ret = []
-    for k in tmp:
-      if k.endswith(remove): ret.append(k[:-len(remove)])
-      else: ret.append(k)
-    return ret
-
-  CC = find_executable("cc")
-  if CC: CC = CC[0]
-  else: CC = None
-  CC = os.environ.get('CC', CC)
-
-  try:
-    cmd = "echo | %s -v -E -" % CC
-    out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
-    out = out.decode()
-    section1 = out[out.find(start1)+len(start1)+1:out.find(start2)]
-    section2 = out[out.find(start2)+len(start2)+1:out.find(end)]
-    section1 = make_list(section1)
-    section2 = make_list(section2)
-    return section1 + section2
-
-  except Exception as e:
-    logger.error('Cannot get system include paths with CC=%s', CC)
-    logger.error('The output of `%s\' was:\n%s', cmd, out)
-    return []
-
-  if CC is None:
-    logger.warn('Cannot get system include paths because CC is NOT set on ' \
-        'the environment nor it can be found on your PATH - set it to fix ' \
-        'possible "-isystem" usage issues ' \
-        '(see: https://stackoverflow.com/questions/37218953/isystem-on-a-system-include-directory-causes-errors)')
-    return []
-
-
-class Extension(DistutilsExtension):
-  """Extension building with pkg-config packages.
-
-  See the documentation for :py:class:`distutils.extension.Extension` for more
-  details on input parameters.
-  """
-
-  def __init__(self, name, sources, **kwargs):
-    """Initialize the extension with parameters.
-
-    External package extensions (mostly coming from pkg-config), adds a single
-    parameter to the standard arguments of the constructor:
-
-    packages : [string]
-      This should be a list of strings indicating the name of the bob
-      (pkg-config) modules you would like to have linked to your extension
-      **additionally** to ``bob-python``. Candidates are module names like
-      "bob-machine" or "bob-math".
-
-      For convenience, you can also specify "opencv" or other 'pkg-config'
-      registered packages as a dependencies.
-
-    boost_modules : [string]
-      A list of boost modules that we need to link against.
-
-    bob_packages : [string]
-      A list of bob libraries (such as ``'bob.core'``) containing C++ code
-      that should be included and linked
-
-    system_include_dirs : [string]
-      A list of include directories that are not in one of our packages,
-      and which should be included with the -isystem compiler option
-
-    """
-
-    packages = []
-
-    if 'packages' in kwargs:
-      if isinstance(kwargs['packages'], str):
-        packages.append(kwargs['packages'])
-      else:
-        packages.extend(kwargs['packages'])
-      del kwargs['packages']
-
-    # uniformize packages
-    packages = normalize_requirements([k.strip().lower() for k in packages])
-
-    # check if we have bob libraries to link against
-    if 'bob_packages' in kwargs:
-      self.bob_packages = kwargs['bob_packages']
-      del kwargs['bob_packages']
-    else:
-      self.bob_packages = None
-
-    bob_includes, bob_libraries, bob_library_dirs, bob_macros = get_bob_libraries(self.bob_packages)
-
-    # system include directories
-    if 'system_include_dirs' in kwargs:
-      system_includes = kwargs['system_include_dirs']
-      del kwargs['system_include_dirs']
-    else:
-      system_includes = []
-
-    # Boost requires a special treatment
-    boost_req = ''
-    for i, pkg in enumerate(packages):
-      if pkg.startswith('boost'):
-        boost_req = pkg
-        del packages[i]
-
-    # We still look for the keyword 'boost_modules'
-    boost_modules = []
-    if 'boost_modules' in kwargs:
-      if isinstance(kwargs['boost_modules'], str):
-        boost_modules.append(kwargs['boost_modules'])
-      else:
-        boost_modules.extend(kwargs['boost_modules'])
-      del kwargs['boost_modules']
-
-    if boost_modules and not boost_req: boost_req = 'boost >= 1.0'
-
-    # Was a version parameter given?
-    version = None
-    if 'version' in kwargs:
-      version = kwargs['version']
-      del kwargs['version']
-
-    # Mixing
-    parameters = {
-        'define_macros': generate_self_macros(name, version) + bob_macros,
-        'extra_compile_args': os.environ.get('CXXFLAGS', '').split(),
-        'extra_link_args': [],
-        'library_dirs': [],
-        'libraries': bob_libraries,
-        }
-
-    # Compilation options for macOS builds
-    if platform.system() == 'Darwin':
-      logger.warn(str(os.environ))
-      logger.warn(rc['bob.extension.macosx_deployment_target'])
-      logger.warn("######################################")
-      
-      target = os.environ.get('MACOSX_DEPLOYMENT_TARGET')
-      if target is None:  #not set on the environment, try resource
-        target = rc['bob.extension.macosx_deployment_target']
-      if target is None or not target:
-        raise EnvironmentError('${MACOSX_DEPLOYMENT_TARGET} environment ' \
-            'variable is **NOT** set - To avoid this error, either set ' \
-            '${MACOSX_DEPLOYMENT_TARGET} or Bob\'s resource ' \
-            '"bob.extension.macosx_deployment_target" to a suitable ' \
-            'value (e.g. ' \
-            '"bob config set bob.extension.macosx_deployment_target 10.9") ' \
-            'before trying to build C/C++ extensions')
-      os.environ.setdefault('MACOSX_DEPLOYMENT_TARGET', target)
-
-      sdkroot = os.environ.get('SDKROOT', os.environ.get('CONDA_BUILD_SYSROOT'))
-      if sdkroot is None:  #not set on the environment, try resource
-        sdkroot = rc['bob.extension.macosx_sdkroot']
-      if sdkroot is None or not sdkroot:
-        raise EnvironmentError('${SDKROOT} environment variable is **NOT** ' \
-            'set - To avoid this error, either set ${SDKROOT} or Bob\'s ' \
-            'resource "bob.extension.macosx_sdkroot" to a suitable value ' \
-            '(e.g. "bob config ' \
-            'set bob.extension.macosx_sdkroot /opt/MacOSX10.9.sdk")' \
-            'before trying to build C/C++ extensions')
-
-      # test for the compatibility between deployment target and sdk root
-      sdkversion = os.path.basename(sdkroot)[6:-4]
-      if sdkversion != target:
-        logger.warn('There is an inconsistence between the value ' \
-            'set for ${MACOSX_DEPLOYMENT_TARGET} (%s) and ' \
-            '${SDKROOT}/${CONDA_BUILD_SYSROOT} (%s) - Fix it by properly ' \
-            'setting up these environment variables via Bob\'s ' \
-            'configuration (refer to bob.extension\'s user guide for ' \
-            'detailed instructions)', target, sdkroot)
-
-      os.environ.setdefault('SDKROOT', sdkroot)
-
-      parameters['extra_compile_args'] = \
-          ['-mmacosx-version-min=%s' % target] + ['-isysroot', sdkroot] + \
-          parameters['extra_compile_args'] + ['-Wno-#warnings']
-
-    user_includes = kwargs.get('include_dirs', [])
-    self.pkg_includes = []
-    self.pkg_libraries = []
-    self.pkg_library_directories = []
-    self.pkg_macros = []
-
-    # Updates for boost
-    if boost_req:
-
-      boost_pkg = boost(boost_req.replace('boost', '').strip())
-
-      # Adds macros
-      parameters['define_macros'] += boost_pkg.macros()
-
-      # Adds the include directory (enough for using just the template library)
-      if boost_pkg.include_directory not in user_includes:
-        system_includes.append(boost_pkg.include_directory)
-        self.pkg_includes.append(boost_pkg.include_directory)
-
-      # Adds specific boost libraries requested by the user
-      if boost_modules:
-        boost_libdirs, boost_libraries = boost_pkg.libconfig(boost_modules)
-        parameters['library_dirs'].extend(boost_libdirs)
-        self.pkg_library_directories.extend(boost_libdirs)
-        parameters['libraries'].extend(boost_libraries)
-        self.pkg_libraries.extend(boost_libraries)
-
-    # Checks all other pkg-config requirements
-    pkgs = check_packages(packages)
-
-    for pkg in pkgs:
-
-      # Adds parameters for each package, in order
-      parameters['define_macros'] += pkg.package_macros()
-      self.pkg_macros += pkg.package_macros()
-
-      # Include directories are added with a special path
-      for k in pkg.include_directories():
-        if k in user_includes or k in self.pkg_includes: continue
-        system_includes.append(k)
-        self.pkg_includes.append(k)
-
-      parameters['library_dirs'] += pkg.library_directories()
-      self.pkg_library_directories += pkg.library_directories()
-
-      if pkg.name.find('bob-') == 0: # one of bob's packages
-
-        # make-up the names of versioned Bob libraries we must link against
-
-        if platform.system() == 'Darwin':
-          libs = ['%s.%s' % (k, pkg.version) for k in pkg.libraries()]
-        elif platform.system() == 'Linux':
-          libs = [':lib%s.so.%s' % (k, pkg.version) for k in pkg.libraries()]
-        else:
-          raise RuntimeError("supports only MacOSX and Linux builds")
-
-      else:
-
-        libs = pkg.libraries()
-
-      parameters['libraries'] += libs
-      self.pkg_libraries += libs
-
-      # if used libraries require extra compilation flags, add them to the mix
-      parameters['extra_compile_args'].extend(
-          pkg.cflags_other().get('extra_compile_args', [])
-          )
-
-      parameters['extra_link_args'] += pkg.other_libraries()
-
-    # add the -isystem to all system include dirs
-    compiler_includes = find_system_include_paths()
-    system_includes = [k for k in system_includes if k not in compiler_includes]
-    for k in system_includes:
-      parameters['extra_compile_args'].extend(['-isystem', k])
-
-    # Filter and make unique
-    for key in parameters.keys():
-
-      # Tune input parameters if they were set, but assure that our parameters come first
-      if key in kwargs:
-        kwargs[key] = parameters[key] + kwargs[key]
-      else: kwargs[key] = parameters[key]
-
-      if key in ('extra_compile_args'): continue
-
-      kwargs[key] = uniq(kwargs[key])
-
-    # add our include dir by default
-    self_include_dir = resource_filename(__name__, 'include')
-    kwargs.setdefault('include_dirs', []).append(self_include_dir)
-    kwargs['include_dirs'] = user_includes + bob_includes + kwargs['include_dirs']
-
-    # Uniq'fy parameters that are not on our parameter list
-    kwargs['include_dirs'] = uniq_paths(kwargs['include_dirs'])
-
-    # Stream-line '-isystem' includes
-    kwargs['extra_compile_args'] = reorganize_isystem(kwargs['extra_compile_args'])
-
-    # Make sure the language is correctly set to C++
-    kwargs['language'] = 'c++'
-
-    # On Linux, set the runtime path
-    if platform.system() == 'Linux':
-      kwargs.setdefault('runtime_library_dirs', [])
-      kwargs['runtime_library_dirs'] += kwargs['library_dirs']
-      kwargs['runtime_library_dirs'] = uniq_paths(kwargs['runtime_library_dirs'])
-
-    # .. except for the bob libraries
-    kwargs['library_dirs'] += bob_library_dirs
-
-    # Uniq'fy library directories
-    kwargs['library_dirs'] = uniq_paths(kwargs['library_dirs'])
-
-    # Run the constructor for the base class
-    DistutilsExtension.__init__(self, name, sources, **kwargs)
-
-
-class Library (Extension):
-  """A class to compile a pure C++ code library used within and outside an extension using CMake."""
-
-  def __init__(self, name, sources, version, bob_packages = [], packages = [], boost_modules=[], include_dirs = [], system_include_dirs = [], libraries = [], library_dirs = [], define_macros = []):
-    """Initializes a pure C++ library that will be compiled with CMake.
-
-    By default, the include directory of this package is automatically added to the ``include_dirs``.
-    It is expected to be in the `include`` directory in the main package directory (which, e.g., is ``bob/core`` for package ``bob.core``).
-
-    .. note::
-      This library, including the library and include directories, is also automatically added to **all other** :py:class:`Extension`'s that are compiled within this package.
-
-    .. warning::
-      IMPORTANT! To compile this library with CMake, the :py:class:`build_ext` class provided in this module is required.
-      Please include::
-
-        cmdclass = {
-          'build_ext': build_ext
-        },
-
-      as a parameter to the ``setup`` function in your setup.py.
-
-    Keyword parameters:
-
-    name : string
-      The name of the library to generate, e.g., ``'bob.core.bob_core'``
-
-    sources : [string]
-      A list of files (relative to the base directory) that should be compiled and linked by CMake
-
-    version : string
-      The version of the library, which is usually identical to the version of the package
-
-    bob_packages : [string]
-      A list of bob packages that the pure C++ code relies on.
-      Libraries and include directories of these packages will be automatically added.
-
-    packages : [string]
-      A list of pkg-config based packages, see :py:class:`Extension`.
-      Macros, libraries and include directories of these packages will be automatically added.
-
-    boost_modules : [string]
-      A list of boost modules that we need to link against.
-
-    include_dirs : [string]
-      An additional list of include directories that is not covered by ``bob_packages`` and ``packages``
-
-    system_include_dirs : [string]
-      A list of include directories that are not in one of our packages,
-      and which should be included with the SYSTEM option
-
-    libraries : [string]
-      An additional list of libraries that is not covered by ``bob_packages`` and ``packages``
-
-    library_dirs : [string]
-      An additional list of library directories that is not covered by ``bob_packages`` and ``packages``
-
-    define_macros : [(string, string)]
-      An additional list of preprocessor definitions that is not covered by ``packages``
+    """A context manager for bob.extension.rc.
+    You can use this context manager to temporarily change a value in
+    ``bob.extension.rc``.
+
+    Example
+    -------
+    >>> from bob.extension import rc, rc_context
+    >>> assert rc.get("non-existing-key") is None
+    >>> with rc_context({"non-existing-key": 1}):
+    ...     a = rc.get("non-existing-key")
+    >>> a
+    1
     """
-    name_split = name.split('.')
-    if len(name_split) <= 1:
-      raise ValueError("The name of the library must contain the package name, e.g., bob.core.bob_core")
-    self.c_name = name_split[-1]
-    self.c_package_directory = os.path.realpath('.')
-    self.c_sub_directory = os.path.join(*(name_split[:-1]))
-    self.c_sources = sources
-    self.c_version = version
-    self.c_self_include_directory = os.path.join(self.c_package_directory, self.c_sub_directory, 'include')
-    self.c_include_directories = [self.c_self_include_directory] + include_dirs
-    self.c_system_include_directories = system_include_dirs
-    self.c_libraries = libraries[:]
-    self.c_library_directories = library_dirs[:]
-    self.c_define_macros = define_macros[:]
-
-    # add includes and libs for bob packages as the PREFERRED path (i.e., in front)
-    bob_includes, bob_libraries, bob_library_dirs, bob_macros = get_bob_libraries(bob_packages)
-    self.c_include_directories = bob_includes + self.c_include_directories
-    self.c_libraries = bob_libraries + self.c_libraries
-    self.c_library_directories = bob_library_dirs + self.c_library_directories
-    self.c_define_macros = bob_macros + self.c_define_macros
-
-    # find the cmake executable
-    cmake = find_executable("cmake")
-    if not cmake:
-      raise OSError("The Library class needs CMake version >= 2.8 to be installed, but CMake cannot be found")
-    self.c_cmake = cmake[0]
+    old_rc = rc.copy()
+    try:
+        rc.update(dict)
+        yield
+    finally:
+        rc.clear()
+        rc.update(old_rc)
 
-    # call base class constructor, i.e., to handle the packages
-    Extension.__init__(self, name, sources, packages=packages, boost_modules=boost_modules)
 
-    # add the include directories for the packages as well
-    self.c_system_include_directories.extend(self.pkg_includes)
-    self.c_libraries.extend(self.pkg_libraries)
-    self.c_library_directories.extend(self.pkg_library_directories)
-    self.c_define_macros.extend(self.pkg_macros)
-
-
-  def compile(self, build_directory, compiler = None, stdout=None):
-    """This function will automatically create a CMakeLists.txt file in the ``package_directory`` including the required information.
-    Afterwards, the library is built using CMake in the given ``build_directory``.
-    The build type is automatically taken from the debug option in the buildout.cfg.
-    To change the compiler, use the ``compiler`` parameter.
-    """
-    self.c_target_directory = os.path.join(os.path.realpath(build_directory), self.c_sub_directory)
-    if not os.path.exists(self.c_target_directory):
-      os.makedirs(self.c_target_directory)
-    # generate CMakeLists.txt makefile
-    generator = CMakeListsGenerator(
-      name = self.c_name,
-      sources = self.c_sources,
-      target_directory = self.c_target_directory,
-      version = self.c_version,
-      include_directories = uniq_paths(self.c_include_directories),
-      system_include_directories = uniq_paths(self.c_system_include_directories),
-      libraries = uniq(self.c_libraries),
-      library_directories = uniq_paths(self.c_library_directories),
-      macros = uniq(self.c_define_macros)
-    )
-
-    # compile our stuff in a different directory
-    final_build_dir = os.path.join(os.path.dirname(os.path.realpath(build_directory)), 'build_cmake', self.c_name)
-    if not os.path.exists(final_build_dir):
-      os.makedirs(final_build_dir)
-    generator.generate(self.c_package_directory, final_build_dir)
-
-    # compile in the build directory
-    import subprocess
-    env = {'VERBOSE' : '1'}
-    env.update(os.environ)
-    if compiler is not None:
-      env['CXX'] = compiler
-    # configure cmake
-    command = [self.c_cmake, final_build_dir]
-    if subprocess.call(command, cwd=final_build_dir, env=env, stdout=stdout) != 0:
-      raise OSError("Could not generate makefiles with CMake")
-    # run make
-    make_call = ['make']
-    if  "BOB_BUILD_PARALLEL" in os.environ: make_call += ['-j%s' % os.environ["BOB_BUILD_PARALLEL"]]
-    if subprocess.call(make_call, cwd=final_build_dir, env=env, stdout=stdout) != 0:
-      raise OSError("CMake compilation stopped with an error; stopping ...")
-
-
-class build_ext(_build_ext):
-  """Compile the C++ :py:class`Library`'s using CMake, and the python extensions afterwards
-
-  See the documentation for :py:class:`distutils.command.build_ext` for more
-  information.
-  """
+def get_config(package=__name__, externals=None, api_version=None):
+    """Returns a string containing the configuration information for the given ``package`` name.
+    By default, it returns the configuration of this package.
 
-  def finalize_options(self):
-    # check if the "BOB_BUILD_DIRECTORY" environment variable is set
-    env = os.environ
-    if 'BOB_BUILD_DIRECTORY' in env and env['BOB_BUILD_DIRECTORY']:
-      # HACKISH: check if we are currently developed by inspecting the way we got called
-      if 'develop' in sys.argv:
-        self.build_temp = os.path.join(env['BOB_BUILD_DIRECTORY'], 'build_temp')
-        self.build_lib = os.path.join(env['BOB_BUILD_DIRECTORY'], 'build_lib')
-    _build_ext.finalize_options(self)
+    This function can be reused by other packages.
+    If these packages have external C or C++ dependencies, the ``externals`` dictionary can be specified.
+    Also, if the package provides an API version, it can be specified.
 
-  def run(self):
-    """Iterates through the list of Extension packages and reorders them, so that the Library's come first
-    """
-    # here, we simply re-order the extensions such that we get the Library first
-    self.extensions = [ext for ext in self.extensions if isinstance(ext, Library)] + [ext for ext in self.extensions if not isinstance(ext, Library)]
-    # call the base class function
-    return _build_ext.run(self)
+    **Keyword parameters:**
 
+    package : *str*
+      The name of the package to get the configuration information from.
+      Usually, the ``__name__`` of the package.
 
-  def build_extension(self, ext):
-    """Builds the given extension.
+    externals : *{dep: description}*
+      A dictionary of external C or C++ dependencies, with the ``dep``endent package name as key, and a free ``description`` as value.
 
-    When the extension is of type Library, it compiles the library with CMake, otherwise the default compilation mechanism is used.
-    Afterwards, it adds the according library, and the include and library directories of the Library's, so that other Extensions can find the newly generated lib.
+    api_version : *int* (usually in hexadecimal)
+      The API version of the ``package``, if any.
     """
 
-    # HACK: remove the "-Wstrict-prototypes" option keyword
-    self.compiler.compiler = [c for c in self.compiler.compiler if c != "-Wstrict-prototypes"]
-    self.compiler.compiler_so = [c for c in self.compiler.compiler_so if c != "-Wstrict-prototypes"]
-    if "-Wno-strict-aliasing" not in self.compiler.compiler:
-      self.compiler.compiler.append("-Wno-strict-aliasing")
-    if "-Wno-strict-aliasing" not in self.compiler.compiler_so:
-      self.compiler.compiler_so.append("-Wno-strict-aliasing")
+    import pkg_resources
 
-    # check if it is our type of extension
-    if isinstance(ext, Library):
-      # TODO: get compiler and add it to the compiler
-      # TODO: get the debug status and add the build_type parameter
-      # build libraries using the provided functions
-      # compile
-      ext.compile(self.build_lib)
-      libs = [ext.c_name]
-      lib_dirs = [ext.c_target_directory]
-      include_dirs = [ext.c_self_include_directory]
+    packages = pkg_resources.require(package)
+    this = packages[0]
+    deps = packages[1:]
 
-      # set the DEFAULT library path and include path for all other extensions
-      for other_ext in self.extensions:
-        if other_ext != ext:
-          other_ext.libraries = libs + (other_ext.libraries if other_ext.libraries else [])
-          other_ext.library_dirs = lib_dirs + (other_ext.library_dirs if other_ext.library_dirs else [])
-          other_ext.include_dirs = include_dirs + (other_ext.include_dirs if other_ext.include_dirs else [])
+    if api_version is not None:
+        retval = "%s: %s [api=0x%04x] (%s)\n" % (
+            this.key,
+            this.version,
+            api_version,
+            this.location,
+        )
     else:
-      # all other libs are build with the default command
-      _build_ext.build_extension(self, ext)
-
-
-  def get_ext_filename(self, fullname):
-    """Returns the library path for the given name"""
-    filename = _build_ext.get_ext_filename(self, fullname)
-    if fullname in self.ext_map:
-      ext = self.ext_map[fullname]
-      if isinstance(ext, Library):
-        # remove any extension that was artificially added by python
-        basename = filename.replace(distutils.sysconfig.get_config_var("SO"), "")
-        # HACK: (for some python versions the above code doesn't seem to work)
-        index = basename.find("cpython-")
-        if index > 0:
-          basename = basename[:index-1]
-
-        return get_full_libname(os.path.basename(basename), os.path.dirname(basename))
-      else:
-        return _build_ext.get_ext_filename(self, fullname)
-
-
-
-## Compile in parallel, when BOB_BUILD_PARALLEL is given
-# see http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
-if "BOB_BUILD_PARALLEL" in os.environ:
-  # monkey-patch for parallel compilation
-  def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None):
-      # those lines are copied from distutils.ccompiler.CCompiler directly
-      macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros, include_dirs, sources, depends, extra_postargs)
-      cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
-      # parallel code
-      N = min(int(os.environ["BOB_BUILD_PARALLEL"]), len(objects)) # number of parallel compilations
-      import multiprocessing.pool
-      def _single_compile(obj):
-          try: src, ext = build[obj]
-          except KeyError: return
-          self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
-      # create process pool
-      pool = multiprocessing.pool.ThreadPool(N)
-      # execute each compilation in a separate process
-      pool.map(_single_compile, objects)
-      # wait until all processes finished
-      pool.close()
-      pool.join()
-
-      return objects
-
-  import distutils.ccompiler
-  distutils.ccompiler.CCompiler.compile=parallelCCompile
-
-
-def get_config(package=__name__, externals=None, api_version=None):
-  """Returns a string containing the configuration information for the given ``package`` name.
-  By default, it returns the configuration of this package.
-
-  This function can be reused by other packages.
-  If these packages have external C or C++ dependencies, the ``externals`` dictionary can be specified.
-  Also, if the package provides an API version, it can be specified.
-
-  **Keyword parameters:**
-
-  package : *str*
-    The name of the package to get the configuration information from.
-    Usually, the ``__name__`` of the package.
-
-  externals : *{dep: description}*
-    A dictionary of external C or C++ dependencies, with the ``dep``endent package name as key, and a free ``description`` as value.
-
-  api_version : *int* (usually in hexadecimal)
-    The API version of the ``package``, if any.
-  """
-
-  import pkg_resources
-  packages = pkg_resources.require(package)
-  this = packages[0]
-  deps = packages[1:]
+        retval = "%s: %s (%s)\n" % (this.key, this.version, this.location)
 
-  if api_version is not None:
-    retval =  "%s: %s [api=0x%04x] (%s)\n" % (this.key, this.version, api_version, this.location)
-  else:
-    retval =  "%s: %s (%s)\n" % (this.key, this.version, this.location)
+    if externals is not None:
+        retval += "* C/C++ dependencies:\n"
+        for k in sorted(externals):
+            retval += "  - %s: %s\n" % (k, externals[k])
 
-  if externals is not None:
-    retval += "* C/C++ dependencies:\n"
-    for k in sorted(externals): retval += "  - %s: %s\n" % (k, externals[k])
+    if len(deps):
+        retval += "* Python dependencies:\n"
+        # sort python dependencies and make them unique
+        deps_dict = {}
+        for d in deps:
+            deps_dict[d.key] = d
+        for k in sorted(deps_dict):
+            retval += "  - %s: %s (%s)\n" % (
+                deps_dict[k].key,
+                deps_dict[k].version,
+                deps_dict[k].location,
+            )
 
-  if len(deps):
-    retval += "* Python dependencies:\n"
-    # sort python dependencies and make them unique
-    deps_dict = {}
-    for d in deps: deps_dict[d.key] = d
-    for k in sorted(deps_dict):
-      retval += "  - %s: %s (%s)\n" % (deps_dict[k].key, deps_dict[k].version, deps_dict[k].location)
+    return retval.strip()
 
-  return retval.strip()
 
 # gets sphinx autodoc done right - don't remove it
-__all__ = [_ for _ in dir() if not _.startswith('_')]
+__all__ = [_ for _ in dir() if not _.startswith("_")]
diff --git a/bob/extension/boost.py b/bob/extension/boost.py
deleted file mode 100644
index 9e1008d92940dc830d1295f3de5fc8e1744c1331..0000000000000000000000000000000000000000
--- a/bob/extension/boost.py
+++ /dev/null
@@ -1,218 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-# Andre Anjos <andre.anjos@idiap.ch>
-# Thu Mar 20 12:38:14 CET 2014
-
-"""Helps looking for Boost on stock file-system locations"""
-
-import os
-import re
-import sys
-import glob
-from distutils.version import LooseVersion
-
-from .utils import uniq, egrep, find_header, find_library
-
-def boost_version(version_hpp):
-
-  matches = egrep(version_hpp, r"^#\s*define\s+BOOST_VERSION\s+(\d+)\s*$")
-  if not len(matches): return None
-
-  # we have a match, produce a string version of the version number
-  version_int = int(matches[0].group(1))
-  version_tuple = (
-      version_int // 100000,
-      (version_int // 100) % 1000,
-      version_int % 100,
-      )
-  return '.'.join([str(k) for k in version_tuple])
-
-class boost:
-  """A class for capturing configuration information from boost
-
-  Example usage:
-
-  .. doctest::
-     :options: +NORMALIZE_WHITESPACE +ELLIPSIS
-
-     >>> from bob.extension import boost
-     >>> pkg = boost('>= 1.35')
-     >>> pkg.include_directory
-     '...'
-     >>> pkg.version
-     '...'
-
-  You can also use this class to retrieve information about installed Boost
-  libraries and link information:
-
-  .. doctest::
-     :options: +NORMALIZE_WHITESPACE +ELLIPSIS
-
-     >>> from bob.extension import boost
-     >>> pkg = boost('>= 1.35')
-     >>> pkg.libconfig(['python', 'system'])
-     (...)
-
-  """
-
-  def __init__ (self, requirement=''):
-    """
-    Searches for the Boost library in stock locations. Allows user to override.
-
-    If the user sets the environment variable BOB_PREFIX_PATH, that prefixes
-    the standard path locations.
-    """
-
-    candidates = find_header('version.hpp', subpaths=['boost', 'boost?*'])
-
-    if not candidates:
-      raise RuntimeError("could not find boost's `version.hpp' - have you installed Boost on this machine?")
-
-    found = False
-
-    if not requirement:
-      # since we use boost headers **including the boost/ directory**, we need to go one level lower
-      self.include_directory = os.path.dirname(os.path.dirname(candidates[0]))
-      self.version = boost_version(candidates[0])
-      found = True
-
-    else:
-
-      # requirement is 'operator' 'version'
-      operator, required = [k.strip() for k in requirement.split(' ', 1)]
-
-      # now check for user requirements
-      for path in candidates:
-        version = boost_version(path)
-        available = LooseVersion(version)
-        if (operator == '<' and available < required) or \
-           (operator == '<=' and available <= required) or \
-           (operator == '>' and available > required) or \
-           (operator == '>=' and available >= required) or \
-           (operator == '==' and available == required):
-          self.include_directory = path
-          self.version = version
-          found = True
-          break
-
-    if not found:
-      raise RuntimeError("could not find the required (%s) version of boost on the file system (looked at: %s)" % (requirement, ', '.join(candidates)))
-
-    # normalize
-    self.include_directory = os.path.normpath(self.include_directory)
-
-
-  def libconfig(self, modules, only_static=False,
-      templates=['boost_%(name)s-mt-%(py)s', 'boost_%(name)s-%(py)s', 'boost_%(name)s%(pyv)s', 'boost_%(name)s-mt', 'boost_%(name)s']):
-    """Returns a tuple containing the library configuration for requested
-    modules.
-
-    This function respects the path location where the include files for Boost
-    are installed.
-
-    Parameters:
-
-    modules (list of strings)
-      A list of string specifying the requested libraries to search for. For
-      example, to search for `libboost_mpi.so`, pass only ``mpi``.
-
-    static (bool)
-      A boolean, indicating if we should try only to search for static versions
-      of the libraries. If not set, any would do.
-
-    templates (list of template strings)
-      A list that defines in which order to search for libraries on the default
-      search path, defined by ``self.include_directory``. Tune this list if you
-      have compiled specific versions of Boost with support to multi-threading
-      (``-mt``), debug (``-g``), STLPORT (``-p``) or required to insert
-      compiler, the underlying thread API used or your own namespace.
-
-      Here are the keywords you can use:
-
-      %(name)s
-        resolves to the module name you are searching for
-
-      %(ver)s
-        resolves to the current boost version string (e.g. ``'1.50.0'``)
-
-      %(py)s
-        resolves to the string ``'pyXY'`` where ``XY`` represent the major and
-        minor versions of the current python interpreter.
-
-      %(pyv)s
-        resolves to the string ``'XY'`` where ``XY`` represent the major and
-        minor versions of the current python interpreter.
-
-      Example templates:
-
-      * ``'boost_%(name)s-mt'``
-      * ``'boost_%(name)s'``
-      * ``'boost_%(name)s-gcc43-%(ver)s'``
-
-    Returns:
-
-    directories (list of strings)
-      A list of directories indicating where the libraries are installed
-
-    libs (list of strings)
-      A list of strings indicating the names of the libraries you can use
-    """
-
-    # make the include header prefix preferential
-    prefix = os.path.dirname(self.include_directory)
-
-    py = 'py%d%d' % sys.version_info[:2]
-    pyv = '%d%d' % sys.version_info[:2]
-
-    filenames = []
-    for module in modules:
-      candidates = []
-      modnames = [k % dict(name=module, ver=self.version, py=py, pyv=pyv) for k in
-          templates]
-
-      for modname in modnames:
-        candidates += find_library(modname, version=self.version,
-            prefixes=[prefix], only_static=only_static)
-
-      if not candidates:
-        raise RuntimeError("cannot find required boost module `%s' - make sure boost is installed on `%s' and that this module is named %s on the filesystem" % (module, prefix, ' or '.join(modnames)))
-
-      # take the first choice that includes the prefix (or the absolute first choice otherwise)
-      index = 0
-      for i, candidate in enumerate(candidates):
-        if candidate.find(prefix) == 0:
-          index = i
-          break
-      filenames.append(candidates[index])
-
-    # libraries
-    libraries = []
-    for f in filenames:
-      name, ext = os.path.splitext(os.path.basename(f))
-      if ext in ['.so', '.a', '.dylib', '.dll']:
-        libraries.append(name[3:]) #strip 'lib' from the name
-      else: #link against the whole thing
-        libraries.append(':' + os.path.basename(f))
-
-    # library paths
-    libpaths = [os.path.dirname(k) for k in filenames]
-
-    return uniq(libpaths), uniq(libraries)
-
-  def macros(self):
-    """Returns package availability and version number macros
-
-    This method returns a python list with 2 macros indicating package
-    availability and a version number, using standard GNU compatible names.
-    Example:
-
-    .. doctest::
-       :options: +NORMALIZE_WHITESPACE +ELLIPSIS
-
-       >>> from bob.extension import boost
-       >>> pkg = boost('>= 1.34')
-       >>> pkg.macros()
-       [('HAVE_BOOST', '1')]
-
-    """
-    return [('HAVE_BOOST', '1')]
diff --git a/bob/extension/cmake.py b/bob/extension/cmake.py
deleted file mode 100644
index 471f9a21379ee5b6421f87d9835a66c90b4a83b0..0000000000000000000000000000000000000000
--- a/bob/extension/cmake.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import os
-
-HEADER = (
-  '\n'
-  '# For both C and C++\n'
-  'set(COMMON_FLAGS "-pedantic -Wall")\n'
-  'if (WIN32)\n'
-  '  set(COMMON_FLAGS "-D_WIN32_WINNT=0x501") # Set min. Windows version to XP\n'
-  'else(WIN32)\n'
-  '  set(COMMON_FLAGS "${COMMON_FLAGS} -pthread")\n'
-  'endif (WIN32)\n'
-  'if (NOT CMAKE_COMPILER_IS_GNUCC)\n'
-  '  # Then, it must be clang/clang++\n'
-  '  set(COMMON_FLAGS "${COMMON_FLAGS} -Qunused-arguments")\n'
-  'endif ()\n'
-  '\n'
-  '# Force __LP64__ scheme on Mac OSX\n'
-  'if(APPLE)\n'
-  '  set(CMAKE_MACOSX_RPATH TRUE CACHE BOOL "Enables the MACOS_RPATH feature for MacOSX builds" FORCE)\n'
-  '  set(COMMON_FLAGS "${COMMON_FLAGS} -m64")\n'
-  'endif(APPLE)\n'
-  '\n'
-  '# For both RELEASE and DEBUG builds\n'
-  'if(APPLE AND CMAKE_COMPILER_IS_GNUCC)\n'
-  '  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.4")\n'
-  '    message(FATAL_ERROR "Minimum GCC version required on OSX is 4.4, but you have ${CMAKE_CXX_COMPILER_VERSION}")\n'
-  '  endif()\n'
-  '  set(COMMON_FLAGS "${COMMON_FLAGS} -Wno-long-long -Wno-variadic-macros")\n'
-  '  set(COMMON_CXX_FLAGS "-std=c++0x")\n'
-  '  set(COMMON_C_FLAGS "-std=c99")\n'
-  'elseif(WIN32)\n'
-  '  set(COMMON_CXX_FLAGS "-std=gnu++0x")\n'
-  '  set(COMMON_C_FLAGS "-std=gnu99")\n'
-  'else()\n'
-  '  set(COMMON_CXX_FLAGS "-std=c++0x")\n'
-  '  set(COMMON_C_FLAGS "-std=c99")\n'
-  'endif()\n'
-  '\n'
-  '# These are used in type checks for cmake, be aware and don\'t change those\n'
-  'set(CMAKE_CXX_FLAGS "${COMMON_CXX_FLAGS} ${COMMON_FLAGS} $ENV{CXXFLAGS} $ENV{CPPFLAGS}" CACHE STRING "Flags used by the compiler during release builds" FORCE)\n'
-  'set(CMAKE_C_FLAGS "${COMMON_C_FLAGS} ${COMMON_FLAGS} $ENV{CFLAGS} $ENV{CPPFLAGS}" CACHE STRING "Flags used by the compiler during release builds" FORCE)\n'
-  '\n'
-  'set(BUILD_SHARED_LIBS "ON" CACHE BOOL "Build shared libs")\n\n'
-)
-
-
-class CMakeListsGenerator:
-  """Generates a CMakeLists.txt file for the given sources, include directories and libraries."""
-
-  def __init__(self, name, sources, target_directory, version = '1.0.0', include_directories = [], system_include_directories=[], libraries = [], library_directories = [], macros = []):
-    """Initializes the CMakeLists generator.
-
-    Keyword parameters:
-
-    name : string
-      The name of the library to generate
-
-    sources : [string]
-      The list of source files that should be compiled with CMake
-
-    target_directory : [string]
-      The directory where the final library should be placed
-
-    version
-      The version of the library, major.minor.patch
-
-    include_directories : [string]
-      A list of include directories required to compile the ``sources``
-
-    system_include_directories : [string]
-      A list of include directories required to compile the ``sources``, which will be added as SYSTEM includes
-
-    libraries : [string]
-      A list of libraries to be linked into the generated library
-
-    library_directories : [string]
-      A list of directories, where the ``libraries`` can be found.
-      Note that the order of this list might be important.
-
-    macros : [(string, string)]
-      A list of preprocessor defines ``name=value`` that will be added to the compilation
-    """
-
-    self.name = name
-    self.sources = sources
-    self.target_directory = target_directory
-    self.version = version
-    self.includes = include_directories
-    self.system_includes = system_include_directories
-    self.libraries = libraries
-    self.library_directories = library_directories
-    self.macros = macros
-
-  def generate(self, source_directory, build_directory):
-    """Generates the CMakeLists.txt file in the given directory."""
-
-    # check if CFLAGS or CXXFLAGS are set, and set them if not
-    if 'CFLAGS' not in os.environ:
-      os.environ['CFLAGS'] = '-O3 -g0 -DNDEBUG -mtune=native'
-    if 'CXXFLAGS' not in os.environ:
-      os.environ['CXXFLAGS'] = '-O3 -g0 -DNDEBUG -mtune=native'
-
-    source_dir = os.path.realpath(source_directory)
-
-    # source and target in different directories -> use absolute paths
-    source_files = [os.path.join(source_dir, s) for s in self.sources]
-
-    filename = os.path.join(build_directory, "CMakeLists.txt")
-    with open(filename, 'w') as f:
-      f.write('# WARNING! This file is automatically generated. Do not change its contents.\n\n')
-      f.write('cmake_minimum_required(VERSION 2.8)\n')
-      f.write('project(%s)\n' % self.name)
-      f.write(HEADER)
-      # add include directories
-      for directory in self.includes:
-        f.write('include_directories(%s)\n' % directory)
-      for directory in self.system_includes:
-        f.write('include_directories(SYSTEM %s)\n' % directory)
-      # add link directories
-      # TODO: handle RPATH and Non-RPATH differently (don't know, how, though)
-      for directory in self.library_directories:
-        f.write('link_directories(%s)\n' % directory)
-      # add defines
-      for macro in self.macros:
-        f.write('add_definitions(-D%s=%s)\n' % macro)
-      # compile this library
-      f.write('\nadd_library(${PROJECT_NAME} \n\t' + "\n\t".join(source_files) + '\n)\n')
-      f.write('set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\n')
-      f.write('set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY %s)\n\n' % self.target_directory)
-      # link libraries
-      if self.libraries:
-        f.write('target_link_libraries(${PROJECT_NAME} %s)\n\n' % " ".join(self.libraries))
diff --git a/bob/extension/config.py b/bob/extension/config.py
index 5421621bcbc032aa969d1b66b1151ec98b5d5b9b..372b84bc76ee02d1f62fae1f9324cb7521c55b81 100644
--- a/bob/extension/config.py
+++ b/bob/extension/config.py
@@ -1,13 +1,15 @@
 #!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 
-'''Functionality to implement python-based config file parsing and loading.
-'''
+"""Functionality to implement python-based config file parsing and loading.
+"""
 
-import types
+import logging
 import pkgutil
+import types
+
 from os.path import isfile
-import logging
+
 import pkg_resources
 
 logger = logging.getLogger(__name__)
@@ -16,276 +18,293 @@ LOADED_CONFIGS = []
 
 
 def _load_context(path, mod):
-  '''Loads the Python file as module, returns a resolved context
+    """Loads the Python file as module, returns a resolved context
+
+    This function is implemented in a way that is both Python 2 and Python 3
+    compatible. It does not directly load the python file, but reads its contents
+    in memory before Python-compiling it. It leaves no traces on the file system.
+
+    Parameters
+    ----------
+    path : str
+        The full path of the Python file to load the module contents
+        from
+    mod : module
+        A preloaded module to use as context for the next module
+        loading. You can create a new module using :py:mod:`types` as in ``m =
+        types.ModuleType('name'); m.__dict__.update(ctxt)`` where ``ctxt`` is a
+        python dictionary with string -> object values representing the contents
+        of the module to be created.
+
+    Returns
+    -------
+    mod : :any:`module`
+        A python module with the fully resolved context
+    """
+
+    # executes the module code on the context of previously imported modules
+    with open(path, "rb") as f:
+        exec(compile(f.read(), path, "exec"), mod.__dict__)
 
-  This function is implemented in a way that is both Python 2 and Python 3
-  compatible. It does not directly load the python file, but reads its contents
-  in memory before Python-compiling it. It leaves no traces on the file system.
-
-  Parameters
-  ----------
-  path : str
-      The full path of the Python file to load the module contents
-      from
-  mod : module
-      A preloaded module to use as context for the next module
-      loading. You can create a new module using :py:mod:`types` as in ``m =
-      types.ModuleType('name'); m.__dict__.update(ctxt)`` where ``ctxt`` is a
-      python dictionary with string -> object values representing the contents
-      of the module to be created.
-
-  Returns
-  -------
-  mod : :any:`module`
-      A python module with the fully resolved context
-  '''
-
-  # executes the module code on the context of previously imported modules
-  with open(path, "rb") as f:
-    exec(compile(f.read(), path, 'exec'), mod.__dict__)
-
-  return mod
+    return mod
 
 
 def _get_module_filename(module_name):
-  """Resolves a module name to an actual Python file.
-
-  Parameters
-  ----------
-  module_name : str
-      The name of the module
-
-  Returns
-  -------
-  str
-      The Python files that corresponds to the module name.
-  """
-  loader = pkgutil.get_loader(module_name)
-  if loader is None:
-    return ''
-  try:
-    return loader.path
-  except AttributeError:
-    return loader.filename
+    """Resolves a module name to an actual Python file.
+
+    Parameters
+    ----------
+    module_name : str
+        The name of the module
+
+    Returns
+    -------
+    str
+        The Python files that corresponds to the module name.
+    """
+    loader = pkgutil.get_loader(module_name)
+    if loader is None:
+        return ""
+    try:
+        return loader.path
+    except AttributeError:
+        return loader.filename
 
 
 def _object_name(path, common_name):
-  path = path.rsplit(':', 1)
-  name = path[1] if len(path) > 1 else common_name
-  path = path[0]
-  return path, name
-
-
-def _resolve_entry_point_or_modules(paths, entry_point_group,
-                                    common_name=None):
-  """Resolves a mixture of paths, entry point names, and module names to just
-  paths. For example paths can be:
-  ``paths = ['/tmp/config.py', 'config1', 'bob.extension.config2']``.
-
-  Parameters
-  ----------
-  paths : [str]
-      An iterable strings that either point to actual files, are entry point
-      names, or are module names.
-  entry_point_group : str
-      The entry point group name to search in entry points.
-  common_name : None or str
-      It will be used as a default name for object names. See the
-      attribute_name parameter from :any:`load`.
-
-  Raises
-  ------
-  ValueError
-      If one of the paths cannot be resolved to an actual path to a file.
-
-  Returns
-  -------
-  paths : [str]
-      The resolved paths pointing to existing files.
-  module_names : [str]
-      The valid python module names to bind each of the files to
-  object_names : [str]
-      The name of objects that are supposed to be picked from paths.
-  """
-
-  entries = {e.name: e for e in
-             pkg_resources.iter_entry_points(entry_point_group)}
-
-  files = []
-  module_names = []
-  object_names = []
-
-  for i, path in enumerate(paths):
-
-    old_path = path
-    module_name = 'user_config'  # fixed module name for files with full paths
-    path, object_name = _object_name(path, common_name)
-
-    # if it already points to a file
-    if isfile(path):
-      pass
-
-    # If it is an entry point name, collect path and module name
-    elif path in entries:
-      entry = entries[path]
-      module_name = entry.module_name
-      object_name = entry.attrs[0] if entry.attrs else common_name
-      path = _get_module_filename(module_name)
-      if not isfile(path):
-        raise ValueError(
-            "The specified entry point: `{}' pointing to module: `{}' and "
-            "resolved to: `{}' does not point to an existing "
-            "file.".format(old_path, module_name, path))
-
-    # If it is not a path nor an entry point name, it is a module name then?
-    else:
-      # if we have gotten here so far then path is the module_name.
-      module_name = path
-      path = _get_module_filename(path)
-      if not isfile(path):
-        raise ValueError(
-            "The specified path: `{}' resolved to: `{}' is not a file, not a "
-            "entry point name of `{}', nor a module name".format(
-                old_path, path, entry_point_group or ''))
-
-    files.append(path)
-    module_names.append(module_name)
-    object_names.append(object_name)
-
-  return files, module_names, object_names
+    path = path.rsplit(":", 1)
+    name = path[1] if len(path) > 1 else common_name
+    path = path[0]
+    return path, name
+
+
+def _resolve_entry_point_or_modules(paths, entry_point_group, common_name=None):
+    """Resolves a mixture of paths, entry point names, and module names to just
+    paths. For example paths can be:
+    ``paths = ['/tmp/config.py', 'config1', 'bob.extension.config2']``.
+
+    Parameters
+    ----------
+    paths : [str]
+        An iterable strings that either point to actual files, are entry point
+        names, or are module names.
+    entry_point_group : str
+        The entry point group name to search in entry points.
+    common_name : None or str
+        It will be used as a default name for object names. See the
+        attribute_name parameter from :any:`load`.
+
+    Raises
+    ------
+    ValueError
+        If one of the paths cannot be resolved to an actual path to a file.
+
+    Returns
+    -------
+    paths : [str]
+        The resolved paths pointing to existing files.
+    module_names : [str]
+        The valid python module names to bind each of the files to
+    object_names : [str]
+        The name of objects that are supposed to be picked from paths.
+    """
+
+    entries = {
+        e.name: e for e in pkg_resources.iter_entry_points(entry_point_group)
+    }
+
+    files = []
+    module_names = []
+    object_names = []
+
+    for i, path in enumerate(paths):
+
+        old_path = path
+        module_name = (
+            "user_config"  # fixed module name for files with full paths
+        )
+        path, object_name = _object_name(path, common_name)
+
+        # if it already points to a file
+        if isfile(path):
+            pass
+
+        # If it is an entry point name, collect path and module name
+        elif path in entries:
+            entry = entries[path]
+            module_name = entry.module_name
+            object_name = entry.attrs[0] if entry.attrs else common_name
+            path = _get_module_filename(module_name)
+            if not isfile(path):
+                raise ValueError(
+                    "The specified entry point: `{}' pointing to module: `{}' and "
+                    "resolved to: `{}' does not point to an existing "
+                    "file.".format(old_path, module_name, path)
+                )
+
+        # If it is not a path nor an entry point name, it is a module name then?
+        else:
+            # if we have gotten here so far then path is the module_name.
+            module_name = path
+            path = _get_module_filename(path)
+            if not isfile(path):
+                raise ValueError(
+                    "The specified path: `{}' resolved to: `{}' is not a file, not a "
+                    "entry point name of `{}', nor a module name".format(
+                        old_path, path, entry_point_group or ""
+                    )
+                )
+
+        files.append(path)
+        module_names.append(module_name)
+        object_names.append(object_name)
+
+    return files, module_names, object_names
 
 
 def load(paths, context=None, entry_point_group=None, attribute_name=None):
-  '''Loads a set of configuration files, in sequence
-
-  This method will load one or more configuration files. Every time a
-  configuration file is loaded, the context (variables) loaded from the
-  previous file is made available, so the new configuration file can override
-  or modify this context.
-
-  Parameters
-  ----------
-  paths : [str]
-      A list or iterable containing paths (relative or absolute) of
-      configuration files that need to be loaded in sequence. Each
-      configuration file is loaded by creating/modifying the context generated
-      after each file readout.
-  context : :py:class:`dict`, optional
-      If provided, start the readout of the first configuration file with the
-      given context. Otherwise, create a new internal context.
-  entry_point_group : :py:class:`str`, optional
-      If provided, it will treat non-existing file paths as entry point names
-      under the ``entry_point_group`` name.
-  attribute_name : None or str
-      If provided, will look for the attribute_name variable inside the loaded
-      files. Paths ending with `some_path:variable_name` can override the
-      attribute_name. The entry_point_group must provided as well
-      attribute_name is not None.
-
-  Returns
-  -------
-  mod : :any:`module` or object
-      A module representing the resolved context, after loading the provided
-      modules and resolving all variables. If attribute_name is given, the
-      object with the attribute_name name (or the name provided by user) is
-      returned instead of the module.
-
-  Raises
-  ------
-  ImportError
-      If attribute_name is given but the object does not exist in the paths.
-  ValueError
-      If attribute_name is given but entry_point_group is not given.
-
-  '''
-  if attribute_name and not entry_point_group:
-    raise ValueError(
-        "entry_point_group must be provided when using the "
-        "attribute_name parameter.")
-
-  # resolve entry points to paths
-  if entry_point_group is not None:
-    paths, names, object_names = _resolve_entry_point_or_modules(
-        paths, entry_point_group, attribute_name)
-  else:
-    names = len(paths) * ['user_config']
-
-  ctxt = types.ModuleType('initial_context')
-  if context is not None:
-    ctxt.__dict__.update(context)
-  # Small gambiarra (https://www.urbandictionary.com/define.php?term=Gambiarra)
-  # to avoid the garbage collector to collect some already imported modules.
-  LOADED_CONFIGS.append(ctxt)
-
-  # if no paths are provided, return context
-  if not paths:
-    return ctxt
-
-  for k, n in zip(paths, names):
-    logger.debug("Loading configuration file `%s'...", k)
-    mod = types.ModuleType(n)
-    # remove the keys that might break the loading of the next config file.
-    ctxt.__dict__.pop('__name__', None)
-    ctxt.__dict__.pop('__package__', None)
-    # do not propogate __ variables
-    context = {k: v for k, v in ctxt.__dict__.items() if not k.startswith('__')}
-    mod.__dict__.update(context)
-    LOADED_CONFIGS.append(mod)
-    ctxt = _load_context(k, mod)
-
-  if not attribute_name:
-    return mod
-
-  # We pick the last object_name here. Normally users should provide just one
-  # path when enabling the attribute_name parameter.
-  attribute_name = object_names[-1]
-  if not hasattr(mod, attribute_name):
-    raise ImportError(
-        "The desired variable '%s' does not exist in any of "
-        "your configuration files: %s" % (attribute_name, ', '.join(paths)))
-
-  return getattr(mod, attribute_name)
+    """Loads a set of configuration files, in sequence
+
+    This method will load one or more configuration files. Every time a
+    configuration file is loaded, the context (variables) loaded from the
+    previous file is made available, so the new configuration file can override
+    or modify this context.
+
+    Parameters
+    ----------
+    paths : [str]
+        A list or iterable containing paths (relative or absolute) of
+        configuration files that need to be loaded in sequence. Each
+        configuration file is loaded by creating/modifying the context generated
+        after each file readout.
+    context : :py:class:`dict`, optional
+        If provided, start the readout of the first configuration file with the
+        given context. Otherwise, create a new internal context.
+    entry_point_group : :py:class:`str`, optional
+        If provided, it will treat non-existing file paths as entry point names
+        under the ``entry_point_group`` name.
+    attribute_name : None or str
+        If provided, will look for the attribute_name variable inside the loaded
+        files. Paths ending with `some_path:variable_name` can override the
+        attribute_name. The entry_point_group must provided as well
+        attribute_name is not None.
+
+    Returns
+    -------
+    mod : :any:`module` or object
+        A module representing the resolved context, after loading the provided
+        modules and resolving all variables. If attribute_name is given, the
+        object with the attribute_name name (or the name provided by user) is
+        returned instead of the module.
+
+    Raises
+    ------
+    ImportError
+        If attribute_name is given but the object does not exist in the paths.
+    ValueError
+        If attribute_name is given but entry_point_group is not given.
+
+    """
+    if attribute_name and not entry_point_group:
+        raise ValueError(
+            "entry_point_group must be provided when using the "
+            "attribute_name parameter."
+        )
+
+    # resolve entry points to paths
+    if entry_point_group is not None:
+        paths, names, object_names = _resolve_entry_point_or_modules(
+            paths, entry_point_group, attribute_name
+        )
+    else:
+        names = len(paths) * ["user_config"]
+
+    ctxt = types.ModuleType("initial_context")
+    if context is not None:
+        ctxt.__dict__.update(context)
+    # Small gambiarra (https://www.urbandictionary.com/define.php?term=Gambiarra)
+    # to avoid the garbage collector to collect some already imported modules.
+    LOADED_CONFIGS.append(ctxt)
+
+    # if no paths are provided, return context
+    if not paths:
+        return ctxt
+
+    for k, n in zip(paths, names):
+        logger.debug("Loading configuration file `%s'...", k)
+        mod = types.ModuleType(n)
+        # remove the keys that might break the loading of the next config file.
+        ctxt.__dict__.pop("__name__", None)
+        ctxt.__dict__.pop("__package__", None)
+        # do not propogate __ variables
+        context = {
+            k: v for k, v in ctxt.__dict__.items() if not k.startswith("__")
+        }
+        mod.__dict__.update(context)
+        LOADED_CONFIGS.append(mod)
+        ctxt = _load_context(k, mod)
+
+    if not attribute_name:
+        return mod
+
+    # We pick the last object_name here. Normally users should provide just one
+    # path when enabling the attribute_name parameter.
+    attribute_name = object_names[-1]
+    if not hasattr(mod, attribute_name):
+        raise ImportError(
+            "The desired variable '%s' does not exist in any of "
+            "your configuration files: %s" % (attribute_name, ", ".join(paths))
+        )
+
+    return getattr(mod, attribute_name)
 
 
 def mod_to_context(mod):
-  """Converts the loaded module of :any:`load` to a dictionary context.
-  This function removes all the variables that start and end with ``__``.
-
-  Parameters
-  ----------
-  mod : object
-      What is returned by :any:`load`
-
-  Returns
-  -------
-  dict
-      The context that was in ``mod``.
-  """
-  return {k: v for k, v in mod.__dict__.items()
-          if not (k.startswith('__') and k.endswith('__'))}
-
-
-def resource_keys(entry_point_group, exclude_packages=[], strip=['dummy']):
-  """Reads and returns all resources that are registered with the given
-  entry_point_group. Entry points from the given ``exclude_packages`` are
-  ignored.
-
-  Parameters
-  ----------
-  entry_point_group : str
-      The entry point group name.
-  exclude_packages : :any:`list`, optional
-      List of packages to exclude when finding resources.
-  strip : :any:`list`, optional
-      Entrypoint names that start with any value in ``strip`` will be ignored.
-
-  Returns
-  -------
-  :any:`list`
-      List of found resources.
-  """
-  ret_list = [entry_point.name for entry_point in
-              pkg_resources.iter_entry_points(entry_point_group)
-              if (entry_point.dist.project_name not in exclude_packages and
-                  not entry_point.name.startswith(tuple(strip)))]
-  return sorted(ret_list)
+    """Converts the loaded module of :any:`load` to a dictionary context.
+    This function removes all the variables that start and end with ``__``.
+
+    Parameters
+    ----------
+    mod : object
+        What is returned by :any:`load`
+
+    Returns
+    -------
+    dict
+        The context that was in ``mod``.
+    """
+    return {
+        k: v
+        for k, v in mod.__dict__.items()
+        if not (k.startswith("__") and k.endswith("__"))
+    }
+
+
+def resource_keys(entry_point_group, exclude_packages=[], strip=["dummy"]):
+    """Reads and returns all resources that are registered with the given
+    entry_point_group. Entry points from the given ``exclude_packages`` are
+    ignored.
+
+    Parameters
+    ----------
+    entry_point_group : str
+        The entry point group name.
+    exclude_packages : :any:`list`, optional
+        List of packages to exclude when finding resources.
+    strip : :any:`list`, optional
+        Entrypoint names that start with any value in ``strip`` will be ignored.
+
+    Returns
+    -------
+    :any:`list`
+        List of found resources.
+    """
+    ret_list = [
+        entry_point.name
+        for entry_point in pkg_resources.iter_entry_points(entry_point_group)
+        if (
+            entry_point.dist.project_name not in exclude_packages
+            and not entry_point.name.startswith(tuple(strip))
+        )
+    ]
+    return sorted(ret_list)
diff --git a/bob/extension/data/config_with_module.py b/bob/extension/data/config_with_module.py
index ecb2d0d7920abe07bf1ef0d7660f457cb9b01861..a0311e761de7e9a2b693068464d572c524686135 100644
--- a/bob/extension/data/config_with_module.py
+++ b/bob/extension/data/config_with_module.py
@@ -1,4 +1,5 @@
 import numpy
 
+
 def return_zeros():
     return numpy.zeros(shape=(2,))
diff --git a/bob/extension/data/defaults-config b/bob/extension/data/defaults-config
index 39e8f86c8c91996ba8d159da85115697914bfff4..f332e2b115a3091f8162ae94d5404bbd03ad614e 100644
--- a/bob/extension/data/defaults-config
+++ b/bob/extension/data/defaults-config
@@ -1,4 +1,4 @@
 {
     "bob.db.atnt.directory": "/home/bob/databases/atnt",
     "bob.db.mobio.directory": "/home/bob/databases/mobio"
-}
\ No newline at end of file
+}
diff --git a/bob/extension/data/load_config.py b/bob/extension/data/load_config.py
index 21abd01b385338741042deb0c5a554810c2fdde8..c908703b9f352b20f38d60353257f8ca4513f1a3 100644
--- a/bob/extension/data/load_config.py
+++ b/bob/extension/data/load_config.py
@@ -1,3 +1,3 @@
 # the b variable from the last config file is available here
-c = b + 1
-b = b + 3
+c = b + 1  # noqa: F821
+b = b + 3  # noqa: F821
diff --git a/bob/extension/data/resource_config.py b/bob/extension/data/resource_config.py
index 2ba8046898a68e9f1b34d121f38441e4644e8db8..a15dd71ba699f4ba70a34ef6bf8ffa53d4fbf542 100644
--- a/bob/extension/data/resource_config.py
+++ b/bob/extension/data/resource_config.py
@@ -1,3 +1,3 @@
 a = 1
 b = a + 2
-from .. import rc
+from .. import rc  # noqa: F401
diff --git a/bob/extension/data/subpackage/config.py b/bob/extension/data/subpackage/config.py
index 3abcf96fdcb4d94ec5a9889224d7bff10ed507d1..ddb815b1328b6d8046f733dfd1a527b21184c412 100644
--- a/bob/extension/data/subpackage/config.py
+++ b/bob/extension/data/subpackage/config.py
@@ -1 +1 @@
-from ..basic_config import *
+from ..basic_config import *  # noqa: F401,F403
diff --git a/bob/extension/download.py b/bob/extension/download.py
index f55aad4743af4a8a1d1ade80d5c41bf9ee1dec81..e4e43826d32def7144899a243b82e3bcab41af8c 100644
--- a/bob/extension/download.py
+++ b/bob/extension/download.py
@@ -8,6 +8,7 @@ import logging
 import os
 import tarfile
 import zipfile
+
 from pathlib import Path
 from shutil import copyfileobj
 from urllib.request import urlopen
@@ -18,7 +19,9 @@ logger = logging.getLogger(__name__)
 
 
 def _bob_data_folder():
-    return rc.get("bob_data_folder", os.path.join(os.path.expanduser("~"), "bob_data"))
+    return rc.get(
+        "bob_data_folder", os.path.join(os.path.expanduser("~"), "bob_data")
+    )
 
 
 def _unzip(zip_file, directory):
@@ -116,7 +119,9 @@ def download_file_from_possible_urls(urls, out_file):
             download_file(url, out_file)
             break
         except Exception:
-            logger.warning("Could not download from the %s url", url, exc_info=True)
+            logger.warning(
+                "Could not download from the %s url", url, exc_info=True
+            )
     else:  # else is for the for loop
         raise RuntimeError(
             f"Could not download the requested file from the following urls: {urls}"
diff --git a/bob/extension/include/bob.extension/defines.h b/bob/extension/include/bob.extension/defines.h
deleted file mode 100644
index 84d0b4af2cce5b62574fbc4aa5401a77abc8a03b..0000000000000000000000000000000000000000
--- a/bob/extension/include/bob.extension/defines.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * @file bob/extension/include/bob.extension/documentation.h
- * @date Fri Nov 21 10:27:38 CET 2014
- * @author Manuel Guenther <manuel.guenther@idiap.ch>
- *
- * @brief Implements a few functions to generate doc strings
- *
- * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
- */
-
-
-/** By including this file, you will be able to
-*
-* 1. Use the Python2 functions PyInt_Check, PyInt_AS_LONG, PyString_Check, PyString_FromString and PyString_AS_STRING within the bindings for python3
-* 2. Add try{ ... } catch {...} blocks around your bindings so that you make sure that **all** exceptions in the C++ code are handled correctly.
-*
-*/
-
-#ifndef BOB_EXTENSION_DEFINES_H_INCLUDED
-#define BOB_EXTENSION_DEFINES_H_INCLUDED
-
-
-#if PY_VERSION_HEX >= 0x03000000
-#define PyInt_Check PyLong_Check
-#define PyInt_AS_LONG PyLong_AS_LONG
-#define PyString_Check PyUnicode_Check
-#define PyString_FromString PyUnicode_FromString
-#define PyString_FromFormat PyUnicode_FromFormat
-#define PyString_AS_STRING(x) PyBytes_AS_STRING(make_safe(PyUnicode_AsUTF8String(x)).get())
-#define PyString_AsString(x) PyUnicode_AsUTF8(x)
-#endif
-
-#define PyBob_NumberCheck(x) (PyInt_Check(x) || PyLong_Check(x) || PyFloat_Check(x) || PyComplex_Check(x))
-
-
-// BOB_TRY is simply a try{
-#define BOB_TRY try{
-
-// for catching exceptions, you can define a message, and you have to select
-// the error return value (i.e., -1 for constructors, and 0 for other
-// functions)
-
-// There exist two macros that will print slightly different messages.
-// BOB_CATCH_MEMBER is to be used within the binding of a class, and it will
-// use the "self" pointer
-// BOB_CATCH_FUNCTION is to be used to bind functions outside a class
-#define BOB_CATCH_MEMBER(message,ret) }\
-  catch (std::exception& e) {\
-    PyErr_Format(PyExc_RuntimeError, "%s - %s: C++ exception caught: '%s'", Py_TYPE(self)->tp_name, message, e.what());\
-    return ret;\
-  } \
-  catch (...) {\
-    PyErr_Format(PyExc_RuntimeError, "%s - %s: unknown exception caught", Py_TYPE(self)->tp_name, message);\
-    return ret;\
-  }
-
-#define BOB_CATCH_FUNCTION(message, ret) }\
-  catch (std::exception& e) {\
-    PyErr_Format(PyExc_RuntimeError, "%s: C++ exception caught: '%s'", message, e.what());\
-    return ret;\
-  } \
-  catch (...) {\
-    PyErr_Format(PyExc_RuntimeError, "%s: unknown exception caught", message);\
-    return ret;\
-  }
-
-#endif // BOB_EXTENSION_DEFINES_H_INCLUDED
diff --git a/bob/extension/include/bob.extension/documentation.h b/bob/extension/include/bob.extension/documentation.h
deleted file mode 100644
index d444cb1340aabdc2654ef0ea42410f03d4b89ad6..0000000000000000000000000000000000000000
--- a/bob/extension/include/bob.extension/documentation.h
+++ /dev/null
@@ -1,779 +0,0 @@
-/**
- * @file bob/extension/include/bob.extension/documentation.h
- * @date Fri Feb 21 18:29:37 CET 2014
- * @author Manuel Guenther <manuel.guenther@idiap.ch>
- *
- * @brief Implements a few functions to generate doc strings
- *
- * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
- */
-
-#ifndef BOB_EXTENSION_DOCUMENTATION_H_INCLUDED
-#define BOB_EXTENSION_DOCUMENTATION_H_INCLUDED
-
-#include <string>
-#include <vector>
-#include <set>
-#include <stdexcept>
-#include <iostream>
-#include <string.h>
-
-#include <bob.extension/defines.h>
-
-namespace bob{
-  namespace extension{
-
-    /**
-     * Use a static object of this class to document a variable.
-     * This class can be used to document both global variables as well as class member variables.
-     */
-    class VariableDoc {
-      friend class ClassDoc;
-      public:
-        /**
-         * Generates a VariableDoc object. Please assure that use use this as a static member variable.
-         * @param variable_name      The name of the variable
-         * @param variable_type      The type of the variable, e.g., "float" or "array_like (float, 2D)"
-         * @param short_description  A short description of the variable
-         * @param long_description   An optional long description of the variable
-         */
-        VariableDoc(
-          const char* const variable_name,
-          const char* const variable_type,
-          const char* const short_description,
-          const char* const long_description = 0
-        );
-
-        /**
-         * Returns the name of the variable that is documented, i.e., the "variable_name" parameter of the constructor.
-         */
-        char* name() const {return const_cast<char*>(variable_name.c_str());}
-
-        /**
-         * Generates and returns the documentation string.
-         * @param alignment The default alignment is 80 characters.
-         *                  Since the package level documentation is automatically indented by 8 spaces in the python documentation, we need to subtract these values here...
-         * @return The documentation string, properly aligned, possibly including "ToDo's" for detected problems.
-         */
-        char* doc(const unsigned alignment = 72) const;
-
-      private:
-         // variable name and type
-         std::string variable_name;
-         std::string variable_type;
-         // variable description
-         std::string variable_description;
-
-         // an internal string that is generated and returned.
-         mutable std::string description;
-
-    };
-
-    /**
-     * Use a static object of this class to generate a properly aligned function documentation.
-     * Documentation generated by this class can be used for non-member functions as well as for member functions and constructors.
-     */
-    class FunctionDoc {
-      friend class ClassDoc;
-      public:
-        /**
-         * Generates a FunctionDoc object. Please assure that use use this as a static member variable.
-         * @param function_name     The name of the function you want to document
-         * @param short_description A short description of what the function does
-         * @param long_description  An optional long description of the function
-         * @param is_member_function  Set this to true if this documentation is the documentation of a member function
-         */
-        FunctionDoc(
-          const char* const function_name,
-          const char* const short_description,
-          const char* const long_description = 0,
-          bool is_member_function = false
-        );
-
-        /**
-         * Copy constructor; will deep-copy the kwlists
-         */
-        FunctionDoc(const FunctionDoc& other);
-
-        /** Destrcutor */
-        ~FunctionDoc();
-
-        /**
-         * Clones this FunctionDoc by providing a new function name
-         * This is useful, when a function is bound with several names.
-         * @param function_name     The new name of the function
-        */
-        FunctionDoc clone(const char* const function_name){
-          FunctionDoc retval = FunctionDoc(*this);
-          retval.function_name = function_name;
-          return retval;
-        }
-
-        /**
-         * Add a prototypical call for this function by defining the parameters and the return values.
-         * This function has to be called at least ones.
-         * @param variables    A string containing a comma-separated list of parameters, e.g., "param1, param2"
-         * @param return_value A string containing a comma-separated list of return values, e.g., "retval1, retval2".
-         *                     If the function does not return anything, this value can be left at its default "None".
-         *                     To document a constructor, please use "" as return value.
-         */
-        FunctionDoc& add_prototype(
-          const char* const variables,
-          const char* const return_value = "None"
-        );
-
-        /**
-         * Add the documentation for a parameter added with the add_prototype function
-         * @param parameter_name   The name of the parameter, e.g. "param1"
-         * @param parameter_type   The type of the parameter, e.g. "float" or "array_like (float, 2D)"; indicate if the parameter is optional here
-         * @param parameter_description  The description of the parameter
-         */
-        FunctionDoc& add_parameter(
-          const char* const parameter_name,
-          const char* const parameter_type,
-          const char* const parameter_description
-        );
-        /**
-         * Add the documentation of a return value added with the add_prototype function
-         * @param return_name   The name assigned to the return value
-         * @param return_type   The tape of the returned value
-         * @param return_description  The description of the return value
-         */
-        FunctionDoc& add_return(
-          const char* const return_name,
-          const char* const return_type,
-          const char* const return_description
-        );
-
-        /**
-         * Returns the name of the function that is documented (i.e., the function_name parameter of the constructor)
-         */
-        const char* const name() const {return function_name.c_str();}
-
-        /**
-         * Generates and returns the documentation string.
-         * A .. todo:: directive is added for each detected mistake.
-         * @param alignment The default alignment is 80 characters.
-         *                  Since the package level documentation is automatically indented by 8 spaces in the python documentation, we need to subtract these values here...
-         * @return The documentation string, properly aligned, possibly including "ToDo's" for detected problems.
-         */
-        const char* const doc(const unsigned alignment = 72, const unsigned indent = 0) const;
-
-        /**
-         * Returns the (NULL-terminated) list of variables for the given prototype index, which can be used as kwlist argument in the bindings.
-         * @param index  The index of the prototype
-         * @return  A NULL-terminated list of variable names
-         */
-        char** kwlist(unsigned index = 0) const{
-          if (index >= kwlists.size()) throw std::runtime_error("The prototype for the given index is not found");
-          return kwlists[index];
-        }
-
-        /**
-         * Generates and prints the usage string, simply listing the possible ways to call the function.
-         *
-         * The error meaasge is printed to the std::cerr stream
-         */
-        void print_usage() const;
-
-
-      private:
-        // the function name
-        std::string function_name;
-        // the description
-        std::string function_description;
-        // if this is a member function, the indentation must be shorter
-        bool is_member;
-        // prototypes
-        std::vector<std::string> prototype_variables;
-        std::vector<std::string> prototype_returns;
-        // parameter documentation
-        std::vector<std::string> parameter_names;
-        std::vector<std::string> parameter_types;
-        std::vector<std::string> parameter_descriptions;
-        // return value documentation
-        std::vector<std::string> return_names;
-        std::vector<std::string> return_types;
-        std::vector<std::string> return_descriptions;
-
-        // the list of wey-word arguments
-        std::vector<char**> kwlists;
-
-        // an internal string that is generated and returned.
-        mutable std::string description;
-    };
-
-
-    /**
-     * Use a static object of this class to document a class.
-     * Documenting a class includes the documentation of the constructor,
-     * but not the documentation of the other member functions.
-     * For those, please use the FunctionDoc class.
-     */
-    class ClassDoc{
-      public:
-        /**
-         * Generates a ClassDoc object. Please assure that use use this as a static member variable.
-         * @param class_name  The name of the class to be documented
-         * @param short_description  A short description of the class
-         * @param long_description  An optional long description of the class
-         */
-        ClassDoc(
-          const char* const class_name,
-          const char* const short_description,
-          const char* const long_description = 0
-        );
-
-        /**
-         * Add the documentation of the constructor.
-         * This function can be called only once.
-         * @param constructor_documentation  An instance of the FunctionDoc class that contains the documentation of the constructor.
-         *                                   Please read the documentation of that class on how to generate constructor documentations.
-         */
-        ClassDoc& add_constructor(
-          const FunctionDoc& constructor_documentation
-        );
-
-        /**
-         * Adds the given function to the highlighted section.
-         * @param function_documentation   An instance of the FunctionDoc class that should be highlighted.
-         */
-        ClassDoc& highlight(
-          const FunctionDoc& function_documentation
-        );
-
-        /**
-         * Adds the given variable to the highlighted section.
-         * @param function_documentation   An instance of the FunctionDoc class that should be highlighted.
-         */
-        ClassDoc& highlight(
-          const VariableDoc& variable_documentation
-        );
-
-        /**
-         * Returns the name of the class that is documented, i.e., the "class_name" parameter of the constructor.
-         */
-        char* name() const {return const_cast<char*>(class_name.c_str());}
-
-        /**
-         * Generates and returns the documentation string.
-         * @param alignment The default alignment is 80 characters.
-         *                  Since the package level documentation is automatically indented by 8 spaces in the python documentation, we need to subtract these values here...
-         * @return The documentation string, properly aligned, possibly including "ToDo's" for detected problems.
-         */
-        char* doc(const unsigned alignment = 72) const;
-
-        /**
-         * Returns the (NULL-terminated) list of variables of the constructor documentation for the given prototype index, which can be used as kwlist argument in the bindings.
-         * @param index  The index of the prototype
-         * @return  A NULL-terminated list of variable names
-         */
-        char** kwlist(unsigned index = 0) const{
-          if (constructor.empty()) throw std::runtime_error("The class documentation does not have constructor documentation");
-          return constructor.front().kwlist(index);
-        }
-
-        /**
-         * Prints the usage string of the constructor, if available.
-         */
-        void print_usage() const {if (!constructor.empty()) constructor.front().print_usage();}
-
-
-      private:
-        // class name
-        std::string class_name;
-        // class description
-        std::string class_description;
-        // constructor; should be only one, though
-        std::vector<FunctionDoc> constructor;
-
-        // highlighting
-        std::vector<FunctionDoc> highlighted_functions;
-        std::vector<VariableDoc> highlighted_variables;
-
-        // an internal string that is generated and returned.
-        mutable std::string description;
-    };
-
-  }
-}
-
-
-/////////////////////////////////////////////////////////////
-//////////////////// Implementations ////////////////////////
-/////////////////////////////////////////////////////////////
-
-
-/////////////////////////////////////////////////////////////
-/// helper functions
-
-#ifndef BOB_SHORT_DOCSTRINGS
-// removes leading and trailing spaces
-static std::string _strip(const std::string& str, const std::string& sep = " []()|"){
-  unsigned first = 0, last = str.size();
-  while (first < str.size() && sep.find(str[first]) != std::string::npos) ++first;
-  while (last > 0 && sep.find(str[last-1]) != std::string::npos) --last;
-  return str.substr(first, last-first);
-}
-
-// splits the given string by the given separator
-static std::vector<std::string> _split(const std::string& str, char limit = ' ', bool allow_empty=true){
-  std::vector<std::string> splits;
-  size_t j = str.find_first_not_of(limit);
-  size_t i = str.find(limit, j);
-  j = 0;
-  while (i != std::string::npos){
-    splits.push_back(str.substr(j, i-j));
-    j = i+1;
-    i = str.find(limit, j);
-  }
-  splits.push_back(str.substr(j));
-  if (!allow_empty && !splits.empty() && splits.back().empty())
-    splits.pop_back();
-  return splits;
-}
-
-// aligns the given string using the given indent to the given alignment length;
-// line breaks are handled carefully.
-static std::string _align(std::string str, unsigned indent, unsigned alignment){
-  // first, split the newlines
-  auto lines = _split(str, '\n');
-
-  std::string aligned;
-  unsigned current_indent = indent;
-  bool first_line = true;
-  // now, split each line
-  for (auto line_it = lines.begin(); line_it != lines.end(); ++line_it){
-    auto words = _split(*line_it);
-    // fill in one line
-    unsigned len = 0;
-    unsigned new_indent = indent;
-    if (!line_it->empty()){
-      // increase indent?
-      const std::string& w = _strip(words[0], " ");
-      if ((w.size() == 2 && w[0] == '.' && w[1] == '.') ||
-          (w.size() >= 1 && '0' <= w[0] && '9' >= w[0]) ||
-          (w.size() == 1 && '*' == w[0]) ){
-        new_indent += w.size() + 1;
-      }
-      size_t first_word_indent = line_it->find_first_not_of(' ');
-      if (first_word_indent != std::string::npos && first_word_indent != 0){
-        new_indent += first_word_indent;
-      }
-    }
-    for (auto word_it = words.begin(); word_it != words.end(); ++word_it){
-      if (aligned.empty() || len + word_it->size() >= alignment || !first_line){
-        // line reached alignment
-        if (!aligned.empty()){
-          aligned += "\n";
-        }
-        // add indent and start new line
-        for (unsigned j = current_indent; j--;) aligned += " ";
-        len = current_indent;
-        first_line = true;
-      }
-      // set new indent
-      current_indent = new_indent;
-      // add word
-      aligned += *word_it + " ";
-      len += word_it->size() + 1;
-    }
-    current_indent = indent;
-    first_line = false;
-  }
-
-  return aligned;
-}
-
-// Aligns the parameter description
-static void _align_parameter(std::string& str, const std::string& name, const std::string& type, const std::string& description, unsigned indent, unsigned alignment){
-  if (type.find(':') != std::string::npos && type.find('`') != std::string::npos)
-    // we expect that this is a :py:class: directive, which is simply written
-    str += _align("``" + name + "`` : " + type + "", indent, alignment) + "\n\n";
-  else
-    // otherwise we emphasize the parameter type with *...*
-    str += _align("``" + name + "`` : *" + type + "*", indent, alignment) + "\n\n";
-  str += _align(description, indent + 4, alignment) + "\n\n";
-}
-
-static std::string _prototype(const std::string& name, const std::string& variables, const std::string& retval){
-  if (retval.empty())
-    return "**" + name + "** (" + variables + ")";
-  else
-    return name + "(" + variables + ") -> " + retval;
-}
-
-static std::string _usage(const std::string& name, const std::string& variables, const std::string& retval){
-  if (retval.empty())
-    return name + "(" + variables + ")";
-  else
-    return name + "(" + variables + ") -> " + retval;
-}
-
-
-static void _check(std::string& doc, const std::vector<std::string>& vars, const std::vector<std::string>& docs, const std::string& type){
-  // check that all parameters are documented. If not, add a TODO
-  std::set<std::string> undoc;
-  std::set<std::string> unused;
-  // gather parameters
-  for (auto pit = vars.begin(); pit != vars.end(); ++pit){
-    const auto splits = _split(*pit, ',');
-    for (auto sit = splits.begin(); sit != splits.end(); ++sit){
-      undoc.insert(_strip(*sit));
-    }
-  }
-  for (auto pit = docs.begin(); pit != docs.end(); ++pit){
-    const auto splits = _split(*pit, ',');
-    for (auto sit = splits.begin(); sit != splits.end(); ++sit){
-      std::string x = _strip(*sit);
-      if (undoc.find(x) == undoc.end()){
-        unused.insert(x);
-      } else {
-        undoc.erase(x);
-     }
-    }
-  }
-  if (undoc.size()){
-    std::string all;
-    for (auto pit = undoc.begin(); pit != undoc.end(); ++pit){
-      if (*pit != "None"){
-        if (!all.empty()) all += ", ";
-        all += *pit;
-      }
-    }
-    if (!all.empty()){
-      doc += "\n" + _align(".. todo:: The " + type + "(s) '" + all + "' are used, but not documented.", 0, (unsigned)-1) + "\n";
-    }
-  }
-  if (unused.size()){
-    std::string all;
-    for (auto pit = unused.begin(); pit != unused.end(); ++pit){
-      if (!all.empty()) all += ", ";
-      all += *pit;
-    }
-    doc += "\n" + _align(".. todo:: The " + type + "(s) '" + all + "' are documented, but nowhere used.", 0, (unsigned)-1) + "\n";
-  }
-}
-
-#endif // ! BOB_SHORT_DOCSTRINGS
-
-
-/////////////////////////////////////////////////////////////
-/// FunctionDoc
-
-inline bob::extension::FunctionDoc::FunctionDoc(
-  const char* const function_name,
-  const char* const short_description,
-  const char* const long_description,
-  bool is_member_function
-) : function_name(function_name), function_description(short_description), is_member(is_member_function)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  if (long_description){
-    function_description += "\n\n";
-    function_description += long_description;
-  }
-#endif
-}
-
-inline bob::extension::FunctionDoc::FunctionDoc(
-  const bob::extension::FunctionDoc& other
-)
-: function_name(other.function_name),
-  function_description(other.function_description),
-  is_member(other.is_member),
-  prototype_variables(other.prototype_variables),
-  prototype_returns(other.prototype_returns),
-  parameter_names(other.parameter_names),
-  parameter_types(other.parameter_types),
-  parameter_descriptions(other.parameter_descriptions),
-  return_names(other.return_names),
-  return_types(other.return_types),
-  return_descriptions(other.return_descriptions)
-{
-  // Add the variables to the kwlists
-  kwlists.resize(other.kwlists.size());
-  for (unsigned i = 0; i < kwlists.size(); ++i){
-    // copy all kwlists
-    unsigned counts = _split(prototype_variables[i], ',', false).size();
-    char** names = new char*[counts + 1];
-    for (unsigned j = 0; j < counts; ++j){
-      names[j] = const_cast<char*>(strdup(other.kwlists[i][j]));
-    }
-    // add terminating NULL pointer
-    names[counts] = 0;
-    kwlists[i] = names;
-  }
-}
-
-inline bob::extension::FunctionDoc::~FunctionDoc(){
-
-  for (unsigned i = 0; i < kwlists.size(); ++i){
-    unsigned counts = _split(prototype_variables[i], ',').size();
-    for (unsigned j = 0; j < counts; ++j){
-      free(kwlists[i][j]);
-    }
-    delete[] kwlists[i];
-  }
-}
-
-inline bob::extension::FunctionDoc& bob::extension::FunctionDoc::add_prototype(
-  const char* const variables,
-  const char* const return_values
-){
-  // Add the variables to the kwlists
-  std::vector<std::string> vars = _split(variables, ',', false);
-  char** names = new char*[vars.size() + 1];
-  for (unsigned i = 0; i < vars.size(); ++i){
-    names[i] = const_cast<char*>(strdup(_strip(vars[i]).c_str()));
-  }
-  // add terminating NULL pointer
-  names[vars.size()] = 0;
-  kwlists.push_back(names);
-
-  prototype_variables.push_back(variables);
-#ifndef BOB_SHORT_DOCSTRINGS
-  if (!return_values)
-    prototype_returns.push_back("");
-  else
-    prototype_returns.push_back(return_values);
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-inline bob::extension::FunctionDoc& bob::extension::FunctionDoc::add_parameter(
-  const char* const parameter_name,
-  const char* const parameter_type,
-  const char* const parameter_description
-)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  parameter_names.push_back(parameter_name);
-  parameter_types.push_back(parameter_type);
-  parameter_descriptions.push_back(parameter_description);
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-inline bob::extension::FunctionDoc& bob::extension::FunctionDoc::add_return(
-  const char* const parameter_name,
-  const char* const parameter_type,
-  const char* const parameter_description
-)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  return_names.push_back(parameter_name);
-  return_types.push_back(parameter_type);
-  return_descriptions.push_back(parameter_description);
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-inline const char* const bob::extension::FunctionDoc::doc(
-  const unsigned alignment,
-  const unsigned indent
-) const
-{
-#ifdef BOB_SHORT_DOCSTRINGS
-  return function_description.c_str();
-#else
-  if (description.empty()){
-    // in case of member functions, the alignment has to be decreased further since class member function are automatically indented by 4 further spaces.
-    unsigned align = is_member ? alignment - 4  : alignment;
-    description = "";
-    switch(prototype_variables.size()){
-      case 0:
-        description = _align(".. todo:: Please use ``FunctionDoc.add_prototype`` to add at least one prototypical way to call this function", indent, (unsigned)-1) + "\n";
-        break;
-      case 1:
-        // only one way to call; use the default way
-        description = _align(_prototype(function_name, prototype_variables[0], prototype_returns[0]), indent, unsigned(-1)) + "\n";
-        break;
-      default:
-        // several ways to call; list them
-        for (unsigned n = 0; n < prototype_variables.size(); ++n)
-          description += _align("* " + _prototype(function_name, prototype_variables[n], prototype_returns[n]), indent, unsigned(-1)) + "\n";
-    }
-    // add function description
-    description += "\n" + _align(function_description, indent, align) + "\n";
-
-    // check that all parameters are documented
-    _check(description, prototype_variables, parameter_names, "parameter");
-
-    // check that all return values are documented
-    _check(description, prototype_returns, return_names, "return value");
-
-    if (!parameter_names.empty()){
-      // add parameter description
-  //    description += "\n" + _align("Parameters") + _align("----------");
-      description += "\n" + _align("**Parameters:**", indent, align) + "\n\n";
-      for (unsigned i = 0; i < parameter_names.size(); ++i){
-        _align_parameter(description, parameter_names[i], parameter_types[i], parameter_descriptions[i], indent, align);
-      }
-    }
-
-    if (!return_names.empty()){
-      // add return value description
-  //    description += "\n" + _align("Returns") + _align("--------");
-      description += "\n" + _align("**Returns:**", indent, align) + "\n\n";
-      for (unsigned i = 0; i < return_names.size(); ++i){
-        _align_parameter(description, return_names[i], return_types[i], return_descriptions[i], indent, align);
-      }
-    }
-  }
-
-  // return the description
-  return description.c_str();
-#endif // BOB_SHORT_DOCSTRINGS
-}
-
-inline void bob::extension::FunctionDoc::print_usage() const
-{
-#ifdef BOB_SHORT_DOCSTRINGS
-  return function_description.c_str();
-#else
-  // in case of member functions, the alignment has to be decreased further since class member function are automatically indented by 4 further spaces.
-  std::cerr << "\nUsage (for details, see help):\n";
-  switch(prototype_variables.size()){
-    case 0:
-      std::cerr << _align("Error: The usage of this function is unknown", 0, (unsigned)-1)  << "\n";
-      break;
-    case 1:
-      // only one way to call; use the default way
-      std::cerr << _align(_usage(function_name, prototype_variables[0], prototype_returns[0]), 0, unsigned(-1)) << "\n";
-      break;
-    default:
-      // several ways to call; list them
-      for (unsigned n = 0; n < prototype_variables.size(); ++n)
-        std::cerr << _align(_usage(function_name, prototype_variables[n], prototype_returns[n]), 0, unsigned(-1)) << "\n";
-  }
-  std::cerr << std::endl;
-#endif // BOB_SHORT_DOCSTRINGS
-}
-
-
-
-/////////////////////////////////////////////////////////////
-/// ClassDoc
-
-inline bob::extension::ClassDoc::ClassDoc(
-  const char* const class_name,
-  const char* const short_description,
-  const char* const long_description
-) : class_name(class_name), class_description(short_description)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  if (long_description){
-    class_description += "\n\n";
-    class_description += long_description;
-  }
-#endif // ! BOB_SHORT_DOCSTRINGS
-}
-
-inline bob::extension::ClassDoc& bob::extension::ClassDoc::add_constructor(
-  const bob::extension::FunctionDoc& constructor_documentation
-)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  if (!constructor.empty()){
-    throw std::runtime_error("The class documentation can have only a single constructor documentation");
-  }
-  constructor.push_back(constructor_documentation);
-  // since we indent the constructor documentation ourselves, we don't need to consider it to be a member function.
-  constructor.back().is_member = false;
-  constructor.back().function_name = class_name;
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-inline bob::extension::ClassDoc& bob::extension::ClassDoc::highlight(
-  const bob::extension::FunctionDoc& function_documentation
-)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  highlighted_functions.push_back(function_documentation);
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-inline bob::extension::ClassDoc& bob::extension::ClassDoc::highlight(
-  const bob::extension::VariableDoc& variable_documentation
-)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  highlighted_variables.push_back(variable_documentation);
-#endif // BOB_SHORT_DOCSTRINGS
-  return *this;
-}
-
-
-inline char* bob::extension::ClassDoc::doc(
-  const unsigned alignment
-) const
-{
-#ifdef BOB_SHORT_DOCSTRINGS
-  return const_cast<char*>(class_description.c_str());
-#else
-  if (description.empty()){
-    description = _align(class_description, 0, alignment) + "\n";
-    if (!constructor.empty()){
-      description += "\n" + _align("**Constructor Documentation:**", 0, alignment) + "\n\n";
-      description += constructor.front().doc(alignment, 4) + std::string("\n");
-    }
-    description += "\n" + _align("**Class Members:**", 0, alignment) + "\n\n";
-    if (!highlighted_functions.empty()){
-  //    description += "\n" + _align("Methods") + _align("-------");
-      description += "\n" + _align("**Highlighted Methods:**", 2, alignment) + "\n\n";
-      for (auto hit = highlighted_functions.begin(); hit != highlighted_functions.end(); ++hit){
-        description += _align("* :func:`" + hit->function_name + "`", 2, alignment) + _align(_split(hit->function_description, '\n')[0], 4, alignment) + "\n";
-      }
-    }
-    if (!highlighted_variables.empty()){
-  //    description += "\n" + _align("Attributes") + _align("----------");
-      description += "\n" + _align("**Highlighted Attributes:**", 2, alignment) + "\n\n";
-      for (auto hit = highlighted_variables.begin(); hit != highlighted_variables.end(); ++hit){
-        description += _align("* :obj:`" + hit->variable_name + "`", 2, alignment) + _align(_split(hit->variable_description, '\n')[0], 4, alignment) + "\n";
-      }
-    }
-  }
-  return const_cast<char*>(description.c_str());
-#endif // BOB_SHORT_DOCSTRINGS
-}
-
-/////////////////////////////////////////////////////////////
-/// VariableDoc
-
-inline bob::extension::VariableDoc::VariableDoc(
-  const char* const variable_name,
-  const char* const variable_type,
-  const char* const short_description,
-  const char* const long_description
-) : variable_name(variable_name), variable_type(variable_type), variable_description(short_description)
-{
-#ifndef BOB_SHORT_DOCSTRINGS
-  if (long_description){
-    variable_description += "\n\n";
-    variable_description += long_description;
-  }
-#endif // ! BOB_SHORT_DOCSTRINGS
-}
-
-inline char* bob::extension::VariableDoc::doc(
-  const unsigned alignment
-) const
-{
-#ifdef BOB_SHORT_DOCSTRINGS
-  return const_cast<char*>(variable_description.c_str());
-#else
-  if (description.empty()){
-    if (variable_type.find(':') != std::string::npos && variable_type.find('`') != std::string::npos)
-      // we expect that this is a :py:class: directive, which is simply written (otherwise the *...*
-      description = _align(variable_type + "  <-- " + variable_description, 0, alignment);
-    else
-      description = _align("*" + variable_type + "*  <-- " + variable_description, 0, alignment);
-  }
-  return const_cast<char*>(description.c_str());
-#endif // BOB_SHORT_DOCSTRINGS
-}
-
-#endif // BOB_EXTENSION_DOCUMENTATION_H_INCLUDED
diff --git a/bob/extension/log.py b/bob/extension/log.py
index 3c12d2f4965b989abb6f905a83e10137e7cd0edf..9b71b80dd4ae6bbd7d7744abb16e6aa71e9df1cb 100644
--- a/bob/extension/log.py
+++ b/bob/extension/log.py
@@ -5,11 +5,11 @@
 """Sets-up logging, centrally for Bob.
 """
 
-import sys
 import logging
+import sys
 
 # get the default root logger of Bob
-_logger = logging.getLogger('bob')
+_logger = logging.getLogger("bob")
 
 # by default, warning and error messages should be written to sys.stderr
 _warn_err = logging.StreamHandler(sys.stderr)
@@ -20,8 +20,8 @@ _logger.addHandler(_warn_err)
 
 
 class _InfoFilter:
-  def filter(self, record):
-    return record.levelno <= logging.INFO
+    def filter(self, record):
+        return record.levelno <= logging.INFO
 
 
 _debug_info = logging.StreamHandler(sys.stdout)
@@ -31,82 +31,84 @@ _logger.addHandler(_debug_info)
 
 
 # helper functions to instantiate and set-up logging
-def setup(logger_name,
-          format="%(name)s@%(asctime)s -- %(levelname)s: %(message)s"):
-  """This function returns a logger object that is set up to perform logging
-  using Bob loggers.
-
-  Parameters
-  ----------
-  logger_name : str
-      The name of the module to generate logs for
-  format : :obj:`str`, optional
-      The format of the logs, see :py:class:`logging.LogRecord` for more
-      details. By default, the log contains the logger name, the log time, the
-      log level and the massage.
-
-  Returns
-  -------
-  logger : :py:class:`logging.Logger`
-      The logger configured for logging. The same logger can be retrieved using
-      the :py:func:`logging.getLogger` function.
-  """
-  # generate new logger object
-  logger = logging.getLogger(logger_name)
-
-  # add log the handlers if not yet done
-  if not logger_name.startswith("bob") and not logger.handlers:
-    logger.addHandler(_warn_err)
-    logger.addHandler(_debug_info)
-
-  # this formats the logger to print the desired information
-  formatter = logging.Formatter(format)
-  # we have to set the formatter to all handlers registered in the current
-  # logger
-  for handler in logger.handlers:
-    handler.setFormatter(formatter)
-
-  # set the same formatter for bob loggers
-  for handler in _logger.handlers:
-    handler.setFormatter(formatter)
-
-  return logger
+def setup(
+    logger_name, format="%(name)s@%(asctime)s -- %(levelname)s: %(message)s"
+):
+    """This function returns a logger object that is set up to perform logging
+    using Bob loggers.
+
+    Parameters
+    ----------
+    logger_name : str
+        The name of the module to generate logs for
+    format : :obj:`str`, optional
+        The format of the logs, see :py:class:`logging.LogRecord` for more
+        details. By default, the log contains the logger name, the log time, the
+        log level and the massage.
+
+    Returns
+    -------
+    logger : :py:class:`logging.Logger`
+        The logger configured for logging. The same logger can be retrieved using
+        the :py:func:`logging.getLogger` function.
+    """
+    # generate new logger object
+    logger = logging.getLogger(logger_name)
+
+    # add log the handlers if not yet done
+    if not logger_name.startswith("bob") and not logger.handlers:
+        logger.addHandler(_warn_err)
+        logger.addHandler(_debug_info)
+
+    # this formats the logger to print the desired information
+    formatter = logging.Formatter(format)
+    # we have to set the formatter to all handlers registered in the current
+    # logger
+    for handler in logger.handlers:
+        handler.setFormatter(formatter)
+
+    # set the same formatter for bob loggers
+    for handler in _logger.handlers:
+        handler.setFormatter(formatter)
+
+    return logger
 
 
 def set_verbosity_level(logger, level):
-  """Sets the log level for the given logger.
-
-  Parameters
-  ----------
-  logger : :py:class:`logging.Logger` or str
-      The logger to generate logs for, or the name  of the module to generate
-      logs for.
-  level : int
-      Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug.
-
-  Raises
-  ------
-  ValueError
-      If the level is not in range(0, 4).
-  """
-  if level not in range(0, 4):
-    raise ValueError(
-        "The verbosity level %d does not exist. Please reduce the number of "
-        "'--verbose' parameters in your command line" % level)
-  # set up the verbosity level of the logging system
-  log_level = {
-      0: logging.ERROR,
-      1: logging.WARNING,
-      2: logging.INFO,
-      3: logging.DEBUG
-  }[level]
-
-  # set this log level to the logger with the specified name
-  if isinstance(logger, str):
-    logger = logging.getLogger(logger)
-  logger.setLevel(log_level)
-  # set the same log level for the bob logger
-  _logger.setLevel(log_level)
-
-
-__all__ = [_ for _ in dir() if not _.startswith('_')]
+    """Sets the log level for the given logger.
+
+    Parameters
+    ----------
+    logger : :py:class:`logging.Logger` or str
+        The logger to generate logs for, or the name  of the module to generate
+        logs for.
+    level : int
+        Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug.
+
+    Raises
+    ------
+    ValueError
+        If the level is not in range(0, 4).
+    """
+    if level not in range(0, 4):
+        raise ValueError(
+            "The verbosity level %d does not exist. Please reduce the number of "
+            "'--verbose' parameters in your command line" % level
+        )
+    # set up the verbosity level of the logging system
+    log_level = {
+        0: logging.ERROR,
+        1: logging.WARNING,
+        2: logging.INFO,
+        3: logging.DEBUG,
+    }[level]
+
+    # set this log level to the logger with the specified name
+    if isinstance(logger, str):
+        logger = logging.getLogger(logger)
+    logger.setLevel(log_level)
+    # set the same log level for the bob logger
+    _logger.setLevel(log_level)
+
+
+__all__ = [_ for _ in dir() if not _.startswith("_")]
diff --git a/bob/extension/pkgconfig.py b/bob/extension/pkgconfig.py
deleted file mode 100644
index bc0d1f7a76323894eb0fdd7c67af11bd2f46de81..0000000000000000000000000000000000000000
--- a/bob/extension/pkgconfig.py
+++ /dev/null
@@ -1,393 +0,0 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Wed 16 Oct 10:08:42 2013 CEST
-
-import os
-import sys
-import subprocess
-import logging
-from .utils import uniq, uniq_paths, find_executable, construct_search_paths
-
-
-def call_pkgconfig(cmd, paths=None):
-  """Calls pkg-config with a constructed PKG_CONFIG_PATH environment variable.
-
-  The PKG_CONFIG_PATH variable is constructed using
-  :any:`construct_search_paths`.
-
-  Parameters
-  ----------
-  cmd : [str]
-      A list of commands to be given to pkg-config.
-  paths : [str]
-      A list of paths to be added to PKG_CONFIG_PATH.
-
-  Returns
-  -------
-  returncode : int
-      The exit status of pkg-config.
-  stdout : str
-      The stdout output of pkg-config.
-  stderr : str
-      The stderr output of pkg-config.
-
-  Raises
-  ------
-  OSError
-      If the `pkg-config' executable is not found.
-  """
-
-  # locates the pkg-config program
-  pkg_config = find_executable('pkg-config')
-  if not pkg_config:
-    raise OSError("Cannot find `pkg-config' - did you install it?")
-
-  pkg_path = construct_search_paths(
-      prefixes=paths, suffix=os.sep + 'lib' + os.sep + 'pkgconfig')
-
-  env = os.environ.copy()
-  var = os.pathsep.join(pkg_path)
-  old = env.get('PKG_CONFIG_PATH', False)
-  env['PKG_CONFIG_PATH'] = os.pathsep.join([var, old]) if old else var
-
-  # calls the program
-  cmd = pkg_config[:1] + [str(k) for k in cmd]
-  subproc = subprocess.Popen(
-      cmd,
-      env=env,
-      stderr=subprocess.PIPE,
-      stdout=subprocess.PIPE
-      )
-
-  logging.debug("Running `%s'" % (" ".join(cmd),))
-  stdout, stderr = subproc.communicate()
-
-  # handles py3k string conversion, if necessary
-  if isinstance(stdout, bytes) and not isinstance(stdout, str):
-    stdout = stdout.decode('utf8')
-
-  if isinstance(stderr, bytes) and not isinstance(stderr, str):
-    stderr = stderr.decode('utf8')
-
-  # always print the stdout
-  logger = logging.getLogger('pkgconfig')
-  for k in stdout.split('\n'):
-    if k: logger.debug(k)
-
-  return subproc.returncode, stdout, stderr
-
-def version():
-  """Determines the version of pkg-config which is installed"""
-
-  status, stdout, stderr = call_pkgconfig(['--version'])
-
-  if status != 0:
-    raise RuntimeError("pkg-config is not installed - please do it")
-
-  return stdout.strip()
-
-
-class pkgconfig:
-  """A class for capturing configuration information from pkg-config
-
-  Example usage:
-
-  .. doctest::
-     :options: +NORMALIZE_WHITESPACE +ELLIPSIS
-
-     >>> from bob.extension import pkgconfig
-     >>> blitz = pkgconfig('blitz')
-     >>> blitz.include_directories()
-     [...]
-     >>> blitz.library_directories()
-     [...]
-
-  If the package does not exist, a RuntimeError is raised. All calls to any
-  methods of a ``pkgconfig`` object are translated into a subprocess call that
-  queries for that specific information. If ``pkg-config`` fails, a
-  RuntimeError is raised.
-  """
-
-  def __init__(self, name, paths=None):
-    """Constructor
-
-    Parameters:
-
-    name
-      The name of the package of interest, as you would pass on the command
-      line
-
-    paths
-      Search paths to be added to the environment's PKG_CONFIG_PATH to search
-      for packages.
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config <name>
-
-    """
-
-    status, stdout, stderr = call_pkgconfig(['--modversion', name], paths)
-
-    if status != 0:
-      raise RuntimeError("pkg-config package `%s' was not found" % name)
-
-    self.name = name
-    self.version = stdout.strip()
-    self.paths = paths
-
-  def __xcall__(self, cmd):
-    """Calls call_pkgconfig() with self.name and self.paths"""
-
-    return call_pkgconfig(cmd + [self.name], self.paths)
-
-  def __cmp__(self, other):
-    """Compares this package with a version number
-
-    We create a new ``distutils.version.LooseVersion`` object out of your input
-    argument and then, compare it to our own version, returning the result.
-
-    Returns an integer smaller than zero if this package's version number is
-    smaller than the provided value. Returns zero in case of a match and
-    greater than zero in the other case.
-    """
-
-    from distutils.version import LooseVersion
-    return cmp(self.version, LooseVersion(other))
-
-  def __ge__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) >= LooseVersion(other)
-
-  def __gt__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) > LooseVersion(other)
-
-  def __le__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) <= LooseVersion(other)
-
-  def __lt__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) < LooseVersion(other)
-
-  def __eq__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) == LooseVersion(other)
-
-  def __ne__(self, other):
-    from distutils.version import LooseVersion
-    return LooseVersion(self.version) != LooseVersion(other)
-
-  def include_directories(self):
-    """Returns a pre-processed list containing include directories.
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --cflags-only-I <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--cflags-only-I'])
-
-    if status != 0:
-      raise RuntimeError("error querying --cflags-only-I for package `%s': %s" % (self.name, stderr))
-
-    retval = []
-    for token in stdout.split():
-      retval.append(token[2:])
-
-    return uniq(retval)
-
-  def cflags_other(self):
-    """Returns a pre-processed dictionary containing compilation options.
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --cflags-only-other <name>
-
-    The returned dictionary contains two entries ``extra_compile_args`` and
-    ``define_macros``. The ``define_macros`` entries are ready for deployment
-    in the ``setup()`` function of your package.
-    """
-
-    status, stdout, stderr = self.__xcall__(['--cflags-only-other'])
-
-    if status != 0:
-      raise RuntimeError("error querying --cflags-only-other for package `%s': %s" % (self.name, stderr))
-
-    flag_map = {
-        '-D': 'define_macros',
-        }
-
-    kw = {}
-
-    for token in stdout.split():
-      if token[:2] in flag_map:
-        kw.setdefault(flag_map.get(token[:2]), []).append(token[2:])
-
-      else: # throw others to extra_link_args
-        kw.setdefault('extra_compile_args', []).append(token)
-
-    # make it uniq
-    for k, v in kw.items(): kw[k] = uniq(v)
-
-    # for macros, separate them so they can be plugged on C/C++ extensions
-    if 'define_macros' in kw:
-      for k, string in enumerate(kw['define_macros']):
-        if string.find('=') != -1:
-          kw['define_macros'][k] = string.split('=', 2)
-        else:
-          kw['define_macros'][k] = (string, None)
-
-    return kw
-
-  def libraries(self):
-    """Returns a pre-processed list containing libraries to link against
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --libs-only-l <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--libs-only-l'])
-
-    if status != 0:
-      raise RuntimeError("error querying --libs-only-l for package `%s': %s" % (self.name, stderr))
-
-    retval = []
-    for token in stdout.split():
-      retval.append(token[2:])
-
-    return uniq(retval)
-
-  def other_libraries(self):
-    """Returns a pre-processed list containing libraries to link against
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --libs-only-other <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--libs-only-other'])
-
-    if status != 0:
-      raise RuntimeError("error querying --libs-only-other for package `%s': %s" % (self.name, stderr))
-
-    return uniq(stdout.split())
-
-  def library_directories(self):
-    """Returns a pre-processed list containing library directories.
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --libs-only-L <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--libs-only-L'])
-
-    if status != 0:
-      raise RuntimeError("error querying --libs-only-L for package `%s': %s" % (self.name, stderr))
-
-    retval = []
-    for token in stdout.split():
-      retval.append(token[2:])
-
-    return uniq(retval)
-
-  def extra_link_args(self):
-    """Returns a pre-processed list containing extra link arguments.
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --libs-only-other <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--libs-only-other'])
-
-    if status != 0:
-      raise RuntimeError("error querying --libs-only-other for package `%s': %s" % (self.name, stderr))
-
-    return stdout.strip().split()
-
-  def variable_names(self):
-    """Returns a list with all variable names know to this package
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --print-variables <name>
-
-    """
-
-    status, stdout, stderr = self.__xcall__(['--print-variables'])
-
-    if status != 0:
-      raise RuntimeError("error querying --print-variables for package `%s': %s" % (self.name, stderr))
-
-    return stdout.strip().split()
-
-  def variable(self, name):
-    """Returns a variable with a specific name (if it exists)
-
-    Equivalent command line version:
-
-    .. code-block:: sh
-
-       $ PKG_CONFIG_PATH=<paths> pkg-config --variable=<variable-name> <name>
-
-    .. warning::
-
-       If a variable does not exist in a package, pkg-config does not signal an
-       error. Instead, it returns an empty string. So, do we.
-    """
-
-    status, stdout, stderr = self.__xcall__(['--variable=%s' % name])
-
-    if status != 0:
-      raise RuntimeError("error querying --variable=%s for package `%s': %s" % (name, self.name, stderr))
-
-    return stdout.strip()
-
-  def package_macros(self):
-    """Returns package availability and version number macros
-
-    This method returns a python list with 1 macro indicating package
-    availability, using standard GNU compatible names. For
-    example, if the package is named ``blitz``, this
-    command would return:
-
-    .. doctest::
-       :options: +NORMALIZE_WHITESPACE +ELLIPSIS
-
-       >>> from bob.extension import pkgconfig
-       >>> blitz = pkgconfig('blitz')
-       >>> blitz.package_macros()
-       [('HAVE_BLITZ', '1')]
-
-    """
-    from re import sub
-    NAME = sub(r'[\.\-\s]', '_', self.name.upper())
-    return [('HAVE_' + NAME, '1')]
-
-__all__ = ['pkgconfig']
diff --git a/bob/extension/rc_config.py b/bob/extension/rc_config.py
index c84252cf11a5db41ecfce717047af165b842f4a5..b318f21abd41312c8884563262a5f621d31356ad 100644
--- a/bob/extension/rc_config.py
+++ b/bob/extension/rc_config.py
@@ -1,98 +1,99 @@
 #!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 
-'''Implements a global configuration system for bob using json.'''
+"""Implements a global configuration system for bob using json."""
 
-from collections import defaultdict
 import json
 import logging
 import os
 
+from collections import defaultdict
+
 logger = logging.getLogger(__name__)
 
-ENVNAME = 'BOBRC'
+ENVNAME = "BOBRC"
 """Name of environment variable to look for an alternative for the RC file"""
 
-RCFILENAME = '~' + os.sep + '.bobrc'
+RCFILENAME = "~" + os.sep + ".bobrc"
 """Default name to be used for the RC file to load"""
 
 
 def _get_rc_path():
-  """Returns the path to the bob rc file.
-  This method will return the path to **exactly** one (global) resource
-  configuration file in this fixed order of preference:
+    """Returns the path to the bob rc file.
+    This method will return the path to **exactly** one (global) resource
+    configuration file in this fixed order of preference:
 
-  1. A path pointed by the environment variable BOBRC
-  2. A file named :py:attr:`RCFILENAME` on your HOME directory
+    1. A path pointed by the environment variable BOBRC
+    2. A file named :py:attr:`RCFILENAME` on your HOME directory
 
-  Returns
-  -------
-  str
-      The path to the rc file.
-  """
-  if 'BOBRC' in os.environ:
-    path = os.environ['BOBRC']
-  else:
-    path = os.path.expanduser(RCFILENAME)
+    Returns
+    -------
+    str
+        The path to the rc file.
+    """
+    if "BOBRC" in os.environ:
+        path = os.environ["BOBRC"]
+    else:
+        path = os.path.expanduser(RCFILENAME)
 
-  return path
+    return path
 
 
 def _loadrc():
-  '''Loads the default configuration file, or an override if provided
+    """Loads the default configuration file, or an override if provided
 
-  This method will load **exactly** one (global) resource configuration file as
-  returned by :py:func:`_get_rc_path`.
+    This method will load **exactly** one (global) resource configuration file as
+    returned by :py:func:`_get_rc_path`.
 
-  Returns:
+    Returns:
 
-    dict: A dictionary of key-values representing the resolved context, after
-    loading the provided modules and resolving all variables.
+      dict: A dictionary of key-values representing the resolved context, after
+      loading the provided modules and resolving all variables.
 
-  '''
+    """
 
-  def _default_none_dict(dct):
-    dct2 = defaultdict(lambda: None)
-    dct2.update(dct)
-    return dct2
+    def _default_none_dict(dct):
+        dct2 = defaultdict(lambda: None)
+        dct2.update(dct)
+        return dct2
 
-  path = _get_rc_path()
-  if not os.path.exists(path):
-    logger.debug("No RC file found")
-    return _default_none_dict({})
+    path = _get_rc_path()
+    if not os.path.exists(path):
+        logger.debug("No RC file found")
+        return _default_none_dict({})
 
-  logger.debug("Loading RC file `%s'...", path)
+    logger.debug("Loading RC file `%s'...", path)
 
-  with open(path, 'rt') as f:
-    return json.load(f, object_hook=_default_none_dict)
+    with open(path, "rt") as f:
+        return json.load(f, object_hook=_default_none_dict)
 
 
 def _rc_to_str(context):
-  """Converts the configurations into a pretty JSON formatted string.
+    """Converts the configurations into a pretty JSON formatted string.
 
-  Parameters
-  ----------
-  context : dict
-      All the configurations to save into the rc file.
+    Parameters
+    ----------
+    context : dict
+        All the configurations to save into the rc file.
 
-  Returns
-  -------
-  str
-      The configurations in a JSON formatted string.
-  """
+    Returns
+    -------
+    str
+        The configurations in a JSON formatted string.
+    """
 
-  return json.dumps(context, sort_keys=True, indent=4, separators=(',', ': '))
+    return json.dumps(context, sort_keys=True, indent=4, separators=(",", ": "))
 
 
 def _saverc(context):
-  """Saves the context into the global rc file.
+    """Saves the context into the global rc file.
 
-  Parameters
-  ----------
-  context : dict
-      All the configurations to save into the rc file.
-  """
+    Parameters
+    ----------
+    context : dict
+        All the configurations to save into the rc file.
+    """
 
-  path = _get_rc_path()
-  with open(path, 'wt') as f:
-    f.write(_rc_to_str(context))
+    path = _get_rc_path()
+    with open(path, "wt") as f:
+        f.write(_rc_to_str(context))
diff --git a/bob/extension/scripts/__init__.py b/bob/extension/scripts/__init__.py
index 58bb841d4f20aef1ff88f6ce03f83925d31fd991..e356eaff0b5e2ebe54b2fd7dd7db84fe8771633c 100644
--- a/bob/extension/scripts/__init__.py
+++ b/bob/extension/scripts/__init__.py
@@ -1,5 +1,5 @@
-from .main_cli import main as main_cli
-from . import click_helper
+from . import click_helper  # noqa: F401
+from .main_cli import main as main_cli  # noqa: F401
 
 # gets sphinx autodoc done right - don't remove it
-__all__ = [_ for _ in dir() if not _.startswith('_')]
+__all__ = [_ for _ in dir() if not _.startswith("_")]
diff --git a/bob/extension/scripts/click_helper.py b/bob/extension/scripts/click_helper.py
index 9de2ec08e6785933b757b48ab455d01f2b525f5a..d840ceae51fdc7785aec39400716fb12b0b8b3d1 100644
--- a/bob/extension/scripts/click_helper.py
+++ b/bob/extension/scripts/click_helper.py
@@ -3,11 +3,10 @@ import time
 import traceback
 
 import click
+
 from click.core import ParameterSource
 
-from ..config import load
-from ..config import mod_to_context
-from ..config import resource_keys
+from ..config import load, mod_to_context, resource_keys
 from ..log import set_verbosity_level
 
 logger = logging.getLogger(__name__)
@@ -191,7 +190,9 @@ class ConfigCommand(click.Command):
       The name of entry point that will be used to load the config files.
     """
 
-    def __init__(self, name, *args, help=None, entry_point_group=None, **kwargs):
+    def __init__(
+        self, name, *args, help=None, entry_point_group=None, **kwargs
+    ):
         self.entry_point_group = entry_point_group
         configs_argument_name = "CONFIG"
         # Augment help for the config file argument
@@ -209,7 +210,9 @@ file.""".format(
 
         # Add the config argument to the command
         def configs_argument_callback(ctx, param, value):
-            config_context = load(value, entry_point_group=self.entry_point_group)
+            config_context = load(
+                value, entry_point_group=self.entry_point_group
+            )
             config_context = mod_to_context(config_context)
             ctx.config_context = config_context
             logger.debug("Augmenting context with config context")
@@ -263,7 +266,9 @@ file.""".format(
             if not isinstance(param, ResourceOption):
                 continue
 
-            config_file.write("\n# %s = %s\n" % (param.name, str(param.default)))
+            config_file.write(
+                "\n# %s = %s\n" % (param.name, str(param.default))
+            )
             config_file.write("'''")
 
             if param.required:
@@ -274,7 +279,8 @@ file.""".format(
                     " [default: {}]".format(param.default),
                 )
             config_file.write(
-                "%s: %s (%s)%s" % (begin, param.name, ", ".join(param.opts), dflt)
+                "%s: %s (%s)%s"
+                % (begin, param.name, ", ".join(param.opts), dflt)
             )
 
             if param.help is not None:
@@ -292,7 +298,9 @@ file.""".format(
 
             config_file.write("'''\n")
             click.echo(
-                "Configuration file '{}' was written; exiting".format(config_file.name)
+                "Configuration file '{}' was written; exiting".format(
+                    config_file.name
+                )
             )
 
         config_file.close()
@@ -373,7 +381,9 @@ class ResourceOption(click.Option):
 
         self.entry_point_group = entry_point_group
         if entry_point_group is not None:
-            name, _, _ = self._parse_decls(param_decls, kwargs.get("expose_value"))
+            name, _, _ = self._parse_decls(
+                param_decls, kwargs.get("expose_value")
+            )
             help = help or ""
             help += (
                 " Can be a ``{entry_point_group}`` entry point, a module name, or "
@@ -399,7 +409,9 @@ class ResourceOption(click.Option):
         self.string_exceptions = string_exceptions or []
 
     def consume_value(self, ctx, opts):
-        if (not hasattr(ctx, "config_context")) and self.entry_point_group is None:
+        if (
+            not hasattr(ctx, "config_context")
+        ) and self.entry_point_group is None:
             raise TypeError(
                 "The ResourceOption class is not meant to be used this way. "
                 "Please see the docs of the class."
@@ -442,7 +454,9 @@ class ResourceOption(click.Option):
 
         # if the value is a string and an entry_point_group is provided, load it
         if self.entry_point_group is not None:
-            while isinstance(value, str) and value not in self.string_exceptions:
+            while (
+                isinstance(value, str) and value not in self.string_exceptions
+            ):
                 value = load(
                     [value],
                     entry_point_group=self.entry_point_group,
@@ -498,7 +512,10 @@ def log_parameters(logger_handle, ignore=tuple()):
 
 def assert_click_runner_result(result, exit_code=0, exception_type=None):
     """Helper for asserting click runner results"""
-    m = "Click command exited with code `{}' and exception:\n{}" "\nThe output was:\n{}"
+    m = (
+        "Click command exited with code `{}' and exception:\n{}"
+        "\nThe output was:\n{}"
+    )
     exception = (
         "None"
         if result.exc_info is None
diff --git a/bob/extension/scripts/config.py b/bob/extension/scripts/config.py
index db7bf5656f22f46d05760cab38b0febe645e7d13..3776304fb152f5a618a11447631bd4ab0c29d015 100644
--- a/bob/extension/scripts/config.py
+++ b/bob/extension/scripts/config.py
@@ -1,11 +1,13 @@
 """The manager for bob's main configuration.
 """
-from .. import rc
-from ..rc_config import _saverc, _rc_to_str, _get_rc_path 
-from .click_helper import verbosity_option, AliasedGroup
 import logging
+
 import click
 
+from .. import rc
+from ..rc_config import _get_rc_path, _rc_to_str, _saverc
+from .click_helper import AliasedGroup, verbosity_option
+
 # Use the normal logging module. Verbosity and format of logging will be set by
 # adding the verbosity_option form bob.extension.scripts.click_helper
 logger = logging.getLogger(__name__)
@@ -18,8 +20,9 @@ def config(**kwargs):
     # Load the config file again. This may be needed since the environment
     # variable might change the config path during the tests. Otherwise, this
     # should not be important.
-    logger.debug('Reloading the global configuration file.')
+    logger.debug("Reloading the global configuration file.")
     from ..rc_config import _loadrc
+
     rc.clear()
     rc.update(_loadrc())
 
@@ -36,7 +39,7 @@ def show():
 
 
 @config.command()
-@click.argument('key')
+@click.argument("key")
 def get(key):
     """Prints a key.
 
@@ -57,13 +60,14 @@ def get(key):
     if value is None:
         # Exit the command line with ClickException in case of errors.
         raise click.ClickException(
-            "The requested key `{}' does not exist".format(key))
+            "The requested key `{}' does not exist".format(key)
+        )
     click.echo(value)
 
 
 @config.command()
-@click.argument('key')
-@click.argument('value')
+@click.argument("key")
+@click.argument("value")
 def set(key, value):
     """Sets the value for a key.
 
@@ -90,10 +94,25 @@ def set(key, value):
         logger.error("Could not configure the rc file", exc_info=True)
         raise click.ClickException("Failed to change the configuration.")
 
+
 @config.command()
-@click.argument('substr')
-@click.option('-c', '--contain',  is_flag=True, default=False, type=click.BOOL, show_default=True)
-@click.option('-f', '--force',  is_flag=True, default=False, type=click.BOOL, show_default=True)
+@click.argument("substr")
+@click.option(
+    "-c",
+    "--contain",
+    is_flag=True,
+    default=False,
+    type=click.BOOL,
+    show_default=True,
+)
+@click.option(
+    "-f",
+    "--force",
+    is_flag=True,
+    default=False,
+    type=click.BOOL,
+    show_default=True,
+)
 def unset(substr, contain=False, force=False):
     """Clear all variables starting (containing) with substring.
 
@@ -106,7 +125,7 @@ def unset(substr, contain=False, force=False):
     ---------
     substring : str
         The starting substring of one or several key(s)
-    
+
     \b
     Parameters
     ----------
@@ -125,15 +144,23 @@ def unset(substr, contain=False, force=False):
             if substr in key:
                 to_delete.append(key)
                 found = True
-   
+
     if not found:
         if not contain:
-            logger.error("The key starting with '{}' was not found in the rc file".format(substr))
+            logger.error(
+                "The key starting with '{}' was not found in the rc file".format(
+                    substr
+                )
+            )
         else:
-            logger.error("The key containing '{}' was not found in the rc file".format(substr))
-   
+            logger.error(
+                "The key containing '{}' was not found in the rc file".format(
+                    substr
+                )
+            )
+
         raise click.ClickException("Failed to change the configuration.")
-  
+
     if force:
         for key in to_delete:
             del rc[key]
@@ -146,4 +173,4 @@ def unset(substr, contain=False, force=False):
             for key in to_delete:
                 del rc[key]
 
-    _saverc(rc) 
+    _saverc(rc)
diff --git a/bob/extension/scripts/main_cli.py b/bob/extension/scripts/main_cli.py
index 46ab57f9b6196e8d8510f0dd6c1bc2e2019fb681..12444d32abda3bd818550889fb74623e2cbe08ac 100644
--- a/bob/extension/scripts/main_cli.py
+++ b/bob/extension/scripts/main_cli.py
@@ -1,17 +1,22 @@
 """This is the main entry to bob's scripts.
 """
-import pkg_resources
 import click
+import pkg_resources
+
 from click_plugins import with_plugins
-from .click_helper import AliasedGroup
+
 from ..log import setup
-logger = setup('bob')
+from .click_helper import AliasedGroup
+
+logger = setup("bob")
 
 
-@with_plugins(pkg_resources.iter_entry_points('bob.cli'))
-@click.group(cls=AliasedGroup,
-             context_settings=dict(help_option_names=['-?', '-h', '--help']))
+@with_plugins(pkg_resources.iter_entry_points("bob.cli"))
+@click.group(
+    cls=AliasedGroup,
+    context_settings=dict(help_option_names=["-?", "-h", "--help"]),
+)
 def main():
-  """The main command line interface for bob. Look below for available
-  commands."""
-  pass
+    """The main command line interface for bob. Look below for available
+    commands."""
+    pass
diff --git a/bob/extension/test_boost.py b/bob/extension/test_boost.py
deleted file mode 100644
index bf6909163c2ead2f01be57a82dc2f901593b9547..0000000000000000000000000000000000000000
--- a/bob/extension/test_boost.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Thu 20 Mar 2014 12:43:48 CET
-
-"""Tests for boost configuration
-"""
-
-import os
-import sys
-import nose
-from .boost import boost
-from distutils.version import LooseVersion
-
-def test_boost_version():
-
-  b = boost('>= 1.30')
-  assert LooseVersion(b.version) >= '1.30'
-
-def test_boost_simple_modules():
-
-  b = boost()
-  directories, libname = b.libconfig(['system'])
-  nose.tools.eq_(len(directories), 1)
-  assert os.path.exists(directories[0])
-  nose.tools.eq_(len(libname), 1)
-
-def test_boost_python_modules():
-
-  b = boost()
-  directories, libname = b.libconfig(['python'])
-  nose.tools.eq_(len(directories), 1)
-  assert os.path.exists(directories[0])
-  nose.tools.eq_(len(libname), 1)
-  #assert libname[0].find('-py%d%d' % sys.version_info[:2]) >= 0
-
-def test_boost_multiple_modules():
-
-  b = boost()
-  directories, libname = b.libconfig(['python', 'system'])
-  nose.tools.eq_(len(directories), 1)
-  assert os.path.exists(directories[0])
-  assert libname
-  nose.tools.eq_(len(libname), 2)
-  #assert libname[0].find('-py%d%d' % sys.version_info[:2]) >= 0
-  #assert libname[1].find('-py%d%d' % sys.version_info[:2]) < 0
-
-def test_common_prefix():
-
-  b = boost()
-  directories, libname = b.libconfig(['python', 'system'])
-  nose.tools.eq_(len(directories), 1)
-  assert os.path.exists(directories[0])
-  os.path.commonprefix([directories[0], b.include_directory])
-  assert len(os.path.commonprefix([directories[0], b.include_directory])) > 1
diff --git a/bob/extension/test_click_helper.py b/bob/extension/test_click_helper.py
index 3f09ee5c80fac7329e28da8dbfe6eaf6717e097d..e5f0c73ce67c85eb702cf6a0c2d7abccec4e429d 100644
--- a/bob/extension/test_click_helper.py
+++ b/bob/extension/test_click_helper.py
@@ -1,21 +1,26 @@
-import click
 import time
+
+import click
 import pkg_resources
+
 from click.testing import CliRunner
+
 from bob.extension.scripts.click_helper import (
-    verbosity_option,
-    bool_option,
-    list_float_option,
+    AliasedGroup,
     ConfigCommand,
     ResourceOption,
-    AliasedGroup,
     assert_click_runner_result,
+    bool_option,
+    list_float_option,
+    verbosity_option,
 )
 
 
 def test_verbosity_option():
 
-    for VERBOSITY, OPTIONS in zip([0, 1, 2, 3], [[], ["-v"], ["-vv"], ["-vvv"]]):
+    for VERBOSITY, OPTIONS in zip(
+        [0, 1, 2, 3], [[], ["-v"], ["-vv"], ["-vvv"]]
+    ):
 
         @click.command()
         @verbosity_option()
@@ -187,6 +192,9 @@ def _assert_config_dump(ref, ref_date):
     with open("TEST_CONF", "r") as f, open(ref, "r") as f2:
         text = f.read().replace("'''", '"""')
         ref_text = f2.read().replace(ref_date, today)
+        # remove the starting whitespace of each line so the tests are more relaxed
+        text = "\n".join(line.lstrip() for line in text.splitlines())
+        ref_text = "\n".join(line.lstrip() for line in ref_text.splitlines())
         assert text == ref_text, "\n".join(
             [text, "########################\n" * 2, ref_text]
         )
@@ -213,7 +221,9 @@ def test_config_dump():
 
     runner = CliRunner()
     with runner.isolated_filesystem():
-        result = runner.invoke(cli, ["test", "-H", "TEST_CONF"], catch_exceptions=False)
+        result = runner.invoke(
+            cli, ["test", "-H", "TEST_CONF"], catch_exceptions=False
+        )
         ref = pkg_resources.resource_filename(
             "bob.extension", "data/test_dump_config.py"
         )
@@ -226,7 +236,9 @@ def test_config_dump2():
     def cli():
         pass
 
-    @cli.command(cls=ConfigCommand, entry_point_group="bob.extension.test_dump_config")
+    @cli.command(
+        cls=ConfigCommand, entry_point_group="bob.extension.test_dump_config"
+    )
     @click.option(
         "--database",
         "-d",
@@ -244,13 +256,21 @@ def test_config_dump2():
         help="bli bli bli",
     )
     @click.option(
-        "--output-dir", "-o", required=True, cls=ResourceOption, help="blo blo blo"
+        "--output-dir",
+        "-o",
+        required=True,
+        cls=ResourceOption,
+        help="blo blo blo",
     )
     @click.option(
         "--force", "-f", is_flag=True, cls=ResourceOption, help="lalalalalala"
     )
     @click.option(
-        "--array", type=click.INT, default=1, cls=ResourceOption, help="lililili"
+        "--array",
+        type=click.INT,
+        default=1,
+        cls=ResourceOption,
+        help="lililili",
     )
     @click.option(
         "--database-directories-file",
@@ -275,7 +295,9 @@ def test_config_dump2():
 
     runner = CliRunner()
     with runner.isolated_filesystem():
-        result = runner.invoke(cli, ["test", "-H", "TEST_CONF"], catch_exceptions=False)
+        result = runner.invoke(
+            cli, ["test", "-H", "TEST_CONF"], catch_exceptions=False
+        )
         ref = pkg_resources.resource_filename(
             "bob.extension", "data/test_dump_config2.py"
         )
diff --git a/bob/extension/test_cmake.py b/bob/extension/test_cmake.py
deleted file mode 100644
index b9b26123303fc6aaa2209203a666b9ee0036d484..0000000000000000000000000000000000000000
--- a/bob/extension/test_cmake.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Thu 20 Mar 2014 12:43:48 CET
-
-"""Tests for file search utilities
-"""
-
-import os
-import sys
-import shutil
-import nose.tools
-import platform
-
-import bob.extension
-import pkg_resources
-
-import tempfile
-
-def _find(lines, start):
-  for i, line in enumerate(lines):
-    if line.find(start) == 0:
-      return i
-  assert False
-
-def test_cmake_list():
-  # checks that the CMakeLists.txt file is generated properly
-
-  generator = bob.extension.CMakeListsGenerator(
-    name = 'bob_cmake_test',
-    sources = ['cmake_test.cpp'],
-    target_directory = "test_target",
-    version = '3.2.1',
-    include_directories = ["/usr/include/test"],
-    system_include_directories = ["/usr/include/test_system"],
-    libraries = ['some_library'],
-    library_directories = ["/usr/include/test"],
-    macros = [("TEST", "MACRO")]
-  )
-
-  temp_dir = tempfile.mkdtemp(prefix="bob_extension_test_")
-
-  generator.generate(temp_dir, temp_dir)
-
-  # read created file
-  lines = [line.rstrip() for line in open(os.path.join(temp_dir, "CMakeLists.txt"))]
-
-  # check that all elements are properly written in the file
-  assert lines[_find(lines, 'project')] == 'project(bob_cmake_test)'
-  assert lines[_find(lines, 'include')] == 'include_directories(/usr/include/test)'
-  assert lines[_find(lines, 'include_directories(SYSTEM')] == 'include_directories(SYSTEM /usr/include/test_system)'
-  assert lines[_find(lines, 'link')] == 'link_directories(/usr/include/test)'
-  assert lines[_find(lines, 'add')] == 'add_definitions(-DTEST=MACRO)'
-
-  index = _find(lines, 'add_library')
-  assert lines[index+1].find('cmake_test.cpp') >= 0
-
-  index = _find(lines, 'set_target_properties')
-  assert lines[index+1].find('test_target') >= 0
-
-  assert lines[_find(lines, 'target_link_libraries')].find('some_library') >= 0
-
-  # finally, clean up the mess
-  shutil.rmtree(temp_dir)
-
-
-def test_library():
-  old_dir = os.getcwd()
-  temp_dir = tempfile.mkdtemp(prefix="bob_extension_test_")
-  target_dir = os.path.join(temp_dir, 'build', 'lib', 'target')
-  # copy test file to temp directory
-  shutil.copyfile(pkg_resources.resource_filename(__name__, 'test_documentation.cpp'), os.path.join(temp_dir, 'test_documentation.cpp'))
-  os.chdir(temp_dir)
-  # check that the library compiles and links properly
-  library = bob.extension.Library(
-    name = 'target.bob_cmake_test',
-    sources = ['test_documentation.cpp'],
-    include_dirs = [pkg_resources.resource_filename(__name__, 'include')],
-    version = '3.2.1'
-  )
-
-  # redirect output of functions to /dev/null to avoid spamming the console
-  devnull = open(os.devnull, 'w')
-  # compile
-  compile_dir = os.path.join(temp_dir, 'build', 'lib')
-  os.makedirs(compile_dir)
-  os.makedirs(target_dir)
-  library.compile(compile_dir,stdout=devnull)
-
-  # check that the library was generated sucessfully
-  if platform.system() == 'Darwin':
-    lib_name = 'libbob_cmake_test.dylib'
-  else:
-    lib_name = 'libbob_cmake_test.so'
-
-  assert os.path.exists(os.path.join(target_dir, lib_name))
-
-  os.chdir(old_dir)
-
-  # TODO: compile a test executable to actually link the library
-
-  # finally, clean up the mess
-  shutil.rmtree(temp_dir)
diff --git a/bob/extension/test_config.py b/bob/extension/test_config.py
index 1f54e4ae7e36c9d9c0f06094e385468a86140154..874be3e8a851e8b4787c6b5347ada95f58bc98f5 100644
--- a/bob/extension/test_config.py
+++ b/bob/extension/test_config.py
@@ -1,84 +1,96 @@
 #!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 
-'''Tests for the python-based config functionality'''
+"""Tests for the python-based config functionality"""
 
 
-from .config import load, mod_to_context
 import os
-import pkg_resources
+
 import numpy
-path = pkg_resources.resource_filename('bob.extension', 'data')
+import pkg_resources
+
+from .config import load, mod_to_context
+
+path = pkg_resources.resource_filename("bob.extension", "data")
 
 
 def test_basic():
 
-  c = load([os.path.join(path, 'basic_config.py')])
-  assert hasattr(c, "a") and c.a == 1
-  assert hasattr(c, "b") and c.b == 3
+    c = load([os.path.join(path, "basic_config.py")])
+    assert hasattr(c, "a") and c.a == 1
+    assert hasattr(c, "b") and c.b == 3
 
-  ctx = mod_to_context(c)
-  assert ctx == {'a': 1, 'b': 3}
+    ctx = mod_to_context(c)
+    assert ctx == {"a": 1, "b": 3}
 
 
 def test_basic_with_context():
 
-  c = load([os.path.join(path, 'basic_config.py')], {'d': 35, 'a': 0})
-  assert hasattr(c, "a") and c.a == 1
-  assert hasattr(c, "b") and c.b == 3
-  assert hasattr(c, "d") and c.d == 35
+    c = load([os.path.join(path, "basic_config.py")], {"d": 35, "a": 0})
+    assert hasattr(c, "a") and c.a == 1
+    assert hasattr(c, "b") and c.b == 3
+    assert hasattr(c, "d") and c.d == 35
 
 
 def test_chain_loading():
 
-  file1 = os.path.join(path, 'basic_config.py')
-  file2 = os.path.join(path, 'load_config.py')
-  c = load([file1, file2])
-  assert hasattr(c, "a") and c.a == 1
-  assert hasattr(c, "b") and c.b == 6
+    file1 = os.path.join(path, "basic_config.py")
+    file2 = os.path.join(path, "load_config.py")
+    c = load([file1, file2])
+    assert hasattr(c, "a") and c.a == 1
+    assert hasattr(c, "b") and c.b == 6
 
 
 def test_config_with_module():
 
-  c = load([os.path.join(path, 'config_with_module.py')])
-  assert hasattr(c, "return_zeros") and numpy.allclose(
-      c.return_zeros(), numpy.zeros(shape=(2,)))
+    c = load([os.path.join(path, "config_with_module.py")])
+    assert hasattr(c, "return_zeros") and numpy.allclose(
+        c.return_zeros(), numpy.zeros(shape=(2,))
+    )
 
 
 def test_entry_point_configs():
 
-  # test when all kinds of paths
-  c = load([
-      os.path.join(path, 'basic_config.py'),
-      'resource_config',
-      'bob.extension.data.resource_config',
-      'bob.extension.data.basic_config',
-      'subpackage_config',
-  ], entry_point_group='bob.extension.test_config_load')
-  assert hasattr(c, "a") and c.a == 1
-  assert hasattr(c, "b") and c.b == 3
-  assert hasattr(c, "rc")
+    # test when all kinds of paths
+    c = load(
+        [
+            os.path.join(path, "basic_config.py"),
+            "resource_config",
+            "bob.extension.data.resource_config",
+            "bob.extension.data.basic_config",
+            "subpackage_config",
+        ],
+        entry_point_group="bob.extension.test_config_load",
+    )
+    assert hasattr(c, "a") and c.a == 1
+    assert hasattr(c, "b") and c.b == 3
+    assert hasattr(c, "rc")
 
 
 def test_load_resource():
-  for p, ref in [
-      (os.path.join(path, 'resource_config2.py'), 1),
-      (os.path.join(path, 'resource_config2.py:a'), 1),
-      (os.path.join(path, 'resource_config2.py:b'), 2),
-      ('resource1', 1),
-      ('resource2', 2),
-      ('bob.extension.data.resource_config2', 1),
-      ('bob.extension.data.resource_config2:a', 1),
-      ('bob.extension.data.resource_config2:b', 2),
-  ]:
-    c = load([p], entry_point_group='bob.extension.test_config_load',
-             attribute_name='a')
-    assert c == ref, c
-
-  try:
-    load(['bob.extension.data.resource_config2:c'],
-         entry_point_group='bob.extension.test_config_load',
-         attribute_name='a')
-    assert False, 'The code above should have raised an ImportError'
-  except ImportError:
-    pass
+    for p, ref in [
+        (os.path.join(path, "resource_config2.py"), 1),
+        (os.path.join(path, "resource_config2.py:a"), 1),
+        (os.path.join(path, "resource_config2.py:b"), 2),
+        ("resource1", 1),
+        ("resource2", 2),
+        ("bob.extension.data.resource_config2", 1),
+        ("bob.extension.data.resource_config2:a", 1),
+        ("bob.extension.data.resource_config2:b", 2),
+    ]:
+        c = load(
+            [p],
+            entry_point_group="bob.extension.test_config_load",
+            attribute_name="a",
+        )
+        assert c == ref, c
+
+    try:
+        load(
+            ["bob.extension.data.resource_config2:c"],
+            entry_point_group="bob.extension.test_config_load",
+            attribute_name="a",
+        )
+        assert False, "The code above should have raised an ImportError"
+    except ImportError:
+        pass
diff --git a/bob/extension/test_documentation.cpp b/bob/extension/test_documentation.cpp
deleted file mode 100644
index c6ee024c1aafc31c2c8132593eda7e6319d3167e..0000000000000000000000000000000000000000
--- a/bob/extension/test_documentation.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <bob.extension/documentation.h>
-
-int test_documenation(){
-
-  auto function_doc = bob::extension::FunctionDoc(
-    "TestClass",
-    "This is the constructor of the TestClass",
-    ".. todo:: Add more information here",
-    true
-  )
-  .add_prototype("para1", "")
-  .add_parameter("para1", "int", "A parameter of type int");
-
-  auto class_doc = bob::extension::ClassDoc(
-    "TestClass",
-    "This is the documentation for a test class.",
-    "Just to check that it works"
-  )
-  .add_constructor(function_doc);
-
-  auto var_doc = bob::extension::VariableDoc(
-    "test_variable",
-    "float",
-    "A float variable",
-    "With more documentation"
-  );
-
-  // create documentation, just to see if it works
-  class_doc.name();
-  class_doc.doc(72);
-
-  var_doc.name();
-  var_doc.doc(72);
-
-  return 0;
-}
diff --git a/bob/extension/test_download.py b/bob/extension/test_download.py
index 27f18f5037eb76f71b6d0d67b23aecb817315201..43ed52638bca86640dec38498e35d51f373d62e2 100644
--- a/bob/extension/test_download.py
+++ b/bob/extension/test_download.py
@@ -3,16 +3,23 @@ import shutil
 import tempfile
 
 import pkg_resources
+
 from bob.extension import rc_context
-from bob.extension.download import download_and_unzip
-from bob.extension.download import find_element_in_tarball, search_file, _untar
-from bob.extension.download import get_file, list_dir
+from bob.extension.download import (
+    _untar,
+    download_and_unzip,
+    find_element_in_tarball,
+    get_file,
+    list_dir,
+    search_file,
+)
 
 
 def test_download_unzip():
     def download(filename):
         download_and_unzip(
-            "http://www.idiap.ch/software/bob/databases/latest/mnist.tar.bz2", filename
+            "http://www.idiap.ch/software/bob/databases/latest/mnist.tar.bz2",
+            filename,
         )
         uncompressed_filename = os.path.join(os.path.dirname(filename), "data")
 
@@ -70,7 +77,8 @@ def test_find_element_in_tarball():
     )
     assert (
         find_element_in_tarball(
-            filename, "example_csv_filelist/protocol_dev_eval/norm/train_world.csv"
+            filename,
+            "example_csv_filelist/protocol_dev_eval/norm/train_world.csv",
         )
         is not None
     )
@@ -90,7 +98,10 @@ def test_search_file():
         __name__, "data/example_csv_filelist.tar.gz"
     )
     # Search in the tarball
-    assert search_file(filename, "protocol_dev_eval/norm/train_world.csv") is not None
+    assert (
+        search_file(filename, "protocol_dev_eval/norm/train_world.csv")
+        is not None
+    )
     assert search_file(filename, "protocol_dev_eval/norm/xuxa.csv") is None
 
     # Search in a file structure
@@ -100,7 +111,10 @@ def test_search_file():
 
     _untar(filename, final_path, ".gz")
 
-    assert search_file(final_path, "protocol_dev_eval/norm/train_world.csv") is not None
+    assert (
+        search_file(final_path, "protocol_dev_eval/norm/train_world.csv")
+        is not None
+    )
     assert search_file(final_path, "protocol_dev_eval/norm/xuxa.csv") is None
 
     shutil.rmtree(final_path)
@@ -115,7 +129,10 @@ def test_list_dir():
 
     for root_folder in (folder, tar1, tar2):
         fldrs = list_dir(root_folder)
-        assert fldrs == ["README.rst", "database1", "database2"], (fldrs, root_folder)
+        assert fldrs == ["README.rst", "database1", "database2"], (
+            fldrs,
+            root_folder,
+        )
         fldrs = list_dir(root_folder, files=False)
         assert fldrs == ["database1", "database2"], (fldrs, root_folder)
         fldrs = list_dir(root_folder, folders=False)
@@ -124,7 +141,10 @@ def test_list_dir():
         assert fldrs == [], (fldrs, root_folder)
 
         fldrs = list_dir(root_folder, "database1")
-        assert fldrs == ["README1.rst", "protocol1", "protocol2"], (fldrs, root_folder)
+        assert fldrs == ["README1.rst", "protocol1", "protocol2"], (
+            fldrs,
+            root_folder,
+        )
         fldrs = list_dir(root_folder, "database1", files=False)
         assert fldrs == ["protocol1", "protocol2"], (fldrs, root_folder)
         fldrs = list_dir(root_folder, "database1", folders=False)
diff --git a/bob/extension/test_pkgconfig.py b/bob/extension/test_pkgconfig.py
deleted file mode 100644
index abf4082564c459f237a4baa7183754f264ce6e8b..0000000000000000000000000000000000000000
--- a/bob/extension/test_pkgconfig.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Wed 16 Oct 12:16:49 2013
-
-"""Tests for pkgconfig
-"""
-
-import nose
-from .pkgconfig import pkgconfig, version
-
-test_package = 'blitz'
-pkg_config_version = '0.0'
-
-def test_pkgconfig_version():
-
-  assert version()
-  global pkg_config_version
-  pkg_config_version = version()
-
-def test_detect_ok():
-  pkg = pkgconfig(test_package)
-  nose.tools.eq_(pkg.name, test_package)
-  assert pkg.version
-  #print pkg.name, pkg.version
-
-@nose.tools.raises(RuntimeError)
-def test_detect_not_ok():
-  pkg = pkgconfig('foobarfoo')
-
-def test_include_directories():
-  pkg = pkgconfig(test_package)
-  obj = pkg.include_directories()
-  assert isinstance(obj, list)
-  #assert obj
-  #for k in obj:
-  #  assert k.find('-I') != 0
-  #print obj
-
-def test_cflags_other():
-  pkg = pkgconfig('freetype2')
-  obj = pkg.cflags_other()
-  #print obj
-
-  #assert obj['extra_compile_args']
-  #assert isinstance(obj['extra_compile_args'], list)
-  #assert isinstance(obj['extra_compile_args'][0], tuple)
-
-  #assert obj['define_macros']
-  #assert isinstance(obj['define_macros'], list)
-  #assert isinstance(obj['define_macros'][0], tuple)
-
-  assert isinstance(obj, dict)
-
-def test_libraries():
-  pkg = pkgconfig(test_package)
-  obj = pkg.libraries()
-  assert isinstance(obj, list)
-  assert obj
-  for k in obj:
-    assert k.find('-l') != 0
-  #print obj
-
-def test_library_directories():
-  pkg = pkgconfig(test_package)
-  obj = pkg.library_directories()
-  assert isinstance(obj, list)
-  #assert obj
-  #for k in obj:
-  #  assert k.find('-L') != 0
-  #print obj
-
-def test_extra_link_args():
-  pkg = pkgconfig(test_package)
-  obj = pkg.extra_link_args()
-  assert isinstance(obj, list)
-  #print obj
-
-def skip_on_pkgconfig_lt_024(func):
-  from nose.plugins.skip import SkipTest
-  from distutils.version import LooseVersion
-  pkg_config_has_variables = LooseVersion(version()) >= '0.24'
-  def _():
-    if not pkg_config_has_variables: raise SkipTest('pkg-config version (%s) does not support variable listing' % pkg_config_version)
-  _.__name__ = func.__name__
-  return _
-
-@skip_on_pkgconfig_lt_024
-def test_variable_names():
-  pkg = pkgconfig(test_package)
-  obj = pkg.variable_names()
-  assert isinstance(obj, list)
-  assert obj
-  #print obj
-
-@skip_on_pkgconfig_lt_024
-def test_variable():
-  pkg = pkgconfig(test_package)
-  names = pkg.variable_names()
-  assert isinstance(names, list)
-  assert names
-  v = pkg.variable(names[0])
-  assert v
-  #print v
-
-def test_macros():
-  pkg = pkgconfig(test_package)
-  macros = pkg.package_macros()
-  assert isinstance(macros, list)
-  assert len(macros) == 1
-  assert len(macros[0]) == 2
-  assert macros[0][0].find('HAVE_') == 0
-  assert macros[0][1] == '1'
diff --git a/bob/extension/test_rc.py b/bob/extension/test_rc.py
index 9fd76402e2fabdceb60ab10acadec56eeb6272c8..b20f342ba11da437f6fe2ec8e0ea66340c35ecbc 100644
--- a/bob/extension/test_rc.py
+++ b/bob/extension/test_rc.py
@@ -1,90 +1,97 @@
-'''Tests for the global bob's configuration functionality'''
+"""Tests for the global bob's configuration functionality"""
 
-from .rc_config import _loadrc, ENVNAME
-from .scripts import main_cli
-from .scripts.click_helper import assert_click_runner_result
-from click.testing import CliRunner
 import os
+
 import pkg_resources
-path = pkg_resources.resource_filename('bob.extension', 'data')
+
+from click.testing import CliRunner
+
+from .rc_config import ENVNAME, _loadrc
+from .scripts import main_cli
+from .scripts.click_helper import assert_click_runner_result
+
+path = pkg_resources.resource_filename("bob.extension", "data")
 
 
 def test_rc_env():
 
-  os.environ[ENVNAME] = os.path.join(path, 'defaults-config')
-  c = _loadrc()  # should load from environment variable
-  REFERENCE = {
-      "bob.db.atnt.directory": "/home/bob/databases/atnt",
-      "bob.db.mobio.directory": "/home/bob/databases/mobio"
-  }
+    os.environ[ENVNAME] = os.path.join(path, "defaults-config")
+    c = _loadrc()  # should load from environment variable
+    REFERENCE = {
+        "bob.db.atnt.directory": "/home/bob/databases/atnt",
+        "bob.db.mobio.directory": "/home/bob/databases/mobio",
+    }
 
-  assert c == REFERENCE
-  assert c['random'] is None
+    assert c == REFERENCE
+    assert c["random"] is None
 
 
 def test_bob_config():
-  defaults_config = os.path.join(path, 'defaults-config')
-  runner = CliRunner(env={ENVNAME: defaults_config})
-
-  # test config show
-  result = runner.invoke(main_cli, ['config', 'show'])
-  assert_click_runner_result(result, 0)
-  assert 'defaults-config' in result.output, result.output
-  assert open(defaults_config).read() in result.output, result.output
-
-  # test config get (existing key)
-  result = runner.invoke(main_cli,
-                         ['config', 'get', 'bob.db.atnt.directory'])
-  assert_click_runner_result(result, 0)
-  assert result.output == '/home/bob/databases/atnt\n', result.output
-
-  # test config get (non-existing key)
-  result = runner.invoke(main_cli, ['config', 'get', 'bob.db.atnt'])
-  assert_click_runner_result(result, 1)
-
-  # test config set
-  runner = CliRunner()
-  with runner.isolated_filesystem():
-    bobrcfile = 'bobrc'
-    result = runner.invoke(
-        main_cli, [
-            'config', 'set', 'bob.db.atnt.directory',
-            '/home/bob/databases/orl_faces'
-        ],
-        env={
-            ENVNAME: bobrcfile
-        })
+    defaults_config = os.path.join(path, "defaults-config")
+    runner = CliRunner(env={ENVNAME: defaults_config})
+
+    # test config show
+    result = runner.invoke(main_cli, ["config", "show"])
     assert_click_runner_result(result, 0)
+    assert "defaults-config" in result.output, result.output
+    assert open(defaults_config).read() in result.output, result.output
 
-    # read the config back to make sure it is ok.
-    result = runner.invoke(
-        main_cli, ['config', 'show'], env={
-            ENVNAME: bobrcfile
-        })
+    # test config get (existing key)
+    result = runner.invoke(main_cli, ["config", "get", "bob.db.atnt.directory"])
     assert_click_runner_result(result, 0)
-    expected_output = '''Displaying `bobrc':
+    assert result.output == "/home/bob/databases/atnt\n", result.output
+
+    # test config get (non-existing key)
+    result = runner.invoke(main_cli, ["config", "get", "bob.db.atnt"])
+    assert_click_runner_result(result, 1)
+
+    # test config set
+    runner = CliRunner()
+    with runner.isolated_filesystem():
+        bobrcfile = "bobrc"
+        result = runner.invoke(
+            main_cli,
+            [
+                "config",
+                "set",
+                "bob.db.atnt.directory",
+                "/home/bob/databases/orl_faces",
+            ],
+            env={ENVNAME: bobrcfile},
+        )
+        assert_click_runner_result(result, 0)
+
+        # read the config back to make sure it is ok.
+        result = runner.invoke(
+            main_cli, ["config", "show"], env={ENVNAME: bobrcfile}
+        )
+        assert_click_runner_result(result, 0)
+        expected_output = """Displaying `bobrc':
 {
     "bob.db.atnt.directory": "/home/bob/databases/orl_faces"
 }
-'''
-    assert expected_output == result.output, result.output
-  
-    # test config unset (with starting substring)
-    result = runner.invoke(main_cli, ['config', 'unset', 'bob.db.atnt'])
-    result = runner.invoke(main_cli, ['config', 'get', 'bob.db.atnt'])
-    assert_click_runner_result(result, 1)
+"""
+        assert expected_output == result.output, result.output
 
-    # test config unset (with substring contained)
-    # reset the key / value pair 
-    result = runner.invoke(
-        main_cli, [
-            'config', 'set', 'bob.db.atnt.directory',
-            '/home/bob/databases/orl_faces'
-        ],
-        env={
-            ENVNAME: bobrcfile
-        })
-    result = runner.invoke(main_cli, ['config', 'unset', '--contain', 'atnt'])
-    result = runner.invoke(main_cli, ['config', 'get', 'bob.db.atnt'])
-    assert_click_runner_result(result, 1)
+        # test config unset (with starting substring)
+        result = runner.invoke(main_cli, ["config", "unset", "bob.db.atnt"])
+        result = runner.invoke(main_cli, ["config", "get", "bob.db.atnt"])
+        assert_click_runner_result(result, 1)
 
+        # test config unset (with substring contained)
+        # reset the key / value pair
+        result = runner.invoke(
+            main_cli,
+            [
+                "config",
+                "set",
+                "bob.db.atnt.directory",
+                "/home/bob/databases/orl_faces",
+            ],
+            env={ENVNAME: bobrcfile},
+        )
+        result = runner.invoke(
+            main_cli, ["config", "unset", "--contain", "atnt"]
+        )
+        result = runner.invoke(main_cli, ["config", "get", "bob.db.atnt"])
+        assert_click_runner_result(result, 1)
diff --git a/bob/extension/test_utils.py b/bob/extension/test_utils.py
index 77af867df9eeb90b645a0b78735797e27e5499b5..ae6bca1ab61750ddcb8d78c7ea0f8c52f397a121 100644
--- a/bob/extension/test_utils.py
+++ b/bob/extension/test_utils.py
@@ -8,97 +8,17 @@
 
 import os
 import sys
-import nose.tools
-import pkg_resources
-from nose.plugins.skip import SkipTest
-from .utils import uniq, egrep, find_file, find_header, find_library, \
-    load_requirements, find_packages, link_documentation
-
-def test_uniq():
-
-  a = [1, 2, 3, 7, 3, 2]
-
-  nose.tools.eq_(uniq(a), [1, 2, 3, 7])
-
-def test_find_file():
-
-  f = find_file('array.h', subpaths=[os.path.join('include', 'blitz')])
-
-  assert f
-
-  nose.tools.eq_(os.path.basename(f[0]), 'array.h')
-
-def test_find_header():
-
-  f1 = find_file('array.h', subpaths=[os.path.join('include', 'blitz')])
-
-  assert f1
-
-  nose.tools.eq_(os.path.basename(f1[0]), 'array.h')
-
-  f2 = find_header(os.path.join('blitz', 'array.h'))
-
-  nose.tools.eq_(os.path.basename(f2[0]), 'array.h')
-
-  assert f2
-
-  nose.tools.eq_(f1, f2)
-
-def test_find_library():
-
-  f = find_library('blitz')
-
-  assert f
 
-  assert len(f) >= 1
-
-  for k in f:
-    assert k.find('blitz') >= 0
-
-def test_egrep():
-
-  f = find_header('version.hpp', subpaths=['boost', 'boost?*'])
-
-  assert f
-
-  matches = egrep(f[0], r"^#\s*define\s+BOOST_VERSION\s+(\d+)\s*$")
-
-  nose.tools.eq_(len(matches), 1)
-
-def test_find_versioned_library():
-
-  f = find_header('version.hpp', subpaths=['boost', 'boost?*'])
-
-  assert f
-
-  matches = egrep(f[0], r"^#\s*define\s+BOOST_VERSION\s+(\d+)\s*$")
-
-  nose.tools.eq_(len(matches), 1)
-
-  version_int = int(matches[0].group(1))
-  version_tuple = (
-      version_int // 100000,
-      (version_int // 100) % 1000,
-      version_int % 100,
-      )
-  version = '.'.join([str(k) for k in version_tuple])
-
-  lib = find_library('boost_system', version=version)
-  lib += find_library('boost_system-mt', version=version)
+import pkg_resources
 
-  assert len(lib) >= 1
+from .utils import find_packages, link_documentation, load_requirements
 
-  for k in lib:
-    assert k.find('boost_system') >= 0
 
 def test_requirement_readout():
 
-  if sys.version_info[0] == 3:
     from io import StringIO as stringio
-  else:
-    from cStringIO import StringIO as stringio
 
-  f = """ # this is my requirements file
+    f = """ # this is my requirements file
 package-a >= 0.42
 package-b
 package-c
@@ -109,32 +29,29 @@ package-z
 -e http://example.com/mypackage-1.0.4.zip
 """
 
-  result = load_requirements(stringio(f))
-  expected = ['package-a >= 0.42', 'package-b', 'package-c', 'package-z']
-  nose.tools.eq_(result, expected)
+    result = load_requirements(stringio(f))
+    expected = ["package-a >= 0.42", "package-b", "package-c", "package-z"]
+    assert result == expected
 
 
 def test_find_packages():
-  # tests the find-packages command inside the bob.extension package
+    # tests the find-packages command inside the bob.extension package
 
-  basedir = pkg_resources.resource_filename('bob.extension', ".")
-  packages = find_packages(os.path.abspath(os.path.join(basedir, "..")))
+    basedir = pkg_resources.resource_filename("bob.extension", ".")
+    packages = find_packages(os.path.abspath(os.path.join(basedir, "..")))
 
-  site_packages = os.path.dirname(os.path.commonprefix(packages))
-  packages = [os.path.relpath(k, site_packages) for k in packages]
+    site_packages = os.path.dirname(os.path.commonprefix(packages))
+    packages = [os.path.relpath(k, site_packages) for k in packages]
 
-  assert 'bob' in packages
-  assert 'bob.extension' in packages
-  assert 'bob.extension.scripts' in packages
+    assert "bob" in packages
+    assert "bob.extension" in packages
+    assert "bob.extension.scripts" in packages
 
 
 def test_documentation_generation():
-  if sys.version_info[0] == 3:
     from io import StringIO as stringio
-  else:
-    from cStringIO import StringIO as stringio
 
-  f = """ # this is my requirements file
+    f = """ # this is my requirements file
 package-a >= 0.42
 package-b
 package-c
@@ -146,80 +63,86 @@ package-z
 -e http://example.com/mypackage-1.0.4.zip
 """
 
-  # keep the nose tests quiet
-  _stdout = sys.stdout
-
-  try:
-    devnull = open(os.devnull, 'w')
-    sys.stdout = devnull
-
-    # test NumPy and SciPy docs
-    try:
-      import numpy
-      result = link_documentation(['numpy'], None)
-      assert len(result) == 1
-      key = list(result.keys())[0]
-      assert 'numpy' in key
-    except ImportError:
-      pass
-
-    try:
-      import scipy
-      result = link_documentation(['scipy'], None)
-      assert len(result) == 1
-      key = list(result.keys())[0]
-      assert 'scipy' in key
-    except ImportError:
-      pass
+    # keep the tests quiet
+    _stdout = sys.stdout
 
     try:
-      import matplotlib
-      result = link_documentation(['matplotlib'], None)
-      assert len(result) == 1
-      key = list(result.keys())[0]
-      assert 'matplotlib' in key
-    except ImportError:
-      pass
-
-    # test pypi packages
-    additional_packages = [
-        'python',
-        'matplotlib',
-        'bob.extension',
-        'gridtk',
-        'other.bob.package',
+        devnull = open(os.devnull, "w")
+        sys.stdout = devnull
+
+        # test NumPy and SciPy docs
+        try:
+            import numpy  # noqa: F401
+
+            result = link_documentation(["numpy"], None)
+            assert len(result) == 1
+            key = list(result.keys())[0]
+            assert "numpy" in key
+        except ImportError:
+            pass
+
+        try:
+            import scipy  # noqa: F401
+
+            result = link_documentation(["scipy"], None)
+            assert len(result) == 1
+            key = list(result.keys())[0]
+            assert "scipy" in key
+        except ImportError:
+            pass
+
+        try:
+            import matplotlib  # noqa: F401
+
+            result = link_documentation(["matplotlib"], None)
+            assert len(result) == 1
+            key = list(result.keys())[0]
+            assert "matplotlib" in key
+        except ImportError:
+            pass
+
+        # test pypi packages
+        additional_packages = [
+            "python",
+            "matplotlib",
+            "bob.extension",
+            "gridtk",
+            "other.bob.package",
         ]
 
-    # test linkage to official documentation
-    server = "http://www.idiap.ch/software/bob/docs/bob/%s/master/"
-    os.environ["BOB_DOCUMENTATION_SERVER"] = server
-    result = link_documentation(additional_packages, stringio(f))
-    expected = [
-        'https://docs.python.org/%d.%d/' % sys.version_info[:2],
-        'https://matplotlib.org/stable/',
-        'https://setuptools.readthedocs.io/en/latest/',
-        server % 'bob.extension',
-        server % 'gridtk',
+        # test linkage to official documentation
+        server = "http://www.idiap.ch/software/bob/docs/bob/%s/master/"
+        os.environ["BOB_DOCUMENTATION_SERVER"] = server
+        result = link_documentation(additional_packages, stringio(f))
+        expected = [
+            "https://docs.python.org/%d.%d/" % sys.version_info[:2],
+            "https://matplotlib.org/stable/",
+            "https://setuptools.readthedocs.io/en/latest/",
+            server % "bob.extension",
+            server % "gridtk",
         ]
-    result = [k[0] for k in result.values()]
-    nose.tools.eq_(sorted(result), sorted(expected))
+        result = [k[0] for k in result.values()]
+        assert sorted(result) == sorted(expected)
 
-  finally:
-    sys.stdout = _stdout
+    finally:
+        sys.stdout = _stdout
 
 
 def test_get_config():
-  # Test the generic get_config() function
-  import bob.extension
-  cfg = bob.extension.get_config()
-  splits = cfg.split("\n")
-  assert splits[0].startswith('bob.extension')
-  assert splits[1].startswith("* Python dependencies")
-  assert any([s.startswith("  - setuptools") for s in splits[2:]])
-
-  cfg = bob.extension.get_config("setuptools", {'MyPackage' : {'my_dict' : 42}}, 0x0204)
-  splits = cfg.split("\n")
-  assert splits[0].startswith('setuptools')
-  assert "api=0x0204" in splits[0]
-  assert splits[1].startswith("* C/C++ dependencies")
-  assert any([s.startswith("  - MyPackage") for s in splits[2:]])
+    # Test the generic get_config() function
+    import bob.extension
+
+    cfg = bob.extension.get_config()
+    splits = cfg.split("\n")
+    assert splits[0].startswith("bob.extension")
+    assert splits[1].startswith("* Python dependencies")
+    assert any([s.startswith("  - setuptools") for s in splits[2:]])
+
+    cfg = bob.extension.get_config(
+        "setuptools", {"MyPackage": {"my_dict": 42}}, 0x0204
+    )
+    splits = cfg.split("\n")
+    assert splits[0].startswith("setuptools")
+    assert "api=0x0204" in splits[0]
+    assert splits[1].startswith("* C/C++ dependencies")
+    assert any([s.startswith("  - MyPackage") for s in splits[2:]])
diff --git a/bob/extension/utils.py b/bob/extension/utils.py
index 8f7b6640d21aa5f32a9fa0930e8e9f8a2a8a9e9f..36f0d13296c179541b38eb731d978638b4fbe501 100644
--- a/bob/extension/utils.py
+++ b/bob/extension/utils.py
@@ -3,602 +3,260 @@
 # Andre Anjos <andre.dos.anjos@gmail.com>
 # Fri 21 Mar 2014 10:37:40 CET
 
-'''General utilities for building extensions'''
+"""General utilities for building extensions"""
 
 import os
 import re
 import sys
-import glob
-import platform
+
 import pkg_resources
-from . import DEFAULT_PREFIXES
-
-
-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
-
-
-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
-
-
-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)
-
-
-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
-
-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)
-
-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
-
-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)])
-
-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
+
 
 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)
-
-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
-
-def link_documentation(additional_packages = ['python', 'numpy'], requirements_file = "../requirements.txt", server = None):
-  """Generates a list of documented packages on our documentation server for the packages read from the "requirements.txt" file and the given list of additional packages.
-
-  Parameters:
-
-  additional_packages : [str]
-    A list of additional bob packages for which the documentation urls are added.
-    By default, 'numpy' is added
-
-  requirements_file : str or file-like
-    The file (relative to the documentation directory), where to read the requirements from.
-    If ``None``, it will be skipped.
-
-  server : str or None
-    The url to the server which provides the documentation.
-    If ``None`` (the default), the ``BOB_DOCUMENTATION_SERVER`` environment variable is taken if existent.
-    If neither ``server`` is specified, nor a ``BOB_DOCUMENTATION_SERVER`` environment variable is set, the default ``"http://www.idiap.ch/software/bob/docs/bob/%(name)s/%(version)s/"`` is used.
-
-  """
-
-  def smaller_than(v1, v2):
-    """Compares scipy/numpy version numbers"""
-
-    c1 = v1.split('.')
-    c2 = v2.split('.')[:len(c1)] #clip to the compared version
-    for i in range(len(c2)):
-      n1 = c1[i]
-      n2 = c2[i]
-      try:
-        n1 = int(n1)
-        n2 = int(n2)
-      except ValueError:
-        n1 = str(n1)
-        n2 = str(n2)
-      if n1 < n2: return True
-      if n1 > n2: return False
-    return False
-
-
-  if sys.version_info[0] <= 2:
-    import urllib2 as urllib
-    from urllib2 import HTTPError, URLError
-  else:
-    import urllib.request as urllib
-    import urllib.error as error
-    HTTPError = error.HTTPError
-    URLError = error.URLError
+    """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)
 
-  # collect packages are automatically included in the list of indexes
-  packages = []
-  version_re = re.compile(r'\s*[\<\>=]+\s*')
-  if requirements_file is not None:
-    if not isinstance(requirements_file, str) or \
-        os.path.exists(requirements_file):
-      requirements = load_requirements(requirements_file)
-      packages += [version_re.split(k)[0] for k in requirements]
-  packages += additional_packages
 
+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
 
-  def _add_index(name, addr, packages=packages):
-    """Helper to add a new doc index to the intersphinx catalog
+
+def link_documentation(
+    additional_packages=["python", "numpy"],
+    requirements_file="../requirements.txt",
+    server=None,
+):
+    """Generates a list of documented packages on our documentation server for the packages read from the "requirements.txt" file and the given list of additional packages.
 
     Parameters:
 
-      name (str): Name of the package that will be added to the catalog
-      addr (str): The URL (except the ``objects.inv`` file), that will be added
+    additional_packages : [str]
+      A list of additional bob packages for which the documentation urls are added.
+      By default, 'numpy' is added
+
+    requirements_file : str or file-like
+      The file (relative to the documentation directory), where to read the requirements from.
+      If ``None``, it will be skipped.
+
+    server : str or None
+      The url to the server which provides the documentation.
+      If ``None`` (the default), the ``BOB_DOCUMENTATION_SERVER`` environment variable is taken if existent.
+      If neither ``server`` is specified, nor a ``BOB_DOCUMENTATION_SERVER`` environment variable is set, the default ``"http://www.idiap.ch/software/bob/docs/bob/%(name)s/%(version)s/"`` is used.
 
     """
 
-    if name in packages:
-      print ("Adding intersphinx source for `%s': %s" % (name, addr))
-      mapping[name] = (addr, None)
-      packages = [k for k in packages if k != name]
+    def smaller_than(v1, v2):
+        """Compares scipy/numpy version numbers"""
+
+        c1 = v1.split(".")
+        c2 = v2.split(".")[: len(c1)]  # clip to the compared version
+        for i in range(len(c2)):
+            n1 = c1[i]
+            n2 = c2[i]
+            try:
+                n1 = int(n1)
+                n2 = int(n2)
+            except ValueError:
+                n1 = str(n1)
+                n2 = str(n2)
+            if n1 < n2:
+                return True
+            if n1 > n2:
+                return False
+        return False
 
+    import urllib.error as error
+    import urllib.request as urllib
 
-  def _add_numpy_index():
-    """Helper to add the numpy manual"""
+    HTTPError = error.HTTPError
+    URLError = error.URLError
 
-    try:
-      import numpy
-      ver = numpy.version.version
-      ver = '.'.join(ver.split('.')[:-1])
-      _add_index('numpy', 'https://numpy.org/doc/%s/' % ver)
+    # collect packages are automatically included in the list of indexes
+    packages = []
+    version_re = re.compile(r"\s*[\<\>=]+\s*")
+    if requirements_file is not None:
+        if not isinstance(requirements_file, str) or os.path.exists(
+            requirements_file
+        ):
+            requirements = load_requirements(requirements_file)
+            packages += [version_re.split(k)[0] for k in requirements]
+    packages += additional_packages
 
-    except ImportError:
-      _add_index('numpy', 'https://numpy.org/devdocs/')
+    def _add_index(name, addr, packages=packages):
+        """Helper to add a new doc index to the intersphinx catalog
 
+        Parameters:
 
-  def _add_scipy_index():
-    """Helper to add the scipy manual"""
+          name (str): Name of the package that will be added to the catalog
+          addr (str): The URL (except the ``objects.inv`` file), that will be added
 
-    try:
-      import scipy
-      ver = scipy.version.version
-      if smaller_than(ver, '0.9.0'):
-        ver = '.'.join(ver.split('.')[:-1]) + '.x'
-      else:
-        ver = '.'.join(ver.split('.')[:-1]) + '.0'
-      _add_index('scipy', 'https://docs.scipy.org/doc/scipy-%s/reference/' % ver)
+        """
 
-    except ImportError:
-      _add_index('scipy', 'https://docs.scipy.org/doc/scipy/reference/')
+        if name in packages:
+            print("Adding intersphinx source for `%s': %s" % (name, addr))
+            mapping[name] = (addr, None)
+            packages = [k for k in packages if k != name]
 
+    def _add_numpy_index():
+        """Helper to add the numpy manual"""
 
-  def _add_click_index():
-    """Helper to add the click manual"""
+        try:
+            import numpy
+
+            ver = numpy.version.version
+            ver = ".".join(ver.split(".")[:-1])
+            _add_index("numpy", "https://numpy.org/doc/%s/" % ver)
+
+        except ImportError:
+            _add_index("numpy", "https://numpy.org/devdocs/")
+
+    def _add_scipy_index():
+        """Helper to add the scipy manual"""
 
-    import click
-    major, minor = [int(x) for x in click.__version__.split('.')[0:2]]
-    if major < 8:
-      ver = f"{major}.x"
-    else:
-      ver = f"{major}.{minor}.x"
-    _add_index('click', 'https://click.palletsprojects.com/en/%s/' % ver)
-
-
-  mapping = {}
-
-  # add indexes for common packages used in Bob
-  _add_index('python', 'https://docs.python.org/%d.%d/' % sys.version_info[:2])
-  _add_numpy_index()
-  _add_index('scipy', 'https://docs.scipy.org/doc/scipy/')
-  _add_index('matplotlib', 'https://matplotlib.org/stable/')
-  _add_index('setuptools', 'https://setuptools.readthedocs.io/en/latest/')
-  _add_index('six', 'https://six.readthedocs.io')
-  _add_index('sqlalchemy', 'https://docs.sqlalchemy.org/en/latest/')
-  _add_index('docopt', 'https://docopt.readthedocs.io/en/latest/')
-  _add_index('scikit-learn', 'https://scikit-learn.org/stable/')
-  _add_index('scikit-image', 'https://scikit-image.org/docs/dev/')
-  _add_index('pillow', 'https://pillow.readthedocs.io/en/latest/')
-  _add_index('PIL', 'https://pillow.readthedocs.io/en/latest/')
-  _add_index('pandas', 'https://pandas.pydata.org/pandas-docs/stable/')
-  _add_index('dask', 'https://docs.dask.org/en/latest/')
-  _add_index('dask-jobqueue', 'https://jobqueue.dask.org/en/latest/')
-  _add_index('distributed', 'https://distributed.dask.org/en/latest/')
-  _add_click_index()
-  _add_index('torch', 'https://pytorch.org/docs/stable/')
-  _add_index('xarray', 'https://xarray.pydata.org/en/stable/')
-  _add_index("h5py", "https://docs.h5py.org/en/stable/")
-
-
-  # get the server for the other packages
-  if server is None:
-    if "BOB_DOCUMENTATION_SERVER" in os.environ:
-      server = os.environ["BOB_DOCUMENTATION_SERVER"]
-    else:
-      server = "http://www.idiap.ch/software/bob/docs/bob/%(name)s/%(version)s/|http://www.idiap.ch/software/bob/docs/bob/%(name)s/master/"
-
-  # array support for BOB_DOCUMENTATION_SERVER
-  # transforms "(file:///path/to/dir  https://example.com/dir| http://bla )"
-  # into ["file:///path/to/dir", "https://example.com/dir", "http://bla"]
-  # so, trim eventual parenthesis/white-spaces and splits by white space or |
-  if server.strip():
-    server = re.split(r'[|\s]+', server.strip('() '))
-  else:
-    server = []
-
-  # check if the packages have documentation on the server
-  for p in packages:
-    if p in mapping: continue #do not add twice...
-
-    for s in server:
-      # generate URL
-      package_name = p.split()[0]
-      if s.count('%s') == 1: #old style
-        url = s % package_name
-      else: #use new style, with mapping, try to link against specific version
         try:
-          version = 'v' + pkg_resources.require(package_name)[0].version
-        except pkg_resources.DistributionNotFound:
-          version = 'stable' #package is not a runtime dep, only referenced
-        url = s % {'name': package_name, 'version': version}
-
-      try:
-        # otherwise, urlopen will fail
-        if url.startswith('file://'):
-          f = urllib.urlopen(urllib.Request(url + 'objects.inv'))
-          url = url[7:] #intersphinx does not like file://
-        else:
-          f = urllib.urlopen(urllib.Request(url))
+            import scipy
 
-        # request url
-        print("Found documentation for %s on %s; adding intersphinx source" % (p, url))
-        mapping[p] = (url, None)
-        break #inner loop, for server, as we found a candidate!
+            ver = scipy.version.version
+            if smaller_than(ver, "0.9.0"):
+                ver = ".".join(ver.split(".")[:-1]) + ".x"
+            else:
+                ver = ".".join(ver.split(".")[:-1]) + ".0"
+            _add_index(
+                "scipy", "https://docs.scipy.org/doc/scipy-%s/reference/" % ver
+            )
 
-      except HTTPError as exc:
-        if exc.code != 404:
-          # url request failed with a something else than 404 Error
-          print("Requesting URL %s returned error: %s" % (url, exc))
-          # notice mapping is not updated here, as the URL does not exist
+        except ImportError:
+            _add_index("scipy", "https://docs.scipy.org/doc/scipy/reference/")
 
-      except URLError as exc:
-        print("Requesting URL %s did not succeed (maybe offline?). " \
-            "The error was: %s" % (url, exc))
+    def _add_click_index():
+        """Helper to add the click manual"""
 
-      except IOError as exc:
-        print ("Path %s does not exist. The error was: %s" % (url, exc))
+        import click
 
-  return mapping
+        major, minor = [int(x) for x in click.__version__.split(".")[0:2]]
+        if major < 8:
+            ver = f"{major}.x"
+        else:
+            ver = f"{major}.{minor}.x"
+        _add_index("click", "https://click.palletsprojects.com/en/%s/" % ver)
+
+    mapping = {}
+
+    # add indexes for common packages used in Bob
+    _add_index(
+        "python", "https://docs.python.org/%d.%d/" % sys.version_info[:2]
+    )
+    _add_numpy_index()
+    _add_index("scipy", "https://docs.scipy.org/doc/scipy/")
+    _add_index("matplotlib", "https://matplotlib.org/stable/")
+    _add_index("setuptools", "https://setuptools.readthedocs.io/en/latest/")
+    _add_index("six", "https://six.readthedocs.io")
+    _add_index("sqlalchemy", "https://docs.sqlalchemy.org/en/latest/")
+    _add_index("docopt", "https://docopt.readthedocs.io/en/latest/")
+    _add_index("scikit-learn", "https://scikit-learn.org/stable/")
+    _add_index("scikit-image", "https://scikit-image.org/docs/dev/")
+    _add_index("pillow", "https://pillow.readthedocs.io/en/latest/")
+    _add_index("PIL", "https://pillow.readthedocs.io/en/latest/")
+    _add_index("pandas", "https://pandas.pydata.org/pandas-docs/stable/")
+    _add_index("dask", "https://docs.dask.org/en/latest/")
+    _add_index("dask-jobqueue", "https://jobqueue.dask.org/en/latest/")
+    _add_index("distributed", "https://distributed.dask.org/en/latest/")
+    _add_click_index()
+    _add_index("torch", "https://pytorch.org/docs/stable/")
+    _add_index("xarray", "https://xarray.pydata.org/en/stable/")
+    _add_index("h5py", "https://docs.h5py.org/en/stable/")
+
+    # get the server for the other packages
+    if server is None:
+        if "BOB_DOCUMENTATION_SERVER" in os.environ:
+            server = os.environ["BOB_DOCUMENTATION_SERVER"]
+        else:
+            server = "http://www.idiap.ch/software/bob/docs/bob/%(name)s/%(version)s/|http://www.idiap.ch/software/bob/docs/bob/%(name)s/master/"
+
+    # array support for BOB_DOCUMENTATION_SERVER
+    # transforms "(file:///path/to/dir  https://example.com/dir| http://bla )"
+    # into ["file:///path/to/dir", "https://example.com/dir", "http://bla"]
+    # so, trim eventual parenthesis/white-spaces and splits by white space or |
+    if server.strip():
+        server = re.split(r"[|\s]+", server.strip("() "))
+    else:
+        server = []
+
+    # check if the packages have documentation on the server
+    for p in packages:
+        if p in mapping:
+            continue  # do not add twice...
+
+        for s in server:
+            # generate URL
+            package_name = p.split()[0]
+            if s.count("%s") == 1:  # old style
+                url = s % package_name
+            else:  # use new style, with mapping, try to link against specific version
+                try:
+                    version = (
+                        "v" + pkg_resources.require(package_name)[0].version
+                    )
+                except pkg_resources.DistributionNotFound:
+                    version = "stable"  # package is not a runtime dep, only referenced
+                url = s % {"name": package_name, "version": version}
+
+            try:
+                # otherwise, urlopen will fail
+                if url.startswith("file://"):
+                    urllib.urlopen(urllib.Request(url + "objects.inv"))
+                    url = url[7:]  # intersphinx does not like file://
+                else:
+                    urllib.urlopen(urllib.Request(url))
+
+                # request url
+                print(
+                    "Found documentation for %s on %s; adding intersphinx source"
+                    % (p, url)
+                )
+                mapping[p] = (url, None)
+                break  # inner loop, for server, as we found a candidate!
+
+            except HTTPError as exc:
+                if exc.code != 404:
+                    # url request failed with a something else than 404 Error
+                    print("Requesting URL %s returned error: %s" % (url, exc))
+                    # notice mapping is not updated here, as the URL does not exist
+
+            except URLError as exc:
+                print(
+                    "Requesting URL %s did not succeed (maybe offline?). "
+                    "The error was: %s" % (url, exc)
+                )
+
+            except IOError as exc:
+                print("Path %s does not exist. The error was: %s" % (url, exc))
+
+    return mapping
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 2c40acc8c73caa3877d42f0cee8d5af22537313f..2bdfc5c1f283159aff294bfaa6bb7eec6db5cae2 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -40,25 +40,18 @@ test:
     - bob config --help
     # fix for the CONDA_BUILD_SYSROOT variable missing at test time
     - export CONDA_BUILD_SYSROOT={{ CONDA_BUILD_SYSROOT }}  # [osx]
-    - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }}
+    - pytest --verbose --cov {{ name }} --cov-report term-missing --cov-report html:{{ project_dir }}/sphinx/coverage --cov-report xml:{{ project_dir }}/coverage.xml --pyargs {{ name }}
     - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx
     - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx
     - conda inspect linkages -p $PREFIX {{ name }}  # [not win]
     - conda inspect objects -p $PREFIX {{ name }}  # [osx]
   requires:
-    - nose {{ nose }}
+    - pytest {{ pytest }}
+    - pytest-cov {{ pytest_cov }}
     - coverage {{ coverage }}
     - sphinx {{ sphinx }}
     - sphinx_rtd_theme {{ sphinx_rtd_theme }}
-    - boost {{ boost }}
-    - cmake {{ cmake }}
-    - make {{ make }}
-    - pkg-config {{ pkg_config }}
-    - freetype {{ freetype }}
-    - libblitz {{ libblitz }}
     - numpy {{ numpy }}
-    - {{ compiler('c') }}
-    - {{ compiler('cxx') }}
 
 about:
   home: https://www.idiap.ch/software/bob/
diff --git a/doc/annotate.py b/doc/annotate.py
index c00b3b7ff0cfdc8588846de8cae5dc26e4177015..96344a59de70b13945cf44a6e4bd28f4abf14f73 100644
--- a/doc/annotate.py
+++ b/doc/annotate.py
@@ -4,34 +4,68 @@
 # line's --help option and its auto-complete feature in terminal (if enabled). Instead,
 # put your imports inside the function.
 import logging
+
 import click
+
 from bob.extension.scripts.click_helper import (
-    verbosity_option, ConfigCommand, ResourceOption, log_parameters)
+    ConfigCommand,
+    ResourceOption,
+    log_parameters,
+    verbosity_option,
+)
 
 logger = logging.getLogger(__name__)
 
 
-@click.command(entry_point_group='bob.bio.config', cls=ConfigCommand,
-               epilog='''\b
+@click.command(
+    entry_point_group="bob.bio.config",
+    cls=ConfigCommand,
+    epilog="""\b
 Examples:
 
   $ bob bio annotate -vvv -d <database> -a <annotator> -o /tmp/annotations
   $ jman submit --array 64 -- bob bio annotate ... --array 64
-''')
-@click.option('--database', '-d', required=True, cls=ResourceOption,
-              entry_point_group='bob.bio.database',
-              help='''The database that you want to annotate.''')
-@click.option('--annotator', '-a', required=True, cls=ResourceOption,
-              entry_point_group='bob.bio.annotator',
-              help='A callable that takes the database and a sample (biofile) '
-              'of the database and returns the annotations in a dictionary.')
-@click.option('--output-dir', '-o', required=True, cls=ResourceOption,
-              help='The directory to save the annotations.')
-@click.option('--force', '-f', is_flag=True, cls=ResourceOption,
-              help='Whether to overwrite existing annotations.')
-@click.option('--array', type=click.INT, default=1, cls=ResourceOption,
-              help='Use this option alongside gridtk to submit this script as '
-              'an array job.')
+""",
+)
+@click.option(
+    "--database",
+    "-d",
+    required=True,
+    cls=ResourceOption,
+    entry_point_group="bob.bio.database",
+    help="""The database that you want to annotate.""",
+)
+@click.option(
+    "--annotator",
+    "-a",
+    required=True,
+    cls=ResourceOption,
+    entry_point_group="bob.bio.annotator",
+    help="A callable that takes the database and a sample (biofile) "
+    "of the database and returns the annotations in a dictionary.",
+)
+@click.option(
+    "--output-dir",
+    "-o",
+    required=True,
+    cls=ResourceOption,
+    help="The directory to save the annotations.",
+)
+@click.option(
+    "--force",
+    "-f",
+    is_flag=True,
+    cls=ResourceOption,
+    help="Whether to overwrite existing annotations.",
+)
+@click.option(
+    "--array",
+    type=click.INT,
+    default=1,
+    cls=ResourceOption,
+    help="Use this option alongside gridtk to submit this script as "
+    "an array job.",
+)
 @verbosity_option(cls=ResourceOption)
 def annotate(database, annotator, output_dir, force, array, **kwargs):
     """Annotates a database.
diff --git a/doc/conf.py b/doc/conf.py
index 77fbf79ebad4761a59e970898570b61339c5b838..68775d7f2d8a01fbd429fe367477c3ac6e25eca7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -2,31 +2,29 @@
 # vim: set fileencoding=utf-8 :
 
 import os
-import sys
-import glob
-import pkg_resources
 
+import pkg_resources
 
 # -- General configuration -----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.3'
+needs_sphinx = "1.3"
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = [
-    'sphinx.ext.todo',
-    'sphinx.ext.coverage',
-    'sphinx.ext.ifconfig',
-    'sphinx.ext.autodoc',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.doctest',
-    'sphinx.ext.graphviz',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.viewcode',
-    'sphinx.ext.mathjax',
-    ]
+    "sphinx.ext.todo",
+    "sphinx.ext.coverage",
+    "sphinx.ext.ifconfig",
+    "sphinx.ext.autodoc",
+    "sphinx.ext.autosummary",
+    "sphinx.ext.doctest",
+    "sphinx.ext.graphviz",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.napoleon",
+    "sphinx.ext.viewcode",
+    "sphinx.ext.mathjax",
+]
 
 # Be picky about warnings
 nitpicky = True
@@ -35,16 +33,12 @@ nitpicky = True
 nitpick_ignore = []
 
 # Allows the user to override warnings from a separate file
-if os.path.exists('nitpick-exceptions.txt'):
-    for line in open('nitpick-exceptions.txt'):
+if os.path.exists("nitpick-exceptions.txt"):
+    for line in open("nitpick-exceptions.txt"):
         if line.strip() == "" or line.startswith("#"):
             continue
         dtype, target = line.split(None, 1)
         target = target.strip()
-        try: # python 2.x
-            target = unicode(target)
-        except NameError:
-            pass
         nitpick_ignore.append((dtype, target))
 
 # Always includes todos
@@ -57,25 +51,27 @@ autosummary_generate = True
 numfig = True
 
 # If we are on OSX, the 'dvipng' path maybe different
-dvipng_osx = '/opt/local/libexec/texlive/binaries/dvipng'
-if os.path.exists(dvipng_osx): pngmath_dvipng = dvipng_osx
+dvipng_osx = "/opt/local/libexec/texlive/binaries/dvipng"
+if os.path.exists(dvipng_osx):
+    pngmath_dvipng = dvipng_osx
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'bob.extension'
+project = "bob.extension"
 import time
-copyright = u'%s, Idiap Research Institute' % time.strftime('%Y')
+
+copyright = "%s, Idiap Research Institute" % time.strftime("%Y")
 
 # Grab the setup entry
 distribution = pkg_resources.require(project)[0]
@@ -91,42 +87,42 @@ release = distribution.version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = ['links.rst']
+exclude_patterns = ["links.rst"]
 
 # The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # Some variables which are useful for generated material
-project_variable = project.replace('.', '_')
-short_description = u'Building of Python/C++ extensions for Bob'
-owner = [u'Idiap Research Institute']
+project_variable = project.replace(".", "_")
+short_description = "Building of Python/C++ extensions for Bob"
+owner = ["Idiap Research Institute"]
 
 
 # -- Options for HTML output ---------------------------------------------------
@@ -134,80 +130,81 @@ owner = [u'Idiap Research Institute']
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 import sphinx_rtd_theme
-html_theme = 'sphinx_rtd_theme'
+
+html_theme = "sphinx_rtd_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
 html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = project_variable
+# html_short_title = project_variable
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-html_logo = 'img/logo.png'
+html_logo = "img/logo.png"
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-html_favicon = 'img/favicon.ico'
+html_favicon = "img/favicon.ico"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
+# html_static_path = ['_static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = project_variable + u'_doc'
+htmlhelp_basename = project_variable + "_doc"
 
 
 # -- Post configuration --------------------------------------------------------
@@ -217,22 +214,27 @@ rst_epilog = """
 .. |project| replace:: Bob
 .. |version| replace:: %s
 .. |current-year| date:: %%Y
-""" % (version,)
+""" % (
+    version,
+)
 
 # Default processing flags for sphinx
-autoclass_content = 'class'
-autodoc_member_order = 'bysource'
+autoclass_content = "class"
+autodoc_member_order = "bysource"
 autodoc_default_options = {
-  "members": True,
-  "undoc-members": True,
-  "show-inheritance": True,
+    "members": True,
+    "undoc-members": True,
+    "show-inheritance": True,
 }
 
 
 # For inter-documentation mapping:
 from bob.extension.utils import link_documentation, load_requirements
+
 sphinx_requirements = "extra-intersphinx.txt"
 if os.path.exists(sphinx_requirements):
-    intersphinx_mapping = link_documentation(additional_packages=load_requirements(sphinx_requirements))
+    intersphinx_mapping = link_documentation(
+        additional_packages=load_requirements(sphinx_requirements)
+    )
 else:
     intersphinx_mapping = link_documentation()
diff --git a/doc/cplusplus_library.rst b/doc/cplusplus_library.rst
deleted file mode 100644
index 5a579b904617c3b1a0f0b55b1fe1872031547573..0000000000000000000000000000000000000000
--- a/doc/cplusplus_library.rst
+++ /dev/null
@@ -1,135 +0,0 @@
-.. vim: set fileencoding=utf-8 :
-
-===============================
-C/C++ libraries in your package
-===============================
-
-Typically, |project|'s core packages include both a pure C++ library as well as Python bindings for the C++ code.
-
-If you want to provide a library with pure C++ code in your package as well,
-you can use the :py:class:`bob.extension.Library` class.  It will automatically
-compile your C/C++ code using `CMake <http://www.cmake.org>`_ into a shared
-library that you can link to your own C/C++-Python bindings, as well as in the
-C++ code of other C++/Python packages. Again, a complete example can be
-downloaded via:
-
-.. code-block:: sh
-
-  $ git clone https://gitlab.idiap.ch/bob/bob.extension.git
-  $ cp -R bob.extension/bob/extension/data/bob.example.extension ./
-  $ rm -rf bob.extension # optionally remove the cloned source of bob.extension
-  $ cd bob.example.extension
-
-
------------------------
-Setting up your package
------------------------
-
-If you would like to generate a Library out of your C++ code, simply add it in the list of ``ext_modules``:
-
-.. code-block:: python
-
-  ...
-  # import the Extension and Library classes and the build_ext function from bob.blitz
-  from bob.blitz.extension import Extension, Library, build_ext
-  ...
-
-  setup(
-
-    ext_modules = [
-      # declare a pure C/C++ library just the same way as an extension
-      Library("bob.example.library.bob_example_library",
-        # list of pure C/C++ files compiled into this library
-        [
-          "bob/example/library/cpp/Function.cpp",
-        ],
-        version = version,
-        bob_packages = bob_packages,
-      ),
-      # all other extensions will automatically link against the Library defined above
-      Extension("bob.example.library._library",
-        # list of files compiled into this extension
-        [
-          # the Python bindings
-          "bob/example/library/main.cpp",
-        ],
-        version = version,
-        bob_packages = bob_packages,
-      ),
-      ... #add more Extensions if you wish
-    ],
-
-    cmdclass = {
-      'build_ext': build_ext
-    },
-
-    ...
-  )
-
-
-
-Again, we use the overloaded library class
-``bob.blitz.extension.Library`` instead of the
-:py:class:`bob.extension.Library`, but the parameters are identical, and
-identical to the ones of the :py:class:`bob.extension.Extension`.  To avoid
-later complications, you should follow the guidelines for libraries in bob
-packages:
-
-1. The name of the C++ library need to be identical to the name of your package (replacing the '.' by '_').
-   Also, the package name need to be part of it.
-   For example, to create a library for the ``bob.example.library`` package, it should be called ``bob.example.library.bob_example_library``.
-   In this way it is assured that the libraries are found by the ``bob_packages`` parameter (see above).
-
-2. All header files that your C++ library should export need to be placed in the directory ``bob/example/library/include/bob.example.library``.
-   Again, this is the default directory, where the ``bob_packages`` expect the includes to be.
-   This is also the directory that is added to your own library and to your extensions, so you don't need to specify that by hand.
-
-3. The include directory should contain a ``config.h`` file, which contains C/C++ preprocessor directives that contains the current version of your C/C++ API.
-   With this, we make sure that the version of the library that is linked into other packages is the expected one.
-   One such file is again given in our ``bob.example.library`` example.
-
-4. To avoid conflicts with other functions, you should put all your exported C++ functions into an appropriate namespace.
-   In our example, this should be something like ``bob::example::library``.
-
-The newly generated Library will be automatically linked to **all other** Extensions in the package.
-No worries, if the library is not used in the extension, the linker should be able to figure that out...
-
-.. note:
-  The clang linker seems not to be smart enough to detect unused libraries...
-
-.. note::
-   You can also export a library without bindings, for it to be used in other C++/Python packages.
-
-
----------------------
-Building your package
----------------------
-
-As shown above, to compile your C++ Python bindings and the pure C++ libraries, you can follow the simple instructions:
-
-.. code-block:: sh
-
-  $ buildout
-  ...
-
-This will automatically check out all required ``bob_packages`` and compile them locally.
-Afterwards, the C++ code from this package will be compiled, using a newly created ``build`` directory for temporary output.
-After compilation, this directory can be safely removed (re-compiling will re-create it).
-
-To get the source code compiled using another build directory, you can define a ``BOB_BUILD_DIRECTORY`` environment variable, e.g.:
-
-.. code-block:: sh
-
-  $ BOB_BUILD_DIRECTORY=/tmp/build_bob buildout
-  ...
-
-The C++ code of this package, **and the code of all other** ``bob_packages`` will be compiled using the selected directory.
-Again, after compilation this directory can be safely removed.
-
-Another environment variable enables parallel compilation of C or C++ code.
-Use ``BOB_BUILD_PARALLEL=X`` (where ``X`` is the number of parallel processes you want) to enable parallel building.
-
-.. note::
-   For macOS-based builds, you may need to setup additional environment
-   variables **before** successfully building libraries.  Refer to the section
-   :ref:`extension-c++` for details.
diff --git a/doc/cplusplus_modules.rst b/doc/cplusplus_modules.rst
deleted file mode 100644
index d595364638d4f8a582a9e60b4c3c1a9fc6ef97fa..0000000000000000000000000000000000000000
--- a/doc/cplusplus_modules.rst
+++ /dev/null
@@ -1,276 +0,0 @@
-.. vim: set fileencoding=utf-8 :
-.. Manuel Guenther <manuel.guenther@idiap.ch>
-.. Mon Oct 13 16:57:44 CEST 2014
-
-.. _extension-c++:
-
-==============================
- C/C++ modules in your package
-==============================
-
-|project| massively relies on a mixture between the user-friendly and easy-to-develop Python interface, and a fast implementation of identified bottlenecks using C++.
-
-Creating C++/Python bindings should be rather straightforward.
-Only few adaptations need to be performed to get the C/C++ code being compiled and added as an extension.
-For simplicity, we created an example package that includes a simple example of a C++ extension.
-You can check it out by:
-
-.. code-block:: sh
-
-  $ git clone https://gitlab.idiap.ch/bob/bob.extension.git
-  $ cp -R bob.extension/bob/extension/data/bob.example.extension ./
-  $ rm -rf bob.extension # optionally remove the cloned source of bob.extension
-  $ cd bob.example.extension
-
-
-Setting up your package
------------------------
-
-Typically, Python extensions written in C/C++ for Bob should use a set of standard APIs allowing C++ Blitz++ Arrays to be transparently converted to Python NumPy Arrays.
-The build of your package will therefore depend on, at least, two packages: (1) bob.extension (this package): will provide build instructions and resources for defining and building your extension (2) bob.blitz: will provide a bridge between pure C++ code, depending on Blitz++ Arrays and NumPy arrays.
-To be able to import ``bob.extension`` and ``bob.blitz`` in the setup.py, we need to include some code:
-
-.. code-block:: python
-
-  setup_packages = ['bob.extension', 'bob.blitz']
-
-  # C++ modules needed at runtime of your package
-  bob_packages = []
-
-  from setuptools import setup, find_packages, dist
-  dist.Distribution(dict(setup_requires = setup_packages + bob_packages))
-
-We keep the ``setup_packages`` and ``bob_packages`` in separate variables since we will need them later.
-The ``bob_packages`` contain a list of bob packages that this extension **directly** depends on.
-In our example, we only depend on ``bob.blitz``, and we can leave the list empty.
-
-.. warning::
-
-   ``bob.blitz`` is required in all C++/Python packages since it contains all the mechanisms
-   to deal with arrays amongst other things.
-
-As the second step, we need to add some lines in the header of the file to tell the ``setuptools`` system to compile our library with our ``Extension`` class:
-
-.. code-block:: python
-
-  # import the Extension class and the build_ext function from bob.blitz
-  from bob.blitz.extension import Extension, build_ext
-
-  # load the requirements.txt for additional requirements
-  from bob.extension.utils import load_requirements
-  build_requires = setup_packages + bob_packages + load_requirements()
-
-In fact, we don't use the extension from :py:class:`bob.extension.Extension`, but the one from ``bob.blitz.extension``, which is a derivation of this package.
-The difference is that in ``bob.blitz.extension.Extension`` all header files and libraries for the ``Blitz++`` library are added.
-
-Third, we have to add an extension using the ``Extension`` class, by listing all C/C++ files that should be compiled into the extension:
-
-.. code-block:: python
-
-  # read version from version.txt file
-  version = open("version.txt").read().rstrip()
-
-  setup(
-    ...
-    setup_requires = build_requires,
-    install_requires = build_requires,
-    ...
-    ext_modules = [
-      Extension("bob.example.extension._module",
-        [
-          # the pure C++ code
-          "bob/example/extension/Function.cpp",
-          # the Python bindings
-          "bob/example/extension/main.cpp",
-        ],
-        version = version,
-        bob_packages = bob_packages
-      ),
-      ... #add more extensions if you wish
-    ],
-    ...
-  )
-
-These modifications will allow you to compile extensions that are linked against our core Python-C++ bridge ``bob.blitz`` (by default).
-You can specify any other ``pkg-config`` module and that will be linked in (for example, ``boost`` or ``opencv``) using the ``packages`` parameter.
-For ``boost`` packages, you might need to define, which boost modules are required.
-By default, when using boost you should at least add the ``system`` module, i.e., by:
-
-.. code-block:: python
-
-  setup(
-    ...
-    ext_modules = [
-      Extension(
-        ...
-        packages = ['boost'],
-        boost_modules = ['system'],
-      ),
-      ...
-    ],
-    ...
-  )
-
-Other modules and options can be set manually using `the standard options for Python extensions <https://docs.python.org/2/extending/building.html>`_.
-
-When your module compiles and links against the pure C++ code, you can simply use the ``bob_packages`` to specify dependencies in your C++ code.
-This will automatically add the desired include and library directories, as well as the libraries and the required preprocessor options.
-
-In our example, we have defined a small C++ function, which also shows the basic bridge between ``numpy.ndarray`` and our C++ pendant ``Blitz++``.
-Basically, there are two C++ files for our extension.
-``bob/example/extension/Function.cpp`` contains the pure C++ implementation of the function.
-In ``bob/example/extension/main.cpp``, we define the Python bindings to that function.
-Finally, the function ``reverse`` from the module ``_library`` is imported into our module in the ``bob/example/extension/__init__.py`` file.
-
-..
-  including the creation of a complete Python module called ``_library``.
-  Additionally, we give a short example of how to use our documentation classes provided in this module (see below for more details).
-
-.. note::
-   In the bindings of the ``reverse`` function in ``bob/example/extension/main.cpp``, we make use of some C++ defines that makes the life easier.
-   see :ref:`helpers`
-
-
-Building your package
----------------------
-
-To compile your C++ Python bindings and the corresponding  C++ implementation,
-just do:
-
-.. code-block:: sh
-
-  $ buildout
-  ...
-
-.. note::
-   By default, we compile the source code (of this and **all dependent packages**, both the ones installed as ``eggs``, and the ones developed using ``mr.developer``) in debug mode.
-   If you want to change that, switch the according flag in the ``buildout.cfg`` to ``debug = False``, and the compilation will be done with optimization flags and C++ exception handling enabled.
-
-.. note::
-   For macOS-based builds, one also needs to ensure the environment variables
-   ``MACOSX_DEPLOYMENT_TARGET``, ``SDKROOT``, and ``CONDA_BUILD_SYSROOT`` are
-   properly set.  This is automatically handled for conda-build based runs.  If
-   you are using buildout or any other setuptools-based system (such as pip
-   installs) to build your package, you should ensure that is the case with one
-   of these 2 methods (more to least recommended):
-
-   1. You set the RC variables (see: :ref:`bob.extension.rc`)
-      `bob.extension.macosx_deployment_target` and
-      `bob.extension.macosx_sdkroot` to suitable values.  Example:
-
-      .. code-block:: sh
-
-         $ bob config get bob.extension.macosx_deployment_target
-         Error: The requested key `bob.extension.macosx_deployment_target` does not exist
-         $ bob config set bob.extension.macosx_deployment_target "10.9"
-
-         $ bob config get bob.extension.macosx_sdkroot
-         Error: The requested key `bob.extension.macosx_sdkroot` does not exist
-         $ bob config set bob.extension.macosx_sdkroot "/opt/MacOSX10.9.sdk"
-
-      With this method you set the default for your particular machine.  It is
-      the recommended way to set up such variables as those settings do not
-      affect builds in other machines and are preserved across package builds,
-      guaranteeing uniformity.
-
-      Unfortunately, the variable `CONDA_BUILD_SYSROOT` must be set on the
-      environment (conda will preset it otherwise).  Change your login profile
-      shell or similar to add the following:
-
-      .. code-block:: sh
-
-         $ export CONDA_BUILD_SYSROOT="/opt/MacOSX10.9.sdk"
-
-   2. You set the environment variables directly on the current environment.
-      Example:
-
-      .. code-block:: sh
-
-         $ export MACOSX_DEPLOYMENT_TARGET="10.9"
-         $ export SDKROOT="/opt/MacOSX10.9.sdk"
-         $ export CONDA_BUILD_SYSROOT="${SDKROOT}"
-
-      Note that this technique is the least ephemeral from all available
-      options.  As soon as you leave the current environment, the variables
-      will not be available anymore.
-
-   **Precedence**: Values set on the environment have precedence over values
-   set on your Bob RC configuration.
-
-   **Compatibility**: We recommend you check our stock
-   `conda_build_config.yaml` for ensuring cross-package compatibility
-   (currently available through our admin package "bob.devtools").  At the time
-   of writing, we use a "10.9" macOS SDK for Bob packages.  That may change in
-   the future.
-
-   **Obtaining an SDK**: We recommend `Phracker macOS SDKs available on Github
-   <https://github.com/phracker/MacOSX-SDKs>`_.  Install the SDK on
-   ``/opt/MacOSX<version>.sdk``.
-
-
-Now, we can use the script ``./bin/bob_example_extension_reverse.py`` (that we have registered in the ``setup.py``) to reverse a list of floats, using the C++ implementation of the ``reverse`` function:
-
-.. code-block:: sh
-
-  $ ./bin/bob_example_extension_reverse.py 1 2 3 4 5
-  [1.0, 2.0, 3.0, 4.0, 5.0] reversed is [ 5.  4.  3.  2.  1.]
-
-We can also see that the function documentation has made it into the module, too:
-
-.. code-block:: sh
-
-  $ ./bin/python
-  >>> import bob.example.extension
-  >>> help(bob.example.extension)
-
-and that we can list version and the dependencies of our package:
-
-.. code-block:: sh
-
-  >>> print (bob.example.extension.version)
-  0.0.1a0
-  >>> print (bob.example.extension.get_config())
-  ...
-
-
-.. _helpers:
-
-Helper utilities
-----------------
-
-In the header file ``<bob.extension/defines.h>`` we have added some functions that help you to keep your code short and clean.
-Particularly, we provide three preprocessor directives:
-
-.. c:macro:: BOB_TRY
-
-   Starts a try-catch block to protect your bound function against exceptions of any kinds (which would lead to a Python interpreter crash otherwise).
-
-.. c:macro:: BOB_CATCH_FUNCTION(message, ret)
-
-   Catches C++ exceptions of any kind, adds the ``message`` in case an unknown exception is caught, and returns with the given error return (which is usually 0 for normal functions or -1 for constructors and setter functions).
-   This macro should be used when binding a stand-alone function, for binding class member functions, please use :c:macro:`BOB_CATCH_MEMBER`.
-
-.. c:macro:: BOB_CATCH_MEMBER(message, ret)
-
-   Catches C++ exceptions of any kind, adds the ``message`` in case an unknown exception is caught, and returns with the given error return (which is usually 0 for normal functions or -1 for constructors and setter functions).
-   This macro should be used when binding a member function of a class, for binding stand-alone functions, please use :c:macro:`BOB_CATCH_FUNCTION`.
-
-These preprocessor directives will catch any C++ exception that is raised inside the C/C++ code that you bind to python and translate them into proper Python exceptions.
-
-.. warning::
-   These directives will only be active in **release** mode, when compiling
-   with ``debug = true``, they will not do anything.  This is in order to
-   support C++ debuggers like ``gdb`` or ``gdb-python`` to be able to handle
-   these exceptions.
-
-Additionally, we added some preprocessor directives that help in the bindings:
-
-.. c:macro:: PyBob_NumberCheck(o)
-
-   Checks if the given object ``o`` is a number, i.e., an int, a long, a float
-   or a complex.
-
-After including the above mentioned header, we also re-define the functions
-:c:func:`PyInt_Check`, :c:func:`PyInt_AS_LONG`, :c:func:`PyString_Check` and
-:c:func:`PyString_AS_STRING` (which doesn't exist in the bindings for Python3)
-so that they can be used in bindings for both Python2 and Python3.
diff --git a/doc/cpp_api.rst b/doc/cpp_api.rst
deleted file mode 100644
index 3e97c773823bc3379ab71fae80e147d5eeace302..0000000000000000000000000000000000000000
--- a/doc/cpp_api.rst
+++ /dev/null
@@ -1,270 +0,0 @@
-.. vim: set fileencoding=utf-8 :
-.. Manuel Guenther <manuel.guenther@idiap.ch>
-.. Fri Oct 10 14:03:53 CEST 2014
-..
-.. Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
-
-
-.. _cpp_api:
-
-
-=====================================
- C++ API of the Documentation classes
-=====================================
-
-This section includes information for using the pure C++ API for the documentation classes, which can be accessed after including:
-
-.. code-block:: c++
-
-   # include <bob.extension/documentation.h>
-
-The classes, which are described in more detail below, can be used to format the documentation of your C/C++ functions that are bound to Python.
-Any free text that you specify to describe your functions will be interpreted as `reStructuredText <http://docutils.sourceforge.net/rst.html>`_.
-Hence, it is possible to use any directives like ``.. note::``, ``.. math::``, and even links inside the documentation like ``:py:class:`` and references as ``[REF]_``.
-
-
-----------------------
-Function Documentation
-----------------------
-
-.. cpp:class:: bob::extension::FunctionDoc
-
-   To document a function (either a stand-alone function or a member function of a class), you should use the :cpp:func:`bob::extension::FunctionDoc`.
-
-   .. cpp:function:: bob::extension::FunctionDoc(\
-        const char* const function_name,\
-        const char* const short_desctiption,\
-        const char* const long_description = NULL,\
-        bool is_member_function = false\
-      )
-
-      In the constructor, you specify the function name and a short description.
-      If wanted, you can define a longer description as well.
-      When you use this FunctionDoc to document a member function of a class, please set ``is_member_function = true``.
-
-   .. cpp:function:: FunctionDoc clone(\
-        const char* const function_name\
-      )
-
-      Returns a copy of this documentation class, where the function name is replaced by the given one.
-      This is useful, when a function is bound with several names.
-
-   .. cpp:function:: FunctionDoc& add_prototype(\
-        const char* const variables,\
-        const char* const return_value = "None"\
-      )
-
-      Adds a prototype of the documented function declaration to the function.
-      All ``variables`` and all ``return_value``'s listed must be documented using the :cpp:func:`add_parameter` or :cpp:func:`add_return` functions.
-      Only the default return value ``None`` does not need documentation.
-
-      ``variables`` is a single string containing a comma-separated list of parameter names.
-      Use ``..., [name]`` to indicate that name is ``name`` is an optional parameter.
-
-      ``return_value`` is a single string containing a comma-separated list of return value names.
-      If a single name is given, only a single value is returned, otherwise a tuple will be returned by your function.
-
-      .. note::
-         Each :cpp:class:`FunctionDoc` needs at least one prototype.
-         In opposition to pure Python functions, specifying multiple prototypes is allowed here as well.
-
-
-   .. cpp:function:: FunctionDoc& add_parameter(\
-        const char* const parameter_name,\
-        const char* const parameter_type,\
-        const char* const parameter_description\
-      )
-
-      Adds a description for a given parameter.
-
-      ``parameter_name`` must be one of the names listed in the ``variables`` of the :cpp:func:`add_prototype` function.
-
-      ``parameter_type`` specifies the expected type of this parameter.
-      You can use any free text to describe the type.
-      When ``:py:class:`` directives or similar are used, they will be interpreted correctly.
-
-      ``parameter_description`` includes free text to describe, what the parameter is used for.
-
-
-   .. cpp:function:: FunctionDoc& add_return(\
-        const char* const return_name,\
-        const char* const return_type,\
-        const char* const return_description\
-      )
-
-      Adds a description for a given return value.
-
-      ``return_name`` must be one of the names listed as a ``return_value`` of the :cpp:func:`add_prototype` function.
-
-      ``return_type`` specifies the type of this return value.
-      You can use any free text to describe the type.
-      When ``:py:class:`` directives or similar are used, they will be interpreted correctly.
-
-      ``return_description`` includes free text to describe, what the return value contains.
-
-
-   .. cpp:function:: const char* const name() const
-
-      Returns the name of the function defined in the constructor.
-
-
-   .. cpp:function:: const char* const doc(const unsigned alignment = 72, const unsigned indent = 0) const
-
-      Generates and returns the documentation string.
-      The free text in the documentation is aligned to ``alignment`` characters, by default 72, so that it can be viewed correctly inside of an 80-character Python console.
-      The ``indent`` is an internal parameter and should not be changed.
-
-
-   .. cpp:function:: char** kwlist(unsigned index) const
-
-      Returns the list of keyword arguments for the given prototype index added with the :cpp:func:`add_prototype` function.
-      This list is in the desired format to be passed as the ``keywords`` parameter to the :c:func:`PyArg_ParseTupleAndKeywords` function during your bindings.
-
-
-   .. cpp:function:: void print_usage() const
-
-      Prints a function usage string to console, including all information specified by the member functions above.
-
-
-All functions adding information to the :cpp:class:`bob::extension::FunctionDoc` return a reference to the current object, so that you can use it inline, like:
-
-.. code-block:: c++
-
-   auto function_doc = bob::extension::FunctionDoc(
-     "function_name",
-     "Short description of the function",
-     "Long description of the function using reStructuredText including directives like :py:class:`bob.blitz.array`."
-   )
-   .add_prototype("param1, [param2]", "ret")
-   .add_parameter("param1", "int", "An int value used for ...")
-   .add_parameter("param2", "float", "[Default: ``0.5``] A float value describing ...")
-   .add_return("ret", ":py:class:`bob.blitz.array`", "An array ...")
-   ;
-
-
-During the binding of your function, you can use it, like:
-
-.. code-block:: c++
-
-   static PyMethodDef module_methods[] = {
-     ...
-     {
-       function_doc.name(),
-       (PyCFunction)function,
-       METH_VARARGS|METH_KEYWORDS,
-       function_doc.doc()
-     },
-     ...
-   };
-
-
------------------------
-Variables Documentation
------------------------
-
-.. cpp:class:: bob::extension::VariableDoc
-
-   To document a variable (either a stand-alone function or a member function
-   of a class), you should use the :cpp:func:`bob::extension::VariableDoc`.
-
-   .. cpp:function:: bob::extension::VariableDoc(\
-        const char* const variable_name,\
-        const char* const variable_type,\
-        const char* const short_desctiption,\
-        const char* const long_description = NULL\
-      )
-
-      In the constructor, you specify the variable name, its type and a short
-      description. The structure is identical to the
-      :cpp:func:`FunctionDoc::add_parameter` function. If wanted, you can
-      define a longer description as well.
-
-
-   .. cpp:function:: char* name() const
-
-      Returns the name of the variable defined in the constructor.
-
-
-   .. cpp:function:: char* doc(const unsigned alignment = 72) const
-
-      Generates and returns the documentation string, which is composed of the
-      information provided in the constructor. The free text in the
-      documentation is aligned to ``alignment`` characters, by default 72, so
-      that it can be viewed correctly inside of an 80-character Python console.
-
-
--------------------
-Class Documentation
--------------------
-
-.. cpp:class:: bob::extension::ClassDoc
-
-   To document a class including its constructor, you should use the :cpp:func:`bob::extension::ClassDoc`.
-
-   .. cpp:function:: bob::extension::ClassDoc(\
-        const char* const class_name,\
-        const char* const short_desctiption,\
-        const char* const long_description = NULL\
-      )
-
-      In the constructor, you specify the class name and a short description.
-      If wanted, you can define a longer description as well.
-
-
-   .. cpp:function:: ClassDoc& add_constructor(\
-        const FunctionDoc& constructor_doc\
-      )
-
-      Adds the documentation of the constructor, which itself is a :cpp:class:`FunctionDoc`.
-
-      .. note::
-         You should specify the return value of your constructor to be ``""`` to overwrite the default value ``"None"``.
-
-      .. note::
-         A class can have only a single constructor documentation.
-         Hence, this function can be called only once for each class.
-
-
-   .. cpp:function:: char* name() const
-
-      Returns the name of the class defined in the constructor.
-
-
-   .. cpp:function:: char* doc(const unsigned alignment = 72) const
-
-      Generates and returns the documentation string, which is composed of the information provided in the constructor, and the constructor documentation.
-      The free text in the documentation is aligned to ``alignment`` characters, by default 72, so that it can be viewed correctly inside of an 80-character Python console.
-
-
-   .. cpp:function:: char** kwlist(unsigned index) const
-
-      Returns the list of keyword arguments of the constructor for the given prototype index added with the :cpp:func:`FunctionDoc::add_prototype` function.
-      This list is in the desired format to be passed as the ``keywords`` parameter to the :c:func:`PyArg_ParseTupleAndKeywords` function during your bindings.
-
-
-   .. cpp:function:: void print_usage() const
-
-      Prints the usage of the constructor.
-      See :cpp:func:`FunctionDoc::print_usage` for details.
-
-
-As for functions, the :cpp:class:`bob::extension::ClassDoc` is designed to be used inline, like:
-
-.. code-block:: c++
-
-   auto class_doc = bob::extension::ClassDoc(
-     "class_name",
-     "Short description of the class",
-     "Long description of the class using reStructuredText including directives like :py:class:`bob.blitz.array`."
-   )
-   .add_constructor(
-      bob::extension::FunctionDoc(
-        "class_name",
-        "Short description of the constructor",
-        "Long description of the constructor"
-        true
-      )
-     .add_prototype("param1, [param2]", "")
-     .add_parameter("param1", "int", "An int value used for ...")
-     .add_parameter("param2", "float", "[Default: ``0.5``] A float value describing ...")
-   );
diff --git a/doc/development.rst b/doc/development.rst
index 2bd96d2a7a6b5ef17a9f05ba32ea7fb9306eaba1..732e9c11f1ebba2510623fcbcc3404d663a3d7f7 100644
--- a/doc/development.rst
+++ b/doc/development.rst
@@ -4,7 +4,9 @@
 Developing existing |project| packages
 =======================================
 
-The sources of Bob_ packages are hosted on Idiap's gitlab_ and they
-are managed by git_. To develop existing |project| packages their source should be checked out and a proper local environment should be set up. The details for developing packages are in `bob development tools`_.
+The sources of Bob_ packages are hosted on Idiap's gitlab_ and they are managed
+by git_. To develop existing |project| packages their source should be checked
+out and a proper local environment should be set up. The details for developing
+packages are in `bob development tools`_.
 
 .. include:: links.rst
diff --git a/doc/documenting.rst b/doc/documenting.rst
index e7a9ad5975ac34dc8917e29d4d0450d84d2a60a7..63b9b22956d8f0dae58464c3ecfbbcad6fb782f2 100644
--- a/doc/documenting.rst
+++ b/doc/documenting.rst
@@ -6,11 +6,11 @@
 Documenting your package
 ========================
 
-If you intend to distribute your newly created package, please consider carefully documenting it. 
+If you intend to distribute your newly created package, please consider carefully documenting it.
 Documentation is an essential starting point for new users.
 Undocumented code tends to be barely re-used and may end up being abandoned.
 
-First you should have a proper README file (such as the one provided in packages previousy provided as examples). 
+First you should have a proper README file (such as the one provided in packages previousy provided as examples).
 We made a simple, minimal ``README.rst`` template that you can get by doing:
 
 .. code-block:: sh
@@ -32,7 +32,7 @@ Replace the following tags by hand if you don't like/trust the `sed` lines above
      for extending Bob_ by building packages using either pure python or a mix of
      C++ and python.`
 
-Additional information should be made available in the documentation. 
+Additional information should be made available in the documentation.
 Ideally, you should write a user's guide for your package. There are plenty of examples in the existing bob packages
 - and don't hesitate to tell us (either by opening an issue on gitlab or through our mailing list) if some are missing or outdated.
 
@@ -43,18 +43,18 @@ Documenting Python code
 To write documentation, use the `Sphinx`_ Documentation Generator.
 Get familiar with Sphinx and then unleash the writer in you.
 
-To automatically generate API documentation, we make use of the `Napoleon`_ Sphinx extension 
+To automatically generate API documentation, we make use of the `Napoleon`_ Sphinx extension
 that enables Sphinx to parse both NumPy and Google style docstrings. It has been agreed
-that the style used to document bob packages is the NumPy style. To get familiar on how to document your Python code, 
+that the style used to document bob packages is the NumPy style. To get familiar on how to document your Python code,
 you can have a look on the `Napoleon`_ website (and the links within) or in existing bob packages. You can also
 refer to the official `numpydoc docstring guide`_.
 
 .. note::
 
-   You should start by doing the following: 
+   You should start by doing the following:
 
    .. code-block:: sh
-   
+
       $ curl -k --silent https://gitlab.idiap.ch/bob/bob.admin/raw/master/templates/sphinx-conf.py > doc/conf.py
       $ mkdir -pv doc/img
       $ curl -k --silent https://gitlab.idiap.ch/bob/bob.admin/raw/master/templates/logo.png > doc/img/logo.png
@@ -69,9 +69,9 @@ refer to the official `numpydoc docstring guide`_.
     + ``extra-intersphinx.txt``, which lists extra packages that should be cross-linked to the documentation (as with Sphinx's intersphinx extension.
       The format of this text file is simple: it contains the PyPI names of packages to cross-reference. One per line.
     + ``nitpick-exceptions.txt``, which lists which documentation objects to ignore (for warnings and errors).
-      The format of this text file is two-column. On the first column, you should refer to Sphinx the object type, e.g. ``py:class``, 
-      followed by a space and then the name of the that should be ignored. E.g.: ``bob.bio.base.Database``. 
-      The file may optionally contain empty lines. Lines starting with # are ignored (so you can comment on why you're ignoring these objects). 
+      The format of this text file is two-column. On the first column, you should refer to Sphinx the object type, e.g. ``py:class``,
+      followed by a space and then the name of the that should be ignored. E.g.: ``bob.bio.base.Database``.
+      The file may optionally contain empty lines. Lines starting with # are ignored (so you can comment on why you're ignoring these objects).
       Ignoring errors should be used only as a last resource. You should first try to fix the errors as best as you can, so your documentation links are properly working.
 
 
@@ -101,192 +101,4 @@ You can now admire the result in your favorite browser:
 
   If the code you are distributing corresponds to the work described in a publication, don't forget to mention it in your ``doc/index.rst`` file.
 
-
-Documenting your C/C++ Python Extension
----------------------------------------
-
-One part of the ``bob.extension`` package consist in some functions that makes it easy to generate a proper Python documentation for your bound C/C++ functions.
-For the API documentation of the package, please read :ref:`cpp_api`.
-
-One example for a function documentation can be found in the file ``bob/example/library/main.cpp``, which you have downloaded before.
-This documentation can be used after:
-
-.. code-block:: c++
-
-   #include <bob.extension/documentation.h>
-
-
-Function documentation
-++++++++++++++++++++++
-
-To generate a properly aligned function documentation, you can use the :cpp:class:`bob::extension::FunctionDoc`:
-
-.. code-block:: c++
-
-   bob::extension::FunctionDoc description(
-     "function_name",
-     "Short function description",
-     "Optional long function description"
-   );
-
-
-.. note::
-
-   If you want to document a member function of a class, you should use set fourth boolean option to true.
-   This is required since the default Python class member documentation is indented four more spaces, which we need to balance:
-
-   .. code-block:: c++
-
-      bob::extension::FunctionDoc member_function_description(
-        "function_name",
-        "Short function description",
-        "Optional long function description",
-        true
-      );
-
-Using this object, you can add several parts of the function that need documentation:
-
-1. ``description.add_prototype("variable1, variable2", "return1, return2");`` can be used to add function definitions (i.e., ways how to use your function).
-   This function needs to be called at least once.
-   If the function does not define a return value, it can be left out (in which case the default ``"None"`` is used).
-
-2. ``description.add_parameter("variable1, variable2", "datatype", "Variable description");`` should be defined for each variable that you have used in the prototypes.
-
-3. ``description.add_return("return1", "datatype", "Return value description");`` should be defined for each return value that you have used in the prototypes.
-
-.. note::
-
-   All these functions return a reference to the object, so that you can use them in line, e.g.:
-
-   .. code-block:: c++
-
-      static auto description = bob::extension::FunctionDoc(...)
-        .add_prototype(...)
-        .add_parameter(...)
-        .add_return(...)
-      ;
-
-A complete working exemplary function documentation from the ``reverse`` function in ``bob.example.library`` package would look like this:
-
-.. code-block:: c++
-
-   static bob::extension::FunctionDoc reverse_doc = bob::extension::FunctionDoc(
-     "reverse",
-     "This is a simple example of bridging between blitz arrays (C++) and numpy.ndarrays (Python)",
-     "Detailed documentation of the function goes here."
-   )
-   .add_prototype("array", "reversed")
-   .add_parameter("array", "array_like (1D, float)", "The array to reverse")
-   .add_return("reversed", "array_like (1D, float)", "A copy of the ``array`` with reversed order of entries")
-   ;
-
-Finally, when binding you function, you can use:
-
-a. ``description.name()`` to get the name of the function
-
-b. ``description.doc()`` to get the aligned documentation of the function, properly indented and broken at 80 characters (by default).
-   This call will check that all parameters and return values are documented, and add a ``.. todo::`` directive if not.
-
-c. ``description.kwlist(index)`` to get the list of keyword arguments for the given prototype ``index`` that can be passed as the ``keywords`` parameter to the :c:func:`PyArg_ParseTupleAndKeywords` function.
-
-which can be used during the binding of the function.
-In our example, it would look like:
-
-.. code-block:: c++
-
-   PyMethodDef methods[] = {
-    ...
-     {
-       reverse_doc.name(),
-       (PyCFunction)PyBobExampleLibrary_Reverse,
-       METH_VARARGS|METH_KEYWORDS,
-       reverse_doc.doc()
-     },
-     ...
-     {NULL}  // Sentinel
-   };
-
-
-Sphinx directives like ``.. note::``, ``.. warning::`` or ``.. math::`` will be automatically detected and aligned, when they are used as one-line directive, e.g.:
-
-.. code-block:: c++
-
-   "(more text)\n\n.. note:: This is a note\n\n(more text)"
-
-Also, enumerations and listings (using the ``*`` character to define a list element) are handled automatically:
-
-.. code-block:: c++
-
-   "(more text)\n\n* Point 1\n* Point 2\n\n(more text)"
-
-.. note::
-
-   Please assure that directives are surrounded by double ``\n`` characters (see example above) so that they are put as paragraphs.
-   Otherwise, they will not be displayed correctly.
-
-.. note::
-
-   The ``.. todo::`` directive seems not to like being broken at 80 characters.
-   If you want to use ``.. todo::``, please call, e.g., ``description.doc(10000)`` to avoid line breaking.
-
-.. note::
-
-   To increase readability, you might want to split your documentation lines, e.g.:
-
-   .. code-block:: c++
-
-      "(more text)\n"
-      "\n"
-      "* Point 1\n"
-      "* Point 2\n"
-      "\n"
-      "(more text)"
-
-Leading white-spaces in the documentation string are handled correctly, so you can use several layers of indentation.
-
-
-**Class documentation**
-+++++++++++++++++++++++
-
-To document a bound class, you can use the :cpp:class:`bob::extension::ClassDoc` to align and wrap your documentation.
-Again, during binding you can use the functions ``description.name()`` and ``description.doc()`` as above.
-
-Additionally, the class documentation has a function to add constructor definitions, which takes an :cpp:class:`bob::extension::FunctionDoc` object.
-The shortest way to get a proper class documentation is:
-
-.. code-block:: c++
-
-   auto my_class_doc =
-     bob::extension::ClassDoc("class_name", "Short description", "Long Description")
-       .add_constructor(
-         bob::extension::FunctionDoc("class_name", "Constructor Description")
-          .add_prototype("param1", "")
-          .add_parameter("param1", "type1", "Description of param1")
-       )
-   ;
-
-.. note::
-
-   The second parameter ``""`` in ``add_prototype`` prevents the output type (which otherwise defaults to ``"None"``) to be written.
-
-.. note::
-
-   For constructor documentations, there is no need to declare them as member functions.
-   This is done automatically for you.
-
-.. note::
-   You can use the :cpp:func:`bob::extension::ClassDoc::kwlist` function to retrieve the ``kwlist`` of the constructor documentation.
-
-Currently, the :cpp:class:`bob::extension::ClassDoc` allows to highlight member functions or variables at the beginning of the class documentation.
-This highlighting is still under development and might not work as expected.
-
-
-Possible speed issues
-=====================
-
-In order to speed up the loading time of the modules, you might want to reduce the amount of documentation that is generated (though I haven't experienced any speed differences).
-For this purpose, just compile your bindings using the ``"-DBOB_SHORT_DOCSTRINGS"`` compiler option, e.g. by simply define an environment variable ``BOB_SHORT_DOCSTRINGS=1`` before invoking ``buildout``.
-
-In any of these cases, only the short descriptions will be returned as the doc string.
-
 .. include:: links.rst
diff --git a/doc/index.rst b/doc/index.rst
index caafe8cca9bb25a467ed451208673ab93f74f35f..e8b8ca8be45a4f59f0c08c635debdba86752e4f4 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -9,21 +9,14 @@
 .. todolist::
 
 This module contains information on how to build, maintain, and distribute Bob_
-packages written in pure Python or a mix of C/C++ and Python.
+packages written in Python.
 
 By following these instructions you will be able to:
 
 * Setup a local development environment.
 * Download and install |project| packages and other software into your
   development environment.
-* Implement your own package including either pure Python code, a mixture of
-  C/C++ and Python code, and even pure C/C++ libraries with clean C/C++
-  interfaces.
-
-  .. note::
-     If possible, you should try to develop new packages using Python only,
-     since they are easier to maintain.
-
+* Implement your own package.
 * Distribute your work to others in a clean and organized manner.
 
 
@@ -35,13 +28,10 @@ Documentation
 
    development
    pure_python
-   cplusplus_modules
-   cplusplus_library
    documenting
    rc
    framework
    py_api
-   cpp_api
 
 Indices and tables
 ------------------
diff --git a/doc/links.rst b/doc/links.rst
index f638bfd3ba60a5b35d91030727da1fc1f777c4ae..9e90b56c2666e981c06e365fe102968b3cd6fddf 100644
--- a/doc/links.rst
+++ b/doc/links.rst
@@ -15,7 +15,6 @@
 .. _gitlab: https://gitlab.idiap.ch/bob/
 .. _idiap: http://www.idiap.ch
 .. _ipython: http://ipython.scipy.org
-.. _nose: https://nose.readthedocs.org/en/latest/
 .. _pep 386: http://www.python.org/dev/peps/pep-0386/
 .. _python: http://www.python.org
 .. _pypi: http://pypi.python.org
diff --git a/doc/py_api.rst b/doc/py_api.rst
index d335423a07a7d3a3f7d47bba797886c886c7dbd4..3b1c4b5ffbead7ffdb498600543d744acd792e03 100644
--- a/doc/py_api.rst
+++ b/doc/py_api.rst
@@ -15,28 +15,9 @@ Core Functionality
 ^^^^^^^^^^^^^^^^^^
 
 .. autosummary::
-    bob.extension.boost
-    bob.extension.build_ext
-    bob.extension.check_packages
-    bob.extension.CMakeListsGenerator
-    bob.extension.construct_search_paths
-    bob.extension.DEFAULT_PREFIXES
-    bob.extension.Extension
-    bob.extension.find_executable
-    bob.extension.find_library
-    bob.extension.generate_self_macros
-    bob.extension.get_bob_libraries
     bob.extension.get_config
-    bob.extension.get_full_libname
-    bob.extension.Library
-    bob.extension.load_bob_library
-    bob.extension.normalize_requirements
-    bob.extension.pkgconfig
     bob.extension.rc
     bob.extension.rc_context
-    bob.extension.reorganize_isystem
-    bob.extension.uniq
-    bob.extension.uniq_paths
     bob.extension.download.get_file
 
 
@@ -44,9 +25,6 @@ Utilities
 ^^^^^^^^^
 
 .. autosummary::
-    bob.extension.utils.egrep
-    bob.extension.utils.find_file
-    bob.extension.utils.find_header
     bob.extension.utils.find_packages
     bob.extension.utils.link_documentation
     bob.extension.utils.load_requirements
@@ -86,7 +64,6 @@ Utilities
 ---------
 
 .. automodule:: bob.extension.utils
-    :exclude-members: find_executable,find_library,uniq,uniq_paths,construct_search_paths
 
 .. automodule:: bob.extension.download
 
diff --git a/pyproject.toml b/pyproject.toml
index 9787c3bdf008a57ae3cb2e27a8261eb3f134ff73..b738dc847ff9705c5769673db7415f2eb9a75f4d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,12 @@
 [build-system]
-requires = ["setuptools", "wheel"]
-build-backend = "setuptools.build_meta"
+    requires = ["setuptools", "wheel", "bob.extension"]
+    build-backend = "setuptools.build_meta"
+
+[tool.isort]
+    profile = "black"
+    line_length = 80
+    order_by_type = true
+    lines_between_types = 1
+
+[tool.black]
+    line-length = 80
diff --git a/setup.py b/setup.py
index d2c8869c12fabf6e002d74f2d687b797b4602a5b..cf6b4c8d828d3a766ba6f76112d74fa69b5abef6 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
 """A package that contains a helper for Bob Python/C++ extension development
 """
 
-from setuptools import setup, find_packages
+from setuptools import find_packages, setup
 
 # Define package version
 version = open("version.txt").read().rstrip()
@@ -12,48 +12,45 @@ setup(
     name="bob.extension",
     version=version,
     description="Building of Python/C++ extensions for Bob",
-    url='http://gitlab.idiap.ch/bob/bob.extension',
+    url="http://gitlab.idiap.ch/bob/bob.extension",
     license="BSD",
-    author='Andre Anjos',
-    author_email='andre.anjos@idiap.ch',
-    long_description=open('README.rst').read(),
-
+    author="Andre Anjos",
+    author_email="andre.anjos@idiap.ch",
+    long_description=open("README.rst").read(),
     packages=find_packages(),
     include_package_data=True,
     zip_safe=False,
-
-    install_requires=['setuptools', 'click >= 8', 'click-plugins'],
-
+    install_requires=["setuptools", "click >= 8", "click-plugins"],
     entry_points={
-        'console_scripts': [
-            'bob = bob.extension.scripts:main_cli',
+        "console_scripts": [
+            "bob = bob.extension.scripts:main_cli",
         ],
-        'bob.cli': [
-            'config = bob.extension.scripts.config:config',
+        "bob.cli": [
+            "config = bob.extension.scripts.config:config",
         ],
         # some test entry_points
-        'bob.extension.test_config_load': [
-            'basic_config = bob.extension.data.basic_config',
-            'verbose_config = bob.extension.data.verbose_config',
-            'resource_config = bob.extension.data.resource_config',
-            'subpackage_config = bob.extension.data.subpackage.config',
-            'resource1 = bob.extension.data.resource_config2',
-            'resource2 = bob.extension.data.resource_config2:b',
+        "bob.extension.test_config_load": [
+            "basic_config = bob.extension.data.basic_config",
+            "verbose_config = bob.extension.data.verbose_config",
+            "resource_config = bob.extension.data.resource_config",
+            "subpackage_config = bob.extension.data.subpackage.config",
+            "resource1 = bob.extension.data.resource_config2",
+            "resource2 = bob.extension.data.resource_config2:b",
         ],
-        'bob.extension.test_dump_config': [
-            'basic_config = bob.extension.data.basic_config',
-            'resource_config = bob.extension.data.resource_config',
-            'subpackage_config = bob.extension.data.subpackage.config',
+        "bob.extension.test_dump_config": [
+            "basic_config = bob.extension.data.basic_config",
+            "resource_config = bob.extension.data.resource_config",
+            "subpackage_config = bob.extension.data.subpackage.config",
         ],
     },
     classifiers=[
-        'Framework :: Bob',
-        'Development Status :: 5 - Production/Stable',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: BSD License',
-        'Natural Language :: English',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Topic :: Software Development :: Libraries :: Python Modules',
+        "Framework :: Bob",
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: BSD License",
+        "Natural Language :: English",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Topic :: Software Development :: Libraries :: Python Modules",
     ],
 )