nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1#! /usr/bin/env nix-shell
2#! nix-shell -i python3 -p python3Packages.pyyaml
3
4import argparse
5import json
6import os
7import re
8import subprocess
9import sys
10import tempfile
11import urllib.request
12from pathlib import Path
13
14import yaml
15
16FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
17
18NIXPKGS_ROOT = (
19 subprocess.Popen(
20 ["git", "rev-parse", "--show-toplevel"], stdout=subprocess.PIPE, text=True
21 )
22 .communicate()[0]
23 .strip()
24)
25
26
27def load_code(name, **kwargs):
28 with Path(
29 f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/update/{name}.in"
30 ).open("r", encoding="utf-8") as f:
31 code = f.read()
32
33 for key, value in kwargs.items():
34 code = code.replace(f"@{key}@", value)
35
36 return code
37
38
39# Return out paths
40def nix_build(code):
41 with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", delete=False) as temp:
42 temp.write(code)
43 temp.flush()
44 os.fsync(temp.fileno())
45 temp_name = temp.name
46
47 process = subprocess.Popen(
48 [
49 "nix-build",
50 "--impure",
51 "--no-out-link",
52 "--expr",
53 f"with import {NIXPKGS_ROOT} {{}}; callPackage {temp_name} {{}}",
54 ],
55 stdout=subprocess.PIPE,
56 text=True,
57 )
58
59 process.wait()
60 Path(temp_name).unlink() # Clean up the temporary file
61 return process.stdout.read().strip().splitlines()[0]
62
63
64# Return errors
65def nix_build_to_fail(code):
66 with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", delete=False) as temp:
67 temp.write(code)
68 temp.flush()
69 os.fsync(temp.fileno())
70 temp_name = temp.name
71
72 process = subprocess.Popen(
73 [
74 "nix-build",
75 "--impure",
76 "--keep-going",
77 "--no-link",
78 "--expr",
79 f"with import {NIXPKGS_ROOT} {{}}; callPackage {temp_name} {{}}",
80 ],
81 stderr=subprocess.PIPE,
82 text=True,
83 )
84
85 stderr = ""
86 while True:
87 line = process.stderr.readline()
88 if not line:
89 break
90 stderr += line
91 print(line.strip())
92
93 process.wait()
94 Path(temp_name).unlink() # Clean up the temporary file
95 return stderr
96
97
98def get_engine_hashes(engine_version, flutter_version):
99 code = load_code(
100 "get-engine-hashes.nix",
101 nixpkgs_root=NIXPKGS_ROOT,
102 flutter_version=flutter_version,
103 engine_version=engine_version,
104 )
105
106 stderr = nix_build_to_fail(code)
107
108 pattern = re.compile(
109 rf"/nix/store/.*-flutter-engine-source-{engine_version}-(.+?-.+?)-(.+?-.+?).drv':\n\s+specified: .*\n\s+got:\s+(.+?)\n"
110 )
111 matches = pattern.findall(stderr)
112 result_dict = {}
113
114 for match in matches:
115 flutter_platform, architecture, got = match
116 result_dict.setdefault(flutter_platform, {})[architecture] = got
117
118 def sort_dict_recursive(d):
119 return {
120 k: sort_dict_recursive(v) if isinstance(v, dict) else v
121 for k, v in sorted(d.items())
122 }
123
124 return sort_dict_recursive(result_dict)
125
126
127def get_artifact_hashes(flutter_compact_version):
128 code = load_code(
129 "get-artifact-hashes.nix",
130 nixpkgs_root=NIXPKGS_ROOT,
131 flutter_compact_version=flutter_compact_version,
132 )
133
134 stderr = nix_build_to_fail(code)
135
136 pattern = re.compile(
137 r"/nix/store/.*-flutter-artifacts-(.+?)-(.+?).drv':\n\s+specified: .*\n\s+got:\s+(.+?)\n"
138 )
139 matches = pattern.findall(stderr)
140 result_dict = {}
141
142 for match in matches:
143 flutter_platform, architecture, got = match
144 result_dict.setdefault(flutter_platform, {})[architecture] = got
145
146 def sort_dict_recursive(d):
147 return {
148 k: sort_dict_recursive(v) if isinstance(v, dict) else v
149 for k, v in sorted(d.items())
150 }
151
152 return sort_dict_recursive(result_dict)
153
154
155def get_dart_hashes(dart_version, channel):
156 platforms = ["x86_64-linux", "aarch64-linux", "x86_64-darwin", "aarch64-darwin"]
157 result_dict = {}
158 for platform in platforms:
159 code = load_code(
160 "get-dart-hashes.nix",
161 dart_version=dart_version,
162 platform=platform,
163 )
164 stderr = nix_build_to_fail(code)
165
166 pattern = re.compile(r"got:\s+(.+?)\n")
167 result_dict[platform] = pattern.findall(stderr)[0]
168
169 return result_dict
170
171
172def get_flutter_hash_and_src(flutter_version):
173 code = load_code("get-flutter.nix", flutter_version=flutter_version, hash="")
174
175 stderr = nix_build_to_fail(code)
176 pattern = re.compile(r"got:\s+(.+?)\n")
177 flutter_hash_value = pattern.findall(stderr)[0]
178
179 code = load_code(
180 "get-flutter.nix", flutter_version=flutter_version, hash=flutter_hash_value
181 )
182
183 return (flutter_hash_value, nix_build(code))
184
185
186def get_pubspec_lock(flutter_compact_version, flutter_src):
187 code = load_code(
188 "get-pubspec-lock.nix",
189 flutter_compact_version=flutter_compact_version,
190 flutter_src=flutter_src,
191 hash="",
192 )
193
194 stderr = nix_build_to_fail(code)
195 pattern = re.compile(r"got:\s+(.+?)\n")
196 pubspec_lock_hash = pattern.findall(stderr)[0]
197
198 code = load_code(
199 "get-pubspec-lock.nix",
200 flutter_compact_version=flutter_compact_version,
201 flutter_src=flutter_src,
202 hash=pubspec_lock_hash,
203 )
204
205 pubspec_lock_file = nix_build(code)
206
207 with Path(pubspec_lock_file).open("r", encoding="utf-8") as f:
208 pubspec_lock_yaml = f.read()
209
210 return yaml.safe_load(pubspec_lock_yaml)
211
212
213def get_engine_swiftshader_rev(engine_version):
214 with urllib.request.urlopen(
215 f"https://github.com/flutter/flutter/raw/{engine_version}/DEPS"
216 ) as f:
217 deps = f.read().decode("utf-8")
218 pattern = re.compile(
219 r"Var\('swiftshader_git'\) \+ '\/SwiftShader\.git' \+ '@' \+ \'([0-9a-fA-F]{40})\'\,"
220 )
221 return pattern.findall(deps)[0]
222
223
224def get_engine_swiftshader_hash(engine_swiftshader_rev):
225 code = load_code(
226 "get-engine-swiftshader.nix",
227 engine_swiftshader_rev=engine_swiftshader_rev,
228 hash="",
229 )
230
231 stderr = nix_build_to_fail(code)
232 pattern = re.compile(r"got:\s+(.+?)\n")
233 return pattern.findall(stderr)[0]
234
235
236def write_data(
237 nixpkgs_flutter_version_directory,
238 flutter_version,
239 channel,
240 engine_hash,
241 engine_hashes,
242 engine_swiftshader_hash,
243 engine_swiftshader_rev,
244 dart_version,
245 dart_hash,
246 flutter_hash,
247 artifact_hashes,
248 pubspec_lock,
249):
250 with Path(f"{nixpkgs_flutter_version_directory}/data.json").open(
251 "w", encoding="utf-8"
252 ) as f:
253 f.write(
254 json.dumps(
255 {
256 "version": flutter_version,
257 "engineVersion": engine_hash,
258 "engineSwiftShaderHash": engine_swiftshader_hash,
259 "engineSwiftShaderRev": engine_swiftshader_rev,
260 "channel": channel,
261 "engineHashes": engine_hashes,
262 "dartVersion": dart_version,
263 "dartHash": dart_hash,
264 "flutterHash": flutter_hash,
265 "artifactHashes": artifact_hashes,
266 "pubspecLock": pubspec_lock,
267 },
268 indent=2,
269 ).strip()
270 + "\n"
271 )
272
273
274def update_all_packages():
275 versions_directory = f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/versions"
276 versions = [d.name for d in Path(versions_directory).iterdir()]
277 versions = sorted(
278 versions,
279 key=lambda x: (int(x.split("_")[0]), int(x.split("_")[1])),
280 reverse=True,
281 )
282
283 new_content = [
284 "flutterPackages-bin = recurseIntoAttrs (callPackage ../development/compilers/flutter { });",
285 "flutterPackages-source = recurseIntoAttrs (",
286 " callPackage ../development/compilers/flutter { useNixpkgsEngine = true; }",
287 ");",
288 "flutterPackages = flutterPackages-bin;",
289 "flutter = flutterPackages.stable;",
290 ] + [
291 f"flutter{version.replace('_', '')} = flutterPackages.v{version};"
292 for version in versions
293 ]
294
295 with Path(f"{NIXPKGS_ROOT}/pkgs/top-level/all-packages.nix").open(
296 "r", encoding="utf-8"
297 ) as file:
298 lines = file.read().splitlines(keepends=True)
299
300 start = -1
301 end = -1
302 for i, line in enumerate(lines):
303 if (
304 "flutterPackages-bin = recurseIntoAttrs (callPackage ../development/compilers/flutter { });"
305 in line
306 ):
307 start = i
308 if start != -1 and len(line.strip()) == 0:
309 end = i
310 break
311
312 if start != -1 and end != -1:
313 del lines[start:end]
314 lines[start:start] = [f" {line}\n" for line in new_content]
315
316 with Path(f"{NIXPKGS_ROOT}/pkgs/top-level/all-packages.nix").open(
317 "w", encoding="utf-8"
318 ) as file:
319 file.write("".join(lines))
320
321
322# Finds Flutter version, Dart version, and Engine hash.
323# If the Flutter version is given, it uses that. Otherwise finds the
324# latest stable Flutter version.
325def find_versions(flutter_version=None, channel=None):
326 engine_hash = None
327 dart_version = None
328
329 releases = json.load(
330 urllib.request.urlopen(
331 "https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json"
332 )
333 )
334
335 if not channel:
336 channel = "stable"
337
338 if not flutter_version:
339 release_hash = releases["current_release"][channel]
340 release = next(
341 filter(
342 lambda release: release["hash"] == release_hash, releases["releases"]
343 )
344 )
345 flutter_version = release["version"]
346
347 tags = (
348 subprocess.Popen(
349 ["git", "ls-remote", "--tags", "https://github.com/flutter/flutter.git"],
350 stdout=subprocess.PIPE,
351 text=True,
352 )
353 .communicate()[0]
354 .strip()
355 )
356
357 try:
358 flutter_hash = (
359 next(
360 filter(
361 lambda line: line.endswith(f"refs/tags/{flutter_version}"),
362 tags.splitlines(),
363 )
364 )
365 .split("refs")[0]
366 .strip()
367 )
368
369 engine_hash = (
370 urllib.request.urlopen(
371 f"https://github.com/flutter/flutter/raw/{flutter_hash}/bin/internal/engine.version"
372 )
373 .read()
374 .decode("utf-8")
375 .strip()
376 )
377 except StopIteration:
378 sys.exit(f"Couldn't find Engine hash for Flutter version: {flutter_version}")
379
380 try:
381 dart_version = next(
382 filter(
383 lambda release: release["version"] == flutter_version,
384 releases["releases"],
385 )
386 )["dart_sdk_version"]
387
388 if " " in dart_version:
389 dart_version = dart_version.split(" ")[2][:-1]
390 except StopIteration:
391 sys.exit(f"Couldn't find Dart version for Flutter version: {flutter_version}")
392
393 return (flutter_version, engine_hash, dart_version, channel)
394
395
396def main():
397 parser = argparse.ArgumentParser(description="Update Flutter in Nixpkgs")
398 parser.add_argument("--version", type=str, help="Specify Flutter version")
399 parser.add_argument("--channel", type=str, help="Specify Flutter release channel")
400 parser.add_argument(
401 "--artifact-hashes", action="store_true", help="Whether to get artifact hashes"
402 )
403 args = parser.parse_args()
404
405 (flutter_version, engine_hash, dart_version, channel) = find_versions(
406 args.version, args.channel
407 )
408
409 flutter_compact_version = "_".join(flutter_version.split(".")[:2])
410
411 if args.artifact_hashes:
412 print(
413 json.dumps(get_artifact_hashes(flutter_compact_version), indent=2).strip()
414 + "\n"
415 )
416 return
417
418 print(
419 f"Flutter version: {flutter_version} ({flutter_compact_version}) on ({channel})"
420 )
421 print(f"Engine hash: {engine_hash}")
422 print(f"Dart version: {dart_version}")
423
424 dart_hash = get_dart_hashes(dart_version, channel)
425 (flutter_hash, flutter_src) = get_flutter_hash_and_src(flutter_version)
426
427 nixpkgs_flutter_version_directory = f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/versions/{flutter_compact_version}"
428
429 if Path(f"{nixpkgs_flutter_version_directory}/data.json").exists():
430 Path(f"{nixpkgs_flutter_version_directory}/data.json").unlink()
431 Path(nixpkgs_flutter_version_directory).mkdir(parents=True, exist_ok=True)
432
433 update_all_packages()
434
435 common_data_args = {
436 "nixpkgs_flutter_version_directory": nixpkgs_flutter_version_directory,
437 "flutter_version": flutter_version,
438 "channel": channel,
439 "dart_version": dart_version,
440 "engine_hash": engine_hash,
441 "flutter_hash": flutter_hash,
442 "dart_hash": dart_hash,
443 }
444
445 write_data(
446 pubspec_lock={},
447 artifact_hashes={},
448 engine_hashes={},
449 engine_swiftshader_hash=FAKE_HASH,
450 engine_swiftshader_rev="0",
451 **common_data_args,
452 )
453
454 pubspec_lock = get_pubspec_lock(flutter_compact_version, flutter_src)
455
456 write_data(
457 pubspec_lock=pubspec_lock,
458 artifact_hashes={},
459 engine_hashes={},
460 engine_swiftshader_hash=FAKE_HASH,
461 engine_swiftshader_rev="0",
462 **common_data_args,
463 )
464
465 artifact_hashes = get_artifact_hashes(flutter_compact_version)
466
467 write_data(
468 pubspec_lock=pubspec_lock,
469 artifact_hashes=artifact_hashes,
470 engine_hashes={},
471 engine_swiftshader_hash=FAKE_HASH,
472 engine_swiftshader_rev="0",
473 **common_data_args,
474 )
475
476 engine_hashes = get_engine_hashes(engine_hash, flutter_version)
477
478 write_data(
479 pubspec_lock=pubspec_lock,
480 artifact_hashes=artifact_hashes,
481 engine_hashes=engine_hashes,
482 engine_swiftshader_hash=FAKE_HASH,
483 engine_swiftshader_rev="0",
484 **common_data_args,
485 )
486
487 engine_swiftshader_rev = get_engine_swiftshader_rev(engine_hash)
488 engine_swiftshader_hash = get_engine_swiftshader_hash(engine_swiftshader_rev)
489
490 write_data(
491 pubspec_lock=pubspec_lock,
492 artifact_hashes=artifact_hashes,
493 engine_hashes=engine_hashes,
494 engine_swiftshader_hash=engine_swiftshader_hash,
495 engine_swiftshader_rev=engine_swiftshader_rev,
496 **common_data_args,
497 )
498
499
500if __name__ == "__main__":
501 main()