diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index c2f7d5b1..17d7410c 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -55,7 +55,7 @@ jobs: fail-fast: false matrix: python_version: [ '3.6', '3.9', 'pypy3' ] - installer: ["pip install", easy_install] + installer: ["pip install"] name: check self install - Python ${{ matrix.python_version }} via ${{ matrix.installer }} steps: - uses: actions/checkout@v1 @@ -67,7 +67,7 @@ jobs: # self install testing needs some clarity # so its being executed without any other tools running # setuptools smaller 52 is needed too di easy_install - - run: pip install -U "setuptools<52" + - run: pip install -U "setuptools<52" tomli - run: python setup.py egg_info - run: python setup.py sdist - run: ${{ matrix.installer }} dist/* diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e57bd018..08a1a9d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,11 @@ +v6.1.1 +======= + +* fix #605: completely disallow bdist_egg - modern enough setuptools>=45 uses pip +* fix #606: re-integrate and harden toml parsing +* fix #597: harden and expand support for figuring the current distribution name from + `pyproject.toml` (`project.name` or `tool.setuptools_scm.dist_name`) section or `setup.cfg` (`metadata.name`) + v6.1.0 ====== diff --git a/pyproject.toml b/pyproject.toml index 452a8278..2ab901eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=45", "wheel"] +requires = ["setuptools>=45", "wheel", "tomli"] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 80d091eb..2f06aca8 100644 --- a/setup.py +++ b/setup.py @@ -28,13 +28,8 @@ def scm_config(): has_entrypoints = os.path.isdir(egg_info) import pkg_resources - pkg_resources.require("setuptools>=45") - sys.path.insert(0, src) pkg_resources.working_set.add_entry(src) - # FIXME: remove debug - print(src) - print(pkg_resources.working_set) from setuptools_scm.hacks import parse_pkginfo from setuptools_scm.git import parse as parse_git @@ -50,13 +45,22 @@ def parse(root): version_scheme=guess_next_dev_version, local_scheme=get_local_node_and_date ) + from setuptools.command.bdist_egg import bdist_egg as original_bdist_egg + + class bdist_egg(original_bdist_egg): + def run(self): + raise SystemExit("bdist_egg is forbidden, please update to setuptools>=45") + if has_entrypoints: - return dict(use_scm_version=config) + return dict(use_scm_version=config, cmdclass={"bdist_egg": bdist_egg}) else: from setuptools_scm import get_version - return dict(version=get_version(root=here, parse=parse, **config)) + return dict( + version=get_version(root=here, parse=parse, **config), + cmdclass={"bdist_egg": bdist_egg}, + ) if __name__ == "__main__": - setuptools.setup(**scm_config()) + setuptools.setup(setup_requires=["setuptools>=45", "tomli"], **scm_config()) diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py index 473402e8..dad28a9f 100644 --- a/src/setuptools_scm/config.py +++ b/src/setuptools_scm/config.py @@ -55,6 +55,12 @@ def _check_absolute_root(root, relative_to): return os.path.abspath(root) +def _lazy_tomli_load(data): + from tomli import loads + + return loads(data) + + class Configuration: """Global configuration model""" @@ -163,16 +169,46 @@ def tag_regex(self, value): self._tag_regex = _check_tag_regex(value) @classmethod - def from_file(cls, name="pyproject.toml", dist_name=None): + def from_file( + cls, + name: str = "pyproject.toml", + dist_name=None, # type: str | None + _load_toml=_lazy_tomli_load, + ): """ 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, encoding="UTF-8") as strm: - defn = __import__("toml").load(strm) - section = defn.get("tool", {})["setuptools_scm"] + data = strm.read() + defn = _load_toml(data) + try: + section = defn.get("tool", {})["setuptools_scm"] + except LookupError: + raise FileNotFoundError( + f"{name} does not contain a tool.setuptools_scm section" + ) from None + if "dist_name" in section: + if dist_name is None: + dist_name = section.pop("dist_name") + else: + assert dist_name == section["dist_name"] + del section["dist_name"] + if dist_name is None: + if "project" in defn: + # minimal pep 621 support for figuring the pretend keys + dist_name = defn["project"].get("name") + if dist_name is None: + # minimal effort to read dist_name off setup.cfg metadata + import configparser + + parser = configparser.ConfigParser() + parser.read(["setup.cfg"]) + dist_name = parser.get("metadata", "name", fallback=None) + return cls(dist_name=dist_name, **section) diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py index 29c85966..42d3ef73 100644 --- a/src/setuptools_scm/integration.py +++ b/src/setuptools_scm/integration.py @@ -1,12 +1,15 @@ +import warnings + +import setuptools + from . import _get_version from . import Configuration from .utils import do from .utils import iter_entry_points from .utils import trace -from .utils import trace_exception -def version_keyword(dist, keyword, value): +def version_keyword(dist: setuptools.Distribution, keyword, value): if not value: return if value is True: @@ -39,17 +42,7 @@ def find_files(path=""): return [] -def _args_from_toml(name="pyproject.toml"): - # todo: more sensible config initialization - # move this helper back to config and unify it with the code from get_config - import tomli - - with open(name, encoding="UTF-8") as strm: - defn = tomli.load(strm) - return defn.get("tool", {})["setuptools_scm"] - - -def infer_version(dist): +def infer_version(dist: setuptools.Distribution): trace( "finalize hook", vars(dist.metadata), @@ -57,6 +50,7 @@ def infer_version(dist): dist_name = dist.metadata.name try: config = Configuration.from_file(dist_name=dist_name) - except Exception: - return trace_exception() - dist.metadata.version = _get_version(config) + except FileNotFoundError as e: + warnings.warn(str(e)) + else: + dist.metadata.version = _get_version(config) diff --git a/testing/test_integration.py b/testing/test_integration.py index 56a1a8f4..dba02d19 100644 --- a/testing/test_integration.py +++ b/testing/test_integration.py @@ -40,30 +40,64 @@ def test_pyproject_support(tmpdir, monkeypatch): assert res == "12.34" -def test_pyproject_support_with_git(tmpdir, monkeypatch, wd): - pytest.importorskip("toml") - pkg = tmpdir.join("wd") - pkg.join("pyproject.toml").write("""[tool.setuptools_scm]""") - pkg.join("setup.py").write( - "__import__('setuptools').setup(name='setuptools_scm_example')" - ) - res = do((sys.executable, "setup.py", "--version"), pkg) +PYPROJECT_FILES = { + "setup.py": "[tool.setuptools_scm]", + "setup.cfg": "[tool.setuptools_scm]", + "pyproject tool.setuptools_scm": ( + "[tool.setuptools_scm]\ndist_name='setuptools_scm_example'" + ), + "pyproject.project": ( + "[project]\nname='setuptools_scm_example'\n[tool.setuptools_scm]" + ), +} + +SETUP_PY_PLAIN = "__import__('setuptools').setup()" +SETUP_PY_WITH_NAME = "__import__('setuptools').setup(name='setuptools_scm_example')" + +SETUP_PY_FILES = { + "setup.py": SETUP_PY_WITH_NAME, + "setup.cfg": SETUP_PY_PLAIN, + "pyproject tool.setuptools_scm": SETUP_PY_PLAIN, + "pyproject.project": SETUP_PY_PLAIN, +} + +SETUP_CFG_FILES = { + "setup.py": "", + "setup.cfg": "[metadata]\nname=setuptools_scm_example", + "pyproject tool.setuptools_scm": "", + "pyproject.project": "", +} + +with_metadata_in = pytest.mark.parametrize( + "metadata_in", + ["setup.py", "setup.cfg", "pyproject tool.setuptools_scm", "pyproject.project"], +) + + +@with_metadata_in +def test_pyproject_support_with_git(wd, metadata_in): + pytest.importorskip("tomli") + wd.write("pyproject.toml", PYPROJECT_FILES[metadata_in]) + wd.write("setup.py", SETUP_PY_FILES[metadata_in]) + wd.write("setup.cfg", SETUP_CFG_FILES[metadata_in]) + res = wd((sys.executable, "setup.py", "--version")) assert res.endswith("0.1.dev0") -def test_pretend_version(tmpdir, monkeypatch, wd): +def test_pretend_version(monkeypatch, wd): monkeypatch.setenv(PRETEND_KEY, "1.0.0") assert wd.get_version() == "1.0.0" assert wd.get_version(dist_name="ignored") == "1.0.0" -def test_pretend_version_named_pyproject_integration(tmpdir, monkeypatch, wd): - test_pyproject_support_with_git(tmpdir, monkeypatch, wd) +@with_metadata_in +def test_pretend_version_named_pyproject_integration(monkeypatch, wd, metadata_in): + test_pyproject_support_with_git(wd, metadata_in) monkeypatch.setenv( PRETEND_KEY_NAMED.format(name="setuptools_scm_example".upper()), "3.2.1" ) - res = do((sys.executable, "setup.py", "--version"), tmpdir / "wd") + res = wd((sys.executable, "setup.py", "--version")) assert res.endswith("3.2.1")