diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 00000000..7c510094 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,3 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..00a7b00c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..e1f43427 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,3 @@ +codecov: + notify: + after_n_builds: 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..b9ceec53 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + uses: ./.github/workflows/step_pre-commit.yml + tests: + needs: [ pre-commit ] + uses: ./.github/workflows/step_test.yml + pass: + if: always() + needs: [ pre-commit, tests ] + runs-on: ubuntu-latest + steps: + - uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index 4e35dd67..00000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: pre-commit - -on: - pull_request: - push: - branches: [main] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4ca4e41..747edf2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: release +name: 🚀 Release on: release: @@ -11,18 +11,21 @@ on: jobs: release: + name: 🚀 Release runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/fmf + permissions: + id-token: write # For pypi-publish steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: ${{ github.event_name == 'release' }} - - uses: actions/checkout@v2 - if: ${{ github.event_name == 'workflow_dispatch' }} + - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.ref }} - - name: Create dist - run: make wheel + if: ${{ github.event_name == 'workflow_dispatch' }} + - name: Build package + run: pipx hatch build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/step_pre-commit.yml b/.github/workflows/step_pre-commit.yml new file mode 100644 index 00000000..85c97eb7 --- /dev/null +++ b/.github/workflows/step_pre-commit.yml @@ -0,0 +1,16 @@ +on: + workflow_call: + +permissions: + contents: read + +jobs: + pre-commit: + name: Run pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/step_test.yml b/.github/workflows/step_test.yml new file mode 100644 index 00000000..d8dcc02b --- /dev/null +++ b/.github/workflows/step_test.yml @@ -0,0 +1,27 @@ +on: + workflow_call: + +permissions: + contents: read + +jobs: + checks: + name: Check 🐍 ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install hatch + run: pip install hatch + - name: Test package + run: hatch run cov:xml + - name: Upload coverage report + uses: codecov/codecov-action@v3 + with: + name: python-${{ matrix.python-version }} diff --git a/.packit.yaml b/.packit.yaml index cf35ac09..f59aedd8 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -5,15 +5,18 @@ synced_files: upstream_package_name: fmf downstream_package_name: fmf +# Epel9 fails to build with dynamic version. Need to create archive with PKG-INFO +# F37 works with setuptools_scm 7.0 actions: create-archive: - - make tarball + - "hatch build -t sdist" + - "sh -c 'echo dist/fmf-*.tar.gz'" get-current-version: - - make version + - "hatch version" srpm_build_deps: - - make - - python3-docutils + - hatch + - python3-hatch-vcs jobs: - job: copr_build diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index b4ac02bd..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include fmf.spec diff --git a/Makefile b/Makefile index 9ee405b3..0c50731a 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,10 @@ # Prepare variables TMP = $(CURDIR)/tmp -VERSION = $(shell grep ^Version fmf.spec | sed 's/.* //') -COMMIT = $(shell git rev-parse --short HEAD) -REPLACE_VERSION = "s/running from the source/$(VERSION) ($(COMMIT))/" +VERSION = $(hatch version) PACKAGE = fmf-$(VERSION) FILES = LICENSE README.rst \ - Makefile fmf.spec setup.py \ - examples fmf bin tests + Makefile fmf.spec pyproject.toml \ + examples fmf tests # Define special targets all: docs packages @@ -19,35 +17,25 @@ tmp: # Run the test suite, optionally with coverage test: tmp - pytest tests/unit -c tests/unit/pytest.ini + hatch run test:unit smoke: tmp - pytest tests/unit/test_smoke.py -c tests/unit/pytest.ini + hatch run test:smoke coverage: tmp - coverage run --source=fmf,bin -m py.test -c tests/unit/pytest.ini tests - coverage report - coverage annotate + hatch run cov:cov # Build documentation, prepare man page docs: man - cd docs && make html -man: source + hatch run docs:html +man: cp docs/header.txt $(TMP)/man.rst tail -n+7 README.rst >> $(TMP)/man.rst rst2man $(TMP)/man.rst > $(TMP)/$(PACKAGE)/fmf.1 # RPM packaging -source: clean tmp - mkdir -p $(TMP)/SOURCES - mkdir -p $(TMP)/$(PACKAGE) - cp -a $(FILES) $(TMP)/$(PACKAGE) - sed -i $(REPLACE_VERSION) $(TMP)/$(PACKAGE)/fmf/__init__.py -tarball: source man - cd $(TMP) && tar cfz SOURCES/$(PACKAGE).tar.gz $(PACKAGE) - @echo ./tmp/SOURCES/$(PACKAGE).tar.gz -version: - @echo "$(VERSION)" +tarball: man + hatch build -t sdist rpm: tarball rpmbuild --define '_topdir $(TMP)' -bb fmf.spec srpm: tarball @@ -57,10 +45,9 @@ packages: rpm srpm # Python packaging wheel: - python setup.py bdist_wheel - python3 setup.py bdist_wheel + hatch build upload: - twine upload dist/*.whl + hatch publish # Vim tags and cleanup diff --git a/bin/fmf b/bin/fmf deleted file mode 100755 index 0b8a45df..00000000 --- a/bin/fmf +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/python -# coding: utf-8 - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# fmf - Flexible Metadata Format -# Author: Petr Šplíchal -# -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# Copyright (c) 2018 Red Hat, Inc. -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation, either version 2 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be -# useful, but WITHOUT ANY WARRANTY; without even the implied -# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. -# -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -import sys - -import fmf.base -import fmf.cli -import fmf.utils - -try: - fmf.cli.main() -except fmf.utils.GeneralError as error: - if "--debug" in sys.argv: - raise - fmf.utils.log.error(error) - raise SystemExit(1) diff --git a/fmf.spec b/fmf.spec index ae8b0cb1..f0b9c78f 100644 --- a/fmf.spec +++ b/fmf.spec @@ -1,16 +1,19 @@ -Name: fmf -Version: 1.4.1 -Release: 1%{?dist} +Name: fmf +Version: 0.0.0 +Release: 1%{?dist} -Summary: Flexible Metadata Format -License: GPLv2+ -BuildArch: noarch +Summary: Flexible Metadata Format +License: GPL-2.0-or-later +BuildArch: noarch -URL: https://github.com/psss/fmf -Source0: https://github.com/psss/fmf/releases/download/%{version}/fmf-%{version}.tar.gz +URL: https://github.com/teemtee/fmf +Source: %{pypi_source fmf} # Main fmf package requires the Python module -Requires: python%{python3_pkgversion}-%{name} == %{version}-%{release} +BuildRequires: python3-devel +BuildRequires: python3dist(docutils) +BuildRequires: git-core +Requires: python3-fmf == %{version}-%{release} %description The fmf Python module and command line tool implement a flexible @@ -20,22 +23,12 @@ with support for inheritance and elasticity it provides an efficient way to organize data into well-sized text documents. This package contains the command line tool. -%?python_enable_dependency_generator - -%package -n python%{python3_pkgversion}-%{name} +%package -n python3-fmf Summary: %{summary} -BuildRequires: python%{python3_pkgversion}-devel -BuildRequires: python%{python3_pkgversion}-setuptools -BuildRequires: python%{python3_pkgversion}-pytest -BuildRequires: python%{python3_pkgversion}-ruamel-yaml -BuildRequires: python%{python3_pkgversion}-filelock -BuildRequires: python%{python3_pkgversion}-jsonschema -BuildRequires: git-core -%{?python_provide:%python_provide python%{python3_pkgversion}-%{name}} Requires: git-core -%description -n python%{python3_pkgversion}-%{name} +%description -n python3-fmf The fmf Python module and command line tool implement a flexible format for defining metadata in plain text files which can be stored close to the source code. Thanks to hierarchical structure @@ -45,24 +38,31 @@ This package contains the Python 3 module. %prep -%autosetup +%autosetup -p1 -n fmf-%{version} + + +%generate_buildrequires +%pyproject_buildrequires -x tests %{?epel:-w} %build -%py3_build +%pyproject_wheel +cp docs/header.txt man.rst +tail -n+7 README.rst >> man.rst +rst2man man.rst > fmf.1 %install -%py3_install +%pyproject_install +%pyproject_save_files fmf + mkdir -p %{buildroot}%{_mandir}/man1 install -pm 644 fmf.1* %{buildroot}%{_mandir}/man1 %check -%{__python3} -m pytest -vv -c tests/unit/pytest.ini -m 'not web' - +%pytest -vv -m 'not web' -%{!?_licensedir:%global license %%doc} %files %{_mandir}/man1/* @@ -70,10 +70,12 @@ install -pm 644 fmf.1* %{buildroot}%{_mandir}/man1 %doc README.rst examples %license LICENSE -%files -n python%{python3_pkgversion}-%{name} -%{python3_sitelib}/%{name}/ -%{python3_sitelib}/%{name}-*.egg-info +%files -n python3-fmf -f %{pyproject_files} +# Epel9 does not tag the license file in pyproject_files as a license. Manually install it in this case +%if 0%{?el9} %license LICENSE +%endif +%doc README.rst %changelog diff --git a/fmf/__init__.py b/fmf/__init__.py index bdca5f5d..12af8b89 100644 --- a/fmf/__init__.py +++ b/fmf/__init__.py @@ -1,14 +1,17 @@ """ Flexible Metadata Format """ -# Version is replaced before building the package -__version__ = 'running from the source' +from __future__ import annotations + +import importlib.metadata + +from fmf.base import Tree +from fmf.context import Context +from fmf.utils import filter + +__version__ = importlib.metadata.version("fmf") __all__ = [ "Context", "Tree", "filter", ] - -from fmf.base import Tree -from fmf.context import Context -from fmf.utils import filter diff --git a/fmf/cli.py b/fmf/cli.py index 7fd1804d..f5dfbf60 100644 --- a/fmf/cli.py +++ b/fmf/cli.py @@ -209,3 +209,12 @@ def main(arguments=None, path=None): """ Parse options, do what is requested """ parser = Parser(arguments, path) return parser.output + + +def cli_entry(): + try: + main() + except fmf.utils.GeneralError as error: + if "--debug" not in sys.argv: + fmf.utils.log.error(error) + raise diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..5d7141bc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,105 @@ +[build-system] +requires = ['hatchling', 'hatch-vcs'] +build-backend = 'hatchling.build' + +[project] +name = 'fmf' +authors = [ + { name = 'Petr Splichal', email = 'psplicha@redhat.com' }, +] +maintainers = [ + { name = 'Petr Splichal', email = 'psplicha@redhat.com' }, +] +description = 'Flexible Metadata Format' +readme = 'README.rst' +license = 'GPL-2.0-or-later' +license-files = { paths = ['LICENSE'] } +requires-python = '>=3.9' +classifiers = [ + 'Natural Language :: English', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Utilities', +] +keywords = [ + 'metadata', + 'testing', +] +dependencies = [ + 'ruamel.yaml', + 'filelock', + 'jsonschema', +] +dynamic = ['version'] + +[project.urls] +Homepage = 'https://github.com/teemtee/fmf' +Documentation = 'https://fmf.readthedocs.io' + +[project.optional-dependencies] +# Needed for tests inside rpm build. Not being pacakged in rpm +tests = [ + 'pytest', +] +# Needed for readthedocs and man page build. Not being packaged in rpm. +docs = [ + 'sphinx', + 'sphinx_rtd_theme', +] + +[project.scripts] +fmf = 'fmf.cli:cli_entry' + +[tool.hatch] +version.source = 'vcs' + +[tool.hatch.envs.default] +platforms = ["linux"] + +[tool.hatch.envs.dev] +description = "Development environment" +dependencies = [ + "pytest-cov" +] +features = ["tests"] + +[tool.hatch.envs.dev.scripts] +type = ["mypy {args:tmt}"] +check = ["lint", "type"] + +unit = "pytest -vvv -ra --showlocals tests/unit" +smoke = "pytest -vvv -ra --showlocals tests/unit/test_cli.py" + +cov = [ + "coverage run --source=fmf -m pytest -vvv -ra --showlocals tests", + "coverage report", + "coverage annotate", + ] + +[tool.hatch.envs.dev-not-editable] +template = "dev" +description = "Same as 'dev', but not using editable install" +dev-mode = false + +[tool.hatch.envs.test] +template = "dev" +description = "Run scripts with multiple Python versions" + +[[tool.hatch.envs.test.matrix]] +python = ["3.9", "3.11", "3.12"] + +[tool.hatch.envs.docs] +features = ["docs"] + +[tool.hatch.envs.docs.scripts] +html = "sphinx-build -b html {root}/docs {root}/docs/_build {args}" + +[tool.pytest.ini_options] +markers = [ + "web: tests which need to access the web", +] +testpaths = [ + 'tests', +] diff --git a/setup.py b/setup.py deleted file mode 100755 index da308297..00000000 --- a/setup.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python - -import re -from io import open - -from setuptools import setup - -# Parse version from the spec file -with open('fmf.spec', encoding='utf-8') as specfile: - lines = "\n".join(line.rstrip() for line in specfile) - version = re.search('Version: (.+)', lines).group(1).rstrip() - -# acceptable version schema: major.minor[.patch][sub] -__version__ = version -__pkg__ = 'fmf' -__pkgdir__ = {} -__pkgs__ = ['fmf'] -__provides__ = ['fmf'] -__desc__ = 'Flexible Metadata Format' -__scripts__ = ['bin/fmf'] - -# Prepare install requires and extra requires -install_requires = [ - 'ruamel.yaml', - 'filelock', - 'jsonschema', - ] -extras_require = { - 'docs': ['sphinx==7.2.4', 'sphinx-rtd-theme==1.3.0'], - 'tests': ['pytest', 'python-coveralls', 'pre-commit'], - } -extras_require['all'] = [ - dependency - for extra in extras_require.values() - for dependency in extra] - -pip_src = 'https://pypi.python.org/packages/source' -__deplinks__ = [] - -# README is in the parent directory -readme = 'README.rst' -with open(readme, encoding='utf-8') as _file: - readme = _file.read() - -github = 'https://github.com/psss/fmf' -download_url = '{0}/archive/master.zip'.format(github) - -default_setup = dict( - url=github, - license='GPLv2', - author='Petr Splichal', - author_email='psplicha@redhat.com', - maintainer='Petr Splichal', - maintainer_email='psplicha@redhat.com', - download_url=download_url, - long_description=readme, - data_files=[], - classifiers=[ - 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', - 'Natural Language :: English', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Topic :: Utilities', - ], - keywords=['metadata', 'testing'], - dependency_links=__deplinks__, - description=__desc__, - install_requires=install_requires, - extras_require=extras_require, - name=__pkg__, - package_dir=__pkgdir__, - packages=__pkgs__, - provides=__provides__, - scripts=__scripts__, - version=__version__, - ) - -setup(**default_setup) diff --git a/tests/unit/pytest.ini b/tests/unit/pytest.ini deleted file mode 100644 index cafe5e5d..00000000 --- a/tests/unit/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -markers = - web: tests which need to access the web