diff --git a/.gitignore b/.gitignore
index 3a49f7bdb6b2d4889a4279efe9c7b337e1fecae6..10b2aefe9ec5070df2c3d485ef42fd0fd43b9735 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ __pycache__
 /cmmnbuild_dep_manager/modules.json
 /cmmnbuild_dep_manager/jars
 /cmmnbuild_dep_manager/lib
+cmmnbuild_dep_manager/_version.py
 *.swp
diff --git a/cmmnbuild_dep_manager/__init__.py b/cmmnbuild_dep_manager/__init__.py
index 3bc76f55c0f2087bf97bf957daaae2d8e8ae232a..a1d40f282f4e5a1883da47381e4f4b07b3c4abac 100644
--- a/cmmnbuild_dep_manager/__init__.py
+++ b/cmmnbuild_dep_manager/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
 from .cmmnbuild_dep_manager import Manager
+from ._version import version as __version__
 
-__version__ = '2.3.1'
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d7c6f2679c53a3bf3a91d8963c6d3f55b35791e4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,7 @@
+[build-system]
+requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+
+# Enable automatic versioning from git.
+[tool.setuptools_scm]
+write_to = "cmmnbuild_dep_manager/_version.py"
\ No newline at end of file
diff --git a/setup.py b/setup.py
index b489a0d37db91709dc788a7479c687db910d1512..958d174867f2897c686cddf76f6db5b2576387d1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,40 +1,63 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+"""
+setup.py for cmmnbuild-dep-manager.
 
-import ast
-import os
-import setuptools
+For reference see
+https://packaging.python.org/guides/distributing-packages-using-setuptools/
 
+"""
+from pathlib import Path
+from setuptools import setup, find_packages
 
-def get_version_from_init():
-    init_file = os.path.join(
-        os.path.dirname(__file__), 'cmmnbuild_dep_manager', '__init__.py'
-    )
-    with open(init_file, 'r') as fd:
-        for line in fd:
-            if line.startswith('__version__'):
-                return ast.literal_eval(line.split('=', 1)[1].strip())
 
-setuptools.setup(
-    name='cmmnbuild-dep-manager',
-    version=get_version_from_init(),
-    description='Manages CERN\'s Java dependencies across multiple Python '
-                'packages',
-    author='CERN MD Scripting Tools Community',
-    author_email='MD-scripting-tools@cern.ch',
-    license='MIT',
-    url='https://gitlab.cern.ch/scripting-tools/cmmnbuild-dep-manager',
-    packages=[
-        'cmmnbuild_dep_manager',
-        'cmmnbuild_dep_manager.resolver'
-    ],
-    install_requires=[
+HERE = Path(__file__).parent.absolute()
+with (HERE / 'README.md').open('rt') as fh:
+    LONG_DESCRIPTION = fh.read().strip()
+
+
+REQUIREMENTS: dict = {
+    'core': [
         'entrypoints',
         'JPype1>=0.6.1',
         'requests',
-        'six'
+        'six',
+    ],
+    'test': [
+        'pytest',
     ],
+}
+
+
+setup(
+    name='cmmnbuild-dep-manager',
+    description="Manages CERN's Java dependencies across multiple Python "
+                "packages",
+    maintainer='CERN Accelerating Python',
+    maintainer_email='acc-py-support@cern.ch',
+    long_description=LONG_DESCRIPTION,
+    long_description_content_type='text/markdown',
+    license='MIT',
+    url='https://gitlab.cern.ch/scripting-tools/cmmnbuild-dep-manager',
+    packages=find_packages(),
+    python_requires='>=3.6, <4',
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "Operating System :: OS Independent",
+    ],
+
+    install_requires=REQUIREMENTS['core'],
+    extras_require={
+        **REQUIREMENTS,
+        # The 'dev' extra is the union of 'test' and 'doc', with an option
+        # to have explicit development dependencies listed.
+        'dev': [req
+                for extra in ['dev', 'test', 'doc']
+                for req in REQUIREMENTS.get(extra, [])],
+        # The 'all' extra is the union of all requirements.
+        'all': [req for reqs in REQUIREMENTS.values() for req in reqs],
+    },
     package_data={'cmmnbuild_dep_manager.resolver': ['gradle-wrapper.zip']},
     include_package_data=True,
+    # cmmnbuild-dep-manager puts special files in the package directory, so it
+    # cannot be installed as a zip.
     zip_safe=False
 )