mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-27 23:47:39 +01:00
CI: improve more ci worflow
This commit is contained in:
@@ -19,6 +19,20 @@ def run(cmd: str) -> str:
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
|
||||
def commit_exists(rev: str) -> bool:
|
||||
if not rev:
|
||||
return False
|
||||
try:
|
||||
subprocess.check_output(
|
||||
f"git rev-parse --verify {rev}^{{commit}}",
|
||||
shell=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def head_commit():
|
||||
return run("git rev-parse HEAD")
|
||||
|
||||
@@ -35,12 +49,8 @@ def repo_url():
|
||||
|
||||
|
||||
def commit_messages(start, end="HEAD"):
|
||||
try:
|
||||
out = run(f"git log {start}..{end} --pretty=format:%s")
|
||||
return out.splitlines() if out else []
|
||||
except subprocess.CalledProcessError:
|
||||
fallback = run("git rev-parse HEAD~5")
|
||||
return run(f"git log {fallback}..{end} --pretty=format:%s").splitlines()
|
||||
out = run(f"git log {start}..{end} --pretty=format:%s")
|
||||
return out.splitlines() if out else []
|
||||
|
||||
|
||||
def authors(range_expr, fmt="%an"):
|
||||
@@ -76,10 +86,35 @@ def fmt_author(author):
|
||||
# release note generation
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def resolve_start_commit(last_successful: str):
|
||||
if commit_exists(last_successful):
|
||||
return last_successful
|
||||
|
||||
try:
|
||||
return run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
return first_commit()
|
||||
|
||||
def generate_release_notes(last_successful, tag, branch):
|
||||
current = head_commit()
|
||||
|
||||
# fallback if missing/invalid
|
||||
if not last_successful or not commit_exists(last_successful):
|
||||
try:
|
||||
last_successful = run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
last_successful = first_commit()
|
||||
|
||||
messages = commit_messages(last_successful, current)
|
||||
|
||||
# fallback if empty range
|
||||
if not messages:
|
||||
try:
|
||||
last_successful = run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
last_successful = first_commit()
|
||||
messages = commit_messages(last_successful, current)
|
||||
|
||||
section = f"{TAG_MARKER} {tag}\n"
|
||||
section += f"{HEADER_MARKER} What's Changed\n"
|
||||
|
||||
@@ -90,7 +125,8 @@ def generate_release_notes(last_successful, tag, branch):
|
||||
section += f"{fmt_msg(m)}\n"
|
||||
|
||||
prev_authors = authors(branch)
|
||||
new_authors = authors(f"{last_successful}..{current}") - prev_authors
|
||||
recent_authors = authors(f"{last_successful}..{current}")
|
||||
new_authors = recent_authors - prev_authors
|
||||
|
||||
if new_authors:
|
||||
section += f"\n{HEADER_MARKER} New Contributors\n"
|
||||
@@ -170,7 +206,7 @@ def update_release_md(existing, new_section, tag):
|
||||
# retrieval
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def retrieve_tag(tag, file_path):
|
||||
def retrieve_tag(tag, file_path: Path):
|
||||
if not file_path.exists():
|
||||
return ""
|
||||
|
||||
@@ -209,30 +245,20 @@ def main():
|
||||
" generate_release_notes.py --retrieve <tag> [--output-dir DIR]"
|
||||
)
|
||||
|
||||
# parse optional output dir
|
||||
output_dir = Path.cwd()
|
||||
|
||||
if "--output-dir" in args:
|
||||
idx = args.index("--output-dir")
|
||||
try:
|
||||
output_dir = Path(args[idx + 1]).resolve()
|
||||
except IndexError:
|
||||
sys.exit("Missing value for --output-dir")
|
||||
|
||||
output_dir = Path(args[idx + 1]).resolve()
|
||||
del args[idx:idx + 2]
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
release_file = output_dir / "release-notes.md"
|
||||
|
||||
# retrieval mode
|
||||
if args[0] == "--retrieve":
|
||||
if len(args) < 2:
|
||||
sys.exit("Missing tag after --retrieve")
|
||||
|
||||
print(retrieve_tag(args[1], release_file))
|
||||
return
|
||||
|
||||
# generation mode
|
||||
last_successful = args[0]
|
||||
tag = args[1] if len(args) > 1 else head_commit()
|
||||
branch = args[2] if len(args) > 2 else (
|
||||
@@ -241,12 +267,7 @@ def main():
|
||||
|
||||
new_section = generate_release_notes(last_successful, tag, branch)
|
||||
|
||||
existing = (
|
||||
release_file.read_text()
|
||||
if release_file.exists()
|
||||
else ""
|
||||
)
|
||||
|
||||
existing = release_file.read_text() if release_file.exists() else ""
|
||||
updated = update_release_md(existing, new_section, tag)
|
||||
|
||||
release_file.write_text(updated)
|
||||
|
||||
@@ -5,16 +5,33 @@ import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# helpers
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def resolve_script(name: str) -> Path:
|
||||
p = Path.cwd() / name
|
||||
if p.exists():
|
||||
return p
|
||||
return SCRIPT_DIR / name
|
||||
|
||||
|
||||
def sh(cmd: str, cwd: Path) -> str:
|
||||
return subprocess.check_output(
|
||||
cmd, shell=True, cwd=cwd
|
||||
).decode().strip()
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
cmd,
|
||||
shell=True,
|
||||
cwd=cwd,
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode().strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode(), file=sys.stderr)
|
||||
raise SystemExit(f"Command failed: {cmd}")
|
||||
|
||||
|
||||
def file_size(path: Path) -> int:
|
||||
@@ -38,35 +55,16 @@ def sha256(path: Path) -> str:
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
|
||||
p.add_argument(
|
||||
"--repo-root",
|
||||
required=True,
|
||||
help="Repo used for git history + release notes",
|
||||
)
|
||||
p.add_argument("--repo-root", required=True)
|
||||
p.add_argument("--ipa", required=True)
|
||||
p.add_argument("--output-dir", required=True)
|
||||
|
||||
p.add_argument(
|
||||
"--ipa",
|
||||
required=True,
|
||||
help="Path to IPA file",
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
"--output-dir",
|
||||
required=True,
|
||||
help="Output Directory where source_metadata.json is written",
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
"--output-name",
|
||||
default="source_metadata.json",
|
||||
help="Output metadata filename",
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
"--release-notes-dir",
|
||||
required=True,
|
||||
help="Output Directory where release-notes.md is generated/read",
|
||||
)
|
||||
p.add_argument("--release-notes-dir", required=True)
|
||||
|
||||
p.add_argument("--release-tag", required=True)
|
||||
p.add_argument("--version", required=True)
|
||||
@@ -74,6 +72,10 @@ def main():
|
||||
p.add_argument("--short-commit", required=True)
|
||||
p.add_argument("--release-channel", required=True)
|
||||
p.add_argument("--bundle-id", required=True)
|
||||
|
||||
# optional
|
||||
p.add_argument("--last-successful-commit")
|
||||
|
||||
p.add_argument("--is-beta", action="store_true")
|
||||
|
||||
args = p.parse_args()
|
||||
@@ -95,19 +97,27 @@ def main():
|
||||
out_file = out_dir / args.output_name
|
||||
|
||||
# ------------------------------------------------------
|
||||
# ensure release notes exist
|
||||
# generate release notes
|
||||
# ------------------------------------------------------
|
||||
|
||||
print("Generating release notes…")
|
||||
|
||||
sh(
|
||||
(
|
||||
"python3 generate_release_notes.py "
|
||||
script = resolve_script("generate_release_notes.py")
|
||||
|
||||
if args.last_successful_commit:
|
||||
gen_cmd = (
|
||||
f"python3 {script} "
|
||||
f"{args.last_successful_commit} {args.release_tag} "
|
||||
f"--output-dir \"{notes_dir}\""
|
||||
)
|
||||
else:
|
||||
gen_cmd = (
|
||||
f"python3 {script} "
|
||||
f"{args.short_commit} {args.release_tag} "
|
||||
f"--output-dir \"{notes_dir}\""
|
||||
),
|
||||
cwd=repo_root,
|
||||
)
|
||||
)
|
||||
|
||||
sh(gen_cmd, cwd=repo_root)
|
||||
|
||||
# ------------------------------------------------------
|
||||
# retrieve release notes
|
||||
@@ -115,7 +125,7 @@ def main():
|
||||
|
||||
notes = sh(
|
||||
(
|
||||
"python3 generate_release_notes.py "
|
||||
f"python3 {script} "
|
||||
f"--retrieve {args.release_tag} "
|
||||
f"--output-dir \"{notes_dir}\""
|
||||
),
|
||||
@@ -126,7 +136,7 @@ def main():
|
||||
# compute metadata
|
||||
# ------------------------------------------------------
|
||||
|
||||
now = datetime.datetime.now(datetime.UTC)
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
formatted = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
human = now.strftime("%c")
|
||||
|
||||
|
||||
@@ -5,23 +5,6 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
'''
|
||||
metadata.json template
|
||||
|
||||
{
|
||||
"version_ipa": "0.0.0",
|
||||
"version_date": "2000-12-18T00:00:00Z",
|
||||
"is_beta": true,
|
||||
"release_channel": "alpha",
|
||||
"size": 0,
|
||||
"sha256": "",
|
||||
"localized_description": "Invalid Update",
|
||||
"download_url": "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa",
|
||||
"bundle_identifier": "com.SideStore.SideStore"
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# args
|
||||
# ----------------------------------------------------------
|
||||
@@ -89,7 +72,7 @@ RELEASE_CHANNEL = RELEASE_CHANNEL.lower()
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# load or create source.json
|
||||
# load source.json
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if source_file.exists():
|
||||
@@ -108,7 +91,7 @@ if int(data.get("version", 1)) < 2:
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ensure app entry exists
|
||||
# locate app
|
||||
# ----------------------------------------------------------
|
||||
|
||||
apps = data.setdefault("apps", [])
|
||||
@@ -128,7 +111,7 @@ if app is None:
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# update logic
|
||||
# update storefront metadata (stable only)
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if RELEASE_CHANNEL == "stable":
|
||||
@@ -141,6 +124,11 @@ if RELEASE_CHANNEL == "stable":
|
||||
"downloadURL": DOWNLOAD_URL,
|
||||
})
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# releaseChannels update (ORIGINAL FORMAT)
|
||||
# ----------------------------------------------------------
|
||||
|
||||
channels = app.setdefault("releaseChannels", [])
|
||||
|
||||
new_version = {
|
||||
@@ -152,19 +140,31 @@ new_version = {
|
||||
"sha256": SHA256,
|
||||
}
|
||||
|
||||
tracks = [t for t in channels if t.get("track") == RELEASE_CHANNEL]
|
||||
# find track
|
||||
tracks = [
|
||||
t for t in channels
|
||||
if isinstance(t, dict) and t.get("track") == RELEASE_CHANNEL
|
||||
]
|
||||
|
||||
if len(tracks) > 1:
|
||||
print(f"Multiple tracks named {RELEASE_CHANNEL}")
|
||||
sys.exit(1)
|
||||
|
||||
if not tracks:
|
||||
# create new track at top (original behaviour)
|
||||
channels.insert(0, {
|
||||
"track": RELEASE_CHANNEL,
|
||||
"releases": [new_version],
|
||||
})
|
||||
else:
|
||||
tracks[0]["releases"][0] = new_version
|
||||
track = tracks[0]
|
||||
releases = track.setdefault("releases", [])
|
||||
|
||||
if not releases:
|
||||
releases.append(new_version)
|
||||
else:
|
||||
# replace top entry only (original logic)
|
||||
releases[0] = new_version
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
@@ -253,7 +253,7 @@ def release_notes(tag):
|
||||
# DEPLOY SOURCE.JSON
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def deploy(repo, source_json, release_tag, short_commit, marketing_version, version, channel, bundle_id, ipa_name):
|
||||
def deploy(repo, source_json, release_tag, short_commit, marketing_version, version, channel, bundle_id, ipa_name, last_successful_commit=None):
|
||||
repo = (ROOT / repo).resolve()
|
||||
ipa_path = ROOT / ipa_name
|
||||
source_path = repo / source_json
|
||||
@@ -268,8 +268,7 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers
|
||||
if not source_path.exists():
|
||||
raise SystemExit(f"{source_json} missing inside repo")
|
||||
|
||||
|
||||
run(
|
||||
cmd = (
|
||||
f"python3 {SCRIPTS}/generate_source_metadata.py "
|
||||
f"--repo-root {ROOT} "
|
||||
f"--ipa {ipa_path} "
|
||||
@@ -284,6 +283,12 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers
|
||||
f"--bundle-id {bundle_id}"
|
||||
)
|
||||
|
||||
# pass only if provided
|
||||
if last_successful_commit:
|
||||
cmd += f" --last-successful-commit {last_successful_commit}"
|
||||
|
||||
run(cmd)
|
||||
|
||||
run("git config user.name 'GitHub Actions'", check=False)
|
||||
run("git config user.email 'github-actions@github.com'", check=False)
|
||||
|
||||
@@ -309,6 +314,27 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers
|
||||
else:
|
||||
raise SystemExit("Deploy push failed after retries")
|
||||
|
||||
def last_successful_commit(workflow, branch):
|
||||
import json
|
||||
|
||||
try:
|
||||
out = runAndGet(
|
||||
f'gh run list '
|
||||
f'--workflow "{workflow}" '
|
||||
f'--json headSha,conclusion,headBranch'
|
||||
)
|
||||
|
||||
runs = json.loads(out)
|
||||
|
||||
for r in runs:
|
||||
if r.get("conclusion") == "success" and r.get("headBranch") == branch:
|
||||
return r["headSha"]
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ENTRYPOINT
|
||||
# ----------------------------------------------------------
|
||||
@@ -357,8 +383,10 @@ COMMANDS = {
|
||||
# ----------------------------------------------------------
|
||||
# RELEASE / DEPLOY
|
||||
# ----------------------------------------------------------
|
||||
"release-notes" : (release_notes, 1, "<tag>"),
|
||||
"deploy" : (deploy, 9, "<repo> <source_json> <release_tag> <short_commit> <marketing_version> <version> <channel> <bundle_id> <ipa_name>"),
|
||||
"last-successful-commit" : (last_successful_commit, 2, "<workflow_name> <branch>"),
|
||||
"release-notes" : (release_notes, 1, "<tag>"),
|
||||
"deploy" : (deploy, 10,
|
||||
"<repo> <source_json> <release_tag> <short_commit> <marketing_version> <version> <channel> <bundle_id> <ipa_name> [last_successful_commit]"),
|
||||
}
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user