GitHub Action: Allow reading version from pyproject.toml (#4294)

Closes #4285

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Sebastian Rittau 2024-04-05 14:40:40 +02:00 committed by GitHub
parent c8f1a5542c
commit 3383f531bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 126 additions and 8 deletions

View File

@ -48,6 +48,9 @@
<!-- For example, Docker, GitHub Actions, pre-commit, editors --> <!-- For example, Docker, GitHub Actions, pre-commit, editors -->
- Add a new option `use_pyproject` to the GitHub Action `psf/black`. This will read the
Black version from `pyproject.toml`. (#4294)
### Documentation ### Documentation
<!-- Major changes to documentation and policies. Small docs changes <!-- Major changes to documentation and policies. Small docs changes

View File

@ -27,6 +27,10 @@ inputs:
description: 'Python Version specifier (PEP440) - e.g. "21.5b1"' description: 'Python Version specifier (PEP440) - e.g. "21.5b1"'
required: false required: false
default: "" default: ""
use_pyproject:
description: Read Black version specifier from pyproject.toml if `true`.
required: false
default: "false"
summary: summary:
description: "Whether to add the output to the workflow summary" description: "Whether to add the output to the workflow summary"
required: false required: false
@ -70,5 +74,6 @@ runs:
INPUT_JUPYTER: ${{ inputs.jupyter }} INPUT_JUPYTER: ${{ inputs.jupyter }}
INPUT_BLACK_ARGS: ${{ inputs.black_args }} INPUT_BLACK_ARGS: ${{ inputs.black_args }}
INPUT_VERSION: ${{ inputs.version }} INPUT_VERSION: ${{ inputs.version }}
INPUT_USE_PYPROJECT: ${{ inputs.use_pyproject }}
pythonioencoding: utf-8 pythonioencoding: utf-8
shell: bash shell: bash

View File

@ -1,9 +1,11 @@
import os import os
import re
import shlex import shlex
import shutil import shutil
import sys import sys
from pathlib import Path from pathlib import Path
from subprocess import PIPE, STDOUT, run from subprocess import PIPE, STDOUT, run
from typing import Union
ACTION_PATH = Path(os.environ["GITHUB_ACTION_PATH"]) ACTION_PATH = Path(os.environ["GITHUB_ACTION_PATH"])
ENV_PATH = ACTION_PATH / ".black-env" ENV_PATH = ACTION_PATH / ".black-env"
@ -13,12 +15,107 @@
JUPYTER = os.getenv("INPUT_JUPYTER") == "true" JUPYTER = os.getenv("INPUT_JUPYTER") == "true"
BLACK_ARGS = os.getenv("INPUT_BLACK_ARGS", default="") BLACK_ARGS = os.getenv("INPUT_BLACK_ARGS", default="")
VERSION = os.getenv("INPUT_VERSION", default="") VERSION = os.getenv("INPUT_VERSION", default="")
USE_PYPROJECT = os.getenv("INPUT_USE_PYPROJECT") == "true"
BLACK_VERSION_RE = re.compile(r"^black([^A-Z0-9._-]+.*)$", re.IGNORECASE)
EXTRAS_RE = re.compile(r"\[.*\]")
def determine_version_specifier() -> str:
"""Determine the version of Black to install.
The version can be specified either via the `with.version` input or via the
pyproject.toml file if `with.use_pyproject` is set to `true`.
"""
if USE_PYPROJECT and VERSION:
print(
"::error::'with.version' and 'with.use_pyproject' inputs are "
"mutually exclusive.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
if USE_PYPROJECT:
return read_version_specifier_from_pyproject()
elif VERSION and VERSION[0] in "0123456789":
return f"=={VERSION}"
else:
return VERSION
def read_version_specifier_from_pyproject() -> str:
if sys.version_info < (3, 11):
print(
"::error::'with.use_pyproject' input requires Python 3.11 or later.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
import tomllib # type: ignore[import-not-found,unreachable]
try:
with Path("pyproject.toml").open("rb") as fp:
pyproject = tomllib.load(fp)
except FileNotFoundError:
print(
"::error::'with.use_pyproject' input requires a pyproject.toml file.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
version = pyproject.get("tool", {}).get("black", {}).get("required-version")
if version is not None:
return f"=={version}"
arrays = [
pyproject.get("project", {}).get("dependencies"),
*pyproject.get("project", {}).get("optional-dependencies", {}).values(),
]
for array in arrays:
version = find_black_version_in_array(array)
if version is not None:
break
if version is None:
print(
"::error::'black' dependency missing from pyproject.toml.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
return version
def find_black_version_in_array(array: object) -> Union[str, None]:
if not isinstance(array, list):
return None
try:
for item in array:
# Rudimentary PEP 508 parsing.
item = item.split(";")[0]
item = EXTRAS_RE.sub("", item).strip()
if item == "black":
print(
"::error::Version specifier missing for 'black' dependency in "
"pyproject.toml.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
elif m := BLACK_VERSION_RE.match(item):
return m.group(1).strip()
except TypeError:
pass
return None
run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True) run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True)
version_specifier = VERSION version_specifier = determine_version_specifier()
if VERSION and VERSION[0] in "0123456789":
version_specifier = f"=={VERSION}"
if JUPYTER: if JUPYTER:
extra_deps = "[colorama,jupyter]" extra_deps = "[colorama,jupyter]"
else: else:

View File

@ -32,12 +32,15 @@ We recommend the use of the `@stable` tag, but per version tags also exist if yo
that. Note that the action's version you select is independent of the version of _Black_ that. Note that the action's version you select is independent of the version of _Black_
the action will use. the action will use.
The version of _Black_ the action will use can be configured via `version`. This can be The version of _Black_ the action will use can be configured via `version` or read from
any the `pyproject.toml` file. `version` can be any
[valid version specifier](https://packaging.python.org/en/latest/glossary/#term-Version-Specifier) [valid version specifier](https://packaging.python.org/en/latest/glossary/#term-Version-Specifier)
or just the version number if you want an exact version. The action defaults to the or just the version number if you want an exact version. To read the version from the
latest release available on PyPI. Only versions available from PyPI are supported, so no `pyproject.toml` file instead, set `use_pyproject` to `true`. This will first look into
commit SHAs or branch names. the `tool.black.required-version` field, then the `project.dependencies` array and
finally the `project.optional-dependencies` table. The action defaults to the latest
release available on PyPI. Only versions available from PyPI are supported, so no commit
SHAs or branch names.
If you want to include Jupyter Notebooks, _Black_ must be installed with the `jupyter` If you want to include Jupyter Notebooks, _Black_ must be installed with the `jupyter`
extra. Installing the extra and including Jupyter Notebook files can be configured via extra. Installing the extra and including Jupyter Notebook files can be configured via
@ -70,3 +73,13 @@ If you want to match versions covered by Black's
src: "./src" src: "./src"
version: "~= 22.0" version: "~= 22.0"
``` ```
If you want to read the version from `pyproject.toml`, set `use_pyproject` to `true`:
```yaml
- uses: psf/black@stable
with:
options: "--check --verbose"
src: "./src"
use_pyproject: true
```