#!/usr/bin/env python3 import os import sys import subprocess import datetime from pathlib import Path ROOT = Path(__file__).resolve().parents[1] # ---------------------------------------------------------- # helpers # ---------------------------------------------------------- def run(cmd, check=True): print(f"$ {cmd}", flush=True) subprocess.run(cmd, shell=True, cwd=ROOT, check=check) def output(name, value): print(f"{name}={value}") out = os.environ.get("GITHUB_OUTPUT") if out: with open(out, "a") as f: f.write(f"{name}={value}\n") def getenv(name, default=""): return os.environ.get(name, default) # ---------------------------------------------------------- # SHARED # ---------------------------------------------------------- def short_commit(): sha = subprocess.check_output( "git rev-parse --short HEAD", shell=True, cwd=ROOT ).decode().strip() output("short_commit", sha) return sha # ---------------------------------------------------------- # VERSION BUMP # ---------------------------------------------------------- def bump_beta(): date = datetime.datetime.utcnow().strftime("%Y.%m.%d") release_channel = getenv("RELEASE_CHANNEL", "beta") build_file = ROOT / "build_number.txt" xcconfig = ROOT / "Build.xcconfig" short = subprocess.check_output( "git rev-parse --short HEAD", shell=True, cwd=ROOT ).decode().strip() def write(num): run( f"""sed -e "/MARKETING_VERSION = .*/s/$/-{release_channel}.{date}.{num}+{short}/" -i '' Build.xcconfig""" ) build_file.write_text(f"{date},{num}") if not build_file.exists(): write(1) return last = build_file.read_text().strip().split(",")[1] write(int(last) + 1) # ---------------------------------------------------------- # VERSION EXTRACTION # ---------------------------------------------------------- def extract_version(): v = subprocess.check_output( "grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g'", shell=True, cwd=ROOT ).decode().strip() output("version", v) return v # ---------------------------------------------------------- # CLEAN # ---------------------------------------------------------- def clean(): run("make clean") clean_derived_data() def clean_derived_data(): run("rm -rf ~/Library/Developer/Xcode/DerivedData/*", check=False) # ---------------------------------------------------------- # BUILD # ---------------------------------------------------------- def build(): run("make clean") run("rm -rf ~/Library/Developer/Xcode/DerivedData/*", check=False) run("mkdir -p build/logs") run( "set -o pipefail && " "NSUnbufferedIO=YES make -B build " "2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions" ) run("make fakesign | tee -a build/logs/build.log") run("make ipa | tee -a build/logs/build.log") run("zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs") # ---------------------------------------------------------- # TESTS BUILD # ---------------------------------------------------------- def tests_build(): run("mkdir -p build/logs") run( "NSUnbufferedIO=YES make -B build-tests " "2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions" ) # ---------------------------------------------------------- # TESTS RUN # ---------------------------------------------------------- def tests_run(): run("mkdir -p build/logs") run("nohup make -B boot-sim-async > build/logs/tests-run.log 2>&1 &") run("make -B sim-boot-check | tee -a build/logs/tests-run.log") run("make run-tests 2>&1 | tee -a build/logs/tests-run.log") run("zip -r -9 ./test-results.zip ./build/tests") # ---------------------------------------------------------- # LOG ENCRYPTION # ---------------------------------------------------------- def encrypt_logs(name): pwd = getenv("BUILD_LOG_ZIP_PASSWORD", "12345") run( f'cd build/logs && zip -e -P "{pwd}" ../../{name}.zip *' ) # ---------------------------------------------------------- # RELEASE NOTES # ---------------------------------------------------------- def release_notes(tag): last = subprocess.check_output( "gh run list --branch $(git branch --show-current) " "--status success --json headSha --jq '.[0].headSha'", shell=True, cwd=ROOT ).decode().strip() if not last or last == "null": last = subprocess.check_output( "git rev-list --max-parents=0 HEAD", shell=True, cwd=ROOT ).decode().strip() run(f"python3 update_release_notes.py {last} {tag}") # ---------------------------------------------------------- # PUBLISH SOURCE.JSON # ---------------------------------------------------------- def publish_apps(short_commit): repo = ROOT / "SideStore/apps-v2.json" run("git config user.name 'GitHub Actions'", check=False) run("git config user.email 'github-actions@github.com'", check=False) run("python3 scripts/update_apps.py './_includes/source.json'", check=False) run("git add ./_includes/source.json", check=False) run( f"git commit -m ' - updated for {short_commit} deployment' || true", check=False ) run("git push", check=False) # ---------------------------------------------------------- # ENTRYPOINT # ---------------------------------------------------------- def main(): cmd = sys.argv[1] if cmd == "commid-id": short_commit() elif cmd == "bump-beta": bump_beta() elif cmd == "version": extract_version() elif cmd == "clean": clean() elif cmd == "cleanDerivedData": clean_derived_data() elif cmd == "build": build() elif cmd == "tests-build": tests_build() elif cmd == "tests-run": tests_run() elif cmd == "encrypt-build": encrypt_logs("encrypted-build-logs") elif cmd == "encrypt-tests-build": encrypt_logs("encrypted-tests-build-logs") elif cmd == "encrypt-tests-run": encrypt_logs("encrypted-tests-run-logs") elif cmd == "release-notes": release_notes(sys.argv[2]) elif cmd == "publish": publish_apps(sys.argv[2]) else: raise SystemExit(f"Unknown command {cmd}") if __name__ == "__main__": main()