blob: 3a8be9530bfd1cfcd7bb0ced03ef0c24e91253d4 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
John McCutchan0de69162016-07-20 12:59:30 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
Zachary Andersonef146f62019-07-29 07:24:02 -07006
Zachary Anderson4d490662017-06-22 09:48:31 -07007import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
Chris Bracken251e82d2018-08-31 13:31:56 -07008import 'package:meta/meta.dart';
Zachary Anderson4d490662017-06-22 09:48:31 -07009
Alexandre Ardhuin8c043d02017-02-23 22:37:26 +010010import 'asset.dart';
John McCutchan11ca0f12016-11-10 08:31:11 -080011import 'base/context.dart';
Todd Volkert8bb27032017-01-06 16:51:44 -080012import 'base/file_system.dart';
Todd Volkert016b5ab2017-01-09 08:37:00 -080013import 'base/io.dart';
Emmanuel Garcia5df4b7d2019-11-20 18:51:25 -080014import 'base/net.dart';
Chris Brackenb5f763b2016-08-25 16:38:19 -070015import 'build_info.dart';
Jacob Richmanf5f70f02018-10-23 10:09:18 -070016import 'bundle.dart';
Alexander Aprelevc5750cd2017-08-31 13:35:55 -070017import 'compile.dart';
Jonah Williams91fd89e2019-01-25 16:16:26 -080018import 'convert.dart' show base64, utf8;
John McCutchan0de69162016-07-20 12:59:30 -070019import 'dart/package_map.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080020import 'globals.dart' as globals;
John McCutchancab7c8d2016-08-11 13:14:13 -070021import 'vmservice.dart';
John McCutchan0de69162016-07-20 12:59:30 -070022
John McCutchan11ca0f12016-11-10 08:31:11 -080023class DevFSConfig {
24 /// Should DevFS assume that symlink targets are stable?
25 bool cacheSymlinks = false;
John McCutchan45c95b22016-11-11 13:28:21 -080026 /// Should DevFS assume that there are no symlinks to directories?
27 bool noDirectorySymlinks = false;
John McCutchan11ca0f12016-11-10 08:31:11 -080028}
29
Jonah Williams0acd3e62019-04-25 15:51:08 -070030DevFSConfig get devFSConfig => context.get<DevFSConfig>();
John McCutchan11ca0f12016-11-10 08:31:11 -080031
Dan Rubel0295def2017-01-22 10:37:10 -050032/// Common superclass for content copied to the device.
33abstract class DevFSContent {
Ian Hicksonefb45ea2017-09-27 16:13:48 -070034 /// Return true if this is the first time this method is called
Dan Rubel0295def2017-01-22 10:37:10 -050035 /// or if the entry has been modified since this method was last called.
36 bool get isModified;
John McCutchan0de69162016-07-20 12:59:30 -070037
jensjoha5ccd5a12018-01-31 16:39:58 +010038 /// Return true if this is the first time this method is called
39 /// or if the entry has been modified after the given time
40 /// or if the given time is null.
41 bool isModifiedAfter(DateTime time);
42
Dan Rubel0295def2017-01-22 10:37:10 -050043 int get size;
44
Jonah Williams27876e02019-05-31 13:17:12 -070045 Future<List<int>> contentsAsBytes();
Dan Rubel0295def2017-01-22 10:37:10 -050046
Jonah Williams27876e02019-05-31 13:17:12 -070047 Stream<List<int>> contentsAsStream();
48
49 Stream<List<int>> contentsAsCompressedStream() {
50 return contentsAsStream().cast<List<int>>().transform<List<int>>(gzip.encoder);
Dan Rubel0295def2017-01-22 10:37:10 -050051 }
P.Y. Laligandd356b2d2017-03-23 09:42:58 -070052
Jonah Williams27876e02019-05-31 13:17:12 -070053 /// Return the list of files this content depends on.
54 List<String> get fileDependencies => <String>[];
Dan Rubel0295def2017-01-22 10:37:10 -050055}
56
Jonah Williams27876e02019-05-31 13:17:12 -070057// File content to be copied to the device.
Dan Rubel0295def2017-01-22 10:37:10 -050058class DevFSFileContent extends DevFSContent {
59 DevFSFileContent(this.file);
John McCutchanb6644732016-07-22 06:59:24 -070060
John McCutchan035afc22016-09-21 11:24:29 -070061 final FileSystemEntity file;
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010062 File _linkTarget;
John McCutchan0de69162016-07-20 12:59:30 -070063 FileStat _fileStat;
John McCutchanb6644732016-07-22 06:59:24 -070064
Dan Rubel0295def2017-01-22 10:37:10 -050065 File _getFile() {
66 if (_linkTarget != null) {
67 return _linkTarget;
John McCutchan0de69162016-07-20 12:59:30 -070068 }
Dan Rubel0295def2017-01-22 10:37:10 -050069 if (file is Link) {
70 // The link target.
Jonah Williamsee7a37f2020-01-06 11:04:20 -080071 return globals.fs.file(file.resolveSymbolicLinksSync());
John McCutchanb11b2a12016-07-28 13:48:48 -070072 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +010073 return file as File;
John McCutchanb11b2a12016-07-28 13:48:48 -070074 }
75
John McCutchan0de69162016-07-20 12:59:30 -070076 void _stat() {
John McCutchan11ca0f12016-11-10 08:31:11 -080077 if (_linkTarget != null) {
78 // Stat the cached symlink target.
Greg Spencer485ed2f2018-10-09 14:33:47 -070079 final FileStat fileStat = _linkTarget.statSync();
80 if (fileStat.type == FileSystemEntityType.notFound) {
81 _linkTarget = null;
82 } else {
83 _fileStat = fileStat;
84 return;
85 }
John McCutchan11ca0f12016-11-10 08:31:11 -080086 }
Greg Spencer485ed2f2018-10-09 14:33:47 -070087 final FileStat fileStat = file.statSync();
88 _fileStat = fileStat.type == FileSystemEntityType.notFound ? null : fileStat;
89 if (_fileStat != null && _fileStat.type == FileSystemEntityType.link) {
John McCutchan11ca0f12016-11-10 08:31:11 -080090 // Resolve, stat, and maybe cache the symlink target.
Chris Bracken7a093162017-03-03 17:50:46 -080091 final String resolved = file.resolveSymbolicLinksSync();
Jonah Williamsee7a37f2020-01-06 11:04:20 -080092 final File linkTarget = globals.fs.file(resolved);
John McCutchan11ca0f12016-11-10 08:31:11 -080093 // Stat the link target.
Greg Spencer485ed2f2018-10-09 14:33:47 -070094 final FileStat fileStat = linkTarget.statSync();
95 if (fileStat.type == FileSystemEntityType.notFound) {
96 _fileStat = null;
97 _linkTarget = null;
98 } else if (devFSConfig.cacheSymlinks) {
John McCutchan11ca0f12016-11-10 08:31:11 -080099 _linkTarget = linkTarget;
100 }
John McCutchan035afc22016-09-21 11:24:29 -0700101 }
Greg Spencer485ed2f2018-10-09 14:33:47 -0700102 if (_fileStat == null) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800103 globals.printError('Unable to get status of file "${file.path}": file not found.');
Greg Spencer485ed2f2018-10-09 14:33:47 -0700104 }
John McCutchan0de69162016-07-20 12:59:30 -0700105 }
John McCutchanb6644732016-07-22 06:59:24 -0700106
Dan Rubel0295def2017-01-22 10:37:10 -0500107 @override
Jonah Williams27876e02019-05-31 13:17:12 -0700108 List<String> get fileDependencies => <String>[_getFile().path];
109
110 @override
Dan Rubel0295def2017-01-22 10:37:10 -0500111 bool get isModified {
Chris Bracken7a093162017-03-03 17:50:46 -0800112 final FileStat _oldFileStat = _fileStat;
Dan Rubel0295def2017-01-22 10:37:10 -0500113 _stat();
Zachary Andersone2340c62019-09-13 14:51:35 -0700114 if (_oldFileStat == null && _fileStat == null) {
Greg Spencer485ed2f2018-10-09 14:33:47 -0700115 return false;
Zachary Andersone2340c62019-09-13 14:51:35 -0700116 }
Greg Spencer485ed2f2018-10-09 14:33:47 -0700117 return _oldFileStat == null || _fileStat == null || _fileStat.modified.isAfter(_oldFileStat.modified);
John McCutchan035afc22016-09-21 11:24:29 -0700118 }
119
Dan Rubel0295def2017-01-22 10:37:10 -0500120 @override
jensjoha5ccd5a12018-01-31 16:39:58 +0100121 bool isModifiedAfter(DateTime time) {
122 final FileStat _oldFileStat = _fileStat;
123 _stat();
Zachary Andersone2340c62019-09-13 14:51:35 -0700124 if (_oldFileStat == null && _fileStat == null) {
Greg Spencer485ed2f2018-10-09 14:33:47 -0700125 return false;
Zachary Andersone2340c62019-09-13 14:51:35 -0700126 }
Greg Spencer485ed2f2018-10-09 14:33:47 -0700127 return time == null
128 || _oldFileStat == null
129 || _fileStat == null
130 || _fileStat.modified.isAfter(time);
jensjoha5ccd5a12018-01-31 16:39:58 +0100131 }
132
133 @override
Dan Rubel0295def2017-01-22 10:37:10 -0500134 int get size {
Zachary Andersone2340c62019-09-13 14:51:35 -0700135 if (_fileStat == null) {
Dan Rubel0295def2017-01-22 10:37:10 -0500136 _stat();
Zachary Andersone2340c62019-09-13 14:51:35 -0700137 }
Greg Spencer485ed2f2018-10-09 14:33:47 -0700138 // Can still be null if the file wasn't found.
139 return _fileStat?.size ?? 0;
John McCutchanb6644732016-07-22 06:59:24 -0700140 }
John McCutchan487f28f2016-08-08 12:42:31 -0700141
Dan Rubel0295def2017-01-22 10:37:10 -0500142 @override
Jonah Williams27876e02019-05-31 13:17:12 -0700143 Future<List<int>> contentsAsBytes() => _getFile().readAsBytes();
John McCutchan487f28f2016-08-08 12:42:31 -0700144
Dan Rubel0295def2017-01-22 10:37:10 -0500145 @override
Jonah Williams27876e02019-05-31 13:17:12 -0700146 Stream<List<int>> contentsAsStream() => _getFile().openRead();
John McCutchan0de69162016-07-20 12:59:30 -0700147}
148
Dan Rubel0295def2017-01-22 10:37:10 -0500149/// Byte content to be copied to the device.
150class DevFSByteContent extends DevFSContent {
151 DevFSByteContent(this._bytes);
152
153 List<int> _bytes;
154
155 bool _isModified = true;
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200156 DateTime _modificationTime = DateTime.now();
Dan Rubel0295def2017-01-22 10:37:10 -0500157
158 List<int> get bytes => _bytes;
159
Alexandre Ardhuinc22812e2017-03-10 09:00:29 +0100160 set bytes(List<int> value) {
161 _bytes = value;
Dan Rubel0295def2017-01-22 10:37:10 -0500162 _isModified = true;
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200163 _modificationTime = DateTime.now();
Dan Rubel0295def2017-01-22 10:37:10 -0500164 }
165
Ian Hicksonefb45ea2017-09-27 16:13:48 -0700166 /// Return true only once so that the content is written to the device only once.
Dan Rubel0295def2017-01-22 10:37:10 -0500167 @override
168 bool get isModified {
Chris Bracken7a093162017-03-03 17:50:46 -0800169 final bool modified = _isModified;
Dan Rubel0295def2017-01-22 10:37:10 -0500170 _isModified = false;
171 return modified;
172 }
173
174 @override
jensjoha5ccd5a12018-01-31 16:39:58 +0100175 bool isModifiedAfter(DateTime time) {
176 return time == null || _modificationTime.isAfter(time);
177 }
178
179 @override
Dan Rubel0295def2017-01-22 10:37:10 -0500180 int get size => _bytes.length;
181
182 @override
Jonah Williams27876e02019-05-31 13:17:12 -0700183 Future<List<int>> contentsAsBytes() async => _bytes;
Dan Rubel0295def2017-01-22 10:37:10 -0500184
185 @override
Jonah Williams27876e02019-05-31 13:17:12 -0700186 Stream<List<int>> contentsAsStream() =>
187 Stream<List<int>>.fromIterable(<List<int>>[_bytes]);
Dan Rubel0295def2017-01-22 10:37:10 -0500188}
189
Jonah Williams27876e02019-05-31 13:17:12 -0700190/// String content to be copied to the device.
Dan Rubel0295def2017-01-22 10:37:10 -0500191class DevFSStringContent extends DevFSByteContent {
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100192 DevFSStringContent(String string)
193 : _string = string,
194 super(utf8.encode(string));
Dan Rubel0295def2017-01-22 10:37:10 -0500195
196 String _string;
197
198 String get string => _string;
199
Alexandre Ardhuinc22812e2017-03-10 09:00:29 +0100200 set string(String value) {
201 _string = value;
Jason Simmons466d1542018-03-12 11:06:32 -0700202 super.bytes = utf8.encode(_string);
Dan Rubel0295def2017-01-22 10:37:10 -0500203 }
204
205 @override
Alexandre Ardhuinc22812e2017-03-10 09:00:29 +0100206 set bytes(List<int> value) {
Jason Simmons466d1542018-03-12 11:06:32 -0700207 string = utf8.decode(value);
Dan Rubel0295def2017-01-22 10:37:10 -0500208 }
209}
John McCutchan0de69162016-07-20 12:59:30 -0700210
Jonah Williams27876e02019-05-31 13:17:12 -0700211/// Abstract DevFS operations interface.
212abstract class DevFSOperations {
213 Future<Uri> create(String fsName);
214 Future<dynamic> destroy(String fsName);
215 Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content);
216}
217
218/// An implementation of [DevFSOperations] that speaks to the
219/// vm service.
220class ServiceProtocolDevFSOperations implements DevFSOperations {
221 ServiceProtocolDevFSOperations(this.vmService);
John McCutchan0de69162016-07-20 12:59:30 -0700222
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200223 final VMService vmService;
Jonah Williams27876e02019-05-31 13:17:12 -0700224
225 @override
226 Future<Uri> create(String fsName) async {
227 final Map<String, dynamic> response = await vmService.vm.createDevFS(fsName);
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100228 return Uri.parse(response['uri'] as String);
Jonah Williams27876e02019-05-31 13:17:12 -0700229 }
230
231 @override
232 Future<dynamic> destroy(String fsName) async {
233 await vmService.vm.deleteDevFS(fsName);
234 }
235
236 @override
237 Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content) async {
238 List<int> bytes;
239 try {
240 bytes = await content.contentsAsBytes();
Zachary Anderson9de77872020-02-27 22:46:23 -0800241 } on Exception catch (e) {
Jonah Williams27876e02019-05-31 13:17:12 -0700242 return e;
243 }
244 final String fileContents = base64.encode(bytes);
245 try {
246 return await vmService.vm.invokeRpcRaw(
247 '_writeDevFSFile',
248 params: <String, dynamic>{
249 'fsName': fsName,
250 'uri': deviceUri.toString(),
251 'fileContents': fileContents,
252 },
253 );
Zachary Anderson9de77872020-02-27 22:46:23 -0800254 } on Exception catch (error) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800255 globals.printTrace('DevFS: Failed to write $deviceUri: $error');
Jonah Williams27876e02019-05-31 13:17:12 -0700256 }
257 }
258}
259
260class DevFSException implements Exception {
261 DevFSException(this.message, [this.error, this.stackTrace]);
262 final String message;
263 final dynamic error;
264 final StackTrace stackTrace;
265}
266
267class _DevFSHttpWriter {
268 _DevFSHttpWriter(this.fsName, VMService serviceProtocol)
Emmanuel Garcia5df4b7d2019-11-20 18:51:25 -0800269 : httpAddress = serviceProtocol.httpAddress,
270 _client = (context.get<HttpClientFactory>() == null)
271 ? HttpClient()
272 : context.get<HttpClientFactory>()();
Jonah Williams27876e02019-05-31 13:17:12 -0700273
John McCutchan487f28f2016-08-08 12:42:31 -0700274 final String fsName;
275 final Uri httpAddress;
Emmanuel Garcia5df4b7d2019-11-20 18:51:25 -0800276 final HttpClient _client;
John McCutchan487f28f2016-08-08 12:42:31 -0700277
278 static const int kMaxInFlight = 6;
279
280 int _inFlight = 0;
Michael Goderbauer17057bb2017-03-01 10:11:56 -0800281 Map<Uri, DevFSContent> _outstanding;
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200282 Completer<void> _completer;
John McCutchan487f28f2016-08-08 12:42:31 -0700283
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200284 Future<void> write(Map<Uri, DevFSContent> entries) async {
John McCutchan487f28f2016-08-08 12:42:31 -0700285 _client.maxConnectionsPerHost = kMaxInFlight;
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200286 _completer = Completer<void>();
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200287 _outstanding = Map<Uri, DevFSContent>.from(entries);
Todd Volkertb7ababe2017-07-17 09:30:56 -0700288 _scheduleWrites();
John McCutchan487f28f2016-08-08 12:42:31 -0700289 await _completer.future;
John McCutchan487f28f2016-08-08 12:42:31 -0700290 }
291
Todd Volkertb7ababe2017-07-17 09:30:56 -0700292 void _scheduleWrites() {
Jason Simmons311cde92019-05-29 19:04:35 -0700293 while ((_inFlight < kMaxInFlight) && (!_completer.isCompleted) && _outstanding.isNotEmpty) {
Chris Bracken7a093162017-03-03 17:50:46 -0800294 final Uri deviceUri = _outstanding.keys.first;
295 final DevFSContent content = _outstanding.remove(deviceUri);
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700296 _startWrite(deviceUri, content, retry: 10);
Jason Simmons311cde92019-05-29 19:04:35 -0700297 _inFlight += 1;
John McCutchan487f28f2016-08-08 12:42:31 -0700298 }
Zachary Andersone2340c62019-09-13 14:51:35 -0700299 if ((_inFlight == 0) && (!_completer.isCompleted) && _outstanding.isEmpty) {
Jason Simmons311cde92019-05-29 19:04:35 -0700300 _completer.complete();
Zachary Andersone2340c62019-09-13 14:51:35 -0700301 }
John McCutchan487f28f2016-08-08 12:42:31 -0700302 }
303
Jason Simmons311cde92019-05-29 19:04:35 -0700304 Future<void> _startWrite(
Michael Goderbauer17057bb2017-03-01 10:11:56 -0800305 Uri deviceUri,
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700306 DevFSContent content, {
Todd Volkertf903a1c2017-02-14 09:59:43 -0800307 int retry = 0,
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700308 }) async {
309 while(true) {
310 try {
311 final HttpClientRequest request = await _client.putUrl(httpAddress);
312 request.headers.removeAll(HttpHeaders.acceptEncodingHeader);
313 request.headers.add('dev_fs_name', fsName);
314 request.headers.add('dev_fs_uri_b64', base64.encode(utf8.encode('$deviceUri')));
315 final Stream<List<int>> contents = content.contentsAsCompressedStream();
316 await request.addStream(contents);
317 final HttpClientResponse response = await request.close();
318 response.listen((_) => null,
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800319 onError: (dynamic error) { globals.printTrace('error: $error'); },
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700320 cancelOnError: true);
321 break;
Zachary Anderson9de77872020-02-27 22:46:23 -0800322 } on Exception catch (error, trace) {
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700323 if (!_completer.isCompleted) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800324 globals.printTrace('Error writing "$deviceUri" to DevFS: $error');
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700325 if (retry > 0) {
326 retry--;
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800327 globals.printTrace('trying again in a few - $retry more attempts left');
Alexander Aprelev839fdbd2019-09-29 21:32:06 -0700328 await Future<void>.delayed(const Duration(milliseconds: 500));
329 continue;
330 }
331 _completer.completeError(error, trace);
332 }
Todd Volkertf903a1c2017-02-14 09:59:43 -0800333 }
Ryan Macnak2ac7d672016-10-25 10:40:54 -0700334 }
Jason Simmons311cde92019-05-29 19:04:35 -0700335 _inFlight -= 1;
336 _scheduleWrites();
John McCutchan487f28f2016-08-08 12:42:31 -0700337 }
338}
339
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800340// Basic statistics for DevFS update operation.
341class UpdateFSReport {
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +0100342 UpdateFSReport({
343 bool success = false,
344 int invalidatedSourcesCount = 0,
345 int syncedBytes = 0,
346 }) {
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800347 _success = success;
348 _invalidatedSourcesCount = invalidatedSourcesCount;
349 _syncedBytes = syncedBytes;
350 }
351
352 bool get success => _success;
353 int get invalidatedSourcesCount => _invalidatedSourcesCount;
354 int get syncedBytes => _syncedBytes;
355
Jonah Williamscc51ad52019-11-07 20:13:02 -0800356 /// JavaScript modules produced by the incremental compiler in `dartdevc`
357 /// mode.
358 ///
359 /// Only used for JavaScript compilation.
360 List<String> invalidatedModules;
361
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800362 void incorporateResults(UpdateFSReport report) {
363 if (!report._success) {
364 _success = false;
365 }
366 _invalidatedSourcesCount += report._invalidatedSourcesCount;
367 _syncedBytes += report._syncedBytes;
Jonah Williamscc51ad52019-11-07 20:13:02 -0800368 invalidatedModules ??= report.invalidatedModules;
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800369 }
370
371 bool _success;
372 int _invalidatedSourcesCount;
373 int _syncedBytes;
374}
375
John McCutchan0de69162016-07-20 12:59:30 -0700376class DevFS {
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200377 /// Create a [DevFS] named [fsName] for the local files in [rootDirectory].
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100378 DevFS(
379 VMService serviceProtocol,
380 this.fsName,
381 this.rootDirectory, {
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100382 String packagesFilePath,
Jonah Williams27876e02019-05-31 13:17:12 -0700383 }) : _operations = ServiceProtocolDevFSOperations(serviceProtocol),
384 _httpWriter = _DevFSHttpWriter(fsName, serviceProtocol),
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800385 _packagesFilePath = packagesFilePath ?? globals.fs.path.join(rootDirectory.path, kPackagesFileName);
John McCutchan0de69162016-07-20 12:59:30 -0700386
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100387 DevFS.operations(
388 this._operations,
389 this.fsName,
390 this.rootDirectory, {
391 String packagesFilePath,
Jonah Williams27876e02019-05-31 13:17:12 -0700392 }) : _httpWriter = null,
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800393 _packagesFilePath = packagesFilePath ?? globals.fs.path.join(rootDirectory.path, kPackagesFileName);
John McCutchan0de69162016-07-20 12:59:30 -0700394
395 final DevFSOperations _operations;
Jonah Williams27876e02019-05-31 13:17:12 -0700396 final _DevFSHttpWriter _httpWriter;
John McCutchan0de69162016-07-20 12:59:30 -0700397 final String fsName;
398 final Directory rootDirectory;
Alexandre Ardhuinc7408be2019-06-25 19:39:34 +0200399 final String _packagesFilePath;
Phil Quitslund802eca22019-03-06 11:05:16 -0800400 final Set<String> assetPathsToEvict = <String>{};
Alexander Aprelev12c4e052019-03-20 21:58:15 -0700401 List<Uri> sources = <Uri>[];
402 DateTime lastCompiled;
John McCutchan487f28f2016-08-08 12:42:31 -0700403
John McCutchan0de69162016-07-20 12:59:30 -0700404 Uri _baseUri;
405 Uri get baseUri => _baseUri;
406
Ryan Macnak07a4b4c2017-10-12 15:58:40 -0700407 Uri deviceUriToHostUri(Uri deviceUri) {
408 final String deviceUriString = deviceUri.toString();
409 final String baseUriString = baseUri.toString();
410 if (deviceUriString.startsWith(baseUriString)) {
411 final String deviceUriSuffix = deviceUriString.substring(baseUriString.length);
412 return rootDirectory.uri.resolve(deviceUriSuffix);
413 }
414 return deviceUri;
415 }
416
John McCutchan0de69162016-07-20 12:59:30 -0700417 Future<Uri> create() async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800418 globals.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)');
Zachary Anderson4d490662017-06-22 09:48:31 -0700419 try {
420 _baseUri = await _operations.create(fsName);
421 } on rpc.RpcException catch (rpcException) {
422 // 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
Zachary Andersone2340c62019-09-13 14:51:35 -0700423 if (rpcException.code != 1001) {
Zachary Anderson4d490662017-06-22 09:48:31 -0700424 rethrow;
Zachary Andersone2340c62019-09-13 14:51:35 -0700425 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800426 globals.printTrace('DevFS: Creating failed. Destroying and trying again');
Zachary Anderson4d490662017-06-22 09:48:31 -0700427 await destroy();
428 _baseUri = await _operations.create(fsName);
429 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800430 globals.printTrace('DevFS: Created new filesystem on the device ($_baseUri)');
John McCutchan0de69162016-07-20 12:59:30 -0700431 return _baseUri;
432 }
433
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200434 Future<void> destroy() async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800435 globals.printTrace('DevFS: Deleting filesystem on the device ($_baseUri)');
John McCutchan0ee7fab2017-03-30 10:03:42 -0700436 await _operations.destroy(fsName);
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800437 globals.printTrace('DevFS: Deleted filesystem on the device ($_baseUri)');
John McCutchan0de69162016-07-20 12:59:30 -0700438 }
439
Chris Bracken0e4770e2018-06-07 18:27:08 -0700440 /// Updates files on the device.
441 ///
442 /// Returns the number of bytes synced.
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800443 Future<UpdateFSReport> update({
Chris Bracken251e82d2018-08-31 13:31:56 -0700444 @required String mainPath,
Alexander Aprelevc5750cd2017-08-31 13:35:55 -0700445 String target,
Todd Volkertb7ababe2017-07-17 09:30:56 -0700446 AssetBundle bundle,
jensjoha5ccd5a12018-01-31 16:39:58 +0100447 DateTime firstBuildTime,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200448 bool bundleFirstUpload = false,
Chris Bracken251e82d2018-08-31 13:31:56 -0700449 @required ResidentCompiler generator,
Keerti Parthasarathy02633942018-03-06 14:41:50 -0800450 String dillOutputPath,
Jacob Richmanf5f70f02018-10-23 10:09:18 -0700451 @required bool trackWidgetCreation,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200452 bool fullRestart = false,
Alexander Aprelevaf74a722018-03-23 19:18:39 -0700453 String projectRootPath,
Chris Bracken251e82d2018-08-31 13:31:56 -0700454 @required String pathToReload,
Alexander Aprelev12c4e052019-03-20 21:58:15 -0700455 @required List<Uri> invalidatedFiles,
Jonah Williams81aa2712019-12-09 21:31:34 -0800456 bool skipAssets = false,
Todd Volkertb7ababe2017-07-17 09:30:56 -0700457 }) async {
Jacob Richmanf5f70f02018-10-23 10:09:18 -0700458 assert(trackWidgetCreation != null);
459 assert(generator != null);
Jonah Williams39f85f92019-10-02 12:46:33 -0700460 final DateTime candidateCompileTime = DateTime.now();
Dan Rubel0295def2017-01-22 10:37:10 -0500461
Jonah Williams377f4452019-03-21 09:03:28 -0700462 // Update modified files
463 final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
464 final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
465
466 int syncedBytes = 0;
Jonah Williams81aa2712019-12-09 21:31:34 -0800467 if (bundle != null && !skipAssets) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800468 globals.printTrace('Scanning asset files');
Jonah Williamscd803ac2019-03-15 15:02:45 -0700469 // We write the assets into the AssetBundle working dir so that they
470 // are in the same location in DevFS and the iOS simulator.
471 final String assetDirectory = getAssetBuildDirectory();
Dan Rubel0295def2017-01-22 10:37:10 -0500472 bundle.entries.forEach((String archivePath, DevFSContent content) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800473 final Uri deviceUri = globals.fs.path.toUri(globals.fs.path.join(assetDirectory, archivePath));
Jonah Williams377f4452019-03-21 09:03:28 -0700474 if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
475 archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
476 }
477 // Only update assets if they have been modified, or if this is the
478 // first upload of the asset bundle.
479 if (content.isModified || (bundleFirstUpload && archivePath != null)) {
480 dirtyEntries[deviceUri] = content;
481 syncedBytes += content.size;
482 if (archivePath != null && !bundleFirstUpload) {
483 assetPathsToEvict.add(archivePath);
484 }
485 }
Dan Rubel0295def2017-01-22 10:37:10 -0500486 });
John McCutchanb6644732016-07-22 06:59:24 -0700487 }
Chris Bracken2ab4ed72018-09-04 16:47:57 -0700488 if (fullRestart) {
489 generator.reset();
490 }
Jonah Williams53457c22020-02-26 08:06:03 -0800491 // On a full restart, or on an initial compile for the attach based workflow,
492 // this will produce a full dill. Subsequent invocations will produce incremental
493 // dill files that depend on the invalidated files.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800494 globals.printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
Chris Bracken2ab4ed72018-09-04 16:47:57 -0700495 final CompilerOutput compilerOutput = await generator.recompile(
496 mainPath,
497 invalidatedFiles,
Jacob Richmanf5f70f02018-10-23 10:09:18 -0700498 outputPath: dillOutputPath ?? getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation),
Chris Bracken2ab4ed72018-09-04 16:47:57 -0700499 packagesFilePath : _packagesFilePath,
500 );
Jonah Williams5ec039d2019-08-15 09:08:19 -0700501 if (compilerOutput == null || compilerOutput.errorCount > 0) {
Jonah Williamsfc9f7de2019-03-21 13:59:38 -0700502 return UpdateFSReport(success: false);
503 }
Jonah Williams39f85f92019-10-02 12:46:33 -0700504 // Only update the last compiled time if we successfully compiled.
505 lastCompiled = candidateCompileTime;
Ben Konyie8b98f92019-03-19 20:01:03 -0700506 // list of sources that needs to be monitored are in [compilerOutput.sources]
Alexander Aprelev12c4e052019-03-20 21:58:15 -0700507 sources = compilerOutput.sources;
Ben Konyie8b98f92019-03-19 20:01:03 -0700508 //
Alexander Aprelev199422c2018-09-13 09:01:47 -0700509 // Don't send full kernel file that would overwrite what VM already
510 // started loading from.
511 if (!bundleFirstUpload) {
512 final String compiledBinary = compilerOutput?.outputFilename;
513 if (compiledBinary != null && compiledBinary.isNotEmpty) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800514 final Uri entryUri = globals.fs.path.toUri(projectRootPath != null
515 ? globals.fs.path.relative(pathToReload, from: projectRootPath)
Alexander Aprelev199422c2018-09-13 09:01:47 -0700516 : pathToReload,
517 );
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800518 final DevFSFileContent content = DevFSFileContent(globals.fs.file(compiledBinary));
Jonah Williamscd803ac2019-03-15 15:02:45 -0700519 syncedBytes += content.size;
520 dirtyEntries[entryUri] = content;
Alexander Aprelevaf74a722018-03-23 19:18:39 -0700521 }
Alexander Aprelev35c47612017-09-13 07:33:52 -0700522 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800523 globals.printTrace('Updating files');
Alexandre Ardhuin69b6bb82017-03-02 07:17:30 +0100524 if (dirtyEntries.isNotEmpty) {
Jonah Williamscd803ac2019-03-15 15:02:45 -0700525 try {
Jonah Williams27876e02019-05-31 13:17:12 -0700526 await _httpWriter.write(dirtyEntries);
Jonah Williamscd803ac2019-03-15 15:02:45 -0700527 } on SocketException catch (socketException, stackTrace) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800528 globals.printTrace('DevFS sync failed. Lost connection to device: $socketException');
Jonah Williamscd803ac2019-03-15 15:02:45 -0700529 throw DevFSException('Lost connection to device.', socketException, stackTrace);
Zachary Anderson9de77872020-02-27 22:46:23 -0800530 } on Exception catch (exception, stackTrace) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800531 globals.printError('Could not update files on device: $exception');
Jonah Williamscd803ac2019-03-15 15:02:45 -0700532 throw DevFSException('Sync failed', exception, stackTrace);
John McCutchan487f28f2016-08-08 12:42:31 -0700533 }
John McCutchan487f28f2016-08-08 12:42:31 -0700534 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800535 globals.printTrace('DevFS: Sync finished');
Alexander Aprelev52bd2cc2018-12-27 09:53:24 -0800536 return UpdateFSReport(success: true, syncedBytes: syncedBytes,
Jonah Williamscd803ac2019-03-15 15:02:45 -0700537 invalidatedSourcesCount: invalidatedFiles.length);
Dan Rubel0295def2017-01-22 10:37:10 -0500538 }
John McCutchan0de69162016-07-20 12:59:30 -0700539}
Jonah Williamscd803ac2019-03-15 15:02:45 -0700540
Greg Spencera60bf8e2019-11-22 08:43:55 -0800541/// Converts a platform-specific file path to a platform-independent URL path.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800542String _asUriPath(String filePath) => globals.fs.path.toUri(filePath).path + '/';