nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at python-updates 501 lines 14 kB view raw
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()