diff --git a/.gitattributes b/.gitattributes index 4630853702..430abc6838 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ -# force LF for pyproject to make hashFiles in CI consistent (windows <3) -# (see https://github.com/actions/runner/issues/498) -pyproject.toml text eol=lf +# - `eol=lf` to make hashFiles in CI consistent (windows <3) (see https://github.com/actions/runner/issues/498) +# - `export-subst` makes git expand/replace the `describe-subst` field value when creating an archive +pyproject.toml text eol=lf export-subst diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 281970baa1..4f46ccc246 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -156,6 +156,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: + fetch-depth: '0' persist-credentials: false - name: Set up environment @@ -243,6 +244,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: + fetch-depth: '0' persist-credentials: false - name: Set up environment diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4572d2d14b..7de10217a3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,6 +19,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: + fetch-depth: '0' persist-credentials: false - name: Set up environment diff --git a/.gitignore b/.gitignore index 3c66053d3d..b69ed18466 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ coverage.xml __pypackages__/ .python-version uv.lock +disnake/_version.py diff --git a/.readthedocs.yml b/.readthedocs.yml index f20bba070c..3bd897e1a2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -15,6 +15,16 @@ build: - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --no-default-groups --extra docs install: - "true" + post_checkout: + - | + while true; do + TAG=$(git tag --merged HEAD --sort=-committerdate 'v*' | head -n 1) + if [ -n "$TAG" ]; then + echo "✅ Found first reachable tag: $TAG" + break + fi + git fetch --deepen=50 + done sphinx: configuration: docs/conf.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 8f98414e11..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include disnake/bin/* -include disnake/py.typed -include disnake/ext/commands/py.typed -include disnake/ext/tasks/py.typed -global-exclude *.py[cod] -exclude uv.lock -exclude noxfile.py diff --git a/changelog/1323.misc.rst b/changelog/1323.misc.rst new file mode 100644 index 0000000000..d6b1d715ff --- /dev/null +++ b/changelog/1323.misc.rst @@ -0,0 +1 @@ +Use hatchling and versioningit for building disnake rather than using setuptools. diff --git a/disnake/__init__.py b/disnake/__init__.py index 92eae6bec7..a674f5003f 100644 --- a/disnake/__init__.py +++ b/disnake/__init__.py @@ -14,14 +14,17 @@ __author__ = "Rapptz, EQUENOS" __license__ = "MIT" __copyright__ = "Copyright 2015-present Rapptz, 2021-present EQUENOS" -__version__ = "2.12.0a" __path__ = __import__("pkgutil").extend_path(__path__, __name__) import logging -from typing import Literal, NamedTuple from . import abc as abc, opus as opus, ui as ui, utils as utils # explicitly re-export modules +from ._version import ( + VersionInfo as VersionInfo, + __version__ as __version__, + version_info as version_info, +) from .activity import * from .app_commands import * from .appinfo import * @@ -77,17 +80,4 @@ from .welcome_screen import * from .widget import * - -class VersionInfo(NamedTuple): - major: int - minor: int - micro: int - releaselevel: Literal["alpha", "beta", "candidate", "final"] - serial: int - - -# fmt: off -version_info: VersionInfo = VersionInfo(major=2, minor=12, micro=0, releaselevel="alpha", serial=0) -# fmt: on - logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/disnake/_version.pyi b/disnake/_version.pyi new file mode 100644 index 0000000000..a935e0ebb4 --- /dev/null +++ b/disnake/_version.pyi @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MIT + +from typing import Literal, NamedTuple + +__version__: str + +class VersionInfo(NamedTuple): + major: int + minor: int + micro: int + releaselevel: Literal["alpha", "beta", "candidate", "final"] + serial: int + +version_info: VersionInfo diff --git a/docs/conf.py b/docs/conf.py index bdbf66be34..39e7d2838c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ import sys from typing import Any, Optional +import versioningit from sphinx.application import Sphinx # If extensions (or modules to document with autodoc) are in another directory, @@ -109,9 +110,16 @@ # # The full version, including alpha/beta/rc tags. release = importlib.metadata.version("disnake") - # The short X.Y version. version = ".".join(release.split(".")[:2]) +# The release for the next release +next_release = versioningit.get_next_version(os.path.abspath("..")) +next_version = ".".join((next_release).split(".", 2)[:2]) + +rst_prolog += f""" +.. |vnext_full| replace:: {next_release} +.. |vnext| replace:: {next_version} +""" _IS_READTHEDOCS = bool(os.getenv("READTHEDOCS")) diff --git a/docs/extensions/versionchange.py b/docs/extensions/versionchange.py index 49c66a95cf..1239b14c2a 100644 --- a/docs/extensions/versionchange.py +++ b/docs/extensions/versionchange.py @@ -18,8 +18,7 @@ def run(self): # If the argument is |vnext|, replace with config version if self.arguments and self.arguments[0] == "|vnext|": # Get the version from the Sphinx config - version = self.env.config.version - self.arguments[0] = version + self.arguments[0] = self.env.config.next_version return super().run() @@ -28,6 +27,8 @@ def setup(app: Sphinx) -> SphinxExtensionMeta: app.add_directive("versionchanged", VersionAddedNext, override=True) app.add_directive("deprecated", VersionAddedNext, override=True) + app.add_config_value("next_version", None, "env", types=[str]) + return { "parallel_read_safe": True, "parallel_write_safe": True, diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 3f0cafccaf..d702658b9f 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -13,7 +13,7 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. Please see :ref:`version_guarantees` for more information. -.. towncrier-draft-entries:: |release| [UNRELEASED] +.. towncrier-draft-entries:: |vnext_full| [UNRELEASED] .. towncrier release notes start diff --git a/noxfile.py b/noxfile.py index 4594656c32..b5b9807b10 100755 --- a/noxfile.py +++ b/noxfile.py @@ -74,11 +74,11 @@ def __post_init__(self) -> None: ExecutionGroup( sessions=("pyright",), python=python, - pyright_paths=("disnake", "tests", "examples", "noxfile.py", "setup.py"), + pyright_paths=("disnake", "tests", "examples", "noxfile.py"), project=True, extras=("speed", "voice"), groups=("test", "nox"), - dependencies=("setuptools", "pytz", "requests"), # needed for type checking + dependencies=("pytz", "requests"), # needed for type checking ) for python in ALL_PYTHONS ), @@ -91,17 +91,18 @@ def __post_init__(self) -> None: # codemodding and pyright ExecutionGroup( sessions=("codemod", "autotyping", "pyright"), - pyright_paths=("scripts",), + pyright_paths=("scripts/codemods", "scripts/ci"), groups=("codemod",), ), # the other sessions, they don't need pyright, but they need to run ExecutionGroup( - sessions=("lint", "slotscheck", "check-manifest", "check-wheel-contents"), + sessions=("lint", "slotscheck", "check-wheel-contents"), groups=("tools",), ), # build ExecutionGroup( - sessions=("build",), + sessions=("build", "pyright"), + pyright_paths=("scripts/versioning.py",), groups=("build",), ), ## testing @@ -250,13 +251,6 @@ def lint(session: nox.Session) -> None: session.run("prek", "run", "--all-files", *session.posargs) -@nox.session(name="check-manifest", tags=["misc"]) -def check_manifest(session: nox.Session) -> None: - """Run check-manifest.""" - install_deps(session) - session.run("check-manifest", "-v") - - @nox.session(python=get_version_for_session("slotscheck"), tags=["misc"]) def slotscheck(session: nox.Session) -> None: """Run slotscheck.""" @@ -264,7 +258,7 @@ def slotscheck(session: nox.Session) -> None: session.run("python", "-m", "slotscheck", "--verbose", "-m", "disnake") -@nox.session(requires=["check-manifest"], tags=["misc"]) +@nox.session(tags=["misc"]) def build(session: nox.Session) -> None: """Build a dist.""" install_deps(session) diff --git a/pyproject.toml b/pyproject.toml index f7c4d6996e..4e97769f0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT [build-system] -requires = ["setuptools>=77.0.3"] -build-backend = "setuptools.build_meta" +requires = ["hatchling>=1.27.0", "versioningit>=3.3.0,<4", "packaging"] +build-backend = "hatchling.build" [project] name = "disnake" @@ -63,6 +63,7 @@ docs = [ "towncrier==23.6.0", "sphinx-notfound-page==0.8.3", "sphinxext-opengraph==0.9.1", + "versioningit>=3.3.0,<4", ] [dependency-groups] @@ -85,7 +86,6 @@ ruff = [ tools = [ "prek>=0.2.0", "slotscheck==0.19.1", - "check-manifest==0.50", "check-wheel-contents~=0.6.3", { include-group = "ruff" }, ] @@ -114,15 +114,60 @@ test = [ build = [ "build>=1.2.2.post1", "twine>=6.1.0", + "versioningit>=3.3.0,<4", ] -[tool.setuptools.packages.find] -where = ["."] -include = ["disnake*"] - [tool.uv] required-version = ">=0.9.2" +[tool.hatch.build] +artifacts = ["disnake/_version.py"] + +[tool.hatch.build.targets.sdist] +only-include = [ + "disnake", + "scripts/versioning.py", +] + +[tool.hatch.version] +source = "versioningit" + +[tool.versioningit] +default-version = "0.0.0" + +[tool.versioningit.vcs] +method = "git-archive" +describe-subst = "$Format:%(describe:tags,match=v*)$" + +[tool.versioningit.format] +distance = "{base_version}a{distance}+{vcs}{rev}" +dirty = "{base_version}+d{build_date:%Y%m%d}" +distance-dirty = "{base_version}a{distance}+{vcs}{rev}.d{build_date:%Y%m%d}" + +[tool.versioningit.template-fields] +method = { module = "scripts.versioning", value = "template_fields"} + +[tool.versioningit.write] +file = "disnake/_version.py" +template = """ +# SPDX-License-Identifier: MIT + +from typing import Literal, NamedTuple + +__version__ = "{version}" + + +class VersionInfo(NamedTuple): + major: int + minor: int + micro: int + releaselevel: Literal["alpha", "beta", "candidate", "final"] + serial: int + +version_info: VersionInfo = VersionInfo{version_tuple} +""" + + [tool.ruff] line-length = 100 @@ -419,25 +464,6 @@ exclude_lines = [ ] -[tool.check-manifest] -ignore = [ - # CI - ".pre-commit-config.yaml", - ".readthedocs.yml", - ".libcst.codemod.yaml", - "noxfile.py", - # docs - "CONTRIBUTING.md", - "RELEASE.md", - "assets/**", - "changelog/**", - "docs/**", - "examples/**", - # tests - "tests/**", - "scripts/**", -] - [tool.check-wheel-contents] toplevel = ["disnake"] package = "disnake" diff --git a/scripts/versioning.py b/scripts/versioning.py new file mode 100644 index 0000000000..f4afab90d7 --- /dev/null +++ b/scripts/versioning.py @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +# This script runs as part of the wheel building process, +# all dependencies MUST be included in pyproject.toml +from typing import Any, Optional + +import packaging.version +import versioningit + + +def template_fields( + *, + version: str, + description: Optional[versioningit.VCSDescription], + base_version: Optional[str], + next_version: Optional[str], + params: dict[str, Any], +) -> dict[str, Any]: + """Implement a custom template_fields function for Disnake.""" + # params = copy.deepcopy(params) + parsed_version = packaging.version.parse(version) + fields: dict[str, Any] = {} + if description is not None: + fields.update(description.fields) + fields["branch"] = description.branch + fields["version"] = version + + releaselevels = { + "a": "alpha", + "b": "beta", + "rc": "candidate", + "": "final", + } + + if parsed_version.pre: + pre = parsed_version.pre + releaselevel = releaselevels.get(pre[0], "final") + serial = pre[1] + else: + releaselevel = "final" + serial = 0 + + fields["version_tuple"] = ( + parsed_version.major, + parsed_version.minor, + parsed_version.micro, + releaselevel, + serial, + ) + return fields diff --git a/setup.py b/setup.py deleted file mode 100644 index bd16e72006..0000000000 --- a/setup.py +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: MIT - -import re - -from setuptools import setup - -version = "" -with open("disnake/__init__.py", encoding="utf-8") as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) # pyright: ignore[reportOptionalMemberAccess] - -if not version: - msg = "version is not set" - raise RuntimeError(msg) - -if version.endswith(("a", "b", "rc")): - # append version identifier based on commit count - try: - import subprocess # noqa: TID251 - - p = subprocess.Popen( - ["git", "rev-list", "--count", "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - out, err = p.communicate() - if out: - version += out.decode("utf-8").strip() - p = subprocess.Popen( - ["git", "rev-parse", "--short", "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - out, err = p.communicate() - if out: - version += "+g" + out.decode("utf-8").strip() - except Exception: - pass - -setup( - version=version, -)