1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:convert'; |
6 | import 'dart:io' |
7 | as io |
8 | show |
9 | Directory, |
10 | File, |
11 | Link, |
12 | Process, |
13 | ProcessException, |
14 | ProcessResult, |
15 | ProcessSignal, |
16 | ProcessStartMode, |
17 | systemEncoding; |
18 | import 'dart:typed_data'; |
19 | |
20 | import 'package:file/file.dart'; |
21 | import 'package:path/path.dart'as p; // flutter_ignore: package_path_import |
22 | import 'package:process/process.dart'; |
23 | |
24 | import 'common.dart' show throwToolExit; |
25 | import 'platform.dart'; |
26 | |
27 | // The Flutter tool hits file system and process errors that only the end-user can address. |
28 | // We would like these errors to not hit crash logging. In these cases, we |
29 | // should exit gracefully and provide potentially useful advice. For example, if |
30 | // a write fails because the target device is full, we can explain that with a |
31 | // ToolExit and a message that is more clear than the FileSystemException by |
32 | // itself. |
33 | |
34 | /// On Windows this is error code 2: ERROR_FILE_NOT_FOUND, and on |
35 | /// macOS/Linux it is error code 2/ENOENT: No such file or directory. |
36 | const int kSystemCodeCannotFindFile = 2; |
37 | |
38 | /// On Windows this error is 3: ERROR_PATH_NOT_FOUND, and on |
39 | /// macOS/Linux, it is error code 3/ESRCH: No such process. |
40 | const int kSystemCodePathNotFound = 3; |
41 | |
42 | /// A [FileSystem] that throws a [ToolExit] on certain errors. |
43 | /// |
44 | /// If a [FileSystem] error is not caused by the Flutter tool, and can only be |
45 | /// addressed by the user, it should be caught by this [FileSystem] and thrown |
46 | /// as a [ToolExit] using [throwToolExit]. |
47 | /// |
48 | /// Cf. If there is some hope that the tool can continue when an operation fails |
49 | /// with an error, then that error/operation should not be handled here. For |
50 | /// example, the tool should generally be able to continue executing even if it |
51 | /// fails to delete a file. |
52 | class ErrorHandlingFileSystem extends ForwardingFileSystem { |
53 | ErrorHandlingFileSystem({required FileSystem delegate, required Platform platform}) |
54 | : _platform = platform, |
55 | super(delegate); |
56 | |
57 | FileSystem get fileSystem => delegate; |
58 | |
59 | final Platform _platform; |
60 | |
61 | /// Allow any file system operations executed within the closure to fail with any |
62 | /// operating system error, rethrowing an [Exception] instead of a [ToolExit]. |
63 | /// |
64 | /// This should not be used with async file system operation. |
65 | /// |
66 | /// This can be used to bypass the [ErrorHandlingFileSystem] permission exit |
67 | /// checks for situations where failure is acceptable, such as the flutter |
68 | /// persistent settings cache. |
69 | static void noExitOnFailure(void Function() operation) { |
70 | final bool previousValue = ErrorHandlingFileSystem._noExitOnFailure; |
71 | try { |
72 | ErrorHandlingFileSystem._noExitOnFailure = true; |
73 | operation(); |
74 | } finally { |
75 | ErrorHandlingFileSystem._noExitOnFailure = previousValue; |
76 | } |
77 | } |
78 | |
79 | /// Delete the file or directory and return true if it exists, take no |
80 | /// action and return false if it does not. |
81 | /// |
82 | /// This method should be preferred to checking if it exists and |
83 | /// then deleting, because it handles the edge case where the file or directory |
84 | /// is deleted by a different program between the two calls. |
85 | static bool deleteIfExists(FileSystemEntity entity, {bool recursive = false}) { |
86 | if (!entity.existsSync()) { |
87 | return false; |
88 | } |
89 | try { |
90 | entity.deleteSync(recursive: recursive); |
91 | } on FileSystemException catch (err) { |
92 | // Certain error codes indicate the file could not be found. It could have |
93 | // been deleted by a different program while the tool was running. |
94 | // if it still exists, the file likely exists on a read-only volume. |
95 | // This check will falsely match "3/ESRCH: No such process" on Linux/macOS, |
96 | // but this should be fine since this code should never come up here. |
97 | final bool codeCorrespondsToPathOrFileNotFound = |
98 | err.osError?.errorCode == kSystemCodeCannotFindFile || |
99 | err.osError?.errorCode == kSystemCodePathNotFound; |
100 | if (!codeCorrespondsToPathOrFileNotFound || _noExitOnFailure) { |
101 | rethrow; |
102 | } |
103 | if (entity.existsSync()) { |
104 | throwToolExit( |
105 | 'Unable to delete file or directory at "${entity.path} ". ' |
106 | 'This may be due to the project being in a read-only ' |
107 | 'volume. Consider relocating the project and trying again.', |
108 | ); |
109 | } |
110 | } |
111 | return true; |
112 | } |
113 | |
114 | static bool _noExitOnFailure = false; |
115 | |
116 | @override |
117 | Directory get currentDirectory { |
118 | try { |
119 | return _runSync(() => directory(delegate.currentDirectory), platform: _platform); |
120 | } on FileSystemException catch (err) { |
121 | // Special handling for OS error 2 for current directory only. |
122 | if (err.osError?.errorCode == kSystemCodeCannotFindFile) { |
123 | throwToolExit( |
124 | 'Unable to read current working directory. This can happen if the directory the ' |
125 | 'Flutter tool was run from was moved or deleted.', |
126 | ); |
127 | } |
128 | rethrow; |
129 | } |
130 | } |
131 | |
132 | @override |
133 | Directory get systemTempDirectory { |
134 | return _runSync(() => directory(delegate.systemTempDirectory), platform: _platform); |
135 | } |
136 | |
137 | @override |
138 | File file(dynamic path) => |
139 | ErrorHandlingFile(platform: _platform, fileSystem: this, delegate: delegate.file(path)); |
140 | |
141 | @override |
142 | Directory directory(dynamic path) => ErrorHandlingDirectory( |
143 | platform: _platform, |
144 | fileSystem: this, |
145 | delegate: delegate.directory(path), |
146 | ); |
147 | |
148 | @override |
149 | Link link(dynamic path) => |
150 | ErrorHandlingLink(platform: _platform, fileSystem: this, delegate: delegate.link(path)); |
151 | |
152 | // Caching the path context here and clearing when the currentDirectory setter |
153 | // is updated works since the flutter tool restricts usage of dart:io directly |
154 | // via the forbidden import tests. Otherwise, the path context's current |
155 | // working directory might get out of sync, leading to unexpected results from |
156 | // methods like `path.relative`. |
157 | @override |
158 | p.Context get path => _cachedPath ??= delegate.path; |
159 | p.Context? _cachedPath; |
160 | |
161 | @override |
162 | set currentDirectory(dynamic path) { |
163 | _cachedPath = null; |
164 | delegate.currentDirectory = path; |
165 | } |
166 | |
167 | @override |
168 | String toString() => delegate.toString(); |
169 | } |
170 | |
171 | class ErrorHandlingFile extends ForwardingFileSystemEntity<File, io.File> with ForwardingFile { |
172 | ErrorHandlingFile({required Platform platform, required this.fileSystem, required this.delegate}) |
173 | : _platform = platform; |
174 | |
175 | @override |
176 | final io.File delegate; |
177 | |
178 | @override |
179 | final ErrorHandlingFileSystem fileSystem; |
180 | |
181 | final Platform _platform; |
182 | |
183 | @override |
184 | File wrapFile(io.File delegate) => |
185 | ErrorHandlingFile(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
186 | |
187 | @override |
188 | Directory wrapDirectory(io.Directory delegate) => |
189 | ErrorHandlingDirectory(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
190 | |
191 | @override |
192 | Link wrapLink(io.Link delegate) => |
193 | ErrorHandlingLink(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
194 | |
195 | @override |
196 | Future<File> writeAsBytes( |
197 | List<int> bytes, { |
198 | FileMode mode = FileMode.write, |
199 | bool flush = false, |
200 | }) async { |
201 | return _run<File>( |
202 | () async => wrap(await delegate.writeAsBytes(bytes, mode: mode, flush: flush)), |
203 | platform: _platform, |
204 | failureMessage: 'Flutter failed to write to a file at "${delegate.path} "', |
205 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
206 | ); |
207 | } |
208 | |
209 | @override |
210 | String readAsStringSync({Encoding encoding = utf8}) { |
211 | return _runSync<String>( |
212 | () => delegate.readAsStringSync(), |
213 | platform: _platform, |
214 | failureMessage: 'Flutter failed to read a file at "${delegate.path} "', |
215 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
216 | ); |
217 | } |
218 | |
219 | @override |
220 | void writeAsBytesSync(List<int> bytes, {FileMode mode = FileMode.write, bool flush = false}) { |
221 | _runSync<void>( |
222 | () => delegate.writeAsBytesSync(bytes, mode: mode, flush: flush), |
223 | platform: _platform, |
224 | failureMessage: 'Flutter failed to write to a file at "${delegate.path} "', |
225 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
226 | ); |
227 | } |
228 | |
229 | @override |
230 | Future<File> writeAsString( |
231 | String contents, { |
232 | FileMode mode = FileMode.write, |
233 | Encoding encoding = utf8, |
234 | bool flush = false, |
235 | }) async { |
236 | return _run<File>( |
237 | () async => wrap( |
238 | await delegate.writeAsString(contents, mode: mode, encoding: encoding, flush: flush), |
239 | ), |
240 | platform: _platform, |
241 | failureMessage: 'Flutter failed to write to a file at "${delegate.path} "', |
242 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
243 | ); |
244 | } |
245 | |
246 | @override |
247 | void writeAsStringSync( |
248 | String contents, { |
249 | FileMode mode = FileMode.write, |
250 | Encoding encoding = utf8, |
251 | bool flush = false, |
252 | }) { |
253 | _runSync<void>( |
254 | () => delegate.writeAsStringSync(contents, mode: mode, encoding: encoding, flush: flush), |
255 | platform: _platform, |
256 | failureMessage: 'Flutter failed to write to a file at "${delegate.path} "', |
257 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
258 | ); |
259 | } |
260 | |
261 | // TODO(aam): Pass `exclusive` through after dartbug.com/49647 lands. |
262 | @override |
263 | void createSync({bool recursive = false, bool exclusive = false}) { |
264 | _runSync<void>( |
265 | () => delegate.createSync(recursive: recursive), |
266 | platform: _platform, |
267 | failureMessage: 'Flutter failed to create file at "${delegate.path} "', |
268 | posixPermissionSuggestion: |
269 | recursive ? null : _posixPermissionSuggestion(<String>[delegate.parent.path]), |
270 | ); |
271 | } |
272 | |
273 | @override |
274 | RandomAccessFile openSync({FileMode mode = FileMode.read}) { |
275 | return _runSync<RandomAccessFile>( |
276 | () => delegate.openSync(mode: mode), |
277 | platform: _platform, |
278 | failureMessage: 'Flutter failed to open a file at "${delegate.path} "', |
279 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
280 | ); |
281 | } |
282 | |
283 | /// This copy method attempts to handle file system errors from both reading |
284 | /// and writing the copied file. |
285 | @override |
286 | File copySync(String newPath) { |
287 | final File resultFile = fileSystem.file(newPath); |
288 | // First check if the source file can be read. If not, bail through error |
289 | // handling. |
290 | _runSync<void>( |
291 | () => delegate.openSync().closeSync(), |
292 | platform: _platform, |
293 | failureMessage: 'Flutter failed to copy$path to$newPath due to source location error', |
294 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[path]), |
295 | ); |
296 | // Next check if the destination file can be written. If not, bail through |
297 | // error handling. |
298 | _runSync<void>( |
299 | () => resultFile.createSync(recursive: true), |
300 | platform: _platform, |
301 | failureMessage: 'Flutter failed to copy$path to$newPath due to destination location error', |
302 | ); |
303 | // If both of the above checks passed, attempt to copy the file and catch |
304 | // any thrown errors. |
305 | try { |
306 | return wrapFile(delegate.copySync(newPath)); |
307 | } on FileSystemException { |
308 | // Proceed below |
309 | } |
310 | // If the copy failed but both of the above checks passed, copy the bytes |
311 | // directly. |
312 | _runSync( |
313 | () { |
314 | RandomAccessFile? source; |
315 | RandomAccessFile? sink; |
316 | try { |
317 | source = delegate.openSync(); |
318 | sink = resultFile.openSync(mode: FileMode.writeOnly); |
319 | // 64k is the same sized buffer used by dart:io for `File.openRead`. |
320 | final Uint8List buffer = Uint8List(64 * 1024); |
321 | final int totalBytes = source.lengthSync(); |
322 | int bytes = 0; |
323 | while (bytes < totalBytes) { |
324 | final int chunkLength = source.readIntoSync(buffer); |
325 | sink.writeFromSync(buffer, 0, chunkLength); |
326 | bytes += chunkLength; |
327 | } |
328 | } catch (err) { |
329 | ErrorHandlingFileSystem.deleteIfExists(resultFile, recursive: true); |
330 | rethrow; |
331 | } finally { |
332 | source?.closeSync(); |
333 | sink?.closeSync(); |
334 | } |
335 | }, |
336 | platform: _platform, |
337 | failureMessage: 'Flutter failed to copy$path to$newPath due to unknown error', |
338 | posixPermissionSuggestion: _posixPermissionSuggestion(<String>[path, resultFile.parent.path]), |
339 | ); |
340 | // The original copy failed, but the manual copy worked. |
341 | return wrapFile(resultFile); |
342 | } |
343 | |
344 | String _posixPermissionSuggestion(List<String> paths) => |
345 | 'Try running:\n' |
346 | ' sudo chown -R \$(whoami)${paths.map(fileSystem.path.absolute).join( ' ')} '; |
347 | |
348 | @override |
349 | String toString() => delegate.toString(); |
350 | } |
351 | |
352 | class ErrorHandlingDirectory extends ForwardingFileSystemEntity<Directory, io.Directory> |
353 | with ForwardingDirectory<Directory> { |
354 | ErrorHandlingDirectory({ |
355 | required Platform platform, |
356 | required this.fileSystem, |
357 | required this.delegate, |
358 | }) : _platform = platform; |
359 | |
360 | @override |
361 | final io.Directory delegate; |
362 | |
363 | @override |
364 | final ErrorHandlingFileSystem fileSystem; |
365 | |
366 | final Platform _platform; |
367 | |
368 | @override |
369 | File wrapFile(io.File delegate) => |
370 | ErrorHandlingFile(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
371 | |
372 | @override |
373 | Directory wrapDirectory(io.Directory delegate) => |
374 | ErrorHandlingDirectory(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
375 | |
376 | @override |
377 | Link wrapLink(io.Link delegate) => |
378 | ErrorHandlingLink(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
379 | |
380 | @override |
381 | Directory childDirectory(String basename) { |
382 | return fileSystem.directory(fileSystem.path.join(path, basename)); |
383 | } |
384 | |
385 | @override |
386 | File childFile(String basename) { |
387 | return fileSystem.file(fileSystem.path.join(path, basename)); |
388 | } |
389 | |
390 | @override |
391 | Link childLink(String basename) { |
392 | return fileSystem.link(fileSystem.path.join(path, basename)); |
393 | } |
394 | |
395 | @override |
396 | void createSync({bool recursive = false}) { |
397 | return _runSync<void>( |
398 | () => delegate.createSync(recursive: recursive), |
399 | platform: _platform, |
400 | failureMessage: 'Flutter failed to create a directory at "${delegate.path} "', |
401 | posixPermissionSuggestion: |
402 | recursive ? null : _posixPermissionSuggestion(delegate.parent.path), |
403 | ); |
404 | } |
405 | |
406 | @override |
407 | Future<Directory> createTemp([String? prefix]) { |
408 | return _run<Directory>( |
409 | () async => wrap(await delegate.createTemp(prefix)), |
410 | platform: _platform, |
411 | failureMessage: 'Flutter failed to create a temporary directory with prefix "$prefix "', |
412 | ); |
413 | } |
414 | |
415 | @override |
416 | Directory createTempSync([String? prefix]) { |
417 | return _runSync<Directory>( |
418 | () => wrap(delegate.createTempSync(prefix)), |
419 | platform: _platform, |
420 | failureMessage: 'Flutter failed to create a temporary directory with prefix "$prefix "', |
421 | ); |
422 | } |
423 | |
424 | @override |
425 | Future<Directory> create({bool recursive = false}) { |
426 | return _run<Directory>( |
427 | () async => wrap(await delegate.create(recursive: recursive)), |
428 | platform: _platform, |
429 | failureMessage: 'Flutter failed to create a directory at "${delegate.path} "', |
430 | posixPermissionSuggestion: |
431 | recursive ? null : _posixPermissionSuggestion(delegate.parent.path), |
432 | ); |
433 | } |
434 | |
435 | @override |
436 | Future<Directory> delete({bool recursive = false}) { |
437 | return _run<Directory>( |
438 | () async => wrap(fileSystem.directory((await delegate.delete(recursive: recursive)).path)), |
439 | platform: _platform, |
440 | failureMessage: 'Flutter failed to delete a directory at "${delegate.path} "', |
441 | posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.path), |
442 | ); |
443 | } |
444 | |
445 | @override |
446 | void deleteSync({bool recursive = false}) { |
447 | return _runSync<void>( |
448 | () => delegate.deleteSync(recursive: recursive), |
449 | platform: _platform, |
450 | failureMessage: 'Flutter failed to delete a directory at "${delegate.path} "', |
451 | posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.path), |
452 | ); |
453 | } |
454 | |
455 | @override |
456 | bool existsSync() { |
457 | return _runSync<bool>( |
458 | () => delegate.existsSync(), |
459 | platform: _platform, |
460 | failureMessage: 'Flutter failed to check for directory existence at "${delegate.path} "', |
461 | posixPermissionSuggestion: _posixPermissionSuggestion(delegate.parent.path), |
462 | ); |
463 | } |
464 | |
465 | String _posixPermissionSuggestion(String path) => |
466 | 'Try running:\n' |
467 | ' sudo chown -R \$(whoami)${fileSystem.path.absolute(path)} '; |
468 | |
469 | @override |
470 | String toString() => delegate.toString(); |
471 | } |
472 | |
473 | class ErrorHandlingLink extends ForwardingFileSystemEntity<Link, io.Link> with ForwardingLink { |
474 | ErrorHandlingLink({required Platform platform, required this.fileSystem, required this.delegate}) |
475 | : _platform = platform; |
476 | |
477 | @override |
478 | final io.Link delegate; |
479 | |
480 | @override |
481 | final ErrorHandlingFileSystem fileSystem; |
482 | |
483 | final Platform _platform; |
484 | |
485 | @override |
486 | File wrapFile(io.File delegate) => |
487 | ErrorHandlingFile(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
488 | |
489 | @override |
490 | Directory wrapDirectory(io.Directory delegate) => |
491 | ErrorHandlingDirectory(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
492 | |
493 | @override |
494 | Link wrapLink(io.Link delegate) => |
495 | ErrorHandlingLink(platform: _platform, fileSystem: fileSystem, delegate: delegate); |
496 | |
497 | @override |
498 | String toString() => delegate.toString(); |
499 | } |
500 | |
501 | const String _kNoExecutableFound = |
502 | 'The Flutter tool could not locate an executable with suitable permissions'; |
503 | |
504 | Future<T> _run<T>( |
505 | Future<T> Function() op, { |
506 | required Platform platform, |
507 | String? failureMessage, |
508 | String? posixPermissionSuggestion, |
509 | }) async { |
510 | try { |
511 | return await op(); |
512 | } on ProcessPackageExecutableNotFoundException catch (e) { |
513 | if (e.candidates.isNotEmpty) { |
514 | throwToolExit('$_kNoExecutableFound :$e '); |
515 | } |
516 | rethrow; |
517 | } on FileSystemException catch (e) { |
518 | if (platform.isWindows) { |
519 | _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); |
520 | } else if (platform.isLinux || platform.isMacOS) { |
521 | _handlePosixException( |
522 | e, |
523 | failureMessage, |
524 | e.osError?.errorCode ?? 0, |
525 | posixPermissionSuggestion, |
526 | ); |
527 | } |
528 | rethrow; |
529 | } on io.ProcessException catch (e) { |
530 | if (platform.isWindows) { |
531 | _handleWindowsException(e, failureMessage, e.errorCode); |
532 | } else if (platform.isLinux) { |
533 | _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
534 | } |
535 | if (platform.isMacOS) { |
536 | _handleMacOSException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
537 | } |
538 | rethrow; |
539 | } |
540 | } |
541 | |
542 | T _runSync<T>( |
543 | T Function() op, { |
544 | required Platform platform, |
545 | String? failureMessage, |
546 | String? posixPermissionSuggestion, |
547 | }) { |
548 | try { |
549 | return op(); |
550 | } on ProcessPackageExecutableNotFoundException catch (e) { |
551 | if (e.candidates.isNotEmpty) { |
552 | throwToolExit('$_kNoExecutableFound :$e '); |
553 | } |
554 | rethrow; |
555 | } on FileSystemException catch (e) { |
556 | if (platform.isWindows) { |
557 | _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); |
558 | } else if (platform.isLinux || platform.isMacOS) { |
559 | _handlePosixException( |
560 | e, |
561 | failureMessage, |
562 | e.osError?.errorCode ?? 0, |
563 | posixPermissionSuggestion, |
564 | ); |
565 | } |
566 | rethrow; |
567 | } on io.ProcessException catch (e) { |
568 | if (platform.isWindows) { |
569 | _handleWindowsException(e, failureMessage, e.errorCode); |
570 | } else if (platform.isLinux) { |
571 | _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
572 | } |
573 | if (platform.isMacOS) { |
574 | _handleMacOSException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
575 | } |
576 | rethrow; |
577 | } |
578 | } |
579 | |
580 | /// A [ProcessManager] that throws a [ToolExit] on certain errors. |
581 | /// |
582 | /// If a [ProcessException] is not caused by the Flutter tool, and can only be |
583 | /// addressed by the user, it should be caught by this [ProcessManager] and thrown |
584 | /// as a [ToolExit] using [throwToolExit]. |
585 | /// |
586 | /// See also: |
587 | /// * [ErrorHandlingFileSystem], for a similar file system strategy. |
588 | class ErrorHandlingProcessManager extends ProcessManager { |
589 | ErrorHandlingProcessManager({required ProcessManager delegate, required Platform platform}) |
590 | : _delegate = delegate, |
591 | _platform = platform; |
592 | |
593 | final ProcessManager _delegate; |
594 | final Platform _platform; |
595 | |
596 | @override |
597 | bool canRun(dynamic executable, {String? workingDirectory}) { |
598 | return _runSync( |
599 | () => _delegate.canRun(executable, workingDirectory: workingDirectory), |
600 | platform: _platform, |
601 | failureMessage: 'Flutter failed to run "$executable "', |
602 | posixPermissionSuggestion: |
603 | 'Try running:\n' |
604 | ' sudo chown -R \$(whoami)$executable && chmod u+rx$executable ', |
605 | ); |
606 | } |
607 | |
608 | @override |
609 | bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) { |
610 | return _runSync(() => _delegate.killPid(pid, signal), platform: _platform); |
611 | } |
612 | |
613 | @override |
614 | Future<io.ProcessResult> run( |
615 | List<Object> command, { |
616 | String? workingDirectory, |
617 | Map<String, String>? environment, |
618 | bool includeParentEnvironment = true, |
619 | bool runInShell = false, |
620 | Encoding? stdoutEncoding = io.systemEncoding, |
621 | Encoding? stderrEncoding = io.systemEncoding, |
622 | }) { |
623 | return _run( |
624 | () { |
625 | return _delegate.run( |
626 | command, |
627 | workingDirectory: workingDirectory, |
628 | environment: environment, |
629 | includeParentEnvironment: includeParentEnvironment, |
630 | runInShell: runInShell, |
631 | stdoutEncoding: stdoutEncoding, |
632 | stderrEncoding: stderrEncoding, |
633 | ); |
634 | }, |
635 | platform: _platform, |
636 | failureMessage: 'Flutter failed to run "${command.join( ' ')} "', |
637 | ); |
638 | } |
639 | |
640 | @override |
641 | Future<io.Process> start( |
642 | List<Object> command, { |
643 | String? workingDirectory, |
644 | Map<String, String>? environment, |
645 | bool includeParentEnvironment = true, |
646 | bool runInShell = false, |
647 | io.ProcessStartMode mode = io.ProcessStartMode.normal, |
648 | }) { |
649 | return _run( |
650 | () { |
651 | return _delegate.start( |
652 | command, |
653 | workingDirectory: workingDirectory, |
654 | environment: environment, |
655 | includeParentEnvironment: includeParentEnvironment, |
656 | runInShell: runInShell, |
657 | mode: mode, |
658 | ); |
659 | }, |
660 | platform: _platform, |
661 | failureMessage: 'Flutter failed to run "${command.join( ' ')} "', |
662 | ); |
663 | } |
664 | |
665 | @override |
666 | io.ProcessResult runSync( |
667 | List<Object> command, { |
668 | String? workingDirectory, |
669 | Map<String, String>? environment, |
670 | bool includeParentEnvironment = true, |
671 | bool runInShell = false, |
672 | Encoding? stdoutEncoding = io.systemEncoding, |
673 | Encoding? stderrEncoding = io.systemEncoding, |
674 | }) { |
675 | return _runSync( |
676 | () { |
677 | return _delegate.runSync( |
678 | command, |
679 | workingDirectory: workingDirectory, |
680 | environment: environment, |
681 | includeParentEnvironment: includeParentEnvironment, |
682 | runInShell: runInShell, |
683 | stdoutEncoding: stdoutEncoding, |
684 | stderrEncoding: stderrEncoding, |
685 | ); |
686 | }, |
687 | platform: _platform, |
688 | failureMessage: 'Flutter failed to run "${command.join( ' ')} "', |
689 | ); |
690 | } |
691 | } |
692 | |
693 | void _handlePosixException( |
694 | Exception e, |
695 | String? message, |
696 | int errorCode, |
697 | String? posixPermissionSuggestion, |
698 | ) { |
699 | // From: |
700 | // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h |
701 | // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h |
702 | // https://github.com/apple/darwin-xnu/blob/main/bsd/dev/dtrace/scripts/errno.d |
703 | const int eperm = 1; |
704 | const int enospc = 28; |
705 | const int eacces = 13; |
706 | // Catch errors and bail when: |
707 | String? errorMessage; |
708 | switch (errorCode) { |
709 | case enospc: |
710 | errorMessage = |
711 | '$message . The target device is full.' |
712 | '\n$e \n' |
713 | 'Free up space and try again.'; |
714 | case eperm: |
715 | case eacces: |
716 | final StringBuffer errorBuffer = StringBuffer(); |
717 | if (message != null && message.isNotEmpty) { |
718 | errorBuffer.writeln('$message .'); |
719 | } else { |
720 | errorBuffer.writeln('The flutter tool cannot access the file or directory.'); |
721 | } |
722 | errorBuffer.writeln( |
723 | 'Please ensure that the SDK and/or project is installed in a location ' |
724 | 'that has read/write permissions for the current user.', |
725 | ); |
726 | if (posixPermissionSuggestion != null && posixPermissionSuggestion.isNotEmpty) { |
727 | errorBuffer.writeln(posixPermissionSuggestion); |
728 | } |
729 | errorMessage = errorBuffer.toString(); |
730 | default: |
731 | // Caller must rethrow the exception. |
732 | break; |
733 | } |
734 | _throwFileSystemException(errorMessage); |
735 | } |
736 | |
737 | void _handleMacOSException( |
738 | Exception e, |
739 | String? message, |
740 | int errorCode, |
741 | String? posixPermissionSuggestion, |
742 | ) { |
743 | // https://github.com/apple/darwin-xnu/blob/main/bsd/dev/dtrace/scripts/errno.d |
744 | const int ebadarch = 86; |
745 | const int eagain = 35; |
746 | if (errorCode == ebadarch) { |
747 | final StringBuffer errorBuffer = StringBuffer(); |
748 | if (message != null) { |
749 | errorBuffer.writeln('$message .'); |
750 | } |
751 | errorBuffer.writeln( |
752 | 'The binary was built with the incorrect architecture to run on this machine.', |
753 | ); |
754 | errorBuffer.writeln( |
755 | 'If you are on an ARM Apple Silicon Mac, Flutter requires the Rosetta translation environment. Try running:', |
756 | ); |
757 | errorBuffer.writeln(' sudo softwareupdate --install-rosetta --agree-to-license'); |
758 | _throwFileSystemException(errorBuffer.toString()); |
759 | } |
760 | if (errorCode == eagain) { |
761 | final StringBuffer errorBuffer = StringBuffer(); |
762 | if (message != null) { |
763 | errorBuffer.writeln('$message .'); |
764 | } |
765 | errorBuffer.writeln( |
766 | 'Your system may be running into its process limits. ' |
767 | 'Consider quitting unused apps and trying again.', |
768 | ); |
769 | throwToolExit(errorBuffer.toString()); |
770 | } |
771 | _handlePosixException(e, message, errorCode, posixPermissionSuggestion); |
772 | } |
773 | |
774 | void _handleWindowsException(Exception e, String? message, int errorCode) { |
775 | // From: |
776 | // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes |
777 | const int kDeviceFull = 112; |
778 | const int kUserMappedSectionOpened = 1224; |
779 | const int kAccessDenied = 5; |
780 | const int kFatalDeviceHardwareError = 483; |
781 | const int kDeviceDoesNotExist = 433; |
782 | |
783 | // Catch errors and bail when: |
784 | String? errorMessage; |
785 | switch (errorCode) { |
786 | case kAccessDenied: |
787 | errorMessage = |
788 | '$message . The flutter tool cannot access the file or directory.\n' |
789 | 'Please ensure that the SDK and/or project is installed in a location ' |
790 | 'that has read/write permissions for the current user.'; |
791 | case kDeviceFull: |
792 | errorMessage = |
793 | '$message . The target device is full.' |
794 | '\n$e \n' |
795 | 'Free up space and try again.'; |
796 | case kUserMappedSectionOpened: |
797 | errorMessage = |
798 | '$message . The file is being used by another program.' |
799 | '\n$e \n' |
800 | 'Do you have an antivirus program running? ' |
801 | 'Try disabling your antivirus program and try again.'; |
802 | case kFatalDeviceHardwareError: |
803 | errorMessage = |
804 | '$message . There is a problem with the device driver ' |
805 | 'that this file or directory is stored on.'; |
806 | case kDeviceDoesNotExist: |
807 | errorMessage = |
808 | '$message . The device was not found.' |
809 | '\n$e \n' |
810 | 'Verify the device is mounted and try again.'; |
811 | default: |
812 | // Caller must rethrow the exception. |
813 | break; |
814 | } |
815 | _throwFileSystemException(errorMessage); |
816 | } |
817 | |
818 | void _throwFileSystemException(String? errorMessage) { |
819 | if (errorMessage == null) { |
820 | return; |
821 | } |
822 | if (ErrorHandlingFileSystem._noExitOnFailure) { |
823 | throw FileSystemException(errorMessage); |
824 | } |
825 | throwToolExit(errorMessage); |
826 | } |
827 |
Definitions
- kSystemCodeCannotFindFile
- kSystemCodePathNotFound
- ErrorHandlingFileSystem
- ErrorHandlingFileSystem
- fileSystem
- noExitOnFailure
- deleteIfExists
- currentDirectory
- systemTempDirectory
- file
- directory
- link
- path
- currentDirectory
- toString
- ErrorHandlingFile
- ErrorHandlingFile
- wrapFile
- wrapDirectory
- wrapLink
- writeAsBytes
- readAsStringSync
- writeAsBytesSync
- writeAsString
- writeAsStringSync
- createSync
- openSync
- copySync
- _posixPermissionSuggestion
- toString
- ErrorHandlingDirectory
- ErrorHandlingDirectory
- wrapFile
- wrapDirectory
- wrapLink
- childDirectory
- childFile
- childLink
- createSync
- createTemp
- createTempSync
- create
- delete
- deleteSync
- existsSync
- _posixPermissionSuggestion
- toString
- ErrorHandlingLink
- ErrorHandlingLink
- wrapFile
- wrapDirectory
- wrapLink
- toString
- _kNoExecutableFound
- _run
- _runSync
- ErrorHandlingProcessManager
- ErrorHandlingProcessManager
- canRun
- killPid
- run
- start
- runSync
- _handlePosixException
- _handleMacOSException
- _handleWindowsException
Learn more about Flutter for embedded and desktop on industrialflutter.com