Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1#! /usr/bin/env nix-shell 2#! nix-shell -i python -p python3.pkgs.joblib python3.pkgs.click python3.pkgs.click-log nix nix-prefetch-git prefetch-yarn-deps prefetch-npm-deps gclient2nix 3""" 4electron updater 5 6A script for updating electron source hashes. 7 8It supports the following modes: 9 10| Mode | Description | 11|------------- | ----------------------------------------------- | 12| `update` | for updating a specific Electron release | 13| `update-all` | for updating all electron releases at once | 14 15The `update` commands requires a `--version` flag 16to specify the major release to be updated. 17The `update-all command updates all non-eol major releases. 18 19The `update` and `update-all` commands accept an optional `--commit` 20flag to automatically commit the changes for you. 21""" 22import base64 23import json 24import logging 25import os 26import random 27import re 28import subprocess 29import sys 30import tempfile 31import urllib.request 32import click 33import click_log 34 35from datetime import datetime 36from typing import Iterable, Tuple 37from urllib.request import urlopen 38from joblib import Parallel, delayed, Memory 39from update_util import * 40 41 42# Relative path to the electron-source info.json 43SOURCE_INFO_JSON = "info.json" 44 45os.chdir(os.path.dirname(__file__)) 46 47memory: Memory = Memory("cache", verbose=0) 48 49logger = logging.getLogger(__name__) 50click_log.basic_config(logger) 51 52 53def get_gclient_data(rev: str) -> any: 54 output = subprocess.check_output( 55 ["gclient2nix", "generate", 56 f"https://github.com/electron/electron@{rev}", 57 "--root", "src/electron"] 58 ) 59 60 return json.loads(output) 61 62 63def get_chromium_file(chromium_tag: str, filepath: str) -> str: 64 return base64.b64decode( 65 urlopen( 66 f"https://chromium.googlesource.com/chromium/src.git/+/{chromium_tag}/{filepath}?format=TEXT" 67 ).read() 68 ).decode("utf-8") 69 70 71def get_electron_file(electron_tag: str, filepath: str) -> str: 72 return ( 73 urlopen( 74 f"https://raw.githubusercontent.com/electron/electron/{electron_tag}/{filepath}" 75 ) 76 .read() 77 .decode("utf-8") 78 ) 79 80 81@memory.cache 82def get_chromium_gn_source(chromium_tag: str) -> dict: 83 gn_pattern = r"'gn_version': 'git_revision:([0-9a-f]{40})'" 84 gn_commit = re.search(gn_pattern, get_chromium_file(chromium_tag, "DEPS")).group(1) 85 gn_prefetch: bytes = subprocess.check_output( 86 [ 87 "nix-prefetch-git", 88 "--quiet", 89 "https://gn.googlesource.com/gn", 90 "--rev", 91 gn_commit, 92 ] 93 ) 94 gn: dict = json.loads(gn_prefetch) 95 return { 96 "gn": { 97 "version": datetime.fromisoformat(gn["date"]).date().isoformat(), 98 "url": gn["url"], 99 "rev": gn["rev"], 100 "hash": gn["hash"], 101 } 102 } 103 104@memory.cache 105def get_electron_yarn_hash(electron_tag: str) -> str: 106 print(f"prefetch-yarn-deps", file=sys.stderr) 107 with tempfile.TemporaryDirectory() as tmp_dir: 108 with open(tmp_dir + "/yarn.lock", "w") as f: 109 f.write(get_electron_file(electron_tag, "yarn.lock")) 110 return ( 111 subprocess.check_output(["prefetch-yarn-deps", tmp_dir + "/yarn.lock"]) 112 .decode("utf-8") 113 .strip() 114 ) 115 116@memory.cache 117def get_chromium_npm_hash(chromium_tag: str) -> str: 118 print(f"prefetch-npm-deps", file=sys.stderr) 119 with tempfile.TemporaryDirectory() as tmp_dir: 120 with open(tmp_dir + "/package-lock.json", "w") as f: 121 f.write(get_chromium_file(chromium_tag, "third_party/node/package-lock.json")) 122 return ( 123 subprocess.check_output( 124 ["prefetch-npm-deps", tmp_dir + "/package-lock.json"] 125 ) 126 .decode("utf-8") 127 .strip() 128 ) 129 130 131def get_update(major_version: str, m: str, gclient_data: any) -> Tuple[str, dict]: 132 133 tasks = [] 134 a = lambda: (("electron_yarn_hash", get_electron_yarn_hash(gclient_data["src/electron"]["args"]["tag"]))) 135 tasks.append(delayed(a)()) 136 a = lambda: ( 137 ( 138 "chromium_npm_hash", 139 get_chromium_npm_hash(gclient_data["src"]["args"]["tag"]), 140 ) 141 ) 142 tasks.append(delayed(a)()) 143 random.shuffle(tasks) 144 145 task_results = { 146 n[0]: n[1] 147 for n in Parallel(n_jobs=3, require="sharedmem", return_as="generator")(tasks) 148 if n != None 149 } 150 151 return ( 152 f"{major_version}", 153 { 154 "deps": gclient_data, 155 **{key: m[key] for key in ["version", "modules", "chrome", "node"]}, 156 "chromium": { 157 "version": m["chrome"], 158 "deps": get_chromium_gn_source(gclient_data["src"]["args"]["tag"]), 159 }, 160 **task_results, 161 }, 162 ) 163 164 165def non_eol_releases(releases: Iterable[int]) -> Iterable[int]: 166 """Returns a list of releases that have not reached end-of-life yet.""" 167 return tuple(filter(lambda x: x in supported_version_range(), releases)) 168 169 170def update_source(version: str, commit: bool) -> None: 171 """Update a given electron-source release 172 173 Args: 174 version: The major version number, e.g. '27' 175 commit: Whether the updater should commit the result 176 """ 177 major_version = version 178 179 package_name = f"electron-source.electron_{major_version}" 180 print(f"Updating electron-source.electron_{major_version}") 181 182 old_info = load_info_json(SOURCE_INFO_JSON) 183 old_version = ( 184 old_info[major_version]["version"] 185 if major_version in old_info 186 else None 187 ) 188 189 m, rev = get_latest_version(major_version) 190 if old_version == m["version"]: 191 print(f"{package_name} is up-to-date") 192 return 193 194 gclient_data = get_gclient_data(rev) 195 new_info = get_update(major_version, m, gclient_data) 196 out = old_info | {new_info[0]: new_info[1]} 197 198 save_info_json(SOURCE_INFO_JSON, out) 199 200 new_version = new_info[1]["version"] 201 if commit: 202 commit_result(package_name, old_version, new_version, SOURCE_INFO_JSON) 203 204 205@click.group() 206def cli() -> None: 207 """A script for updating electron-source hashes""" 208 pass 209 210 211@cli.command("update", help="Update a single major release") 212@click.option("-v", "--version", required=True, type=str, help="The major version, e.g. '23'") 213@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result") 214def update(version: str, commit: bool) -> None: 215 update_source(version, commit) 216 217 218@cli.command("update-all", help="Update all releases at once") 219@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result") 220def update_all(commit: bool) -> None: 221 """Update all eletron-source releases at once 222 223 Args: 224 commit: Whether to commit the result 225 """ 226 old_info = load_info_json(SOURCE_INFO_JSON) 227 228 filtered_releases = non_eol_releases(tuple(map(lambda x: int(x), old_info.keys()))) 229 230 for major_version in filtered_releases: 231 update_source(str(major_version), commit) 232 233 234if __name__ == "__main__": 235 cli()