1#! /usr/bin/env nix-shell
2#! nix-shell -i python3 -p python3Packages.pyyaml
3
4import shutil
5import json
6import urllib.request
7import tempfile
8from sys import exit
9import os
10import subprocess
11import re
12import json
13import argparse
14import yaml
15import json
16
17
18NIXPKGS_ROOT = subprocess.Popen(['git',
19 'rev-parse',
20 '--show-toplevel'],
21 stdout=subprocess.PIPE,
22 text=True).communicate()[0].strip()
23
24
25def load_code(name, **kwargs):
26 with open(f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/update/{name}.in", 'r') as f:
27 code = f.read()
28
29 for (key, value) in kwargs.items():
30 code = code.replace(f"@{key}@", value)
31
32 return code
33
34
35# Return out paths
36def nix_build(code):
37 temp = tempfile.NamedTemporaryFile(mode='w')
38 temp.write(code)
39 temp.flush()
40 os.fsync(temp.fileno())
41
42 process = subprocess.Popen(
43 [
44 "nix-build",
45 "--impure",
46 "--no-out-link",
47 "--expr",
48 f"with import {NIXPKGS_ROOT} {{}}; callPackage {temp.name} {{}}"],
49 stdout=subprocess.PIPE,
50 text=True)
51
52 process.wait()
53 temp.close()
54 return process.stdout.read().strip().splitlines()[0]
55
56
57# Return errors
58def nix_build_to_fail(code):
59 temp = tempfile.NamedTemporaryFile(mode='w')
60 temp.write(code)
61 temp.flush()
62 os.fsync(temp.fileno())
63
64 process = subprocess.Popen(
65 [
66 "nix-build",
67 "--impure",
68 "--keep-going",
69 "--no-link",
70 "--expr",
71 f"with import {NIXPKGS_ROOT} {{}}; callPackage {temp.name} {{}}"],
72 stderr=subprocess.PIPE,
73 text=True)
74
75 stderr = ""
76 while True:
77 line = process.stderr.readline()
78 if not line:
79 break
80 stderr += line
81 print(line.strip())
82
83 process.wait()
84 temp.close()
85 return stderr
86
87
88def get_artifact_hashes(flutter_compact_version):
89 code = load_code("get-artifact-hashes.nix",
90 nixpkgs_root=NIXPKGS_ROOT,
91 flutter_compact_version=flutter_compact_version)
92
93 stderr = nix_build_to_fail(code)
94
95 pattern = re.compile(
96 r"/nix/store/.*-flutter-artifacts-(.+?)-(.+?).drv':\n\s+specified: .*\n\s+got:\s+(.+?)\n")
97 matches = pattern.findall(stderr)
98 result_dict = {}
99
100 for match in matches:
101 flutter_platform, architecture, got = match
102 result_dict.setdefault(flutter_platform, {})[architecture] = got
103
104 def sort_dict_recursive(d):
105 return {
106 k: sort_dict_recursive(v) if isinstance(
107 v, dict) else v for k, v in sorted(
108 d.items())}
109 result_dict = sort_dict_recursive(result_dict)
110
111 return result_dict
112
113
114def get_dart_hashes(dart_version):
115 platforms = [
116 "x86_64-linux",
117 "aarch64-linux",
118 "x86_64-darwin",
119 "aarch64-darwin"]
120 result_dict = {}
121 for platform in platforms:
122 code = load_code(
123 "get-dart-hashes.nix",
124 dart_version=dart_version,
125 platform=platform)
126 stderr = nix_build_to_fail(code)
127
128 pattern = re.compile(r"got:\s+(.+?)\n")
129 result_dict[platform] = pattern.findall(stderr)[0]
130
131 return result_dict
132
133
134def get_flutter_hash_and_src(flutter_version):
135 code = load_code(
136 "get-flutter.nix",
137 flutter_version=flutter_version,
138 hash="")
139
140 stderr = nix_build_to_fail(code)
141 pattern = re.compile(r"got:\s+(.+?)\n")
142 hash = pattern.findall(stderr)[0]
143
144 code = load_code(
145 "get-flutter.nix",
146 flutter_version=flutter_version,
147 hash=hash)
148
149 return (hash, nix_build(code))
150
151
152def get_pubspec_lock(flutter_compact_version, flutter_src):
153 code = load_code(
154 "get-pubspec-lock.nix",
155 flutter_compact_version=flutter_compact_version,
156 flutter_src=flutter_src,
157 hash="")
158
159 stderr = nix_build_to_fail(code)
160 pattern = re.compile(r"got:\s+(.+?)\n")
161 hash = pattern.findall(stderr)[0]
162
163 code = load_code(
164 "get-pubspec-lock.nix",
165 flutter_compact_version=flutter_compact_version,
166 flutter_src=flutter_src,
167 hash=hash)
168
169 pubspec_lock_file = nix_build(code)
170
171 with open(pubspec_lock_file, 'r') as f:
172 pubspec_lock_yaml = f.read()
173
174 return yaml.safe_load(pubspec_lock_yaml)
175
176
177def write_data(
178 nixpkgs_flutter_version_directory,
179 flutter_version,
180 engine_hash,
181 dart_version,
182 dart_hash,
183 flutter_hash,
184 artifact_hashes,
185 pubspec_lock):
186 with open(f"{nixpkgs_flutter_version_directory}/data.json", "w") as f:
187 f.write(json.dumps({
188 "version": flutter_version,
189 "engineVersion": engine_hash,
190 "dartVersion": dart_version,
191 "dartHash": dart_hash,
192 "flutterHash": flutter_hash,
193 "artifactHashes": artifact_hashes,
194 "pubspecLock": pubspec_lock,
195 }, indent=2).strip() + "\n")
196
197
198def update_all_packages():
199 versions_directory = f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/versions"
200 versions = [directory for directory in os.listdir(versions_directory)]
201 versions = sorted(versions, key=lambda x: (
202 int(x.split('_')[0]), int(x.split('_')[1])), reverse=True)
203
204 new_content = [
205 "flutterPackages = recurseIntoAttrs (callPackage ../development/compilers/flutter { });",
206 "flutter = flutterPackages.stable;",
207 ] + [f"flutter{version.replace('_', '')} = flutterPackages.v{version};" for version in versions]
208
209 with open(f"{NIXPKGS_ROOT}/pkgs/top-level/all-packages.nix", 'r') as file:
210 lines = file.read().splitlines(keepends=True)
211
212 start = -1
213 end = -1
214 for i, line in enumerate(lines):
215 if "flutterPackages = recurseIntoAttrs (callPackage ../development/compilers/flutter { });" in line:
216 start = i
217 if start != -1 and len(line.strip()) == 0:
218 end = i
219 break
220
221 if start != -1 and end != -1:
222 del lines[start:end]
223 lines[start:start] = [f" {l}\n" for l in new_content]
224
225 with open(f"{NIXPKGS_ROOT}/pkgs/top-level/all-packages.nix", 'w') as file:
226 file.write("".join(lines))
227
228
229# Finds Flutter version, Dart version, and Engine hash.
230# If the Flutter version is given, it uses that. Otherwise finds the
231# latest stable Flutter version.
232def find_versions(flutter_version=None):
233 engine_hash = None
234 dart_version = None
235
236 releases = json.load(urllib.request.urlopen(
237 "https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json"))
238
239 if not flutter_version:
240 stable_hash = releases['current_release']['stable']
241 release = next(
242 filter(
243 lambda release: release['hash'] == stable_hash,
244 releases['releases']))
245 flutter_version = release['version']
246
247 tags = subprocess.Popen(['git',
248 'ls-remote',
249 '--tags',
250 'https://github.com/flutter/engine.git'],
251 stdout=subprocess.PIPE,
252 text=True).communicate()[0].strip()
253
254 try:
255 engine_hash = next(
256 filter(
257 lambda line: line.endswith(f'refs/tags/{flutter_version}'),
258 tags.splitlines())).split('refs')[0].strip()
259 except StopIteration:
260 exit(
261 f"Couldn't find Engine hash for Flutter version: {flutter_version}")
262
263 try:
264 dart_version = next(
265 filter(
266 lambda release: release['version'] == flutter_version,
267 releases['releases']))['dart_sdk_version']
268 except StopIteration:
269 exit(
270 f"Couldn't find Dart version for Flutter version: {flutter_version}")
271
272 return (flutter_version, engine_hash, dart_version)
273
274
275def main():
276 parser = argparse.ArgumentParser(description='Update Flutter in Nixpkgs')
277 parser.add_argument('--version', type=str, help='Specify Flutter version')
278 parser.add_argument('--artifact-hashes', action='store_true',
279 help='Whether to get artifact hashes')
280 args = parser.parse_args()
281
282 (flutter_version, engine_hash, dart_version) = find_versions(args.version)
283
284 flutter_compact_version = '_'.join(flutter_version.split('.')[:2])
285
286 if args.artifact_hashes:
287 print(
288 json.dumps(
289 get_artifact_hashes(flutter_compact_version),
290 indent=2).strip() +
291 "\n")
292 return
293
294 print(f"Flutter version: {flutter_version} ({flutter_compact_version})")
295 print(f"Engine hash: {engine_hash}")
296 print(f"Dart version: {dart_version}")
297
298 dart_hash = get_dart_hashes(dart_version)
299 (flutter_hash, flutter_src) = get_flutter_hash_and_src(flutter_version)
300
301 nixpkgs_flutter_version_directory = f"{NIXPKGS_ROOT}/pkgs/development/compilers/flutter/versions/{flutter_compact_version}"
302
303 if os.path.exists(f"{nixpkgs_flutter_version_directory}/data.json"):
304 os.remove(f"{nixpkgs_flutter_version_directory}/data.json")
305 os.makedirs(nixpkgs_flutter_version_directory, exist_ok=True)
306
307 update_all_packages()
308
309 common_data_args = {
310 "nixpkgs_flutter_version_directory": nixpkgs_flutter_version_directory,
311 "flutter_version": flutter_version,
312 "dart_version": dart_version,
313 "engine_hash": engine_hash,
314 "flutter_hash": flutter_hash,
315 "dart_hash": dart_hash,
316 }
317
318 write_data(
319 pubspec_lock={},
320 artifact_hashes={},
321 **common_data_args)
322
323 pubspec_lock = get_pubspec_lock(flutter_compact_version, flutter_src)
324
325 write_data(
326 pubspec_lock=pubspec_lock,
327 artifact_hashes={},
328 **common_data_args)
329
330 artifact_hashes = get_artifact_hashes(flutter_compact_version)
331
332 write_data(
333 pubspec_lock=pubspec_lock,
334 artifact_hashes=artifact_hashes,
335 **common_data_args)
336
337
338if __name__ == "__main__":
339 main()