jbweston / miniver

Like Versioneer, but smaller
Creative Commons Zero v1.0 Universal
53 stars 10 forks source link

Support setup.cfg #60

Open aeisenbarth opened 1 year ago

aeisenbarth commented 1 year ago

Setup.py has been replaced by setup.cfg and is now moving towards pyproject.toml. Probably there aren't many projects on setup.py anymore.

While I was cleaning up an old setup.py & miniver project, migration seemed straight forward (with https://github.com/pypa/setuptools/issues/2570 now implemented).

Except that cmdclass dict values must be module paths to a public, top-level class. Since the change 1c9f8ca1dfc759488a078b67f14c8ed41300eb47, the build command classes can only be created through function calls, which does not work for the declarative setup.cfg.

I tried working around by creating a helper object with lazy properties (so I can preserve the lazy import of super classes), but that is not detected by setuptools and gives an error.

_version.py … ```python PACKAGE_SOURCE_PATH = os.path.dirname(__file__) class CmdClass: build_py = None sdist = None def __init__(self, pkg_source_path): self._pkg_source_path = pkg_source_path @property def build_py(self): from setuptools.command.build_py import build_py as build_py_orig pkg_source_path = self._pkg_source_path class _build_py(build_py_orig): def run(self): super().run() src_marker = "".join(["src", os.path.sep]) if pkg_source_path.startswith(src_marker): path = pkg_source_path[len(src_marker):] else: path = pkg_source_path _write_version(os.path.join(self.build_lib, path, STATIC_VERSION_FILE)) return _build_py @property def sdist(self): from setuptools.command.sdist import sdist as sdist_orig pkg_source_path = self._pkg_source_path class _sdist(sdist_orig): def make_release_tree(self, base_dir, files): super().make_release_tree(base_dir, files) _write_version(os.path.join(base_dir, pkg_source_path, STATIC_VERSION_FILE)) return _sdist cmdclass = CmdClass(pkg_source_path=PACKAGE_SOURCE_PATH) def get_cmdclass(pkg_source_path=PACKAGE_SOURCE_PATH): cmds = CmdClass(pkg_source_path=pkg_source_path) return dict(sdist=cmds.sdist, build_py=cmds.build_py) # Unlike expected, setup.cfg cannot access cmdclass.build_py, but the following would work: # build_py = cmdclass.build_py # sdist = cmdclass.sdist ```
setup.cfg … ```ini [options] cmdclass = build_py = my_package._version.cmdclass.build_py sdist = my_package._version.cmdclass.sdist ```
ModuleNotFoundError: __path__ attribute not found on 'my_package._version' while trying to find 'my_package._version.cmdclass'

In short, first I think it would be good to support setup.cfg, and second, I am not sure whether it is feasible without moving the build command classes to the top level.

jsacrist commented 1 year ago

I have only played with standalone setup.py and with setup.py + pyproject.toml, so I'm not very familiar with setup.cfg

From my understanding, in a setting that has setup.py + pyproject.toml you can specify under the [project] section that "version" is dynamic:

#### pyproject.toml
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["."]

[project]
name = "some_package"
# ... some other unrelated fields
dynamic = ["version", "readme"]

[tool.setuptools.dynamic]
version = {attr = "some_package.__version__"}
readme = {file = "README.md", content-type = "text/markdown"}
#### setup.py
from setuptools import setup

pkg = "some_package"

# Loads _version.py module without importing the whole package.
def get_version_and_cmdclass(package_name):
    import os
    from importlib.util import module_from_spec, spec_from_file_location

    spec = spec_from_file_location("version", os.path.join(package_name, "_version.py"))
    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    return module.__version__, module.get_cmdclass(pkg)

version, cmdclass = get_version_and_cmdclass(pkg)
setup(
    version=version,
    cmdclass=cmdclass,
)

Is it not the case that you can do something similar with a setting if you have setup.py and with setup.cfg ?

aeisenbarth commented 1 year ago

We don't have setup.py anymore, just pyproject.toml for build-system and tools and setup.cfg for project metadata and requirements. Thanks for the dynamic version trick, I didn't know about that!

This request may also become obsolete when we can move to have everything in pyproject.toml. Although then still, it would be great being able to point cmdclass to a public class without needing a setup.py for that.

In the declarative formats (setup.cfg, and maybe in future pyproject.toml), the command class can not be dynamically returned by a getter function like get_cmdclass, but must be a public object so it can be accessed through an object path similar to entry points syntax without colon:

cmdclass =
    build_py = mypackage.some_module.build_py