at master 7.6 kB view raw
1#! /usr/bin/env nix-shell 2#! nix-shell -i python -p python3.pkgs.joblib python3.pkgs.click python3.pkgs.click-log nix nurl 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, UTC 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 47# Absolute path of nixpkgs top-level directory 48NIXPKGS_PATH = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip() 49 50memory: Memory = Memory("cache", verbose=0) 51 52logger = logging.getLogger(__name__) 53click_log.basic_config(logger) 54 55 56def get_gclient_data(rev: str) -> any: 57 output = subprocess.check_output( 58 ["gclient2nix", "generate", 59 f"https://github.com/electron/electron@{rev}", 60 "--root", "src/electron"] 61 ) 62 63 return json.loads(output) 64 65 66def get_chromium_file(chromium_tag: str, filepath: str) -> str: 67 return base64.b64decode( 68 urlopen( 69 f"https://chromium.googlesource.com/chromium/src.git/+/{chromium_tag}/{filepath}?format=TEXT" 70 ).read() 71 ).decode("utf-8") 72 73 74def get_electron_file(electron_tag: str, filepath: str) -> str: 75 return ( 76 urlopen( 77 f"https://raw.githubusercontent.com/electron/electron/{electron_tag}/{filepath}" 78 ) 79 .read() 80 .decode("utf-8") 81 ) 82 83 84@memory.cache 85def get_gn_hash(gn_version, gn_commit): 86 print("gn.override", file=sys.stderr) 87 expr = f'(import {NIXPKGS_PATH} {{}}).gn.override {{ version = "{gn_version}"; rev = "{gn_commit}"; hash = ""; }}' 88 out = subprocess.check_output(["nurl", "--hash", "--expr", expr]) 89 return out.decode("utf-8").strip() 90 91@memory.cache 92def get_chromium_gn_source(chromium_tag: str) -> dict: 93 gn_pattern = r"'gn_version': 'git_revision:([0-9a-f]{40})'" 94 gn_commit = re.search(gn_pattern, get_chromium_file(chromium_tag, "DEPS")).group(1) 95 96 gn_commit_info = json.loads( 97 urlopen(f"https://gn.googlesource.com/gn/+/{gn_commit}?format=json") 98 .read() 99 .decode("utf-8") 100 .split(")]}'\n")[1] 101 ) 102 103 gn_commit_date = datetime.strptime(gn_commit_info["committer"]["time"], "%a %b %d %H:%M:%S %Y %z") 104 gn_date = gn_commit_date.astimezone(UTC).date().isoformat() 105 gn_version = f"0-unstable-{gn_date}" 106 107 return { 108 "gn": { 109 "version": gn_version, 110 "rev": gn_commit, 111 "hash": get_gn_hash(gn_version, gn_commit), 112 } 113 } 114 115@memory.cache 116def get_electron_yarn_hash(electron_tag: str) -> str: 117 print(f"prefetch-yarn-deps", file=sys.stderr) 118 with tempfile.TemporaryDirectory() as tmp_dir: 119 with open(tmp_dir + "/yarn.lock", "w") as f: 120 f.write(get_electron_file(electron_tag, "yarn.lock")) 121 return ( 122 subprocess.check_output(["prefetch-yarn-deps", tmp_dir + "/yarn.lock"]) 123 .decode("utf-8") 124 .strip() 125 ) 126 127@memory.cache 128def get_chromium_npm_hash(chromium_tag: str) -> str: 129 print(f"prefetch-npm-deps", file=sys.stderr) 130 with tempfile.TemporaryDirectory() as tmp_dir: 131 with open(tmp_dir + "/package-lock.json", "w") as f: 132 f.write(get_chromium_file(chromium_tag, "third_party/node/package-lock.json")) 133 return ( 134 subprocess.check_output( 135 ["prefetch-npm-deps", tmp_dir + "/package-lock.json"] 136 ) 137 .decode("utf-8") 138 .strip() 139 ) 140 141 142def get_update(major_version: str, m: str, gclient_data: any) -> Tuple[str, dict]: 143 144 tasks = [] 145 a = lambda: (("electron_yarn_hash", get_electron_yarn_hash(gclient_data["src/electron"]["args"]["tag"]))) 146 tasks.append(delayed(a)()) 147 a = lambda: ( 148 ( 149 "chromium_npm_hash", 150 get_chromium_npm_hash(gclient_data["src"]["args"]["tag"]), 151 ) 152 ) 153 tasks.append(delayed(a)()) 154 random.shuffle(tasks) 155 156 task_results = { 157 n[0]: n[1] 158 for n in Parallel(n_jobs=3, require="sharedmem", return_as="generator")(tasks) 159 if n != None 160 } 161 162 return ( 163 f"{major_version}", 164 { 165 "deps": gclient_data, 166 **{key: m[key] for key in ["version", "modules", "chrome", "node"]}, 167 "chromium": { 168 "version": m["chrome"], 169 "deps": get_chromium_gn_source(gclient_data["src"]["args"]["tag"]), 170 }, 171 **task_results, 172 }, 173 ) 174 175 176def non_eol_releases(releases: Iterable[int]) -> Iterable[int]: 177 """Returns a list of releases that have not reached end-of-life yet.""" 178 return tuple(filter(lambda x: x in supported_version_range(), releases)) 179 180 181def update_source(version: str, commit: bool) -> None: 182 """Update a given electron-source release 183 184 Args: 185 version: The major version number, e.g. '27' 186 commit: Whether the updater should commit the result 187 """ 188 major_version = version 189 190 package_name = f"electron-source.electron_{major_version}" 191 print(f"Updating electron-source.electron_{major_version}") 192 193 old_info = load_info_json(SOURCE_INFO_JSON) 194 old_version = ( 195 old_info[major_version]["version"] 196 if major_version in old_info 197 else None 198 ) 199 200 m, rev = get_latest_version(major_version) 201 if old_version == m["version"]: 202 print(f"{package_name} is up-to-date") 203 return 204 205 gclient_data = get_gclient_data(rev) 206 new_info = get_update(major_version, m, gclient_data) 207 out = old_info | {new_info[0]: new_info[1]} 208 209 save_info_json(SOURCE_INFO_JSON, out) 210 211 new_version = new_info[1]["version"] 212 if commit: 213 commit_result(package_name, old_version, new_version, SOURCE_INFO_JSON) 214 215 216@click.group() 217def cli() -> None: 218 """A script for updating electron-source hashes""" 219 pass 220 221 222@cli.command("update", help="Update a single major release") 223@click.option("-v", "--version", required=True, type=str, help="The major version, e.g. '23'") 224@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result") 225def update(version: str, commit: bool) -> None: 226 update_source(version, commit) 227 228 229@cli.command("update-all", help="Update all releases at once") 230@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result") 231def update_all(commit: bool) -> None: 232 """Update all eletron-source releases at once 233 234 Args: 235 commit: Whether to commit the result 236 """ 237 old_info = load_info_json(SOURCE_INFO_JSON) 238 239 filtered_releases = non_eol_releases(tuple(map(lambda x: int(x), old_info.keys()))) 240 241 for major_version in filtered_releases: 242 update_source(str(major_version), commit) 243 244 245if __name__ == "__main__": 246 cli()