[flutter_tool] Make a couple file operations synchronous (#37044)

diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 876609b..68fd2ad 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -141,12 +141,21 @@
     if (!_lockEnabled)
       return;
     assert(_lock == null);
-    _lock = await fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'lockfile')).open(mode: FileMode.write);
+    final File lockFile =
+        fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'lockfile'));
+    try {
+      _lock = lockFile.openSync(mode: FileMode.write);
+    } on FileSystemException catch (e) {
+      printError('Failed to open or create the artifact cache lockfile: "$e"');
+      printError('Please ensure you have permissions to create or open '
+                 '${lockFile.path}');
+      throwToolExit('Failed to open or create the lockfile');
+    }
     bool locked = false;
     bool printed = false;
     while (!locked) {
       try {
-        await _lock.lock();
+        _lock.lockSync();
         locked = true;
       } on FileSystemException {
         if (!printed) {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 0e991b3..de8e4f9 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -367,7 +367,13 @@
           flutterUsage.suppressAnalytics = true;
 
         _checkFlutterCopy();
-        await FlutterVersion.instance.ensureVersionFile();
+        try {
+          await FlutterVersion.instance.ensureVersionFile();
+        } on FileSystemException catch (e) {
+          printError('Failed to write the version file to the artifact cache: "$e".');
+          printError('Please ensure you have permissions in the artifact cache directory.');
+          throwToolExit('Failed to write the version file');
+        }
         if (topLevelResults.command?.name != 'upgrade' && topLevelResults['version-check']) {
           await FlutterVersion.instance.checkFlutterVersionFreshness();
         }
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index c3c14c4..f1f4a42 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -99,8 +99,8 @@
   String get engineRevision => Cache.instance.engineRevision;
   String get engineRevisionShort => _shortGitRevision(engineRevision);
 
-  Future<void> ensureVersionFile() {
-    return fs.file(fs.path.join(Cache.flutterRoot, 'version')).writeAsString(_frameworkVersion);
+  Future<void> ensureVersionFile() async {
+    fs.file(fs.path.join(Cache.flutterRoot, 'version')).writeAsStringSync(_frameworkVersion);
   }
 
   @override
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index 07c5c18..f4a8ef4 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
 import 'package:file_testing/file_testing.dart';
@@ -11,6 +9,7 @@
 import 'package:mockito/mockito.dart';
 import 'package:platform/platform.dart';
 
+import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException;
@@ -23,7 +22,18 @@
 
 void main() {
   group('$Cache.checkLockAcquired', () {
+    MockFileSystem mockFileSystem;
+    MemoryFileSystem memoryFileSystem;
+    MockFile mockFile;
+    MockRandomAccessFile mockRandomAccessFile;
+
     setUp(() {
+      mockFileSystem = MockFileSystem();
+      memoryFileSystem = MemoryFileSystem();
+      mockFile = MockFile();
+      mockRandomAccessFile = MockRandomAccessFile();
+      when(mockFileSystem.path).thenReturn(memoryFileSystem.path);
+
       Cache.enableLocking();
     });
 
@@ -31,6 +41,7 @@
       // Restore locking to prevent potential side-effects in
       // tests outside this group (this option is globally shared).
       Cache.enableLocking();
+      Cache.releaseLockEarly();
     });
 
     test('should throw when locking is not acquired', () {
@@ -43,10 +54,21 @@
     });
 
     testUsingContext('should not throw when lock is acquired', () async {
+      when(mockFileSystem.file(argThat(endsWith('lockfile')))).thenReturn(mockFile);
+      when(mockFile.openSync(mode: anyNamed('mode'))).thenReturn(mockRandomAccessFile);
       await Cache.lock();
       Cache.checkLockAcquired();
+      Cache.releaseLockEarly();
     }, overrides: <Type, Generator>{
-      FileSystem: () => MockFileSystem(),
+      FileSystem: () => mockFileSystem,
+    });
+
+    testUsingContext('throws tool exit when lockfile open fails', () async {
+      when(mockFileSystem.file(argThat(endsWith('lockfile')))).thenReturn(mockFile);
+      when(mockFile.openSync(mode: anyNamed('mode'))).thenThrow(const FileSystemException());
+      expect(() async => await Cache.lock(), throwsA(isA<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => mockFileSystem,
     });
 
     testUsingContext('should not throw when FLUTTER_ALREADY_LOCKED is set', () async {
@@ -166,7 +188,7 @@
     expect(flattenNameSubdirs(Uri.parse('http://docs.flutter.io/foo/bar')), 'docs.flutter.io/foo/bar');
     expect(flattenNameSubdirs(Uri.parse('https://www.flutter.dev')), 'www.flutter.dev');
   }, overrides: <Type, Generator>{
-    FileSystem: () => MockFileSystem(),
+    FileSystem: () => MemoryFileSystem(),
   });
 
   test('Unstable artifacts', () {
@@ -247,21 +269,8 @@
   List<String> getPackageDirs() => packageDirs;
 }
 
-class MockFileSystem extends ForwardingFileSystem {
-  MockFileSystem() : super(MemoryFileSystem());
-
-  @override
-  File file(dynamic path) {
-    return MockFile();
-  }
-}
-
-class MockFile extends Mock implements File {
-  @override
-  Future<RandomAccessFile> open({ FileMode mode = FileMode.read }) async {
-    return MockRandomAccessFile();
-  }
-}
+class MockFileSystem extends Mock implements FileSystem {}
+class MockFile extends Mock implements File {}
 
 class MockRandomAccessFile extends Mock implements RandomAccessFile {}
 class MockCachedArtifact extends Mock implements CachedArtifact {}
diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart
index 3680746..a5c5541 100644
--- a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
@@ -69,6 +70,17 @@
         Platform: () => platform,
       }, initializeFlutterRoot: false);
 
+      testUsingContext('throw tool exit if the version file cannot be written', () async {
+        final MockFlutterVersion version = FlutterVersion.instance;
+        when(version.ensureVersionFile()).thenThrow(const FileSystemException());
+
+        expect(() async => await runner.run(<String>['dummy']), throwsA(isA<ToolExit>()));
+
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        Platform: () => platform,
+      }, initializeFlutterRoot: false);
+
       testUsingContext('works if --local-engine is specified and --local-engine-src-path is determined by sky_engine', () async {
         fs.directory('$_kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/').createSync(recursive: true);
         fs.directory('$_kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true);