diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml new file mode 100644 index 0000000..968d1f8 --- /dev/null +++ b/.github/workflows/release-check.yml @@ -0,0 +1,11 @@ +name: "🔖 Release PR check" + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, edited] + +jobs: + release-check: + name: "🤙 Call SDK release-check workflow" + uses: clamsproject/.github/.github/workflows/sdk-release-check.yml@main diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8939f1..0e177ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,6 +75,13 @@ from clams.ver import __version__ For a dev install without a matching tag, `setuptools-scm` generates a version like `1.4.1.dev20+gaf551a4e4.d20260325`. +## Releases + +Release PRs (from `develop` to `main`) are gated by the `release-check` CI workflow. Before opening one: + +1. Title the PR exactly `releasing X.Y.Z` (strict semver); `release-check` reads the version from the title. +2. Run `python build-tools/prep_release.py X.Y.Z` and commit its changes. See inside the prep script to see what's actually prepared. + ## Migration from Makefile The old `Makefile` and `setup.py` have been removed. If you are diff --git a/build-tools/prep_release.py b/build-tools/prep_release.py new file mode 100644 index 0000000..416107c --- /dev/null +++ b/build-tools/prep_release.py @@ -0,0 +1,104 @@ +""" +Prepare release artifacts for the clams-python package. + +Run while preparing a release PR, passing the version being released:: + + python build-tools/prep_release.py X.Y.Z + +Currently this adds a row to ``documentation/target-versions.csv`` for +the release version. The ``mmif-python`` column is read from this +package's pinned dependency in ``pyproject.toml``, and the target MMIF +spec version from the installed ``mmif.__specver__``. This script is the +single place that writes the target-versions table; the docs build only +renders it, and CI verifies (on the release PR) that a row for the PR's +version exists. + +Requires an editable install (``pip install -e .``) so that +``mmif.__specver__`` is importable. +""" +import argparse +import re +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).parent.parent +CSV_PATH = PROJECT_ROOT / 'documentation' / 'target-versions.csv' +PYPROJECT_PATH = PROJECT_ROOT / 'pyproject.toml' + +VERSION_RE = re.compile(r'^\d+\.\d+\.\d+$') +TOP_ROW_RE = re.compile(r'`(\d+\.\d+\.\d+) <') +MMIF_PIN_RE = re.compile(r'mmif-python\s*==\s*(\d+\.\d+\.\d+)') + + +def _row(version, mmif_ver, specver): + return ( + f'`{version} `__,' + f'`{mmif_ver} `__,' + f'`{specver} `__' + ) + + +def _version_tuple(v): + return tuple(int(x) for x in v.split('.')) + + +def _top_version(data_lines): + if not data_lines: + return None + m = TOP_ROW_RE.match(data_lines[0]) + return m.group(1) if m else None + + +def _pinned_mmif_version(): + m = MMIF_PIN_RE.search(PYPROJECT_PATH.read_text()) + if not m: + sys.exit("Error: could not find a pinned 'mmif-python==X.Y.Z' " + "dependency in pyproject.toml.") + return m.group(1) + + +def main(): + parser = argparse.ArgumentParser( + description="Prepare release artifacts for clams-python." + ) + parser.add_argument( + "version", metavar="X.Y.Z", + help="the release version being prepared", + ) + args = parser.parse_args() + version = args.version + + if not VERSION_RE.match(version): + sys.exit(f"Error: '{version}' is not a valid X.Y.Z version.") + + mmif_ver = _pinned_mmif_version() + try: + import mmif + specver = mmif.__specver__ + except (ImportError, AttributeError): + sys.exit("Error: cannot read mmif.__specver__. " + "Run `pip install -e .` first.") + + lines = CSV_PATH.read_text().splitlines() + header, data = lines[0], lines[1:] + top = _top_version(data) + + if top is not None and _version_tuple(version) < _version_tuple(top): + sys.exit(f"Error: {version} is older than the current top row " + f"{top}; refusing to insert an out-of-order version.") + + new_row = _row(version, mmif_ver, specver) + if top == version: + data[0] = new_row # update in place + action = "updated" + else: + data.insert(0, new_row) # prepend as the new latest + action = "added" + + CSV_PATH.write_text('\n'.join([header] + data) + '\n') + print(f"target-versions.csv: {action} row {version} " + f"-> mmif-python {mmif_ver}, MMIF spec {specver}") + + +if __name__ == "__main__": + main() diff --git a/documentation/conf.py b/documentation/conf.py index e056cd2..437c394 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -14,8 +14,6 @@ import inspect import json import os -import re -import shutil import subprocess import sys from pathlib import Path @@ -196,24 +194,6 @@ def generate_jsonschema(app): json.dump(schema_dict, f, indent=2) -def update_target_spec(app): - target_vers_csv = Path(__file__).parent / 'target-versions.csv' - version = _get_version('clams-python') - # Skip dev/dummy versions to avoid dirtying the git-tracked CSV - if 'dev' in version or not re.match(r'^\d+\.\d+\.\d+$', version): - return - mmifver = mmif.__version__ - specver = mmif.__specver__ - with open(target_vers_csv) as in_f, open(f'{target_vers_csv}.new', 'w') as out_f: - lines = in_f.readlines() - if not lines[1].startswith(f"`{version}"): - lines.insert(1, f"`{version} `__,`{mmifver} `__,`{specver} `__\n") - for line in lines: - out_f.write(line) - shutil.move(out_f.name, in_f.name) - - def setup(app): app.connect('builder-inited', generate_whatsnew_rst) app.connect('builder-inited', generate_jsonschema) - app.connect('builder-inited', update_target_spec) diff --git a/documentation/target-versions.csv b/documentation/target-versions.csv index 5ae092a..8436941 100644 --- a/documentation/target-versions.csv +++ b/documentation/target-versions.csv @@ -1,4 +1,9 @@ "``clams-python`` version","``mmif-python`` version","Target MMIF Specification" +`1.7.1 `__,`1.5.1 `__,`1.2.0 `__ +`1.7.0 `__,`1.5.0 `__,`1.2.0 `__ +`1.6.0 `__,`1.4.0 `__,`1.1.1 `__ +`1.5.0 `__,`1.3.1 `__,`1.1.0 `__ +`1.4.0 `__,`1.2.1 `__,`1.1.0 `__ `1.3.3 `__,`1.1.2 `__,`1.1.0 `__ `1.3.2 `__,`1.1.1 `__,`1.1.0 `__ `1.3.1 `__,`1.0.19 `__,`1.0.5 `__ @@ -50,4 +55,4 @@ `0.1.7 `__,`0.2.2 `__,`0.2.1 `__ `0.1.6 `__,`0.2.1 `__,`0.2.1 `__ `0.1.5 `__,`0.2.1 `__,`0.2.1 `__ -`0.1.4 `__,`0.2.1 `__,`0.2.1 `__ \ No newline at end of file +`0.1.4 `__,`0.2.1 `__,`0.2.1 `__ diff --git a/pyproject.toml b/pyproject.toml index 048410d..b7f9fc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3 :: Only", ] dependencies = [ - "mmif-python==1.5.0", + "mmif-python==1.5.1", "Flask>=2", "Flask-RESTful>=0.3.9", "gunicorn>=20",