Use semantic CI package versions

This commit is contained in:
José Arturo García
2026-07-22 20:11:21 -04:00
parent 295d5dca68
commit 79e93b1748
+113 -17
View File
@@ -24,23 +24,127 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
python -m pip install build twine python -m pip install build twine
- name: Check publishing credentials
env:
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}
TWINE_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
test -n "$TWINE_USERNAME" || { echo "Missing REGISTRY_USER secret"; exit 1; }
test -n "$TWINE_PASSWORD" || { echo "Missing REGISTRY_PASSWORD secret"; exit 1; }
- name: Set CI package version - name: Set CI package version
env:
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}
TWINE_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: | run: |
python - <<'PY' python - <<'PY'
from pathlib import Path from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
import base64
import json
import os import os
import re import re
import tomllib
path = Path('tryton.cfg') OWNER = 'tryton-do'
text = path.read_text() PACKAGE_TYPE = 'pypi'
match = re.search(r'(?m)^version=(.+)$', text) API_ROOT = 'https://gitea.joseagrc.com/api/v1'
if not match:
def normalize(name):
return re.sub(r'[-_.]+', '-', name).lower()
cfg_path = Path('tryton.cfg')
cfg_text = cfg_path.read_text()
cfg_match = re.search(r'(?m)^version=(.+)$', cfg_text)
if not cfg_match:
raise SystemExit('tryton.cfg has no version entry') raise SystemExit('tryton.cfg has no version entry')
base_version = match.group(1).strip() base_version = cfg_match.group(1).strip()
run_number = os.environ.get('GITHUB_RUN_NUMBER', '0') version_match = re.fullmatch(r'(\d+)\.(\d+)\.(\d+)', base_version)
ci_version = f'{base_version}.post{run_number}' if not version_match:
text = re.sub(r'(?m)^version=.+$', f'version={ci_version}', text) raise SystemExit(
path.write_text(text) f'tryton.cfg version must be X.Y.Z, got {base_version!r}')
major, minor, patch = map(int, version_match.groups())
pyproject = tomllib.loads(Path('pyproject.toml').read_text())
package_name = pyproject['project']['name']
normalized_name = normalize(package_name)
username = os.environ['TWINE_USERNAME']
password = os.environ['TWINE_PASSWORD']
token = base64.b64encode(
f'{username}:{password}'.encode()).decode()
def request_json(path, params=None, missing_ok=False):
url = f'{API_ROOT}{path}'
if params:
url = f'{url}?{urlencode(params)}'
request = Request(url, headers={
'Accept': 'application/json',
'Authorization': f'Basic {token}',
})
try:
with urlopen(request, timeout=30) as response:
return json.load(response)
except HTTPError as error:
if missing_ok and error.code == 404:
return []
raise
def collect_versions(payload, package_specific=False):
versions = []
if isinstance(payload, list):
for item in payload:
versions.extend(collect_versions(item, package_specific))
elif isinstance(payload, dict):
item_name = payload.get('name') or payload.get('package_name')
item_type = payload.get('type') or payload.get('package_type')
item_version = payload.get('version')
if item_version and (
package_specific
or (item_type == PACKAGE_TYPE
and normalize(item_name or '') == normalized_name)):
versions.append(str(item_version))
for value in payload.values():
if isinstance(value, (list, dict)):
versions.extend(collect_versions(value, package_specific))
return versions
versions = []
for page in range(1, 11):
payload = request_json(
f'/packages/{OWNER}/{PACKAGE_TYPE}/{package_name}',
{'page': page, 'limit': 100},
missing_ok=True)
page_versions = collect_versions(payload, package_specific=True)
versions.extend(page_versions)
if not page_versions:
break
for page in range(1, 11):
payload = request_json(
f'/packages/{OWNER}',
{'page': page, 'limit': 100, 'type': PACKAGE_TYPE,
'q': package_name})
page_versions = collect_versions(payload)
versions.extend(page_versions)
if not page_versions:
break
patch_pattern = re.compile(rf'^{major}\.{minor}\.(\d+)$')
patches = [patch]
for version in set(versions):
match = patch_pattern.fullmatch(version)
if match:
patches.append(int(match.group(1)))
ci_version = f'{major}.{minor}.{max(patches) + 1}'
cfg_text = re.sub(
r'(?m)^version=.+$', f'version={ci_version}', cfg_text)
cfg_path.write_text(cfg_text)
print(f'Package name: {package_name}')
print(f'Published versions found: {sorted(set(versions))}')
print(f'Package version: {ci_version}') print(f'Package version: {ci_version}')
PY PY
@@ -52,14 +156,6 @@ jobs:
run: | run: |
python -m twine check dist/* python -m twine check dist/*
- name: Check publishing credentials
env:
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}
TWINE_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
test -n "$TWINE_USERNAME" || { echo "Missing REGISTRY_USER secret"; exit 1; }
test -n "$TWINE_PASSWORD" || { echo "Missing REGISTRY_PASSWORD secret"; exit 1; }
- name: Publish to Gitea PyPI registry - name: Publish to Gitea PyPI registry
env: env:
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }} TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}