Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions .github/workflows/release-builtins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,65 @@ jobs:
build:
name: Build distribution
runs-on: ubuntu-latest

outputs:
release: ${{ steps.check.outputs.release }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
persist-credentials: false
- name: update core
env:
TAG: ${{ inputs.tag || 'origin/master' }}
# needs to detach because we can update to a tag
run: git -C uap-core switch --detach "$TAG"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Check necessity of release
id: check
env:
PYPI: ${{ github.event.inputs.environment }}
REF: ${{ inputs.tag || 'HEAD' }}
run: |
case $PYPI in
pypi)
DOMAIN=pypi.org
;;
testpypi)
DOMAIN=test.pypi.org
;;
*)
exit 1
esac

RELREV=$(python scripts/relrev.py --domain "$DOMAIN")
VERSION=$(date +%Y%m)
CURREV=$(python scripts/tagcore.py --ref $REF --version $VERSION)

if [ -n "$CURREV" -a "$RELREV" = "$CURREV" ]
then
echo "current rev matches latest release, skip new release"
else
echo release=true >> $GITHUB_OUTPUT
fi
- name: Install pypa/build
if: ${{ steps.check.outputs.release == 'true' }}
run: python3 -m pip install build --user
- name: Build wheel
if: ${{ steps.check.outputs.release == 'true' }}
run: |
python3 -m build -w ua-parser-builtins
mv ua-parser-builtins/dist .
- name: Store the distribution packages
if: ${{ steps.check.outputs.release == 'true' }}
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/

publish-to-testpypi:
name: Publish to TestPyPI
if: ${{ github.event.inputs.environment == 'testpypi' }}
needs:
- build
needs: build
if: ${{ github.event.inputs.environment == 'testpypi' && needs.build.outputs.release == 'true' }}
runs-on: ubuntu-latest

environment:
Expand All @@ -78,9 +103,8 @@ jobs:

publish-to-pypi:
name: publish
if: ${{ github.event_name == 'schedule' || github.event.inputs.environment == 'pypi' }}
needs:
- build
needs: build
if: ${{ (github.event_name == 'schedule' || github.event.inputs.environment == 'pypi') && needs.build.outputs.release == 'true' }}
runs-on: ubuntu-latest
environment:
name: pypi
Expand Down
67 changes: 67 additions & 0 deletions scripts/relrev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import argparse
import contextlib
import hashlib
import json
import re
import shutil
import sys
import tempfile
import zipfile
from urllib import parse, request

parser = argparse.ArgumentParser(
description="Retrieves the revision for the latest release of ua-parser-builtins",
)
parser.add_argument(
"--domain",
default="pypi.org",
)
args = parser.parse_args()

url = parse.urlunsplit(("https", args.domain, "simple/ua-parser-builtins", "", ""))

print("checking", url, file=sys.stderr)
res = request.urlopen(
request.Request(
url,
headers={
"Accept": "application/vnd.pypi.simple.v1+json",
},
)
)
if res.status != 200:
exit(f"Failed to retrieve project distributions: {res.status}")

distributions = json.load(res)
version, distribution = next(
(v, d)
for v, d in zip(
reversed(distributions["versions"]), reversed(distributions["files"])
)
if not d["yanked"]
if re.fullmatch(
r"(\d+!)?\d+(\.\d+)*(\.post\d+)?",
v,
flags=re.ASCII,
)
)
print("latest version:", version, file=sys.stderr)

res = request.urlopen(distribution["url"])
if res.status != 200:
exit(f"Failed to retrieve wheel: {res.status}")

with tempfile.SpooledTemporaryFile(256 * 1024) as tf:
shutil.copyfileobj(res, tf)
for name, val in distribution["hashes"].items():
tf.seek(0)
d = hashlib.file_digest(tf, name).hexdigest()
if d != val:
exit(f"{name} mismatch: expected {val!r} got {d!r}")
tf.seek(0)
with zipfile.ZipFile(tf) as z:
# if the REVISION file is not found then it's fine it's a
# pre-calver release (hopefully) and that means we should cut
# a calver one
with contextlib.suppress(KeyError):
print(z.read("REVISION").decode())
72 changes: 72 additions & 0 deletions scripts/tagcore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import argparse
import datetime
import pathlib
import shutil
import subprocess

CORE_REMOTE = "https://github.com/ua-parser/uap-core"


parser = argparse.ArgumentParser(
description="""Updates `uap-core` to `ref` and tags it with `version`

If successful, writes the commit to `REVISION` and prints it to stdout.
"""
)
parser.add_argument(
"--ref",
default="HEAD",
help="uap-core ref to build, defaults to HEAD (the head of the default branch)",
)
parser.add_argument(
"--version",
help="version to tag the package as, defaults to an YMD calendar version matching the ref's commit date",
)
args = parser.parse_args()


if not shutil.which("git"):
exit("git required")

r = subprocess.run(
["git", "ls-remote", CORE_REMOTE, args.ref],
encoding="utf-8",
stdout=subprocess.PIPE,
)
if r.returncode:
exit("Unable to query uap-core repo")

if r.stdout:
if r.stdout.count("\n") > 1:
exit(f"Found multiple matching refs for {args.ref}:\n{r.stdout}")
commit, _rest = r.stdout.split("\t", 1)
else:
try:
int(args.ref, 16)
commit = args.ref
except ValueError:
exit(f"Unknown or invalid ref {args.ref!r}")

CORE_PATH = pathlib.Path(__file__).resolve().parent.parent / "uap-core"

r = subprocess.run(["git", "-C", CORE_PATH, "fetch", CORE_REMOTE, commit])
if r.returncode:
exit(f"Unable to retrieve commit {commit!r}")

if args.version:
tagname = args.version
else:
r = subprocess.run(
["git", "-C", CORE_PATH, "show", "-s", "--format=%cs", commit],
encoding="utf-8",
stdout=subprocess.PIPE,
)
if r.returncode or not r.stdout:
exit(f"Unable to retrieve commit date from commit {commit!r}")

tagname = datetime.date.fromisoformat(r.stdout.rstrip()).strftime("%Y%m%d")

subprocess.run(["git", "-C", CORE_PATH, "switch", "-d", commit])
subprocess.run(["git", "-C", CORE_PATH, "tag", tagname, commit])
CORE_PATH.joinpath("REVISION").write_text(commit)
print(commit)
4 changes: 4 additions & 0 deletions ua-parser-builtins/hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def initialize(
version: str,
build_data: dict[str, Any],
) -> None:
rev = os.path.join(self.root, "uap-core/REVISION")
if os.path.exists(rev):
build_data["force_include"][rev] = "REVISION"

with open(os.path.join(self.root, "uap-core/regexes.yaml"), "rb") as f:
data = yaml.safe_load(f)

Expand Down
Loading