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()