Repository: pypa/setuptools_scm

Issue:
setuptools_scm does not work with setup.cfg
I was debuging  the behaviour of `setuptools_scm` because I received exception from function `version_keyword`
``` 
File "/home/oglop/.virtualenvs/b/lib/python3.6/site-packages/setuptools_scm/integration.py", line 22, in version_keyword
    dist.metadata.version = get_version(**value)
```

in `setuptools` file `config.py`, all options go through parser except additional options like `use_scm_version`, I guess this should be handled by `setuptools_scm`
*file* : `setuptools/config.py`
```
class ConfigOptionsHandler(ConfigHandler):

    section_prefix = 'options'

    @property
    def parsers(self):
        """Metadata item name to parser function mapping."""
        parse_list = self._parse_list
        parse_list_semicolon = partial(self._parse_list, separator=';')
        parse_bool = self._parse_bool
        parse_dict = self._parse_dict

        return {
            'zip_safe': parse_bool,
            'use_2to3': parse_bool,
            'include_package_data': parse_bool,
            'package_dir': parse_dict,
            'use_2to3_fixers': parse_list,
            'use_2to3_exclude_fixers': parse_list,
            'convert_2to3_doctests': parse_list,
            'scripts': parse_list,
            'eager_resources': parse_list,
            'dependency_links': parse_list,
            'namespace_packages': parse_list,
            'install_requires': parse_list_semicolon,
            'setup_requires': parse_list_semicolon,
            'tests_require': parse_list_semicolon,
            'packages': self._parse_packages,
            'entry_points': self._parse_file,
            'py_modules': parse_list,
        }
```

in `setuptools_scm`
 something goes wrong here:
```
[distutils.setup_keywords]
        use_scm_version = setuptools_scm.integration:version_keyword
```
because function `version_keyword` receives parameter `value` as `str` but that function expects `bool` - because the value did not go through the *parsing process*

what I do not know is how that `version_keyword` is called 

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c6a1968..c30f2bc 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,9 @@
+v3.4.0
+======
+
+* fix #181 - add support for projects built under setuptools declarative config
+  by way of the setuptools.finalize_distribution_options hook in Setuptools 42.
+
 * fix #305 - ensure the git file finder closes filedescriptors even when errors happen
 
 v3.3.3
diff --git a/README.rst b/README.rst
index f55c6df..ebad26d 100644
--- a/README.rst
+++ b/README.rst
@@ -13,9 +13,63 @@ It also handles file finders for the supported SCMs.
 .. image:: https://tidelift.com/badges/github/pypa/setuptools_scm
    :target: https://tidelift.com/subscription/pkg/pypi-setuptools_scm?utm_source=pypi-setuptools_scm&utm_medium=readme
 
+``pyproject.toml`` usage
+------------------------
+
+The preferred way to configure ``setuptools_scm`` is to author
+settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``.
+
+This feature requires Setuptools 42 or later, released in Nov, 2019.
+If your project needs to support build from sdist on older versions
+of Setuptools, you will need to also implement the ``setup.py usage``
+for those legacy environments.
+
+First, ensure that ``setuptools_scm`` is present during the project's
+built step by specifying it as one of the build requirements.
+
+.. code:: ini
+
+    # pyproject.toml
+    [build-system]
+    requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+Note that the ``toml`` extra must be supplied.
+
+That will be sufficient to require ``setuptools_scm`` for projects
+that support PEP 518 (`pip <https://pypi.org/project/pip>`_ and
+`pep517 <https://pypi.org/project/pep517/>`_). Many tools,
+especially those that invoke ``setup.py`` for any reason, may
+continue to rely on ``setup_requires``. For maximum compatibility
+with those uses, consider also including a ``setup_requires`` directive
+(described below in ``setup.py usage`` and ``setup.cfg``).
+
+To enable version inference, add this section to your pyproject.toml:
+
+.. code:: ini
+
+    # pyproject.toml
+    [tools.setuptools_scm]
+
+Including this section is comparable to supplying
+``use_scm_version=True`` in ``setup.py``. Additionally,
+include arbitrary keyword arguments in that section
+to be supplied to ``get_version()``. For example::
+
+.. code:: ini
+
+    # pyproject.toml
+    [tools.setuptools_scm]
+    write_to = pkg/version.py
+
+
 ``setup.py`` usage
 ------------------
 
+The following settings are considered legacy behavior and
+superseded by the ``pyproject.toml`` usage, but for maximal
+compatibility, projects may also supply the configuration in
+this older form.
+
 To use ``setuptools_scm`` just modify your project's ``setup.py`` file
 like this:
 
diff --git a/setup.py b/setup.py
index 2eb533b..ced9a3b 100644
--- a/setup.py
+++ b/setup.py
@@ -68,6 +68,9 @@ arguments = dict(
         [setuptools.file_finders]
         setuptools_scm = setuptools_scm.integration:find_files
 
+        [setuptools.finalize_distribution_options]
+        setuptools_scm = setuptools_scm.integration:infer_version
+
         [setuptools_scm.parse_scm]
         .hg = setuptools_scm.hg:parse
         .git = setuptools_scm.git:parse
@@ -111,6 +114,7 @@ arguments = dict(
         "Topic :: Utilities",
     ],
     python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+    extras_require=dict(toml=["toml"]),
 )
 
 if __name__ == "__main__":
diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py
index f3886ed..2e05006 100644
--- a/src/setuptools_scm/__init__.py
+++ b/src/setuptools_scm/__init__.py
@@ -146,18 +146,23 @@ def get_version(
     config.fallback_version = fallback_version
     config.parse = parse
     config.git_describe_command = git_describe_command
+    return _get_version(config)
 
+
+def _get_version(config):
     parsed_version = _do_parse(config)
 
     if parsed_version:
         version_string = format_version(
-            parsed_version, version_scheme=version_scheme, local_scheme=local_scheme
+            parsed_version,
+            version_scheme=config.version_scheme,
+            local_scheme=config.local_scheme,
         )
         dump_version(
-            root=root,
+            root=config.root,
             version=version_string,
-            write_to=write_to,
-            template=write_to_template,
+            write_to=config.write_to,
+            template=config.write_to_template,
         )
 
         return version_string
diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py
index 38f79ae..d548c81 100644
--- a/src/setuptools_scm/config.py
+++ b/src/setuptools_scm/config.py
@@ -105,3 +105,19 @@ class Configuration(object):
     @tag_regex.setter
     def tag_regex(self, value):
         self._tag_regex = _check_tag_regex(value)
+
+    def _load(self, values):
+        vars(self).update(values)
+        return self
+
+    @classmethod
+    def from_file(cls, name="pyproject.toml"):
+        """
+        Read Configuration from pyproject.toml (or similar).
+        Raises exceptions when file is not found or toml is
+        not installed or the file has invalid format or does
+        not contain the [tool.setuptools_scm] section.
+        """
+        with open(name) as strm:
+            defn = __import__("toml").load(strm)
+        return cls()._load(defn.get("tool", {})["setuptools_scm"])
diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py
index e18b3e5..c5cc25d 100644
--- a/src/setuptools_scm/integration.py
+++ b/src/setuptools_scm/integration.py
@@ -1,8 +1,9 @@
 from pkg_resources import iter_entry_points
 
 from .version import _warn_if_setuptools_outdated
-from .utils import do
-from . import get_version
+from .config import Configuration
+from .utils import do, trace_exception
+from . import get_version, _get_version
 
 
 def version_keyword(dist, keyword, value):
@@ -28,3 +29,11 @@ def find_files(path=""):
         if res:
             return res
     return []
+
+
+def infer_version(dist):
+    try:
+        config = Configuration.from_file()
+    except Exception:
+        return trace_exception()
+    dist.metadata.version = _get_version(config)
diff --git a/src/setuptools_scm/utils.py b/src/setuptools_scm/utils.py
index 5b59005..d2c5b63 100644
--- a/src/setuptools_scm/utils.py
+++ b/src/setuptools_scm/utils.py
@@ -10,6 +10,7 @@ import subprocess
 import os
 import io
 import platform
+import traceback
 
 
 DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
@@ -25,6 +26,10 @@ def trace(*k):
         sys.stdout.flush()
 
 
+def trace_exception():
+    DEBUG and traceback.print_exc()
+
+
 def ensure_stripped_str(str_or_bytes):
     if isinstance(str_or_bytes, str):
         return str_or_bytes.strip()
diff --git a/tox.ini b/tox.ini
index 129f9b4..b51ef3d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -25,9 +25,12 @@ skip_install=
     test: False
 deps=
     pytest
+    setuptools >= 42
 commands=
     test: py.test []
     selfcheck: python setup.py --version
+extras =
+    toml
 
 [testenv:flake8]
 skip_install=True


Test patch:
diff --git a/testing/test_config.py b/testing/test_config.py
index eadbaf3..38bc8f4 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
 from setuptools_scm.config import Configuration
 
 import pytest
@@ -18,3 +20,9 @@ def test_tag_regex(tag, expected_version):
     match = config.tag_regex.match(tag)
     version = match.group("version")
     assert version == expected_version
+
+
+def test_config_from_pyproject(tmpdir):
+    fn = tmpdir / "pyproject.toml"
+    fn.write_text("[tool.setuptools_scm]\n", encoding="utf-8")
+    assert Configuration.from_file(str(fn))
diff --git a/testing/test_integration.py b/testing/test_integration.py
new file mode 100644
index 0000000..a0d1143
--- /dev/null
+++ b/testing/test_integration.py
@@ -0,0 +1,16 @@
+import sys
+
+from setuptools_scm.utils import do
+
+
+def test_pyproject_support(tmpdir, monkeypatch):
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    pkg = tmpdir.ensure("package", dir=42)
+    pkg.join("pyproject.toml").write(
+        """[tool.setuptools_scm]
+fallback_version = "12.34"
+"""
+    )
+    pkg.join("setup.py").write("__import__('setuptools').setup()")
+    res = do((sys.executable, "setup.py", "--version"), pkg)
+    assert res == "12.34"