automation/update-from-application-services.py (128 lines of code) (raw):

#!/usr/bin/python3 from pathlib import Path from urllib.request import urlopen import argparse import fileinput import hashlib import json import subprocess import shutil import sys import tarfile import tempfile ROOT_DIR = Path(__file__).parent.parent PACKAGE_SWIFT = ROOT_DIR / "Package.swift" # Latest nightly nightly.json file from the taskcluster index NIGHTLY_JSON_URL = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.nightly.latest/artifacts/public%2Fbuild%2Fnightly.json" def main(): args = parse_args() version = VersionInfo(args.version) tag = version.swift_version if rev_exists(tag): print(f"Tag {tag} already exists, quitting") sys.exit(1) update_source(version) if not repo_has_changes(): print("No changes detected, quitting") sys.exit(0) subprocess.check_call([ "git", "commit", "--author", "Firefox Sync Engineering<sync-team@mozilla.com>", "--message", f"Nightly auto-update ({version.swift_version})" ]) subprocess.check_call(["git", "tag", tag]) if args.push: subprocess.check_call(["git", "push", args.remote]) subprocess.check_call(["git", "push", args.remote, tag]) def update_source(version): print("Updating Package.swift xcframework info", flush=True) update_package_swift(version) print("Updating swift source files", flush=True) with tempfile.TemporaryDirectory() as temp_dir: temp_dir = Path(temp_dir) extract_tarball(version, temp_dir) replace_all_files(temp_dir) def parse_args(): parser = argparse.ArgumentParser(prog='build-and-test-swift.py') parser.add_argument('version', help="version to use (or `nightly`)") parser.add_argument('--push', help="Push changes to remote repository", action="store_true") parser.add_argument('--remote', help="Remote repository name", default="origin") return parser.parse_args() class VersionInfo: def __init__(self, app_services_version): self.is_nightly = app_services_version == "nightly" if self.is_nightly: with urlopen(NIGHTLY_JSON_URL) as stream: data = json.loads(stream.read()) app_services_version = data['version'] components = app_services_version.split(".") # check if the app services version is using the 2 or 3 component semver if len(components) == 2: # app_services_version is the 2-component version we normally use for application services self.app_services_version = app_services_version # swift_version is the 3-component version we use for Swift so that it's semver-compatible self.swift_version = f"{components[0]}.0.{components[1]}" # if it's 3-component, use as-is elif len(components) == 3: self.app_services_version = app_services_version self.swift_version = app_services_version else: raise ValueError(f"Invalid app_services_version: {app_services_version}") def rev_exists(branch): result = subprocess.run( ["git", "rev-parse", "--verify", branch], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return result.returncode == 0 def update_package_swift(version): url = swift_artifact_url(version, "MozillaRustComponents.xcframework.zip") focus_url = swift_artifact_url(version, "FocusRustComponents.xcframework.zip") checksum = compute_checksum(url) focus_checksum = compute_checksum(focus_url) replacements = { "let version =": f'let version = "{version.swift_version}"', "let url =": f'let url = "{url}"', "let checksum =": f'let checksum = "{checksum}"', "let focusUrl =": f'let focusUrl = "{focus_url}"', "let focusChecksum =": f'let focusChecksum = "{focus_checksum}"', } for line in fileinput.input(PACKAGE_SWIFT, inplace=True): for (line_start, replacement) in replacements.items(): if line.strip().startswith(line_start): line = f"{replacement}\n" break sys.stdout.write(line) subprocess.check_call(["git", "add", PACKAGE_SWIFT]) def compute_checksum(url): with urlopen(url) as stream: return hashlib.sha256(stream.read()).hexdigest() def extract_tarball(version, temp_dir): with urlopen(swift_artifact_url(version, "swift-components.tar.xz")) as f: with tarfile.open(mode="r|xz", fileobj=f) as tar: for member in tar: if not Path(member.name).name.startswith("._"): tar.extract(member, path=temp_dir) def replace_all_files(temp_dir): replace_files(temp_dir / "swift-components/all", "swift-source/all") replace_files(temp_dir / "swift-components/focus", "swift-source/focus") subprocess.check_call(["git", "add", "swift-source"]) """ Replace files in the git repo with files extracted from the tarball Args: source_dir: directory to look for sources repo_dir: relative directory in the repo to replace files in """ def replace_files(source_dir, repo_dir): shutil.rmtree(repo_dir) shutil.copytree(source_dir, repo_dir) def swift_artifact_url(version, filename): if version.is_nightly: return ("https://firefox-ci-tc.services.mozilla.com" "/api/index/v1/task/project.application-services.v2" f".swift.{version.app_services_version}/artifacts/public/build/{filename}") else: return ("https://archive.mozilla.org" "/pub/app-services/releases/" f"{version.app_services_version}/{filename}") def repo_has_changes(): result = subprocess.run([ "git", "diff-index", "--quiet", "HEAD", ]) return result.returncode != 0 if __name__ == '__main__': main()