diff --git a/.gitignore b/.gitignore
index 234ed7ae298c484971dbc36646f6ef1bcba9e022..37d1fef28e90f91a21e60cf18907a75030fd75b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,8 @@ dist
 .gdb_history
 build
 *.egg
-src/
 *.sql3
 record.txt
 *.DS_Store
+.gitlab-ci-local*
+html/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 845b719f2378d396a8b75296a68d1e32c840e7c7..feea02856cf6935a78083041f6e125e391c46170 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1 +1,4 @@
-include: 'https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/data/gitlab-ci/single-package.yaml'
+include:
+  - project: bob/dev-profile
+    ref: main
+    file: /gitlab/python.yml
diff --git a/MANIFEST.in b/MANIFEST.in
index 1b422a5d6892f59d701c62183e3601418148b795..08b0470c9f6818bda1dc326fd7e537ceeb617ec3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,8 +1,8 @@
-include README.rst bootstrap-buildout.py buildout.cfg develop.cfg version.txt requirements.txt
+include README.rst
 recursive-include doc *.py *.rst
 recursive-include bob *.txt *.hdf5
 recursive-include bob *.sql3
-recursive-include bob/pad/base/test/data scores-* *.sql3 *.wav
-recursive-include bob/pad/base/test/data/example_filelist *.lst
-recursive-include bob/pad/base/test/ *.csv
-recursive-include bob/pad/base/test/ *.tar.gz
+recursive-include tests/data scores-* *.sql3 *.wav
+recursive-include tests/example_filelist *.lst
+recursive-include tests/ *.csv
+recursive-include tests/ *.tar.gz
diff --git a/README.rst b/README.rst
index a33a489b99f7979d519bb53cf0b9b1d88061a1e0..fcd5ff686a96487a7272a64c74d67c26e7cb388f 100644
--- a/README.rst
+++ b/README.rst
@@ -3,11 +3,11 @@
 .. Wed 19 Oct 22:36:22 2016 CET
 
 .. image:: https://img.shields.io/badge/docs-latest-orange.svg
-   :target: https://www.idiap.ch/software/bob/docs/bob/bob.pad.base/master/index.html
+   :target: https://www.idiap.ch/software/bob/docs/bob/bob.pad.base/master/sphinx/index.html
 .. image:: https://gitlab.idiap.ch/bob/bob.pad.base/badges/master/pipeline.svg
    :target: https://gitlab.idiap.ch/bob/bob.pad.base/commits/master
 .. image:: https://gitlab.idiap.ch/bob/bob.pad.base/badges/master/coverage.svg
-   :target: https://gitlab.idiap.ch/bob/bob.pad.base/commits/master
+   :target: https://www.idiap.ch/software/bob/docs/bob/bob.pad.base/master/coverage/
 .. image:: https://img.shields.io/badge/gitlab-project-0000c0.svg
    :target: https://gitlab.idiap.ch/bob/bob.pad.base
 
diff --git a/bob/pad/base/test/data/test_db.sql3 b/bob/pad/base/test/data/test_db.sql3
deleted file mode 100755
index 3ed135e225dd562d40e5175ac310aa92f661675f..0000000000000000000000000000000000000000
Binary files a/bob/pad/base/test/data/test_db.sql3 and /dev/null differ
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 0b85449b386e39996ad91d9187f57616f0090702..0000000000000000000000000000000000000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,14 +0,0 @@
-; vim: set fileencoding=utf-8 :
-; Tue 16 Aug 15:00:20 CEST 2016
-
-[buildout]
-parts = scripts
-develop = .
-eggs = bob.pad.base
-extensions = bob.buildout
-newest = false
-verbose = true
-
-[scripts]
-recipe = bob.buildout:scripts
-dependent-scripts = true
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 0f7d57f2954c3010fc3163d28e9b8640cf87b83e..1201d1cfff885a7a554cb8ac11eca142401d266d 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -1,22 +1,20 @@
-{% set name = 'bob.pad.base' %}
-{% set project_dir = environ.get('RECIPE_DIR') + '/..' %}
+{% set data = load_file_data(RECIPE_DIR + '/../pyproject.toml') %}
+{% set name = data['project']['name'] %}
 
 package:
   name: {{ name }}
-  version: {{ environ.get('BOB_PACKAGE_VERSION', '0.0.1') }}
+  version: {{ data['project']['version'] }}
+
+source:
+  path: ..
 
 build:
-  entry_points:
-    - spoof.py         = bob.pad.base.script.spoof:main
+  noarch: python
   number: {{ environ.get('BOB_BUILD_NUMBER', 0) }}
   run_exports:
     - {{ pin_subpackage(name) }}
   script:
-    - cd {{ project_dir }}
-    {% if environ.get('BUILD_EGG') %}
-    - "{{ PYTHON }} setup.py sdist --formats=zip"
-    {% endif %}
-    - "{{ PYTHON }} -m pip install . -vv"
+    - "{{ PYTHON }} -m pip install {{ SRC_DIR }} -vv"
 
 requirements:
   host:
@@ -40,35 +38,30 @@ requirements:
   run:
     - python
     - setuptools
+    - bob.extension
+    - bob.io.base
+    - bob.pipelines
+    - bob.measure
+    - bob.bio.base
     - {{ pin_compatible('click') }}
     - {{ pin_compatible('click-plugins') }}
     - {{ pin_compatible('dask') }}
     - {{ pin_compatible('matplotlib') }}
     - {{ pin_compatible('numpy') }}
-    - {{ pin_compatible('scikit-learn', min_pin='x.x') }}
+    - {{ pin_compatible('scikit-learn') }}
     - {{ pin_compatible('scipy') }}
     - {{ pin_compatible('sqlalchemy') }}
     - {{ pin_compatible('tabulate') }}
 
 test:
   imports:
-    - {{ name }}
+    - {{ name.replace('-','_') }}
   commands:
-    - bob pad --help
-    - 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:
-    - pytest {{ pytest }}
-    - pytest-cov {{ pytest_cov }}
-    - coverage {{ coverage }}
-    - sphinx {{ sphinx }}
-    - sphinx_rtd_theme {{ sphinx_rtd_theme }}
 
 about:
-  home: https://www.idiap.ch/software/bob/
-  license: GNU General Public License v3 (GPLv3)
-  summary: A framework for executing the chain of presentation attack detection (PAD) experiments
+  home: {{ data['project']['urls']['homepage'] }}
+  summary: {{ data['project']['description'] }}
+  license: {{ data['project']['license']['text'] }}
   license_family: GPL
diff --git a/doc/catalog.json b/doc/catalog.json
new file mode 100644
index 0000000000000000000000000000000000000000..d502c82dc61d364048fac634fef3c9e8332b15c3
--- /dev/null
+++ b/doc/catalog.json
@@ -0,0 +1,8 @@
+{
+  "bob.extension": {
+    "versions": {
+      "latest": "https://www.idiap.ch/software/bob/docs/bob/bob.extension/master"
+    },
+    "sources": {}
+  }
+}
diff --git a/doc/conf.py b/doc/conf.py
index 2c71f27125dfd482ab653f57d6163fc8e6970e38..20a84dc67d274fb159e6085dd241cacc7f4900e1 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -24,6 +24,7 @@ extensions = [
     "sphinx.ext.napoleon",
     "sphinx.ext.viewcode",
     "sphinx.ext.mathjax",
+    "auto_intersphinx",
 ]
 
 # Be picky about warnings
@@ -234,13 +235,15 @@ autodoc_default_options = {
     "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)
-    )
-else:
-    intersphinx_mapping = link_documentation()
+auto_intersphinx_packages = [
+    ("python", "3"),
+    "numpy",
+    "bob.extension",
+    "bob.pipelines",
+    "bob.bio.base",
+    "bob.pad.face",
+    "setuptools",
+    "dask",
+    "scikit-learn",
+]
+auto_intersphinx_catalog = "catalog.json"
diff --git a/doc/extra-intersphinx.txt b/doc/extra-intersphinx.txt
index 989e8b49ce55345f4155769927d8f27f96ba7576..b5fd3457bb3738edca78133420d3779f48ed7f55 100644
--- a/doc/extra-intersphinx.txt
+++ b/doc/extra-intersphinx.txt
@@ -1,7 +1,19 @@
 python
 numpy
+bob.extension
+bob.pipelines
+bob.measure
 bob.io.base
 bob.bio.base
 bob.bio.spear
 bob.bio.face
 bob.pad.face
+setuptools
+click
+click-plugins
+dask
+matplotlib
+scikit-learn
+scipy
+sqlalchemy
+tabulate
diff --git a/pyproject.toml b/pyproject.toml
index b738dc847ff9705c5769673db7415f2eb9a75f4d..971265f780d7c96db274b1385161c59880aba729 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,95 @@
 [build-system]
-    requires = ["setuptools", "wheel", "bob.extension"]
+    requires = ["setuptools>=61.0.0", "wheel"]
     build-backend = "setuptools.build_meta"
 
+[project]
+    name = "bob.pad.base"
+    version = "5.0.5b0"
+    requires-python = ">=3.9"
+    description = "A framework for executing the chain of presentation attack detection (PAD) experiments"
+    dynamic = ["readme"]
+    license = {text = "GPLv3 License"}
+    authors = [
+    {name = "Pavel Korshunov"},
+    {email = "pavel.korshunov@idiap.ch"},
+    ]
+    keywords = ["PAD framework", "grid support", "pipeline organization"]
+
+    classifiers=[
+        "Framework :: Bob",
+        "Development Status :: 3 - Alpha",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+        "Natural Language :: English",
+        "Programming Language :: Python",
+        "Topic :: Scientific/Engineering :: Artificial Intelligence",
+    ]
+    dependencies = [
+        "setuptools",
+        "bob.extension",
+        "bob.io.base",
+        "bob.pipelines",
+        "bob.measure",
+        "bob.bio.base",
+        "click",
+        "click-plugins",
+        "dask",
+        "matplotlib",
+        "numpy",
+        "scikit-learn",
+        "scipy",
+        "sqlalchemy",
+        "tabulate",
+    ]
+
+[project.urls]
+    documentation = "https://www.idiap.ch/software/bob/docs/bob/bob.pad.base/stable/"
+    homepage = "https://pypi.org/project/bob.pad.base/"
+    repository = "https://gitlab.idiap.ch/bob/bob.pad.base"
+    changelog = "https://gitlab.idiap.ch/bob/bob.pad.base/-/releases"
+
+[project.optional-dependencies]
+    qa = ["pre-commit"]
+    doc = [
+        "sphinx",
+        "sphinx_rtd_theme",
+        "sphinx-autodoc-typehints",
+        "auto-intersphinx",
+        "sphinxcontrib-programoutput",
+        "matplotlib",
+        "bob.pad.face",
+        ]
+    test = [
+        "pytest",
+        "pytest-cov",
+        "coverage",
+        ]
+
+[tool.setuptools]
+    zip-safe = false
+    package-dir = {"" = "src"}
+
+[tool.setuptools.dynamic]
+    readme = {file = "README.rst"}
+
+[project.entry-points."bob.cli"]
+    pad               = "bob.pad.base.script.pad:pad"
+
+[project.entry-points."bob.pad.cli"]
+    metrics          = "bob.pad.base.script.pad_commands:metrics"
+    multi-metrics    = "bob.pad.base.script.pad_commands:multi_metrics"
+    hist             = "bob.pad.base.script.pad_commands:hist"
+    det              = "bob.pad.base.script.pad_commands:det"
+    roc              = "bob.pad.base.script.pad_commands:roc"
+    epc              = "bob.pad.base.script.pad_commands:epc"
+    gen              = "bob.pad.base.script.pad_commands:gen"
+    evaluate         = "bob.pad.base.script.pad_commands:evaluate"
+    cross            = "bob.pad.base.script.cross:cross"
+    finalize-scores  = "bob.pad.base.script.finalize_scores:finalize_scores"
+    run-pipeline     = "bob.pad.base.script.run_pipeline:run_pipeline"
+
+[tool.distutils.bdist_wheel]
+    universal = true
 [tool.isort]
     profile = "black"
     line_length = 80
@@ -10,3 +98,12 @@
 
 [tool.black]
     line-length = 80
+
+[tool.pytest.ini_options]
+    addopts = [
+        "--import-mode=append",
+        "--cov-report=term-missing",
+        "--cov=bob.pad.base",
+    ]
+    junit_logging = "all"
+    junit_log_passing_tests = false
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 17185041e64d6ef9bfdbfaebb2c9536736c40a54..0000000000000000000000000000000000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-setuptools
-bob.extension
-bob.io.base
-bob.pipelines
-bob.measure
-bob.bio.base
-click
-click-plugins
-dask
-matplotlib
-numpy
-scikit-learn
-scipy
-sqlalchemy
-tabulate
diff --git a/setup.py b/setup.py
index de4d81bd6a02826eb65d6d036633955685d02e40..606849326a4002007fd42060b51e69a19c18675c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,125 +1,3 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Mon 16 Apr 08:18:08 2012 CEST
-#
-# Copyright (C) Idiap Research Institute, Martigny, Switzerland
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, version 3 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from setuptools import setup
 
-# This file contains the python (distutils/setuptools) instructions so your
-# package can be installed on **any** host system. It defines some basic
-# information like the package name for instance, or its homepage.
-#
-# It also defines which other packages this python package depends on and that
-# are required for this package's operation. The python subsystem will make
-# sure all dependent packages are installed or will install them for you upon
-# the installation of this package.
-#
-# The 'buildout' system we use here will go further and wrap this package in
-# such a way to create an isolated python working environment. Buildout will
-# make sure that dependencies which are not yet installed do get installed, but
-# **without** requiring administrative privileges on the host system. This
-# allows you to test your package with new python dependencies w/o requiring
-# administrative interventions.
-
-from setuptools import dist, setup
-
-dist.Distribution(dict(setup_requires=["bob.extension"]))
-
-from bob.extension.utils import find_packages, load_requirements
-
-install_requires = load_requirements()
-
-# The only thing we do in this file is to call the setup() function with all
-# parameters that define our package.
-setup(
-    # This is the basic information about your project. Modify all this
-    # information before releasing code publicly.
-    name="bob.pad.base",
-    version=open("version.txt").read().rstrip(),
-    description="A framework for executing the chain of presentation attack detection (PAD) experiments",
-    url="https://gitlab.idiap.ch/bob/bob.pad.base",
-    license="GPLv3",
-    author="Pavel Korshunov",
-    author_email="pavel.korshunov@idiap.ch",
-    keywords="PAD framework, grid support, pipeline organization",
-    # If you have a better, long description of your package, place it on the
-    # 'doc' directory and then hook it here
-    long_description=open("README.rst").read(),
-    # This line is required for any distutils based packaging.
-    packages=find_packages(),
-    include_package_data=True,
-    # This line defines which packages should be installed when you "install"
-    # this package. All packages that are mentioned here, but are not installed
-    # on the current system will be installed locally and only visible to the
-    # scripts of this package. Don't worry - You won't need administrative
-    # privileges when using buildout.
-    install_requires=install_requires,
-    # Your project should be called something like 'bob.<foo>' or
-    # 'bob.<foo>.<bar>'. To implement this correctly and still get all your
-    # packages to be imported w/o problems, you need to implement namespaces
-    # on the various levels of the package and declare them here. See more
-    # about this here:
-    # http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
-    #
-    # Our database packages are good examples of namespace implementations
-    # using several layers. You can check them out here:
-    # https://github.com/idiap/bob/wiki/Satellite-Packages
-    # This entry defines which scripts you will have inside the 'bin' directory
-    # once you install the package (or run 'bin/buildout'). The order of each
-    # entry under 'console_scripts' is like this:
-    #   script-name-at-bin-directory = module.at.your.library:function
-    #
-    # The module.at.your.library is the python file within your library, using
-    # the python syntax for directories (i.e., a '.' instead of '/' or '\').
-    # This syntax also omits the '.py' extension of the filename. So, a file
-    # installed under 'example/foo.py' that contains a function which
-    # implements the 'main()' function of particular script you want to have
-    # should be referred as 'example.foo:main'.
-    #
-    # In this simple example we will create a single program that will print
-    # the version of bob.
-    entry_points={
-        # main entry for bob pad cli
-        "bob.cli": [
-            "pad               = bob.pad.base.script.pad:pad",
-        ],
-        # bob pad scripts
-        "bob.pad.cli": [
-            "metrics          = bob.pad.base.script.pad_commands:metrics",
-            "multi-metrics    = bob.pad.base.script.pad_commands:multi_metrics",
-            "hist             = bob.pad.base.script.pad_commands:hist",
-            "det              = bob.pad.base.script.pad_commands:det",
-            "roc              = bob.pad.base.script.pad_commands:roc",
-            "epc              = bob.pad.base.script.pad_commands:epc",
-            "gen              = bob.pad.base.script.pad_commands:gen",
-            "evaluate         = bob.pad.base.script.pad_commands:evaluate",
-            "cross            = bob.pad.base.script.cross:cross",
-            "finalize-scores  = bob.pad.base.script.finalize_scores:finalize_scores",
-            "run-pipeline     = bob.pad.base.script.run_pipeline:run_pipeline",
-        ],
-    },
-    # Classifiers are important if you plan to distribute this package through
-    # PyPI. You can find the complete list of classifiers that are valid and
-    # useful here (http://pypi.python.org/pypi?%3Aaction=list_classifiers).
-    classifiers=[
-        "Framework :: Bob",
-        "Development Status :: 3 - Alpha",
-        "Intended Audience :: Developers",
-        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
-        "Natural Language :: English",
-        "Programming Language :: Python",
-        "Topic :: Scientific/Engineering :: Artificial Intelligence",
-    ],
-)
+setup()
diff --git a/bob/__init__.py b/src/bob/__init__.py
similarity index 100%
rename from bob/__init__.py
rename to src/bob/__init__.py
diff --git a/bob/pad/__init__.py b/src/bob/pad/__init__.py
similarity index 100%
rename from bob/pad/__init__.py
rename to src/bob/pad/__init__.py
diff --git a/bob/pad/base/__init__.py b/src/bob/pad/base/__init__.py
similarity index 54%
rename from bob/pad/base/__init__.py
rename to src/bob/pad/base/__init__.py
index 11bb1980cd3f9714cb47296e6eda828e233f196f..9e6fb4ebc5a14df31da6cef271f8541721efb933 100644
--- a/bob/pad/base/__init__.py
+++ b/src/bob/pad/base/__init__.py
@@ -2,15 +2,6 @@
 from . import database  # noqa: F401
 from . import error_utils  # noqa: F401
 from . import script  # noqa: F401
-from . import test  # noqa: F401
-
-
-def get_config():
-    """Returns a string containing the configuration information."""
-    import bob.extension
-
-    return bob.extension.get_config(__name__)
-
 
 # gets sphinx autodoc done right - don't remove it
 __all__ = [_ for _ in dir() if not _.startswith("_")]
diff --git a/bob/pad/base/config/__init__.py b/src/bob/pad/base/config/__init__.py
similarity index 100%
rename from bob/pad/base/config/__init__.py
rename to src/bob/pad/base/config/__init__.py
diff --git a/bob/pad/base/database/__init__.py b/src/bob/pad/base/database/__init__.py
similarity index 100%
rename from bob/pad/base/database/__init__.py
rename to src/bob/pad/base/database/__init__.py
diff --git a/bob/pad/base/database/csv_dataset.py b/src/bob/pad/base/database/csv_dataset.py
similarity index 100%
rename from bob/pad/base/database/csv_dataset.py
rename to src/bob/pad/base/database/csv_dataset.py
diff --git a/bob/pad/base/error_utils.py b/src/bob/pad/base/error_utils.py
similarity index 100%
rename from bob/pad/base/error_utils.py
rename to src/bob/pad/base/error_utils.py
diff --git a/bob/pad/base/pipelines/__init__.py b/src/bob/pad/base/pipelines/__init__.py
similarity index 100%
rename from bob/pad/base/pipelines/__init__.py
rename to src/bob/pad/base/pipelines/__init__.py
diff --git a/bob/pad/base/pipelines/abstract_classes.py b/src/bob/pad/base/pipelines/abstract_classes.py
similarity index 100%
rename from bob/pad/base/pipelines/abstract_classes.py
rename to src/bob/pad/base/pipelines/abstract_classes.py
diff --git a/bob/pad/base/script/__init__.py b/src/bob/pad/base/script/__init__.py
similarity index 100%
rename from bob/pad/base/script/__init__.py
rename to src/bob/pad/base/script/__init__.py
diff --git a/bob/pad/base/script/cross.py b/src/bob/pad/base/script/cross.py
similarity index 90%
rename from bob/pad/base/script/cross.py
rename to src/bob/pad/base/script/cross.py
index 62b8f26fe0ab140b832427bc43ab4ee618de0d6c..a3ab03e2c50b5a0271b2a4a33262d129bf6d216d 100644
--- a/bob/pad/base/script/cross.py
+++ b/src/bob/pad/base/script/cross.py
@@ -10,14 +10,10 @@ import click
 import jinja2
 import yaml
 
+from exposed.click import log_parameters, verbosity_option
 from tabulate import tabulate
 
 from bob.bio.base.score.load import get_negatives_positives, load_score
-from bob.extension.scripts.click_helper import (
-    bool_option,
-    log_parameters,
-    verbosity_option,
-)
 from bob.measure import farfrr
 from bob.measure.script import common_options
 from bob.measure.utils import get_fta
@@ -28,6 +24,47 @@ from .pad_commands import CRITERIA
 logger = logging.getLogger(__name__)
 
 
+def bool_option(name, short_name, desc, dflt=False, **kwargs):
+    """Generic provider for boolean options
+
+    Parameters
+    ----------
+    name : str
+        name of the option
+    short_name : str
+        short name for the option
+    desc : str
+        short description for the option
+    dflt : bool or None
+        Default value
+    **kwargs
+        All kwargs are passed to click.option.
+
+    Returns
+    -------
+    ``callable``
+        A decorator to be used for adding this option.
+    """
+
+    def custom_bool_option(func):
+        def callback(ctx, param, value):
+            ctx.meta[name.replace("-", "_")] = value
+            return value
+
+        return click.option(
+            "-%s/-n%s" % (short_name, short_name),
+            "--%s/--no-%s" % (name, name),
+            default=dflt,
+            help=desc,
+            show_default=True,
+            callback=callback,
+            is_eager=True,
+            **kwargs,
+        )(func)
+
+    return custom_bool_option
+
+
 def _ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=dict):
     """Loads the contents of the YAML stream into :py:class:`collections.OrderedDict`'s
 
@@ -234,7 +271,7 @@ Examples:
 @common_options.table_option()
 @common_options.output_log_metric_option()
 @common_options.decimal_option(dflt=2, short="-dec")
-@verbosity_option()
+@verbosity_option(logger)
 @click.pass_context
 def cross(
     ctx,
diff --git a/bob/pad/base/script/finalize_scores.py b/src/bob/pad/base/script/finalize_scores.py
similarity index 92%
rename from bob/pad/base/script/finalize_scores.py
rename to src/bob/pad/base/script/finalize_scores.py
index 6164d901a886782907a19b831719432f264da69a..7d81e0b9a54ca12cbdf2a903b1af7a514d1fe0c6 100644
--- a/bob/pad/base/script/finalize_scores.py
+++ b/src/bob/pad/base/script/finalize_scores.py
@@ -1,8 +1,12 @@
 """Finalizes the scores that are produced by bob pad run-pipeline.
 """
+import logging
+
 import click
 
-from bob.extension.scripts.click_helper import log_parameters, verbosity_option
+from exposed.click import log_parameters, verbosity_option
+
+logger = logging.getLogger(__name__)
 
 
 @click.command(
@@ -27,7 +31,7 @@ Examples:
 @click.option(
     "--backup/--no-backup", default=True, help="Whether to backup scores."
 )
-@verbosity_option()
+@verbosity_option(logger)
 def finalize_scores(scores, method, backup, verbose):
     """Finalizes the scores given by bob pad run-pipeline
     When using bob.pad.base, Algorithms can produce several score values for
@@ -37,13 +41,11 @@ def finalize_scores(scores, method, backup, verbose):
     The conversion is done in-place (original files will be backed up).
     The order of scores will change.
     """
-    import logging
     import shutil
 
     import numpy
     import pandas as pd
 
-    logger = logging.getLogger(__name__)
     log_parameters(logger)
 
     mean = {
diff --git a/bob/pad/base/script/pad.py b/src/bob/pad/base/script/pad.py
similarity index 82%
rename from bob/pad/base/script/pad.py
rename to src/bob/pad/base/script/pad.py
index e798bb200f6b9c5fd348bc7395a125576ea42bdc..518ebb2a50ea9e1bfa7b8f8c9ce93003f243eb44 100644
--- a/bob/pad/base/script/pad.py
+++ b/src/bob/pad/base/script/pad.py
@@ -4,8 +4,7 @@ import click
 import pkg_resources
 
 from click_plugins import with_plugins
-
-from bob.extension.scripts.click_helper import AliasedGroup
+from exposed.click import AliasedGroup
 
 
 @with_plugins(pkg_resources.iter_entry_points("bob.pad.cli"))
diff --git a/bob/pad/base/script/pad_commands.py b/src/bob/pad/base/script/pad_commands.py
similarity index 98%
rename from bob/pad/base/script/pad_commands.py
rename to src/bob/pad/base/script/pad_commands.py
index 4fdabe9cb94173ed7c1b7cc9253a3a7f94bd0daa..b8227131279b3699e1ac867de4a2bf1d6b09ba6a 100644
--- a/bob/pad/base/script/pad_commands.py
+++ b/src/bob/pad/base/script/pad_commands.py
@@ -1,5 +1,6 @@
 """The main entry for bob pad commands.
 """
+import logging
 import os
 
 from csv import DictWriter
@@ -8,14 +9,17 @@ from functools import partial
 import click
 import numpy
 
+from exposed.click import verbosity_option
+
 import bob.measure.script.figure as measure_figure
 
-from bob.extension.scripts.click_helper import verbosity_option
 from bob.measure.script import common_options
 
 from ..error_utils import split_csv_pad, split_csv_pad_per_pai
 from . import pad_figure as figure
 
+logger = logging.getLogger(__name__)
+
 SCORE_FORMAT = "Files must be in CSV format."
 CRITERIA = (
     "eer",
@@ -190,7 +194,7 @@ def gen_pad_csv_scores(
 )
 @click.option("-s", "--n-samples", default=2, type=click.INT, show_default=True)
 @click.option("-a", "--n-attacks", default=2, type=click.INT, show_default=True)
-@verbosity_option()
+@verbosity_option(logger)
 @click.pass_context
 def gen(
     ctx,
diff --git a/bob/pad/base/script/pad_figure.py b/src/bob/pad/base/script/pad_figure.py
similarity index 100%
rename from bob/pad/base/script/pad_figure.py
rename to src/bob/pad/base/script/pad_figure.py
diff --git a/bob/pad/base/script/run_pipeline.py b/src/bob/pad/base/script/run_pipeline.py
similarity index 98%
rename from bob/pad/base/script/run_pipeline.py
rename to src/bob/pad/base/script/run_pipeline.py
index 1686efeab0855b9372803f43104999e270a1e87c..f8f7e77fe635fa6c049b22a23f09e97167f57857 100644
--- a/bob/pad/base/script/run_pipeline.py
+++ b/src/bob/pad/base/script/run_pipeline.py
@@ -5,11 +5,13 @@ import logging
 
 import click
 
-from bob.extension.scripts.click_helper import (
+from exposed.click import (
     ConfigCommand,
     ResourceOption,
+    log_parameters,
     verbosity_option,
 )
+
 from bob.pipelines.distributed import (
     VALID_DASK_CLIENT_STRINGS,
     dask_get_partition_size,
@@ -113,7 +115,7 @@ logger = logging.getLogger(__name__)
     help="If set, it will not use Dask for the execution of the pipeline.",
     cls=ResourceOption,
 )
-@verbosity_option(cls=ResourceOption)
+@verbosity_option(cls=ResourceOption, logger=logger)
 def run_pipeline(
     pipeline,
     decision_function,
@@ -129,8 +131,6 @@ def run_pipeline(
 ):
     """Runs the simplest PAD pipeline."""
 
-    from bob.extension.scripts.click_helper import log_parameters
-
     log_parameters(logger)
 
     execute_pipeline(
diff --git a/bob/pad/base/test/__init__.py b/tests/__init__.py
similarity index 100%
rename from bob/pad/base/test/__init__.py
rename to tests/__init__.py
diff --git a/bob/pad/base/test/data/csv_dataset.tar.gz b/tests/data/csv_dataset.tar.gz
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset.tar.gz
rename to tests/data/csv_dataset.tar.gz
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/dev/for_attack.csv b/tests/data/csv_dataset/protocol1/dev/for_attack.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/dev/for_attack.csv
rename to tests/data/csv_dataset/protocol1/dev/for_attack.csv
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/dev/for_real.csv b/tests/data/csv_dataset/protocol1/dev/for_real.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/dev/for_real.csv
rename to tests/data/csv_dataset/protocol1/dev/for_real.csv
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/eval/for_attack.csv b/tests/data/csv_dataset/protocol1/eval/for_attack.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/eval/for_attack.csv
rename to tests/data/csv_dataset/protocol1/eval/for_attack.csv
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/eval/for_real.csv b/tests/data/csv_dataset/protocol1/eval/for_real.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/eval/for_real.csv
rename to tests/data/csv_dataset/protocol1/eval/for_real.csv
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/train/for_attack.csv b/tests/data/csv_dataset/protocol1/train/for_attack.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/train/for_attack.csv
rename to tests/data/csv_dataset/protocol1/train/for_attack.csv
diff --git a/bob/pad/base/test/data/csv_dataset/protocol1/train/for_real.csv b/tests/data/csv_dataset/protocol1/train/for_real.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_dataset/protocol1/train/for_real.csv
rename to tests/data/csv_dataset/protocol1/train/for_real.csv
diff --git a/bob/pad/base/test/data/csv_scores/scores-dev.csv b/tests/data/csv_scores/scores-dev.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_scores/scores-dev.csv
rename to tests/data/csv_scores/scores-dev.csv
diff --git a/bob/pad/base/test/data/csv_scores/scores-eval.csv b/tests/data/csv_scores/scores-eval.csv
similarity index 100%
rename from bob/pad/base/test/data/csv_scores/scores-eval.csv
rename to tests/data/csv_scores/scores-eval.csv
diff --git a/bob/pad/base/test/data/example_filelist/dev/for_attack.lst b/tests/data/example_filelist/dev/for_attack.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/dev/for_attack.lst
rename to tests/data/example_filelist/dev/for_attack.lst
diff --git a/bob/pad/base/test/data/example_filelist/dev/for_real.lst b/tests/data/example_filelist/dev/for_real.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/dev/for_real.lst
rename to tests/data/example_filelist/dev/for_real.lst
diff --git a/bob/pad/base/test/data/example_filelist/eval/for_attack.lst b/tests/data/example_filelist/eval/for_attack.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/eval/for_attack.lst
rename to tests/data/example_filelist/eval/for_attack.lst
diff --git a/bob/pad/base/test/data/example_filelist/eval/for_real.lst b/tests/data/example_filelist/eval/for_real.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/eval/for_real.lst
rename to tests/data/example_filelist/eval/for_real.lst
diff --git a/bob/pad/base/test/data/example_filelist/train/for_attack.lst b/tests/data/example_filelist/train/for_attack.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/train/for_attack.lst
rename to tests/data/example_filelist/train/for_attack.lst
diff --git a/bob/pad/base/test/data/example_filelist/train/for_real.lst b/tests/data/example_filelist/train/for_real.lst
similarity index 100%
rename from bob/pad/base/test/data/example_filelist/train/for_real.lst
rename to tests/data/example_filelist/train/for_real.lst
diff --git a/bob/pad/base/test/data/licit/scores-dev b/tests/data/licit/scores-dev
similarity index 100%
rename from bob/pad/base/test/data/licit/scores-dev
rename to tests/data/licit/scores-dev
diff --git a/bob/pad/base/test/data/licit/scores-eval b/tests/data/licit/scores-eval
similarity index 100%
rename from bob/pad/base/test/data/licit/scores-eval
rename to tests/data/licit/scores-eval
diff --git a/bob/pad/base/test/data/per_pai_scores/scores-dev b/tests/data/per_pai_scores/scores-dev
similarity index 100%
rename from bob/pad/base/test/data/per_pai_scores/scores-dev
rename to tests/data/per_pai_scores/scores-dev
diff --git a/bob/pad/base/test/data/per_pai_scores/scores-dev-0.hdf5 b/tests/data/per_pai_scores/scores-dev-0.hdf5
similarity index 100%
rename from bob/pad/base/test/data/per_pai_scores/scores-dev-0.hdf5
rename to tests/data/per_pai_scores/scores-dev-0.hdf5
diff --git a/bob/pad/base/test/data/per_pai_scores/scores-dev-1.hdf5 b/tests/data/per_pai_scores/scores-dev-1.hdf5
similarity index 100%
rename from bob/pad/base/test/data/per_pai_scores/scores-dev-1.hdf5
rename to tests/data/per_pai_scores/scores-dev-1.hdf5
diff --git a/bob/pad/base/test/data/per_pai_scores/scores-dev.csv b/tests/data/per_pai_scores/scores-dev.csv
similarity index 100%
rename from bob/pad/base/test/data/per_pai_scores/scores-dev.csv
rename to tests/data/per_pai_scores/scores-dev.csv
diff --git a/bob/pad/base/test/data/scores-dev b/tests/data/scores-dev
similarity index 100%
rename from bob/pad/base/test/data/scores-dev
rename to tests/data/scores-dev
diff --git a/bob/pad/base/test/data/scores-dev-attack b/tests/data/scores-dev-attack
similarity index 100%
rename from bob/pad/base/test/data/scores-dev-attack
rename to tests/data/scores-dev-attack
diff --git a/bob/pad/base/test/data/scores-dev-real b/tests/data/scores-dev-real
similarity index 100%
rename from bob/pad/base/test/data/scores-dev-real
rename to tests/data/scores-dev-real
diff --git a/bob/pad/base/test/data/scores-eval b/tests/data/scores-eval
similarity index 100%
rename from bob/pad/base/test/data/scores-eval
rename to tests/data/scores-eval
diff --git a/bob/pad/base/test/data/scores-eval-attack b/tests/data/scores-eval-attack
similarity index 100%
rename from bob/pad/base/test/data/scores-eval-attack
rename to tests/data/scores-eval-attack
diff --git a/bob/pad/base/test/data/scores-eval-real b/tests/data/scores-eval-real
similarity index 100%
rename from bob/pad/base/test/data/scores-eval-real
rename to tests/data/scores-eval-real
diff --git a/bob/pad/base/test/data/scores-train b/tests/data/scores-train
similarity index 100%
rename from bob/pad/base/test/data/scores-train
rename to tests/data/scores-train
diff --git a/bob/pad/base/test/data/scores-train-attack b/tests/data/scores-train-attack
similarity index 100%
rename from bob/pad/base/test/data/scores-train-attack
rename to tests/data/scores-train-attack
diff --git a/bob/pad/base/test/data/scores-train-real b/tests/data/scores-train-real
similarity index 100%
rename from bob/pad/base/test/data/scores-train-real
rename to tests/data/scores-train-real
diff --git a/bob/pad/base/test/data/spoof/scores-dev b/tests/data/spoof/scores-dev
similarity index 100%
rename from bob/pad/base/test/data/spoof/scores-dev
rename to tests/data/spoof/scores-dev
diff --git a/bob/pad/base/test/data/spoof/scores-eval b/tests/data/spoof/scores-eval
similarity index 100%
rename from bob/pad/base/test/data/spoof/scores-eval
rename to tests/data/spoof/scores-eval
diff --git a/bob/pad/base/test/test_commands.py b/tests/test_commands.py
similarity index 81%
rename from bob/pad/base/test/test_commands.py
rename to tests/test_commands.py
index 53d570025d7b897816a72710725086bd24c73cac..4192bb5b6753ec7949c268a86b1b17f25b02f624 100644
--- a/bob/pad/base/test/test_commands.py
+++ b/tests/test_commands.py
@@ -4,17 +4,16 @@ import pkg_resources
 
 from click.testing import CliRunner
 
-from bob.extension.scripts.click_helper import assert_click_runner_result
-
-from ..script import pad_commands
+from bob.io.base.testing_utils import assert_click_runner_result
+from bob.pad.base.script import pad_commands
 
 
 def test_gen_pad():
     dev_ref_file = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-dev.csv"
+        __name__, "data/csv_scores/scores-dev.csv"
     )
     eval_ref_file = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-eval.csv"
+        __name__, "data/csv_scores/scores-eval.csv"
     )
     with open(dev_ref_file, "r") as f:
         dev_ref = f.readlines()
@@ -48,10 +47,10 @@ def test_gen_pad():
 
 def test_det_pad():
     dev = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-dev.csv"
+        __name__, "data/csv_scores/scores-dev.csv"
     )
     test = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-eval.csv"
+        __name__, "data/csv_scores/scores-eval.csv"
     )
     runner = CliRunner()
     with runner.isolated_filesystem():
@@ -63,10 +62,10 @@ def test_det_pad():
 
 def test_hist_pad():
     dev = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-dev.csv"
+        __name__, "data/csv_scores/scores-dev.csv"
     )
     test = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-eval.csv"
+        __name__, "data/csv_scores/scores-eval.csv"
     )
     runner = CliRunner()
     with runner.isolated_filesystem():
@@ -108,10 +107,10 @@ def test_hist_pad():
 
 def test_metrics_pad():
     dev = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-dev.csv"
+        __name__, "data/csv_scores/scores-dev.csv"
     )
     test = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-eval.csv"
+        __name__, "data/csv_scores/scores-eval.csv"
     )
     runner = CliRunner()
     with runner.isolated_filesystem():
@@ -121,10 +120,10 @@ def test_metrics_pad():
 
 def test_evaluate_pad():
     dev = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-dev.csv"
+        __name__, "data/csv_scores/scores-dev.csv"
     )
     test = pkg_resources.resource_filename(
-        "bob.pad.base.test", "data/csv_scores/scores-eval.csv"
+        __name__, "data/csv_scores/scores-eval.csv"
     )
     runner = CliRunner()
     with runner.isolated_filesystem():
diff --git a/bob/pad/base/test/test_error_utils.py b/tests/test_error_utils.py
similarity index 98%
rename from bob/pad/base/test/test_error_utils.py
rename to tests/test_error_utils.py
index 43fd9026d704c639beda1a232d78bb16b7486c53..8ebc9083fa79397b1fffcc30b116e60cb5a4f040 100644
--- a/bob/pad/base/test/test_error_utils.py
+++ b/tests/test_error_utils.py
@@ -1,7 +1,7 @@
 import h5py
 import numpy as np
 
-from bob.io.base.test_utils import datafile
+from bob.io.base.testing_utils import datafile
 from bob.pad.base.error_utils import (
     apcer_bpcer,
     calc_threshold,
diff --git a/bob/pad/base/test/test_pipelines.py b/tests/test_pipelines.py
similarity index 95%
rename from bob/pad/base/test/test_pipelines.py
rename to tests/test_pipelines.py
index ade45f615b2450ff4d8417ecd2280693e6fff9e6..39c8efdabc64a5da1cd3ea7556be8925c505322c 100644
--- a/bob/pad/base/test/test_pipelines.py
+++ b/tests/test_pipelines.py
@@ -11,7 +11,7 @@ from sklearn.pipeline import Pipeline
 import bob.measure
 import bob.pipelines as mario
 
-from bob.extension.scripts.click_helper import assert_click_runner_result
+from bob.io.base.testing_utils import assert_click_runner_result
 from bob.pad.base.error_utils import split_csv_pad
 from bob.pad.base.pipelines import Database
 from bob.pad.base.script.run_pipeline import run_pipeline as run_pipeline_cli
@@ -73,7 +73,7 @@ def _create_config_file(path):
     with open(path, "w") as f:
         f.write(
             """
-from bob.pad.base.test.test_pipelines import DummyPadDatabase, dummy_pipeline
+from tests.test_pipelines import DummyPadDatabase, dummy_pipeline
 database = DummyPadDatabase()
 pipeline = dummy_pipeline()
 """
diff --git a/version.txt b/version.txt
deleted file mode 100644
index f4939080b892a87661f310207a4c09a52639a640..0000000000000000000000000000000000000000
--- a/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-5.0.5b0