mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
FDroid automation in pipeline.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import hashlib
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
APK_URL = "https://releases.grayjay.app/app-universal-release.apk"
|
||||
|
||||
FDROID_REPO_SSH = "git@gitlab.futo.org:fdroid/repo-v2.git"
|
||||
FDROID_INDEX_PATH = "apps/Grayjay/index.yml"
|
||||
UNIVERSAL_APK_GLOB = "app/build/outputs/apk/stable/release/*universal*.apk"
|
||||
|
||||
GIT_USER_NAME = "koen"
|
||||
GIT_USER_EMAIL = "koen@futo.org"
|
||||
|
||||
class Fatal(Exception):
|
||||
pass
|
||||
|
||||
def run(cmd: list[str], *, cwd: Optional[str] = None) -> str:
|
||||
p = subprocess.run(cmd, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
if p.returncode != 0:
|
||||
raise Fatal(f"Command failed ({p.returncode}): {' '.join(cmd)}\n{p.stdout}")
|
||||
return p.stdout.strip()
|
||||
|
||||
def sha256_of_file(path: str) -> str:
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
def pick_universal_apk() -> str:
|
||||
matches = sorted(glob.glob(UNIVERSAL_APK_GLOB))
|
||||
if not matches:
|
||||
raise Fatal(f"No universal APK found via glob: {UNIVERSAL_APK_GLOB}")
|
||||
|
||||
for m in matches:
|
||||
base = os.path.basename(m)
|
||||
if "app-stable-universal" in base:
|
||||
return m
|
||||
|
||||
return matches[-1]
|
||||
|
||||
def get_release_date_today() -> str:
|
||||
return datetime.datetime.now(datetime.timezone.utc).date().isoformat()
|
||||
|
||||
def get_version_code_from_tag() -> int:
|
||||
tag = os.environ.get("CI_COMMIT_TAG", "").strip()
|
||||
if not tag:
|
||||
tag = run(["git", "describe", "--tags"]).strip()
|
||||
|
||||
m = re.search(r"(\d+)", tag)
|
||||
if not m:
|
||||
raise Fatal(f"Could not parse an integer versionCode from tag '{tag}'")
|
||||
|
||||
return int(m.group(1))
|
||||
|
||||
def update_index_yml(path: str, sha256sum: str, date_str: str, version_code: int) -> None:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
url_line_idx = None
|
||||
url_line_indent = ""
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("- url:") and APK_URL in stripped:
|
||||
url_line_idx = i
|
||||
url_line_indent = line[: len(line) - len(stripped)]
|
||||
break
|
||||
if url_line_idx is None:
|
||||
raise Fatal(f"Did not find an apk entry with url {APK_URL} in {path}")
|
||||
|
||||
def is_url_line_same_level(s: str) -> bool:
|
||||
st = s.lstrip()
|
||||
indent = s[: len(s) - len(st)]
|
||||
return st.startswith("- url:") and indent == url_line_indent
|
||||
|
||||
end = len(lines)
|
||||
for j in range(url_line_idx + 1, len(lines)):
|
||||
if is_url_line_same_level(lines[j]):
|
||||
end = j
|
||||
break
|
||||
|
||||
child_indent = url_line_indent + " "
|
||||
found_sha = found_date = found_vc = False
|
||||
|
||||
for j in range(url_line_idx + 1, end):
|
||||
st = lines[j].lstrip()
|
||||
indent = lines[j][: len(lines[j]) - len(st)]
|
||||
if st.startswith("sha256sum:"):
|
||||
lines[j] = f"{indent}sha256sum: {sha256sum}\n"
|
||||
found_sha = True
|
||||
elif st.startswith("date:"):
|
||||
lines[j] = f"{indent}date: {date_str}\n"
|
||||
found_date = True
|
||||
elif st.startswith("version-code:"):
|
||||
lines[j] = f"{indent}version-code: {version_code}\n"
|
||||
found_vc = True
|
||||
|
||||
insert_pos = url_line_idx + 1
|
||||
to_insert: list[str] = []
|
||||
if not found_sha:
|
||||
to_insert.append(f"{child_indent}sha256sum: {sha256sum}\n")
|
||||
if not found_date:
|
||||
to_insert.append(f"{child_indent}date: {date_str}\n")
|
||||
if not found_vc:
|
||||
to_insert.append(f"{child_indent}version-code: {version_code}\n")
|
||||
if to_insert:
|
||||
lines[insert_pos:insert_pos] = to_insert
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
version_code = get_version_code_from_tag()
|
||||
date_str = get_release_date_today()
|
||||
|
||||
apk_path = pick_universal_apk()
|
||||
print(f"Computing sha256 for {apk_path} ...")
|
||||
sha = sha256_of_file(apk_path)
|
||||
print(f"sha256: {sha}")
|
||||
print(f"date: {date_str}")
|
||||
print(f"version-code: {version_code}")
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="fdroid-repo-")
|
||||
try:
|
||||
print(f"Cloning {FDROID_REPO_SSH} ...")
|
||||
run(["git", "clone", "--depth", "1", FDROID_REPO_SSH, tmpdir])
|
||||
|
||||
run(["git", "config", "user.name", GIT_USER_NAME], cwd=tmpdir)
|
||||
run(["git", "config", "user.email", GIT_USER_EMAIL], cwd=tmpdir)
|
||||
|
||||
index_path = os.path.join(tmpdir, FDROID_INDEX_PATH)
|
||||
if not os.path.exists(index_path):
|
||||
raise Fatal(f"Missing {FDROID_INDEX_PATH} in repo-v2")
|
||||
|
||||
update_index_yml(index_path, sha, date_str, version_code)
|
||||
|
||||
run(["git", "add", FDROID_INDEX_PATH], cwd=tmpdir)
|
||||
|
||||
diff_rc = subprocess.run(["git", "diff", "--cached", "--quiet"], cwd=tmpdir).returncode
|
||||
if diff_rc == 0:
|
||||
print("No changes to commit.")
|
||||
return 0
|
||||
|
||||
msg = f"Grayjay: update sha/date/version-code to {version_code} ({date_str})"
|
||||
run(["git", "commit", "-m", msg], cwd=tmpdir)
|
||||
run(["git", "push"], cwd=tmpdir)
|
||||
|
||||
print("Pushed update to fdroid/repo-v2.")
|
||||
return 0
|
||||
finally:
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Fatal as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
Reference in New Issue
Block a user