From d1e6c60414f99aab601342f77f312599ebd4cd53 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 18 Oct 2019 20:03:51 -0400 Subject: [PATCH 01/11] Illustrate how setuptools_scm could adapt to a 'setuptools.finalize_distribution_options' entry point. --- setup.py | 3 +++ src/setuptools_scm/__init__.py | 17 +++++++++++++---- src/setuptools_scm/config.py | 7 +++++++ src/setuptools_scm/integration.py | 9 ++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 2eb533bc..f5fe2445 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,9 @@ def parse(root): [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 diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py index f3886ed2..542f2649 100644 --- a/src/setuptools_scm/__init__.py +++ b/src/setuptools_scm/__init__.py @@ -80,6 +80,9 @@ def dump_version(root, version, write_to, template=None): def _do_parse(config): + if not config.enabled: + return + pretended = os.environ.get(PRETEND_KEY) if pretended: # we use meta here since the pretended version @@ -146,18 +149,24 @@ def get_version( config.fallback_version = fallback_version config.parse = parse config.git_describe_command = git_describe_command + config.enabled = True + 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 38f79aed..e69d8fe8 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -105,3 +105,10 @@ def tag_regex(self): @tag_regex.setter def tag_regex(self, value): self._tag_regex = _check_tag_regex(value) + + @classmethod + def from_file(cls): + """ + Read Configuration from pyproject.toml (or similar) + """ + # stubbed diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py index e18b3e57..a1904d98 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 .config import Configuration from .utils import do -from . import get_version +from . import get_version, _get_version def version_keyword(dist, keyword, value): @@ -28,3 +29,9 @@ def find_files(path=""): if res: return res return [] + + +def infer_version(dist): + version = _get_version(Configuration.from_file()) + if version: + dist.metadata.version = version From eb59201397e15d6de33b4aa741d04ae27da3317d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 21 Oct 2019 19:59:01 -0400 Subject: [PATCH 02/11] Illustrate how reading the config from a file might operate. --- setup.py | 1 + src/setuptools_scm/config.py | 8 ++++++-- testing/test_config.py | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f5fe2445..84747da1 100644 --- a/setup.py +++ b/setup.py @@ -113,6 +113,7 @@ def parse(root): "Topic :: System :: Software Distribution", "Topic :: Utilities", ], + install_requires=["toml"], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", ) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index e69d8fe8..20e97b3b 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -107,8 +107,12 @@ def tag_regex(self, value): self._tag_regex = _check_tag_regex(value) @classmethod - def from_file(cls): + def from_file(cls, name='pyproject.toml'): """ Read Configuration from pyproject.toml (or similar) """ - # stubbed + with open(name) as strm: + defn = __import__('toml').load(strm) + config = cls() + vars(config).update(defn.get('setuptools_scm', {})) + return config diff --git a/testing/test_config.py b/testing/test_config.py index eadbaf3d..742605ea 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -18,3 +18,10 @@ 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('[setuptools_scm]\nenabled = true\n', encoding='utf-8') + config = Configuration.from_file(str(fn)) + assert config.enabled From 59fbdf845f4d5b17ceaeba15413f182499ba6a52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 Nov 2019 18:48:25 -0500 Subject: [PATCH 03/11] Move section to `tool:` namespace Co-Authored-By: Ronny Pfannschmidt --- src/setuptools_scm/config.py | 2 +- testing/test_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index 20e97b3b..c87118ac 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -114,5 +114,5 @@ def from_file(cls, name='pyproject.toml'): with open(name) as strm: defn = __import__('toml').load(strm) config = cls() - vars(config).update(defn.get('setuptools_scm', {})) + vars(config).update(defn.get("tool",{}).get('setuptools_scm', {})) return config diff --git a/testing/test_config.py b/testing/test_config.py index 742605ea..e1d4285d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -22,6 +22,6 @@ def test_tag_regex(tag, expected_version): def test_config_from_pyproject(tmpdir): fn = tmpdir / 'pyproject.toml' - fn.write_text('[setuptools_scm]\nenabled = true\n', encoding='utf-8') + fn.write_text('[tool.setuptools_scm]\nenabled = true\n', encoding='utf-8') config = Configuration.from_file(str(fn)) assert config.enabled From 6fb77d9553dfddcc4c6650670546d6f93e0ab2be Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 Nov 2019 18:51:40 -0500 Subject: [PATCH 04/11] Make toml an extra requirement --- setup.py | 2 +- tox.ini | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 84747da1..ced9a3b2 100644 --- a/setup.py +++ b/setup.py @@ -113,8 +113,8 @@ def parse(root): "Topic :: System :: Software Distribution", "Topic :: Utilities", ], - install_requires=["toml"], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + extras_require=dict(toml=["toml"]), ) if __name__ == "__main__": diff --git a/tox.ini b/tox.ini index 129f9b47..4ab3c791 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,8 @@ deps= commands= test: py.test [] selfcheck: python setup.py --version +extras = + toml [testenv:flake8] skip_install=True From 548cbcbd823898fb5b9271344926f840f1a7c2d0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 06:54:26 -0500 Subject: [PATCH 05/11] Fade to black --- src/setuptools_scm/config.py | 6 +++--- testing/test_config.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index c87118ac..13a379f9 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -107,12 +107,12 @@ def tag_regex(self, value): self._tag_regex = _check_tag_regex(value) @classmethod - def from_file(cls, name='pyproject.toml'): + def from_file(cls, name="pyproject.toml"): """ Read Configuration from pyproject.toml (or similar) """ with open(name) as strm: - defn = __import__('toml').load(strm) + defn = __import__("toml").load(strm) config = cls() - vars(config).update(defn.get("tool",{}).get('setuptools_scm', {})) + vars(config).update(defn.get("tool", {}).get("setuptools_scm", {})) return config diff --git a/testing/test_config.py b/testing/test_config.py index e1d4285d..f3f392f0 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -21,7 +21,7 @@ def test_tag_regex(tag, expected_version): def test_config_from_pyproject(tmpdir): - fn = tmpdir / 'pyproject.toml' - fn.write_text('[tool.setuptools_scm]\nenabled = true\n', encoding='utf-8') + fn = tmpdir / "pyproject.toml" + fn.write_text("[tool.setuptools_scm]\nenabled = true\n", encoding="utf-8") config = Configuration.from_file(str(fn)) assert config.enabled From a6ae8303f39314fc9021d904032b2a5ee264bd6f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 07:02:52 -0500 Subject: [PATCH 06/11] Restore Python 2.7 compatibility --- testing/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/test_config.py b/testing/test_config.py index f3f392f0..dcf48096 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 From bba4a3a4f980a83b78864c66e80367c5476c4a9f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 08:05:43 -0500 Subject: [PATCH 07/11] setuptools_scm is disabled by default --- src/setuptools_scm/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index 13a379f9..90f88716 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -49,6 +49,7 @@ class Configuration(object): parse = None _tag_regex = None _absolute_root = None + enabled = False def __init__(self, relative_to=None, root="."): # TODO: From 1fe1c650a6b01e8f5f9c8dff7a1bb5ad132588db Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 08:07:35 -0500 Subject: [PATCH 08/11] Avoid file-not-found errors when pyproject.toml isn't present --- src/setuptools_scm/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index 90f88716..88e65d96 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -112,6 +112,8 @@ def from_file(cls, name="pyproject.toml"): """ Read Configuration from pyproject.toml (or similar) """ + if not os.path.isfile(name): + return cls() with open(name) as strm: defn = __import__("toml").load(strm) config = cls() From 31d22f3515c8ad49ceab68cd43e55f70838b7251 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 08:07:52 -0500 Subject: [PATCH 09/11] Add integration test --- testing/test_integration.py | 17 +++++++++++++++++ tox.ini | 1 + 2 files changed, 18 insertions(+) create mode 100644 testing/test_integration.py diff --git a/testing/test_integration.py b/testing/test_integration.py new file mode 100644 index 00000000..2a245597 --- /dev/null +++ b/testing/test_integration.py @@ -0,0 +1,17 @@ +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] +enabled = true +fallback_version = "12.34" +""" + ) + pkg.join("setup.py").write("__import__('setuptools').setup()") + res = do((sys.executable, "setup.py", "--version"), pkg) + assert res == "12.34" diff --git a/tox.ini b/tox.ini index 4ab3c791..b51ef3d2 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,7 @@ skip_install= test: False deps= pytest + setuptools >= 42 commands= test: py.test [] selfcheck: python setup.py --version From bf15ea917a10aa4f939eb40206dc07e178460f20 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 17:58:11 -0500 Subject: [PATCH 10/11] Remove 'enabled' boolean and rely on presence of 'tool.setuptools_scm' section to enable the functionality. --- src/setuptools_scm/__init__.py | 4 ---- src/setuptools_scm/config.py | 16 +++++++++------- src/setuptools_scm/integration.py | 10 ++++++---- src/setuptools_scm/utils.py | 5 +++++ testing/test_config.py | 5 ++--- testing/test_integration.py | 1 - 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py index 542f2649..2e050061 100644 --- a/src/setuptools_scm/__init__.py +++ b/src/setuptools_scm/__init__.py @@ -80,9 +80,6 @@ def dump_version(root, version, write_to, template=None): def _do_parse(config): - if not config.enabled: - return - pretended = os.environ.get(PRETEND_KEY) if pretended: # we use meta here since the pretended version @@ -149,7 +146,6 @@ def get_version( config.fallback_version = fallback_version config.parse = parse config.git_describe_command = git_describe_command - config.enabled = True return _get_version(config) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index 88e65d96..d548c818 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -49,7 +49,6 @@ class Configuration(object): parse = None _tag_regex = None _absolute_root = None - enabled = False def __init__(self, relative_to=None, root="."): # TODO: @@ -107,15 +106,18 @@ def tag_regex(self): 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) + 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. """ - if not os.path.isfile(name): - return cls() with open(name) as strm: defn = __import__("toml").load(strm) - config = cls() - vars(config).update(defn.get("tool", {}).get("setuptools_scm", {})) - return config + return cls()._load(defn.get("tool", {})["setuptools_scm"]) diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py index a1904d98..c5cc25d8 100644 --- a/src/setuptools_scm/integration.py +++ b/src/setuptools_scm/integration.py @@ -2,7 +2,7 @@ from .version import _warn_if_setuptools_outdated from .config import Configuration -from .utils import do +from .utils import do, trace_exception from . import get_version, _get_version @@ -32,6 +32,8 @@ def find_files(path=""): def infer_version(dist): - version = _get_version(Configuration.from_file()) - if version: - dist.metadata.version = version + 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 5b59005a..d2c5b635 100644 --- a/src/setuptools_scm/utils.py +++ b/src/setuptools_scm/utils.py @@ -10,6 +10,7 @@ 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/testing/test_config.py b/testing/test_config.py index dcf48096..38bc8f4c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -24,6 +24,5 @@ def test_tag_regex(tag, expected_version): def test_config_from_pyproject(tmpdir): fn = tmpdir / "pyproject.toml" - fn.write_text("[tool.setuptools_scm]\nenabled = true\n", encoding="utf-8") - config = Configuration.from_file(str(fn)) - assert config.enabled + 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 index 2a245597..a0d11430 100644 --- a/testing/test_integration.py +++ b/testing/test_integration.py @@ -8,7 +8,6 @@ def test_pyproject_support(tmpdir, monkeypatch): pkg = tmpdir.ensure("package", dir=42) pkg.join("pyproject.toml").write( """[tool.setuptools_scm] -enabled = true fallback_version = "12.34" """ ) From 0fa62726ea81919d02166e7c1b1372a11dec3b4d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 24 Nov 2019 19:45:56 -0500 Subject: [PATCH 11/11] Update changelog and add documentation about usage of the feature. --- CHANGELOG.rst | 6 ++++++ README.rst | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c6a1968e..c30f2bce 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 f55c6df8..ebad26de 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 `_ and +`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: