167 lines
5.9 KiB
YAML
167 lines
5.9 KiB
YAML
name: Publish Package
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- "**"
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
publish:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Install build tools
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
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
|
|
env:
|
|
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}
|
|
TWINE_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
python - <<'PY'
|
|
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 re
|
|
import tomllib
|
|
|
|
OWNER = 'tryton-do'
|
|
PACKAGE_TYPE = 'pypi'
|
|
API_ROOT = 'https://gitea.joseagrc.com/api/v1'
|
|
|
|
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')
|
|
base_version = cfg_match.group(1).strip()
|
|
version_match = re.fullmatch(r'(\d+)\.(\d+)\.(\d+)', base_version)
|
|
if not version_match:
|
|
raise SystemExit(
|
|
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}')
|
|
PY
|
|
|
|
- name: Build package
|
|
run: |
|
|
python -m build
|
|
|
|
- name: Check package
|
|
run: |
|
|
python -m twine check dist/*
|
|
|
|
- name: Publish to Gitea PyPI registry
|
|
env:
|
|
TWINE_USERNAME: ${{ secrets.REGISTRY_USER }}
|
|
TWINE_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
python -m twine upload \
|
|
--repository-url https://gitea.joseagrc.com/api/packages/tryton-do/pypi \
|
|
dist/*
|