diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..634fdcb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + # Workflow files stored in the default location of `.github/workflows` + # You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`. + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..31bfc6e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: Lint + +on: + push: + workflow_call: + workflow_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "pyproject.toml" + + - name: Set up Python 3.10 + run: uv python install 3.10 + + - name: Install Project + run: make install + + - name: Run checks + run: make check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..16f9006 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +--- + name: Build & upload PyPI package + + on: + push: + branches: [main] + tags: ["*"] + release: + types: + - published + workflow_dispatch: + + jobs: + tests: + uses: "./.github/workflows/test.yml" + lint: + uses: "./.github/workflows/lint.yml" + typecheck: + uses: "./.github/workflows/typecheck.yml" + + # Always build & lint package. + build-package: + name: Build & verify package + needs: + - lint + - tests + - typecheck + runs-on: ubuntu-latest + permissions: + attestations: write + id-token: write + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: hynek/build-and-inspect-python-package@v2 + with: + attest-build-provenance-github: 'true' + + # Upload to Test PyPI on every commit on main. + release-test-pypi: + name: Publish in-dev package to test.pypi.org + environment: release-test-pypi + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + needs: + - build-package + permissions: + id-token: write + + steps: + - name: Download packages built by build-and-inspect-python-package + uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + + - name: Upload package to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + # Upload to real PyPI on GitHub Releases. + release-pypi: + name: Publish released package to pypi.org + environment: release-pypi + if: github.event.action == 'published' + runs-on: ubuntu-latest + needs: + - build-package + permissions: + id-token: write + + steps: + - name: Download packages built by build-and-inspect-python-package + uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + + - name: Upload package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + docs: + needs: + - release-pypi + uses: "./.github/workflows/docs.yml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecb4b9f..3b0c290 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,50 +2,43 @@ name: Tests on: push: - branches: [ main ] - pull_request: - branches: [ main ] + workflow_call: + workflow_dispatch: jobs: - test: - runs-on: ubuntu-latest + build: strategy: - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false - + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + os: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - name: Set up uv + uses: astral-sh/setup-uv@v7 with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: | - uv pip install --system -e ".[test]" - uv pip install --system ruff isort - - - name: Run ruff check - run: ruff check src/ - - - name: Run isort check - run: isort --check src/ - - - name: Run tests with coverage - run: pytest --cov=mxrepo --cov-report=term-missing --cov-report=xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - if: matrix.python-version == '3.13' - with: - file: ./coverage.xml - fail_ci_if_error: false - continue-on-error: true + enable-cache: true + cache-dependency-glob: "pyproject.toml" + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + - name: Install Project (Windows) + if: runner.os == 'Windows' + run: make MAKESHELL='C:/Program Files/Git/usr/bin/bash' install + - name: Install Project (Unix) + if: runner.os != 'Windows' + run: make install + - name: Run Tests (Windows) + if: runner.os == 'Windows' + run: make MAKESHELL='C:/Program Files/Git/usr/bin/bash' test + - name: Run Tests (Unix) + if: runner.os != 'Windows' + run: make test diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 0000000..3f924cb --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,28 @@ +name: Type checks + +on: + push: + workflow_call: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "pyproject.toml" + + - name: Set up Python 3.13 + run: uv python install 3.13 + + - name: Install Project + run: make install + + - name: Run Typechecks + run: make typecheck diff --git a/.gitignore b/.gitignore index 91dcf14..46bad0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ -*.egg-info __pycache__ +.claude +.coverage +*.egg-info /.mxmake/ +/dist/ +/reports/ /requirements-mxdev.txt /.claude/ CLAUDE.md diff --git a/CHANGES.md b/CHANGES.md index 78a8dea..3fcbabb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changelog -## 1.0 (unreleased) +## 1.0.0 +- Added tests, CI workflows and hatchling/ trusted publishing + [@jensens, 2025-11-03] - Initial porting from https://github.com/rnixx/githelper + [@rnix, 2025-03-03] diff --git a/Makefile b/Makefile index bd4174a..27ca715 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,13 @@ INCLUDE_MAKEFILE?=include.mk # No default value. EXTRA_PATH?= +# Path to Python project relative to Makefile (repository root). +# Leave empty if Python project is in the same directory as Makefile. +# For monorepo setups, set to subdirectory name (e.g., `backend`). +# Future-proofed for multi-language monorepos (e.g., PROJECT_PATH_NODEJS). +# No default value. +PROJECT_PATH_PYTHON?= + ## core.mxenv # Primary Python interpreter to use. It is used to create the @@ -156,6 +163,9 @@ FORMAT_TARGETS?= export PATH:=$(if $(EXTRA_PATH),$(EXTRA_PATH):,)$(PATH) +# Helper variable: adds trailing slash to PROJECT_PATH_PYTHON only if non-empty +PYTHON_PROJECT_PREFIX=$(if $(PROJECT_PATH_PYTHON),$(PROJECT_PATH_PYTHON)/,) + # Defensive settings for make: https://tech.davis-hansson.com/p/make/ SHELL:=bash .ONESHELL: @@ -299,6 +309,11 @@ CLEAN_TARGETS+=mxenv-clean # ruff ############################################################################## +# Adjust RUFF_SRC to respect PROJECT_PATH_PYTHON if still at default +ifeq ($(RUFF_SRC),src) +RUFF_SRC:=$(PYTHON_PROJECT_PREFIX)src +endif + RUFF_TARGET:=$(SENTINEL_FOLDER)/ruff.sentinel $(RUFF_TARGET): $(MXENV_TARGET) @echo "Install Ruff" @@ -334,6 +349,11 @@ CLEAN_TARGETS+=ruff-clean # isort ############################################################################## +# Adjust ISORT_SRC to respect PROJECT_PATH_PYTHON if still at default +ifeq ($(ISORT_SRC),src) +ISORT_SRC:=$(PYTHON_PROJECT_PREFIX)src +endif + ISORT_TARGET:=$(SENTINEL_FOLDER)/isort.sentinel $(ISORT_TARGET): $(MXENV_TARGET) @echo "Install isort" @@ -391,7 +411,7 @@ else @echo "[settings]" > $(PROJECT_CONFIG) endif -LOCAL_PACKAGE_FILES:=$(wildcard pyproject.toml setup.cfg setup.py requirements.txt constraints.txt) +LOCAL_PACKAGE_FILES:=$(wildcard $(PYTHON_PROJECT_PREFIX)pyproject.toml $(PYTHON_PROJECT_PREFIX)setup.cfg $(PYTHON_PROJECT_PREFIX)setup.py $(PYTHON_PROJECT_PREFIX)requirements.txt $(PYTHON_PROJECT_PREFIX)constraints.txt) FILES_TARGET:=requirements-mxdev.txt $(FILES_TARGET): $(PROJECT_CONFIG) $(MXENV_TARGET) $(SOURCES_TARGET) $(LOCAL_PACKAGE_FILES) diff --git a/pyproject.toml b/pyproject.toml index 729b013..e720e42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,11 @@ [project] name = "mxrepo" description = "Helper script for working with multiple git repositories." -version = "1.0.0.dev0" keywords = ["development", "deployment", "git"] authors = [ {name = "MX Stack Developers", email = "dev@bluedynamics.com" } ] -requires-python = ">=3.9" +requires-python = ">=3.10" license = { text = "BSD 2-Clause License" } classifiers = [ "Intended Audience :: Developers", @@ -14,14 +13,13 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] -dynamic = ["readme"] +dynamic = ["version", "readme"] [project.optional-dependencies] test = [ @@ -39,14 +37,26 @@ Source = "https://github.com/mxstack/mxrepo/tree/main" mxrepo = "mxrepo.main:main" [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" +requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" -[tool.distutils.bdist_wheel] -universal = true +[tool.hatch.version] +source = "vcs" -[tool.setuptools.dynamic] -readme = {file = ["README.md", "CHANGES.md", "LICENSE.md"], content-type = "text/markdown"} +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "CHANGES.md" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "LICENSE.md" [tool.isort] profile = "black" @@ -56,7 +66,7 @@ lines_after_imports = 2 [tool.mypy] ignore_missing_imports = true -python_version = "3.9" +python_version = "3.10" [tool.ruff] line-length = 88