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