From c212b764006952872b9a37ad38f593fbde34a471 Mon Sep 17 00:00:00 2001 From: jrfk Date: Sat, 4 Oct 2025 00:44:00 +0900 Subject: [PATCH 1/5] ci: set up github actions for automated build and release --- .github/workflows/determine_version.py | 45 ++++++++++ .github/workflows/workflow.yml | 119 +++++++++++++++++++++++++ pyproject.toml | 13 ++- uv.lock | 30 +++---- 4 files changed, 191 insertions(+), 16 deletions(-) create mode 100755 .github/workflows/determine_version.py create mode 100644 .github/workflows/workflow.yml diff --git a/.github/workflows/determine_version.py b/.github/workflows/determine_version.py new file mode 100755 index 0000000..b44736d --- /dev/null +++ b/.github/workflows/determine_version.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import os +import pathlib +import re +import sys + +PYPROJECT_PATH = pathlib.Path("pyproject.toml") + + +def main() -> int: + try: + text = PYPROJECT_PATH.read_text(encoding="utf-8") + except FileNotFoundError: + print("::error file=pyproject.toml::pyproject.toml not found", file=sys.stderr) + return 1 + + match = re.search(r'^version\s*=\s*"([^\"]+)"', text, re.MULTILINE) + if not match: + print("::error file=pyproject.toml::version not found in pyproject.toml", file=sys.stderr) + return 1 + + base_version = match.group(1) + + github_ref = os.environ.get("GITHUB_REF", "") + github_ref_name = os.environ.get("GITHUB_REF_NAME", "") + github_sha = os.environ.get("GITHUB_SHA", "") + github_run_number = os.environ.get("GITHUB_RUN_NUMBER", "") + + if github_ref.startswith("refs/tags/"): + tag_version = github_ref_name[1:] if github_ref_name.startswith("v") else github_ref_name + package_version = tag_version + else: + short_sha = github_sha[:7] + package_version = f"{base_version}.dev{github_run_number}+git.{short_sha}" + + print(f"::notice::Using package version: {package_version}", file=sys.stderr) + print(f"package_version={package_version}") + print(f"base_version={base_version}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..841f58f --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,119 @@ +# ref: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ +name: build + +on: push + +jobs: + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + env: + UV_PYTHON_INSTALL_ALLOW_PRERELEASE: 1 + outputs: + package-version: ${{ steps.meta.outputs.package_version }} + base-version: ${{ steps.meta.outputs.base_version }} + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install Python 3.14 + run: uv python install 3.14 + - name: Determine package version + id: meta + shell: bash + run: | + set -euo pipefail + .github/workflows/determine_version.py >> "${GITHUB_OUTPUT}" + - name: Build + run: uv build --package-version "${{ steps.meta.outputs.package_version }}" + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: + - build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/atv + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-to-pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/atv + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: Sign the Python 🐍 distribution 📦 with Sigstore and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + "${GITHUB_REF_NAME}" + --repo "${GITHUB_REPOSITORY}" + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + "${GITHUB_REF_NAME}" dist/** + --repo "${GITHUB_REPOSITORY}" diff --git a/pyproject.toml b/pyproject.toml index f1f8210..c22c35d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "atv" -version = "0.1.1" +version = "0.1.2" description = "atv(async task viewer): a TUI tool for experimenting with Python 3.14 asyncio ps/pstree features" readme = "README.md" requires-python = ">=3.14" @@ -9,8 +9,19 @@ dependencies = [ "psutil>=5.9", ] +[project.urls] +Repository = "https://github.com/jrfk/atv" + [project.scripts] atv = "atv.cli:main" [tool.uv] package = true + +[build-system] +requires = ["uv_build >= 0.8.17, <0.9.0"] +build-backend = "uv_build" + +[tool.uv.build-backend] +module-name = "atv" +module-root = "" diff --git a/uv.lock b/uv.lock index 2feba8b..708d386 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,21 @@ version = 1 revision = 3 requires-python = ">=3.14" +[[package]] +name = "atv" +version = "0.1.1" +source = { editable = "." } +dependencies = [ + { name = "psutil" }, + { name = "textual" }, +] + +[package.metadata] +requires-dist = [ + { name = "psutil", specifier = ">=5.9" }, + { name = "textual", specifier = ">=0.57,<0.61" }, +] + [[package]] name = "linkify-it-py" version = "2.0.3" @@ -93,21 +108,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] -[[package]] -name = "atv" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "psutil" }, - { name = "textual" }, -] - -[package.metadata] -requires-dist = [ - { name = "psutil", specifier = ">=5.9" }, - { name = "textual", specifier = ">=0.57,<0.61" }, -] - [[package]] name = "textual" version = "0.60.1" From 47659ffc31a0e7a71754a5c84eba96a38c057cf8 Mon Sep 17 00:00:00 2001 From: jrfk Date: Sat, 4 Oct 2025 00:47:32 +0900 Subject: [PATCH 2/5] build(deps): update atv package version --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 708d386..3ef6d6a 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.14" [[package]] name = "atv" -version = "0.1.1" +version = "0.1.2" source = { editable = "." } dependencies = [ { name = "psutil" }, From 73c93206d40b605ad0886815e72146f8e00de75c Mon Sep 17 00:00:00 2001 From: jrfk Date: Sat, 4 Oct 2025 01:16:58 +0900 Subject: [PATCH 3/5] refactor(build): use hatch for dynamic versioning --- .github/workflows/determine_version.py | 45 -------------------------- .github/workflows/workflow.yml | 12 ++----- atv/__about__.py | 34 +++++++++++++++++++ atv/__init__.py | 7 +++- pyproject.toml | 29 +++++++++++++---- uv.lock | 1 - 6 files changed, 65 insertions(+), 63 deletions(-) delete mode 100755 .github/workflows/determine_version.py create mode 100644 atv/__about__.py diff --git a/.github/workflows/determine_version.py b/.github/workflows/determine_version.py deleted file mode 100755 index b44736d..0000000 --- a/.github/workflows/determine_version.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import os -import pathlib -import re -import sys - -PYPROJECT_PATH = pathlib.Path("pyproject.toml") - - -def main() -> int: - try: - text = PYPROJECT_PATH.read_text(encoding="utf-8") - except FileNotFoundError: - print("::error file=pyproject.toml::pyproject.toml not found", file=sys.stderr) - return 1 - - match = re.search(r'^version\s*=\s*"([^\"]+)"', text, re.MULTILINE) - if not match: - print("::error file=pyproject.toml::version not found in pyproject.toml", file=sys.stderr) - return 1 - - base_version = match.group(1) - - github_ref = os.environ.get("GITHUB_REF", "") - github_ref_name = os.environ.get("GITHUB_REF_NAME", "") - github_sha = os.environ.get("GITHUB_SHA", "") - github_run_number = os.environ.get("GITHUB_RUN_NUMBER", "") - - if github_ref.startswith("refs/tags/"): - tag_version = github_ref_name[1:] if github_ref_name.startswith("v") else github_ref_name - package_version = tag_version - else: - short_sha = github_sha[:7] - package_version = f"{base_version}.dev{github_run_number}+git.{short_sha}" - - print(f"::notice::Using package version: {package_version}", file=sys.stderr) - print(f"package_version={package_version}") - print(f"base_version={base_version}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 841f58f..5d4c1ff 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -9,26 +9,18 @@ jobs: runs-on: ubuntu-latest env: UV_PYTHON_INSTALL_ALLOW_PRERELEASE: 1 - outputs: - package-version: ${{ steps.meta.outputs.package_version }} - base-version: ${{ steps.meta.outputs.base_version }} steps: - uses: actions/checkout@v5 with: + fetch-depth: 0 persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v6 - name: Install Python 3.14 run: uv python install 3.14 - - name: Determine package version - id: meta - shell: bash - run: | - set -euo pipefail - .github/workflows/determine_version.py >> "${GITHUB_OUTPUT}" - name: Build - run: uv build --package-version "${{ steps.meta.outputs.package_version }}" + run: uv build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: diff --git a/atv/__about__.py b/atv/__about__.py new file mode 100644 index 0000000..c52cb1a --- /dev/null +++ b/atv/__about__.py @@ -0,0 +1,34 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] +else: + VERSION_TUPLE = object + COMMIT_ID = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID + +__version__ = version = '0.1.2.dev2+g47659ffc3.d20251003' +__version_tuple__ = version_tuple = (0, 1, 2, 'dev2', 'g47659ffc3.d20251003') + +__commit_id__ = commit_id = None diff --git a/atv/__init__.py b/atv/__init__.py index 87df7bd..29c3d2e 100644 --- a/atv/__init__.py +++ b/atv/__init__.py @@ -2,6 +2,11 @@ from __future__ import annotations +from importlib import metadata + __all__ = ["__version__"] -__version__ = "0.1.0" +try: + from .__about__ import __version__ +except ImportError: # pragma: no cover - fallback for editable installs without version file + __version__ = metadata.version("atv") diff --git a/pyproject.toml b/pyproject.toml index c22c35d..b15ef34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "atv" -version = "0.1.2" description = "atv(async task viewer): a TUI tool for experimenting with Python 3.14 asyncio ps/pstree features" readme = "README.md" requires-python = ">=3.14" @@ -8,6 +7,7 @@ dependencies = [ "textual>=0.57,<0.61", "psutil>=5.9", ] +dynamic = ["version"] [project.urls] Repository = "https://github.com/jrfk/atv" @@ -19,9 +19,26 @@ atv = "atv.cli:main" package = true [build-system] -requires = ["uv_build >= 0.8.17, <0.9.0"] -build-backend = "uv_build" +requires = [ + "hatchling>=1.25", + "hatch-vcs>=0.4", +] +build-backend = "hatchling.build" + +[tool.hatch.version] +source = "vcs" +tag-pattern = "v(?P.+)" +fallback-version = "0.0.0" + +[tool.hatch.build.hooks.vcs] +version-file = "atv/__about__.py" -[tool.uv.build-backend] -module-name = "atv" -module-root = "" +[tool.hatch.build.targets.sdist] +exclude = [ + "dist", +] + +[tool.hatch.build.targets.wheel] +exclude = [ + "dist", +] diff --git a/uv.lock b/uv.lock index 3ef6d6a..e6332c4 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,6 @@ requires-python = ">=3.14" [[package]] name = "atv" -version = "0.1.2" source = { editable = "." } dependencies = [ { name = "psutil" }, From de47b3c4636652051fdf9b512ae043e974857d2c Mon Sep 17 00:00:00 2001 From: jrfk Date: Sat, 4 Oct 2025 01:23:25 +0900 Subject: [PATCH 4/5] docs(readme): add project badges --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index ae2ed46..a6f3445 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # atv 🌱 +[![PyPI][pypi-badge]][pypi-link] +[![Python versions][versions-badge]][pypi-link] +[![GitHub Actions][github-ci-badge]][github-ci-link] +[![PyPI - Download][downloads-badge]][downloads-link] +[![License][license-badge]][license-link] + image @@ -50,3 +56,13 @@ uvx --from . atv ## License This project is licensed under the MIT License. See `LICENSE` for details. + +[pypi-badge]: https://img.shields.io/pypi/v/atv.svg +[pypi-link]: https://pypi.org/project/atv/ +[versions-badge]: https://img.shields.io/pypi/pyversions/atv.svg +[github-ci-badge]: https://github.com/jrfk/atv/actions/workflows/workflow.yml/badge.svg +[github-ci-link]: https://github.com/jrfk/atv/actions/workflows/workflow.yml +[downloads-badge]: https://img.shields.io/pypi/dm/atv.svg +[downloads-link]: https://pypistats.org/packages/atv +[license-badge]: https://img.shields.io/pypi/l/atv.svg +[license-link]: https://github.com/jrfk/atv/blob/main/LICENSE From 4aa971123b1f16b41b07f5b8702676d7db94d8ef Mon Sep 17 00:00:00 2001 From: jrfk Date: Sat, 4 Oct 2025 01:34:21 +0900 Subject: [PATCH 5/5] chore(readme): update badges --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index a6f3445..e6f42c6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![PyPI][pypi-badge]][pypi-link] [![Python versions][versions-badge]][pypi-link] [![GitHub Actions][github-ci-badge]][github-ci-link] -[![PyPI - Download][downloads-badge]][downloads-link] [![License][license-badge]][license-link] image @@ -62,7 +61,5 @@ This project is licensed under the MIT License. See `LICENSE` for details. [versions-badge]: https://img.shields.io/pypi/pyversions/atv.svg [github-ci-badge]: https://github.com/jrfk/atv/actions/workflows/workflow.yml/badge.svg [github-ci-link]: https://github.com/jrfk/atv/actions/workflows/workflow.yml -[downloads-badge]: https://img.shields.io/pypi/dm/atv.svg -[downloads-link]: https://pypistats.org/packages/atv -[license-badge]: https://img.shields.io/pypi/l/atv.svg +[license-badge]: https://img.shields.io/github/license/jrfk/atv [license-link]: https://github.com/jrfk/atv/blob/main/LICENSE