diff --git a/.gitignore b/.gitignore
index 361aab47cd91c6038f7df9b1587e551cfcf15555..d1dfa18ce8c26f10ec4887002a30462ead44df74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ build
 src/
 record.txt
 dask-worker-space
+.gitlab-ci-local*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 845b719f2378d396a8b75296a68d1e32c840e7c7..80ad328f094433833f53a5512cfc85f93eb799a5 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/citools
+    ref: master
+    file: /src/citools/data/python.yml
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 044f2367cdf0522cb7d4a8b7159e47be68d2f696..0000000000000000000000000000000000000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,14 +0,0 @@
-; vim: set fileencoding=utf-8 :
-; Mon 15 Aug 2016 09:48:28 CEST
-
-[buildout]
-parts = scripts
-develop = .
-eggs = bob.learn.em
-extensions = bob.buildout
-newest = false
-verbose = true
-debug = true
-
-[scripts]
-recipe = bob.buildout:scripts
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 709acddfc5ea1853ed1d5075da29bcbb85483f81..679e793afeb697e81ef79e65fab2eed646978d70 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -1,9 +1,10 @@
-{% set name = 'bob.learn.em' %}
+{% set data = load_file_data(RECIPE_DIR + '/../pyproject.toml') %}
+{% set name = data['project']['name'] %}
 {% set project_dir = environ.get('RECIPE_DIR') + '/..' %}
 
 package:
   name: {{ name }}
-  version: {{ environ.get('BOB_PACKAGE_VERSION', '0.0.1') }}
+  version: {{ data['project']['version'] }}
 
 build:
   number: {{ environ.get('BOB_BUILD_NUMBER', 0) }}
@@ -11,9 +12,6 @@ build:
     - {{ pin_subpackage(name) }}
   script:
     - cd {{ project_dir }}
-    {% if environ.get('BUILD_EGG') %}
-    - "{{ PYTHON }} setup.py sdist --formats=zip"
-    {% endif %}
     - "{{ PYTHON }} -m pip install . -vv"
     # installs the documentation source, readme to share/doc so it is available
     # during test time
@@ -50,21 +48,11 @@ test:
     # runs tests for package only, report only what is in the package
     # creates xml tests report and place it in a specific directory
     # creates html and xml coverage reports and place them in a specific directory
-    - pytest --verbose --cov {{ name }} --cov-report term-missing --cov-report html:{{ project_dir }}/sphinx/coverage --cov-report xml:{{ project_dir }}/coverage.xml --junitxml={{ project_dir }}/test_results.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 }}
-    - matplotlib {{ matplotlib }}
 
 about:
-  home: https://www.idiap.ch/software/bob/
-  license: BSD 3-Clause
-  summary: Bindings for emelaneous machines and trainers of Bob
+  home: {{ data['project']['urls']['homepage'] }}
+  license: {{ data['project']['description'] }}
+  summary: {{ data['project']['license']['text'] }}
   license_family: BSD
diff --git a/doc/conf.py b/doc/conf.py
index 59964034e8fac4f87cb9144f31c4ff36fc5ce89b..2999f19cee4d71de6a3484d9bf3f590ec0a7a2da 100755
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -2,10 +2,13 @@
 # vim: set fileencoding=utf-8 :
 
 import os
+import sys
 
 import pkg_resources
 
 # -- General configuration -----------------------------------------------------
+# Adding the parent directory to the python path.
+sys.path.insert(0, os.path.abspath("../"))
 
 # If your documentation needs a minimal Sphinx version, state it here.
 needs_sphinx = "1.3"
diff --git a/pyproject.toml b/pyproject.toml
index b738dc847ff9705c5769673db7415f2eb9a75f4d..e517f61d9e686c2a9629958e88320856ded5de28 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,65 @@
 [build-system]
-    requires = ["setuptools", "wheel", "bob.extension"]
+    requires = ["setuptools>=61.0.0", "wheel"]
     build-backend = "setuptools.build_meta"
 
+[project]
+    name = "bob.learn.em"
+    version = "3.0.5b0"
+    requires-python = ">=3.9"
+    description = "Bindings for EM machines and trainers of Bob"
+    dynamic = ["readme", "dependencies"]
+    license = {text = "BSD 3-Clause License"}
+    authors = [
+    {name = "Andre Anjos"},
+    {email = "andre.anjos@idiap.ch"},
+    ]
+    keywords = ["bob", "em", "expectation-maximization"]
+
+    classifiers=[
+        "Framework :: Bob",
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: BSD License",
+        "Natural Language :: English",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+    ]
+
+[project.urls]
+    documentation = "https://www.idiap.ch/software/bob/docs/bob/bob.learn.em/stable/"
+    homepage = "https://pypi.org/project/bob.learn.em/"
+    repository = "https://gitlab.idiap.ch/bob/bob.learn.em"
+    changelog = "https://gitlab.idiap.ch/bob/bob.learn.em/-/releases"
+
+[project.optional-dependencies]
+    qa = ["pre-commit"]
+    doc = [
+        "sphinx",
+        "sphinx_rtd_theme",
+        "sphinx-autodoc-typehints",
+        "sphinxcontrib-programoutput",
+        "matplotlib",
+        ]
+    test = [
+        "pytest",
+        "pytest-cov",
+        "coverage",
+        ]
+
+[tool.setuptools]
+    zip-safe = false
+    include-package-data = true
+    packages = ["bob"]
+
+[tool.setuptools.dynamic]
+    readme = {file = "README.rst"}
+    dependencies = {file = "requirements.txt"}
+
+
+[tool.distutils.bdist_wheel]
+    universal = true
+
 [tool.isort]
     profile = "black"
     line_length = 80
@@ -10,3 +68,15 @@
 
 [tool.black]
     line-length = 80
+
+[tool.coverage.run]
+    relative_files = true
+
+[tool.pytest.ini_options]
+    addopts = [
+        "--import-mode=append",
+        "--cov-report=term-missing",
+        "--cov=bob.learn.em",
+    ]
+    junit_logging = "all"
+    junit_log_passing_tests = false
diff --git a/setup.py b/setup.py
index 21a202ff96ece87c8c563c8ff2d2d1322a04a7d2..3a3aaadbd1b7a7371f5208fe978855323b3c5291 100644
--- a/setup.py
+++ b/setup.py
@@ -1,37 +1,6 @@
 #!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 
-from setuptools import dist, setup
+from setuptools import setup
 
-dist.Distribution(dict(setup_requires=["bob.extension"]))
-
-from bob.extension.utils import find_packages, load_requirements
-
-install_requires = load_requirements()
-
-
-setup(
-    name="bob.learn.em",
-    version=open("version.txt").read().rstrip(),
-    description="Bindings for EM machines and trainers of Bob",
-    url="http://gitlab.idiap.ch/bob/bob.learn.em",
-    license="BSD",
-    author="Andre Anjos",
-    author_email="andre.anjos@idiap.ch",
-    keywords="bob, em, expectation-maximization",
-    long_description=open("README.rst").read(),
-    packages=find_packages(),
-    include_package_data=True,
-    zip_safe=False,
-    install_requires=install_requires,
-    classifiers=[
-        "Framework :: Bob",
-        "Development Status :: 4 - Beta",
-        "Intended Audience :: Developers",
-        "License :: OSI Approved :: BSD License",
-        "Natural Language :: English",
-        "Programming Language :: Python",
-        "Programming Language :: Python :: 3",
-        "Topic :: Software Development :: Libraries :: Python Modules",
-    ],
-)
+setup()
diff --git a/version.txt b/version.txt
deleted file mode 100644
index ac54c0b9afedd1d236b2da0e956ad4fe33770a55..0000000000000000000000000000000000000000
--- a/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-3.2.1b0