nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
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()